linux如何防止堆空间被覆盖,堆溢出学习笔记(linux)

本文主要是linux下堆的数据结构及堆调试、堆溢出利用的一些基础知识

首先,linux下堆的数据结构如下

/*

This struct declaration is misleading (but accurate and necessary).

It declares a "view" into memory allowing access to necessary

fields at known offsets from a given base. See explanation below.

*/ struct malloc_chunk { INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */ INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */ struct malloc_chunk* fd; /* double links -- used only if free. */ struct malloc_chunk* bk; /* Only used for large blocks: pointer to next larger size. */ struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ struct malloc_chunk* bk_nextsize; };

prev_size, 如果该 chunk 的物理相邻的前一地址chunk(两个指针的地址差值为前一chunk大小)是空闲的话,那该字段记录的是前一个 chunk 的大小(包括 chunk 头)。否则,该字段可以用来存储物理相邻的前一个chunk 的数据。这里的前一 chunk 指的是较低地址的 chunk。

size ,该 chunk 的大小,大小必须是 2 * SIZE_SZ 的整数倍。如果申请的内存大小不是 2 * SIZE_SZ 的整数倍,会被转换满足大小的最小的 2 * SIZE_SZ 的倍数。32 位系统中,SIZE_SZ 是 4;64 位系统中,SIZE_SZ 是 8。 该字段的低三个比特位对 chunk 的大小没有影响,它们从高到低分别表示

NON_MAIN_ARENA,记录当前 chunk 是否不属于主线程,1表示不属于,0表示属于。

IS_MAPPED,记录当前 chunk 是否是由 mmap 分配的。

PREV_INUSE,记录前一个 chunk 块是否被分配。一般来说,堆中第一个被分配的内存块的 size 字段的P位都会被设置为1,以便于防止访问前面的非法内存。当一个 chunk 的 size 的 P 位为 0 时,我们能通过 prev_size 字段来获取上一个 chunk 的大小以及地址。这也方便进行空闲chunk之间的合并。

fd,bk。 chunk 处于分配状态时,从 fd 字段开始是用户的数据。chunk 空闲时,会被添加到对应的空闲管理链表中,其字段的含义如下

fd 指向下一个(非物理相邻)空闲的 chunk

bk 指向上一个(非物理相邻)空闲的 chunk

通过 fd 和 bk 可以将空闲的 chunk 块加入到空闲的 chunk 块链表进行统一管理

fd_nextsize, bk_nextsize,也是只有 chunk 空闲的时候才使用,不过其用于较大的 chunk(large chunk)。

fd_nextsize 指向前一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。

bk_nextsize 指向后一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。

一般空闲的 large chunk 在 fd 的遍历顺序中,按照由大到小的顺序排列。这样做可以避免在寻找合适chunk 时挨个遍历。

以上内容摘自CTF-WIKI https://ctf-wiki.github.io/ctf-wiki/pwn/heap/heap_structure/#top-chunk

给出一个简单堆溢出的例子

#include

int main(void)

{char *chunk;

chunk=malloc(24);

puts("Get input:");

gets(chunk);return 0;

}

gcc -no-pie -p example1 example1.c编译程序

objdump -d example1查看main函数地址,然后gdb在main函数起始位置下断点。

f7aba0fb92ba9fe35016c07491c93240.png

当执行到0x400589时查看rax内容即为malloc分配堆的起始地址。

08149e7e33309a262df35f419384ff89.png

执行完0x405a5时 x/10xg 0x602250用‘A'*100覆盖堆查看堆溢出情况(Gdb指令查看手册https://darkdust.net/files/GDB%20Cheat%20Sheet.pdf)

29f28644884f1ece31668e8d8af6f3ee.png

这里查看0x602250的原因是INTERNAL_SIZE_T默认和size_t一致,32位系统下size_t 4字节,64位系统下size_t 8字节。malloc返回的堆地址指针实际是struct malloc_chunk* fd的fd,所以64位系统下查看堆首需要用返回的堆地址减去16字节的堆头。

我们在重新看一下堆覆盖之前的堆内容

48371f2b6c2b6e98856ef34455e61d97.png

size部分的最后三个字节分别表示特定含义(见上数据结构),用户真正可用的堆地址是0X602260-0X60226F,共16字节。申请24字节分配16字节的原因是32位系统8字节对齐,64位16位对齐;而分配的16字节能够存储24字节的原因是借用了下一块的pre_size域,16+8=24

关于申请内存到实际分配内存的转换

/*pad request bytes into a usable size -- internal version*/

//MALLOC_ALIGN_MASK = 2 * SIZE_SZ -1

#define request2size(req) \(((req)+ SIZE_SZ + MALLOC_ALIGN_MASK

: ((req)+ SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

当申请内存+堆头大于MINSIZE时,返回((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)这个结果。这个结果的意思是(用户申请内存大小+堆头)&~MALLOC_ALIGN_MASK。以64位系统申请24字节堆块为例,24+堆头=40 (101000);MALLOC_ALIGN_MASK 01111取反10000,按位与的结果就是100000了,即32。

这样做可以满足用户申请内存需求的原因是,~(2*SIZE_SZ-1)其实就是操作系统的双字长,比如64位系统就是10000,这样按位与就能保证分配的内存最低4位为0,也就保证了分配堆块的字节对齐。

0X602270是top chunk的内容,top chunk是在第一次执行malloc时heap 会被分为两块,一块给用户,剩下的那块就是 top chunk。top chunk就是当前堆的物理地址最高chunk,这个chunk不属于任何bin。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值