Setup
下载题目文件到Linux系统环境中
文件名有点长,做下重命名
mv babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c heap
赋予运行权限
chmod +x heap
运行
./heap
不能运行的话,可能是因为使用的是64位系统
- 在x86_64环境中运行不能直接运行x86的二进制程序
Ubuntu 14.04以后版本
dpkg --add-architecture i386 apt-get update apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 $ apt-get install gcc-multilib g++-multilib
$ file heap heap: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.24, BuildID[sha1]=1b4e88004c13ca18ef78ac90b298c1e247c1d4e5, with debug_info, not stripped $ ldd heap linux-gate.so.1 (0xf7ee0000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7ce8000) /lib/ld-linux.so.2 (0xf7ee2000) $ checksec --file heap #http://www.trapkit.de/tools/checksec.html RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 113 Symbols No 0 8 heap1
2
3
4
5
6
## 初步调查
- 每次都需要做的初步检查
-Relocation section '.rel.plt' at offset 0x558 contains 14 entries: Offset Info Type Sym.Value Sym.Name 0804c000 00000107 R_386_JUMP_SLOT 00000000 mprotect@GLIBC_2.0 //更改内存属性 0804c004 00000207 R_386_JUMP_SLOT 00000000 printf@GLIBC_2.0 //显示文字 0804c008 00000307 R_386_JUMP_SLOT 00000000 memcpy@GLIBC_2.0 //拷贝内存 0804c00c 00000407 R_386_JUMP_SLOT 00000000 signal@GLIBC_2.0 //定义收到信号时的操作 0804c010 00000507 R_386_JUMP_SLOT 00000000 alarm@GLIBC_2.0 //在一段时间后提出异常 0804c014 00000607 R_386_JUMP_SLOT 00000000 _IO_getc@GLIBC_2.0 //读取一个字符 0804c018 00000707 R_386_JUMP_SLOT 00000000 puts@GLIBC_2.0 //显示一行 0804c01c 00000807 R_386_JUMP_SLOT 00000000 __gmon_start__ //使用gprof等收集统计信息时使用的函数。由于通常不使用它,因此可以忽略它 0804c020 00000907 R_386_JUMP_SLOT 00000000 exit@GLIBC_2.0 //退出函数 0804c024 00000a07 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0 //调用main的包装函数 0804c028 00000b07 R_386_JUMP_SLOT 00000000 fprintf@GLIBC_2.0 //向文件指针输出内容 0804c02c 00000c07 R_386_JUMP_SLOT 00000000 setvbuf@GLIBC_2.0 //更改缓冲行为 0804c030 00000d07 R_386_JUMP_SLOT 00000000 memset@GLIBC_2.0 //设置内存值 0804c034 00000e07 R_386_JUMP_SLOT 00000000 sbrk@GLIBC_2.0 //扩张heap区域1
2
3
4
- 使用的库函数列表
-$ ./heap ... [ALLOC][loc=84354B0][size=823] Write to object [size=260]: AAAAA // 尝试输入简单的值 Copied 6 bytes. [FREE][address=8431008] ... [FREE][address=84354B0] Did you forget to read the flag with your shellcode? Exiting // 什么也没发生1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
上述函数没有bind, listen, accept等,因为没有实现NX处理
## fork-server型与xinetd型
- CTF题目常见的有两种
- fork-server型,xinetd型
- fork-server型
- 二进制程序中有bind->listen->accept的类型
- ![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019032501.jpg)
- 这种情况下,需要去调试子进程
- 如果未在gdb中设置set follow-fork-mode child,则仅调试父进程(并且父进程将继续存在,因此您需要每次都kill父进程)
- xinetd型
- 二进程程序中没有bind->listen->accept的类型(用xinetd代替)
- ![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019032502.jpg)
- 在这种情况下,使用gdb等工具调试时,不需要考虑NW
- 但是,在实际编写exploit进行测试时,可以用过socat来添加NW关系
- 也就是说,对于xintd型,在调试时不需要考虑NW交换,但在编写exploit时需要考虑NW上的通信
## 简单运行了解下流程$ python -c 'print "A"*260' | ./heap //使用python生成260个字符,发送给二进制程序 ... [ALLOC][loc=8E3E4B0][size=823] Write to object [size=260]: Copied 261 bytes. [FREE][address=8E3A008] ... [FREE][address=8E3C058] [1] 7910 done python -c 'print "A"*260' | 7911 segmentation fault (core dumped) ./heap //crash了,如果是259个字符,则刚好不会crash1
2
## 由于之前有[size = 260]的描述,尝试发送260个字节setvbuf(stdout, 0, 2, 0); signal(14, sig_alarm_handler); alarm(0x5Au); //90秒后,调用sig_alarm_handler并将其杀死 mysrand(0x1234u); //可能是随机数种子设置(但每次都固定) puts("\nWelcome to your first heap overflow..."); puts("I am going to allocate 20 objects..."); puts("Using Dougle Lee Allocator 2.6.1...\nGoodluck!\n"); exit_func = do_exit; //复制退出时调用的函数指针 printf("Exit function pointer is at %X address.\n", &exit_func);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
# 静态分析
- 下载安装IDA Free版本
- https://www.hex-rays.com/products/ida/support/download.shtml
- 启动后,加载要分析的二进制文件并等待一段时间以完成分析
- Options→General更改一些设置
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019032503.jpg)
- Graph View的整体视图如下所示
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019032504.jpg)
- 基本上,可以通过call指令和引用字符串作为提示推测程序流程
- 即便不怎么读程序汇编代码,也能够通过使用方式来了解大概
- 很难一口气阅读所有内容
- 我试着一点一点分组阅读
- 对于很明显的循环部分,可以先放在一起
## 循环部分先放在一起,稍后再看
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019032505.jpg)
左边这些选中部分注释为ALLOC LOOP,由于左下方块中存在字符串ALLOC,因此可能执行此类处理
- 其他的循环也同样添加注释,最后看起来应该是这样的(很容易想象)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019032506.jpg)
点击这个将重新展开
## 分析第一块
- 堆栈顶部是变量
- 选择变量后按'N'键可以进行命名
- ':' 对这一行进行注释
- 'Insert'在这一行上方进行注释
- 'Shift'+'insert'在这一行下方进行注释
- 对于不知道的函数,可以通过`man <function name>`进行检索
- 深蓝色文字是符号,可以双击跳转
- 如果需要返回可以按'Esc',或者工具栏上的'←'按钮
- 粗略阅读时,使用这些会使得可读性很好
- 目的是为了理解代码内容以及解释反编译的代码
## 第一块看起来像这样
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019032507.jpg)
C语言代码大概像这样for ( i = 0; i <= 0x13; ++i ) //20次循环 { random_size = randrange(0x200u, 0x500u); //很容易理解的随机数生成 if ( i == 10 ) //第11次固定260 random_size = 260; array[i].ptr = malloc(random_size); //分配random_size的内存 array[i].size = random_size; //保存这个随机值 printf("[ALLOC][loc=%X][size=%d]\n", array[i].ptr, random_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
## 第二块看起来像这样
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019032508.jpg)
- 仔细看这段代码
- stack上看起来有ptr, size, ptr, size…这样的20个排列
- Structures窗口中,制作结构体会更加容易
- Insert→指定结构体名称
- 可以通过反复按'D'切换类型
- db: 1byte
- dw: 2byte
- dd: 4byte
- 按'N'对变量重命名
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019032509.jpg)
- 切换到Stack frame窗口,选中这个变量
- Edit→Struct var,应用创建的结构体
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019032511.jpg)
- 'N'重命名为array
- 然后使用数字键盘上的'*'进行数组排列
- size是20
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019032512.jpg)
- stack上进行结构体应用后大概像这样
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019032510.jpg)
- C语言代码大概像这样
-printf("Write to object [size=%d]:\n", array[10].size); count = get_my_line(&buff, 0x1000u); //stack上的buffer中读取最多0x1000个字符 memcpy(array[10].ptr, &buff, count); //复制到指针指向区域(产生堆溢出) printf("Copied %d bytes.\n", count);1
2
3
4
5
6
## 第三块看起来像这样
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019032601.jpg)
C语言代码大概像这样:for ( i = 0; i <= 0x13; ++i ) //20次free { printf("[FREE][address=%X]\n", array[i].ptr); free(array[i].ptr); }1
2
3
4
5
6
## 第四块看起来像这样
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019032602.jpg)
C语言代码大概像这样exit_func(1u); return 0;1
2
3
4
5
6
## 第五块看起来像这样
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019032603.jpg)
C语言代码大概像这样int main(void){ setvbuf(stdout, 0, 2, 0); signal(14, sig_alarm_handler); alarm(0x5Au); //90秒后,调用sig_alarm_handler并将其杀死 mysrand(0x1234u); //可能是随机数种子设置(但每次都固定) puts("\nWelcome to your first heap overflow..."); puts("I am going to allocate 20 objects..."); puts("Using Dougle Lee Allocator 2.6.1...\nGoodluck!\n"); exit_func = do_exit; //复制退出时调用的函数指针 printf("Exit function pointer is at %X address.\n", &exit_func);1
2
## 总体代码逻辑
for ( i = 0; i <= 0x13; ++i ) //20次循环
{
random_size = randrange(0x200u, 0x500u); //很容易理解的随机数生成
if ( i == 10 ) //第11次固定260
random_size = 260;
array[i].ptr = malloc(random_size); //分配random_size的内存
array[i].size = random_size; //保存这个随机值
printf(“[ALLOC][loc=%X][size=%d]\n”, array[i].ptr, random_size);
}
printf(“Write to object [size=%d]:\n”, array[10].size);
count = get_my_line(&buff, 0x1000u); //stack上的buffer中读取最多0x1000个字符
memcpy(array[10].ptr, &buff, count); //复制到指针指向区域(产生堆溢出)
printf(“Copied %d bytes.\n”, count);for ( i = 0; i <= 0x13; ++i ) //20次free
{
printf(“[FREE][address=%X]\n”, array[i].ptr);
free(array[i].ptr);
}exit_func(1u);
return 0;
}
时间序列表示堆的状态
20次重复,第11个固定260byte
指针及size一起存储在stack中
第11个节点进行堆溢出
从前到后依次free()
已知信息
只是申请内存,覆盖和释放
- 申请的内存不用于其他
是一道关于覆盖堆管理区域的问题
这也可以从启动消息中理解
Welcome to your first heap overflow…
I am going to allocate 20 objects…
Using Dougle Lee Allocator 2.6.1…
Goodluck!
看起来就是想让使用Unlink Attack