ctf-pwn-堆-调试

零、目的

        写这篇文章的目的是为了更好地去理解堆,不要只停留在理论知识阶段

        注:读这篇文章需要一定的堆基础,可以先去下面的参考链接学一些基础知识,也可以自行找一些堆相关文章。

        参考链接:ctf-wiki

一、调试环境

        ubuntu16.04

        参考链接:ubuntu16.04安装教程(需翻墙访问,去年写的教程,不知道还能不能用)

二、源C代码&编译指令

//源C代码  cs1g.c
//gcc -g cs1g.c -o cs1g
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
    int i;
    void *xy1,*xy2,*xy3,*xy4,*xy5,*xy6; //0之前vmmap指令查看不到heap堆

    xy1 = malloc(0x30);
    xy2 = malloc(0x30);
    xy3 = malloc(0x30);
    free(xy1);

    *(long long *)((long long)xy2) = 0x112233445566778899121314151617181920;
    *(long long *)((long long)xy3) = 0x55667788;
    xy4 = malloc(0x30);
    xy5 = malloc(0x30);
    free(xy2);
    xy6 = malloc(0x30);
    free(xy3);
    free(xy4);
    free(xy5);
    free(xy6);

    return 0;
}

三、一些调试指令

pwndbg> top_chunk             #打印top chunk初始地址
pwndbg> vmmap                 #查看vmmap表
pwndbg> bin                   #查看bin表
pwndbg> heap                  #查看堆

pwndbg> file ./<二进制文件名>  #导入源代码,方便调试
pwndbg> x/150gx 0x<地址>      #打印堆信息  x/150gx 0x555555559000
pwndbg> x/150xb 0x<地址>

四、调试过程 

1、目前还未新建堆

2、新建一个大小为0x30的堆块

 3、新建三个大小为0x30的堆块

 4、free(xy1); free操作,xy2、xy3写入数据

 

5、 chunk xy4

6、chunk xy5

7、free(xy2);

8、chunk xy6

9、free(xy3-xy6); ,bin中出现4个大小为0x40的空闲堆块

按先后顺序依次free xy3-xy6 ,则 fastbin 中chunk逻辑连接顺序为xy6->xy5->xy4->xy3

物理连接:内存地址上相互相邻

逻辑连接:由指针前后相连,内存地址上可以不相邻

注:仅给出调试过程与部分知识,其他有关堆基础的知识请自行去别的地方学

其他:

//gcc -g xxx.c -o xxx
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
    int i,num;
    num = 0x90;
    void *xy1,*xy2,*xy3,*xy4,*xy5,*xy6; //0之前vmmap指令查看不到heap堆

    xy1 = malloc(num);
    xy2 = malloc(num);
    xy3 = malloc(num);
    free(xy1);

    *(long long *)((long long)xy2) = 0x112233445566778899121314151617181920;
    *(long long *)((long long)xy3) = 0x55667788;
    xy4 = malloc(num);
    xy5 = malloc(num);
    free(xy2);
    xy6 = malloc(num);
    free(xy3);
    free(xy4);
    free(xy5);
    free(xy6);

    return 0;
}

上面的代码中执行free()时相邻的chunk会被合并所以不太直观

//gcc -g xxx.c -o xxx
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
    int i,num;
    num = 0x90;    //修改num值用于观察small bin、large bin等
    void *xy1,*xy2,*xy3,*xy4,*xy5,*xy6,*xy7,*xy8; //0之前vmmap指令查看不到heap堆

    xy1 = malloc(num);
    xy2 = malloc(num);
    xy3 = malloc(num);
    xy4 = malloc(num);
    xy5 = malloc(num);
    xy6 = malloc(num);
    xy7 = malloc(num);
    xy8 = malloc(num);
    free(xy2);
    free(xy4);
    free(xy6);
    free(xy8);

    return 0;
}

下图中我们可以看到xy8与top chunk合并了,xy8的size为top chunk的size+xy8的size

同时xy2->xy4->xy6,可以观察他们的fd和bk(图中的fk改成fd)

 chunk未被分配时:

        fd指向前一个空闲chunk(指链表中的前一个,物理地址可能不相邻)

        bk指向后一个空闲chunk

将前一个代码中的num修改为0x420, 触发的还是unsortedbin,这不是我想要的结果...

参考链接:large bin

ok,参考以上链接,我们写个新代码试试能不能触发

//gcc -g xxx.c -o xxx
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
    int i,num;
    num = 0x420;    //修改num值用于观察small bin、large bin等
    void *xy0,*xy1,*xy2,*xy3,*xy4,*xy5,*xy6,*xy7,*xy8; //0之前vmmap指令查看不到heap堆

    xy0 = malloc(num);
    xy1 = malloc(num-0x20);
    xy2 = malloc(num+0x20);
    free(xy1);    //占据unsortedbin
    xy3 = malloc(num+0x40);
    xy4 = malloc(num+0x60);
    free(xy3);
    xy5 = malloc(num+0x80);
    xy6 = malloc(num+0x100);
    free(xy5);
    xy7 = malloc(num+0x120);
    xy8 = malloc(num+0x140);
    free(xy7);

    return 0;
}

 ok,成功触发large bin

因为每个chunk大小不一致,所以large bin并没有形成链,所以每个被free的chunk的fd和bk都指向自身,这句话只是对结果的分析不知道对不对(应该是不对的,请自行找相关文章了解),我对堆的了解还比较少,有兴趣的可以自行修改代码调试

夹在large bin的size和fd、bk之间的应该就是fd_nextsize与bk_nextsize

总结:

        参考链接:堆相关数据结构

        参考链接:

        1、unsortedbin 只能存同一大小的多个空闲堆块(被free的chunk)

        2、fast bin 最大存储大小为 0x80 的空闲堆块, fastbin 最多可以支持的 bin 的个数为 10 个(我目前的理解是能有10条链,unsortedbin只能有一条链)

        3、small bins 中一共有 62 个循环双向链表,每个链表中存储的 chunk 大小都一致

        4、large bins 中一共包括 63 个 bin,每个 bin 中的 chunk 的大小不一致

        5、前向合并&后向合并

        6、..........

笔记就做到这,主要目的是供自己复习。

五、use after free(UAF)

参考链接:UAF

参考链接:ctf-wiki

例1 引用参考链接中的代码

//gcc -g uaf.c -o uaf -m32
#include <stdio.h>
#include <cstdlib>
#include <string.h>
int main()
{
    char *p1;
    p1 = (char *) malloc(sizeof(char)*10);//申请内存空间
    memcpy(p1,"hello",10);
    printf("p1 addr:%x,%s\n",p1,p1);
    free(p1);//释放内存空间
    char *p2;
    p2 = (char *)malloc(sizeof(char)*10);//二次申请内存空间,与第一次大小相同,申请到了同一块内存
    memcpy(p1,"world",10);//对内存进行修改
    printf("p2 addr:%x,%s\n",p2,p1);//验证
    return 0;
}

通过以上代码我对 UAF 理解是:以为glibc的堆回收机制free(p1)后并没有真正释放掉p1,而是把堆块放进了 bin 的 fastbin 中,所以p1指针还是指向了原堆块的物理地址(内存地址,或者说p1的值没变成空),当p2新申请一块堆块时,因为大小与原堆块一致,所以把bin中p1申请的堆块给了p2(也就是把p2的值变为原堆块的物理地址),这是指向原堆块的指针就有了p1、p2,所以p1、p2都可以修改原堆块的数据。

这例子感觉没啥好调的,这里就不调试了。

例2 修改自参考链接中的代码

//源C代码  cs1i.c
//gcc -g -z norelro -no-pie -z execstack -fno-stack-protector -o cs1i cs1i.c -m32
//gcc -g -o cs1i cs1i.c -m32
#include <stdio.h>
#include <stdlib.h>

typedef void (*func_ptr)(char *);

void evil_fuc(char command[])
{
    system(command);
}

void echo(char content[])
{
    printf("%s",content);
}

int main()
{
    int a=1;
    func_ptr *p1=(func_ptr*)malloc(4*sizeof(int));
    printf("malloc addr: %p\n",p1);
    p1[a]=echo;
    printf("p1=%p &p1=%p *p1=%p p1[0]=%p &p1[0]=%p p1[a]=%p &p1[a]=%p\n",p1,&p1,*p1,p1[0],&p1[0],p1[a],&p1[a]);
    p1[a]("hello world\n");
    free(p1); //在这里free了p1,但并未将p1置空,导致后续可以再使用p1指针
    p1[a]("hello again\n"); //p1指针未被置空,虽然free了,但仍可使用.
    func_ptr *p2=(func_ptr*)malloc(4*sizeof(int));//malloc在free一块内存后,再次申请同样大小的指针会把刚刚释放的内存分配出来.
    printf("malloc addr: %p\n",p2);
    printf("malloc addr: %p\n",p1);//p2与p1指针指向的内存为同一地址
    p2[a]=evil_fuc; //在这里将p1指针里面保存的echo函数指针覆盖成为了evil_func指针.
    p1[a]("/bin/sh");
    return 0;
}

运行结果如下:

①:*p1为空是因为堆上没有数据,全0

②:p1[1]是指p1存储的堆地址往后4*8=32个字节,同理p1[2]是指p1往后64个字节,同时p1、&p1[0]、&p1[1]、&p1[2]都是堆地址

③:p1[a] = echo; 是指把echo函数的地址写入&p1[a]这一地址的存储单元中,p1[q]就是这一存储单元,同时也是我们申请的堆存储单元(堆空间、堆),总之就是echo的函数地址被写入到了我们申请的堆空间中的某个位置。从上面的p1[a]=0x8048492可知echo函数的地址为0x8048492

通过readelf -a cs1i就可以查找echo函数的地址

 这例子我感觉也没啥好调的,有兴趣的可以自己调调看。

六、fastbin attack—double free

        链接:fastbin attack - double free

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值