初级堆溢出-unlink漏洞

Linux下堆的unlink漏洞

参考文章:https://blog.youkuaiyun.com/qq_25201379/article/details/81545128

首先介绍一下Linux的堆块结构:

struct malloc_chunk {
INTERNAL_SIZE_T prev_size;
INTERNAL_SIZE_T size;
struct malloc_chunk *fd;
Struct malloc_chunk *bk;
}

0x01、其中前两个结构体成员组成了堆块的块首:1、prev_size字段仅在该堆块是空闲时有意义,代表了前一个堆块的size(包括块首的大小在内),注意国内很多相关blog中将prev解释成“后一个”堆块,这非常的别扭,此处我们将prev当作前一个。 2、size字段表示该堆块的大小,由于堆块的大小必须是8字节的整数倍,因此size字段的后三个二进制位是不表示大小的,因此作为其他标志位,我们需要知道的是,最后一位表示prev堆块是否空闲,若prev空闲,则最后一位为0。 3、至于fd和bk,也是在空闲堆块中才有意义的数据,应用在双链表中表示前后堆块(指向块首),而在in_use堆块中它们是用户数据。

0x02、关于malloc的返回值:malloc返回的指针是用户态指针,是指向chunk_body的,不包括块首,而malloc的参数表示的size也是用户态size

0x03、链表安全检查:Linux的堆内存管理有一个很重要的机制,会检查  p->bk->fd==p && p->fd->bk==p,我们不讨论宏中没有这个机制的漏洞利用

0x04、漏洞利用详解:我们不先讲原理,直接看过程来体会

(图中8byte有误,应该是16byte,图片不方便换了,聊以填坑。。)

我们需要至少两个堆块,堆内存分布如上,此处不要引起误会,虽然每个chunk都列出了fd和bk字段,但是只是为了方便读者参考,并不代表这些堆块是空闲的

首先malloc p堆块和引线堆块,那么如果放到空闲链表中看,p就是引线堆块的prev_chunk,但是此时两个堆块都是非空闲的。

往下方是高地址这个不用多说,我们在p堆块中打一个溢出,踩到引线堆块,怎么踩呢?要把引线堆块的prev_size覆盖成p堆块的用户区大小,把引线堆块的size字段的最后一个二进制位弄成0,这样就造成了p堆块是free态的假象;此外在这个溢出过程中,由于写操作是从p发起的,要顺便看一下能否写到p_user的2和3个的偏移,也就是p_user[2]和p_user[3],如果能,就把它们分别覆盖成fake的fd和bk,不能的话也不慌,寻找一下有没有别的可行写操作或者可以从更低地址堆块打来溢出。

那么fake的fd和bk应该改成多少呢?注意改了以后还要过链表安全检查的!我们来看一个巧夺天工的构造:

fd = &p - 3*size(int); bk = &p - 2*size(int) 我们来分析一下这个小小的艺术品奇妙在哪里:

首先我们需要清楚结构体的寻址是按偏移来寻址的,然后我们来看一下free函数的具体实现:

FD = p->fd;
BK = p->bk;
FD->bk = BK;
BK->fd = FD;

 其实就是一个很简单的双向链表拆卸,不多说;

然后我们来看,fd = &p - 3*size(int),bk = &p - 2*size(int) ,所以FD=&p - 3*size(int) , BK=&p - 2*size(int)

FD->bk按照偏移寻址,就是FD+3*size(int)==&p,FD->bk==p,同理 BK->fd==p,这样一来就绕过了安全检查

检查通过就按照上述代码来free,第三行等价于p=&p - 2*size(int)

但是第四行又把值覆盖回来了,最终执行完毕后就变成了p=&p - 3*size(int)

回到本例中,此时如果程序free(引线堆块),那么由于检查到引线堆块的size最后一位是0,因此认为p堆块空闲,进行合并,触发p的unlink

现在大家应该就明白“引线堆块”这个名字是怎么来的了,free相当于点火,堆块就是引线,触发prev的unlink爆炸

还记得我们已经将引线堆块的prev_size覆盖成了prev用户区的大小,因此会造成一种假象,认为prev用户区的起始就是prev的块首起始,因此做unlink时,进行fd和bk操作的时候,fd和bk就能成功定位到之前的fake值!

这样一来,在没有触发检查报警的情况下,成功将指针p_user劫持到了存放p_user自己的内存往上三个单位的内存处:

(图中p为p_user)

 

此时p_user=&p_user - _3

但是,此时在受害程序视角上,堆块p并非空闲态,也就是说,此时程序可能继续以指针p_user为接口继续进行读写操作。此时,便可以为所欲为。

这里举例一个具体操作:(p指p_user)

比如接下来存在p堆块的写操作,原程序中正常的堆块操作是,首先写p[3],然后写p[0]

假定我们现在已经知道了libc在内存中的映像地址,即得知了&free_got(free_plt)、system_got

那么我们就可以在写操作中执行p[3]=&free_got,p[0]=system_got

这两个操作分别实现的效果是,p[3]指向p即p[0],将p[0]的值即p的值改成了&free_got,之后p[0]=system_got,就相当于实现了*(&free_got)=system_got

这样一来,在程序执行任何free操作的时候,都会被劫持到system函数,get shell

当然有一个问题忘了提到,就是伪造fd和bk的时候,我们需要事先知道&p的值,这个估计需要具体的内存泄露,不再赘述。

希望对各位pwn?们有所裨益,有不当之处欢迎指正!

(尊重版权,转载请注明出处,谢谢!)

 

转载于:https://www.cnblogs.com/Magpie/p/9705942.html

### 陶晶驰串口单片机通信的数据发送方法 #### 单片机侧准备 为了使陶晶驰串口能够向单片机发送数据,首先需要确保单片机已经配置好用于接收来自串口的信息。这通常涉及到初始化UART接口,并设置相应的波特率以匹配串口的要求[^1]。 对于基于STM32系列的单片机而言,可以通过如下方式完成基本配置: ```c // 初始化USART外设函数 void USART_Init(void){ // 配置GPIO引脚作为USART功能使用... // 设置USART参数, 如波特率为9600bps等... // 开启USART中断允许标志位... } ``` #### 串口编程 接着,在串口上定义触控区域(Touch Area),当用户点击这些预设好的按钮时,会触发相应事件并向连接着的单片机发出指定命令字符串或数值。例如,如果希望实现按下某个键后往单片机传递字符'2'的操作,则可以在串口编辑界面里编写类似下面这样的语句: `t0.txt=t0.txt+"2"` 这里假设`to`代表了某一个文本框对象;每当此表达式被执行一次,“2”就会被追加到该文本框的内容后面去[^3]。 实际上,更常见的是直接利用串口内置的功能来构建交互逻辑而无需手动拼接字符串。比如创建一个名为`Button_1`的按钮组件,并为其绑定动作——一旦检测到触摸行为就立即输出固定格式的消息至外部设备(即这里的单片机)。 #### 连接测试 最后一步就是物理上的连线工作:将串口的TXD管脚接到单片机对应的RXD管脚上,同时注意两者间GND信号的地线也要相连以便形成完整的回路[^2]。此时便可通过串口助手工具或者其他手段验证整个链路上下文消息能否正常流通无误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值