论坛首页 漏洞分析研究区 阅读主题

[原创]堆学习:Unlink attack

358 浏览 0 回复
#1 楼主 2026-06-01 21:09:19
58fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0N6r3k6Q4x3X3c8%4K9h3E0A6i4K6u0W2L8%4u0Y4i4K6u0r3M7s2N6F1i4K6u0r3L8r3W2F1N6i4S2Q4x3V1k6#2M7$3g2J5i4K6u0V1L8h3!0V1k6g2)9J5c8X3S2W2j5i4m8Q4x3V1k6H3N6r3#2S2L8r3I4G2j5K6u0Q4x3V1k6A6L8i4m8D9k6h3#2W2L8Y4c8S2N6r3W2G2L8W2)9J5c8X3u0S2M7$3W2U0i4K6u0r3959K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0N6r3k6Q4x3X3c8%4K9h3E0A6i4K6u0W2L8%4u0Y4i4K6u0r3M7s2N6F1i4K6u0r3L8r3W2F1N6i4S2Q4x3V1k6#2M7$3g2J5i4K6u0V1L8h3!0V1k6g2)9J5c8X3S2W2j5i4m8Q4x3V1k6H3N6r3#2S2L8r3I4G2j5K6u0Q4x3V1k6A6L8i4m8D9k6h3#2W2L8Y4c8S2N6r3W2G2L8W2)9J5c8X3k6J5k6h3g2Q4x3V1j5`.c6aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0N6r3k6Q4x3X3c8%4K9h3E0A6i4K6u0W2L8%4u0Y4i4K6u0r3M7s2N6F1i4K6u0r3L8r3W2F1N6i4S2Q4x3V1k6#2M7$3g2J5i4K6u0V1L8h3!0V1k6g2)9J5c8X3S2W2j5i4m8Q4x3V1k6H3N6r3#2S2L8r3I4G2j5K6u0Q4x3V1k6#2L8X3I4A6L8X3E0Q4x3V1j5`.在 Linux 的 glibc 堆管理机制中,unlink是一个核心操作,其字面意思为“脱链”。当堆管理器释放某一 chunk时,如果发现其相邻的 chunk 处于空闲状态,便会尝试将这两个 chunk 合并成一个更大的空闲 chunk。为了完成合并,就需要将相邻的空闲 chunk 从其所在的双向链表(例如 small bins、unsorted bins 等)中取出,这个“取出”的过程就是unlink。以下两段代码分别是free时的后向合并和前向合并,可以看到都有unlink操作Unlink 攻击的核心在于,通过漏洞(如堆溢出、off-by-one 等)篡改堆块的关键元数据,伪造一个“看似合法”的空闲 chunk 结构。当程序后续进行unlink操作时,会按照我们伪造的指针执行写入操作,从而实现任意地址写。以下这段源码是从ctfwiki复制过来的,我觉得里面的注释已经很详细了分别对应以下三个检查针对这些检查,攻击者需要精心构造 fd和 bk指针来绕过。一个经典的绕过方法是:将伪造 chunk 的 fd指针设置为 &target_address - 0x18。将 bk指针设置为 &target_address - 0x10。这样,在检查 FD->bk(即 *(fd + 0x18))和 BK->fd(即 *(bk + 0x10))时,计算出的地址都恰好指向 target_address本身,如果 target_address处存储的正是指向这个伪造 chunk 的指针 P,检查就能被绕过。随后,unlink操作会执行 FD->bk = BK和 BK->fd = FD,其最终效果是将 target_address处的值修改为 &target_address - 0x18。如果 target_address是某个全局函数指针(如 free@got)或重要数据,攻击者就获得了修改它的能力。具体操作可以看例题一的分析过程。查看保护,Partial RELRO说明got表可写从main函数可以看到v3有4种取值:1 2 3 4当v3=1时,可以申请自定义大小的内存,返回的数据部分地址存入了全局变量s,dword_602100是已申请的个数。(&::s)[++dword_602100] = v2等价于dword_602100+=1;s[dword_602100]=v2;第一次申请时,就是把返回的地址存入0x602148这个位置,也就是s[1],从1开始存。当v3=2时,可以自定义写入内存的长度,存在堆溢出,可以覆写高地址chunk的内容。当v3=3,就是一个free chunk的功能,没啥毛病当v3=4时,emmmm好像啥也没用看了这四个函数之后,可以知道v3=1时是一个申请内存的功能,v3=2时是往内存里写东西,存在堆溢出,v3=3时是回收内存,v3=4啥也没用。先写下三个函数的交互先申请三个内存,数据部分大小分别是0x20,0x30,0x80,记为chunk1,chunk2,chunk3 。chunk2和chunk3是相邻的chunk接着利用编辑功能往chunk2写入内容,主要是伪造一个fake chunk,绕过unlink的两个检查,修改chunk3的inuse标志位。接着我们free chunk3,因为chunk3的size大于fastbin的最大值,所以不会放入fastbin,而且inuse标志位被我们改成了0,此时就会后向合并触发unlink,会把p-presize的chunk出链,这里的p指的是chunk3,也就是0x1f8bc490-0x30=0x1f8bc460,刚好是我们伪造的fake chunk。后向合并对应源码这个部分。之后就会触发unlink了,unlink的时候会有三个检查,因为chunk3不是largebin,我们注意绕过前两个检查就行。也就是以下源码的前两个if第一个检查是chunksize(P) != prev_size (next_chunk(P)),这里的P指的是fake chunk,左边的chunksize(P)是0x20,右边的prev_size(nextchunk(P))就是P+size位置,带入我们刚才的地址,p+size也就是0x1f8bc460+0x20=0x1f8bc480,这个位置的值可不就是0x20,成功绕过了。接下来看第二个检查,我们先跟着这个源码走,代入我们的payload。这里的P指的是fake chunk的地址,FD = P->fd = &s[2] - 0x18;接着BK = P->bk = &s[2] - 0x10fd指针是在chunk的0x10偏移处,bk指针是在chunk的0x18偏移处,那么 __builtin_expect (FD->bk != P || BK->fd != P, 0)这个检查里 FD->bk = FD + 0x18 = s[2];BK -> fd = BK + 0x10 = s[2];此时s[2]存储的是fake chunk的地址也就是P,那这个检查不就饶过了吗!接着看下面的操作FD->bk = BK;从上面分析过程我们知道 FD ->bk = s[2];BK = &s[2]-0x10;那FD->bk = BK不就变成了s[

...(已截断)

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-289464.htm

暂无回复,快来抢沙发吧!

请登录后参与讨论

立即登录 注册账号