fuzzing?fuzzing!(ᗜᴗᗜ)
| 0 | 前言
如果你也遇到过:IDA反编译出来的数据结构充满了各种交叉引用与奇奇怪怪的数据结构,别说逆向分析了,就算是用gdb调试出来都是越看越晕✋别担心,fuzzing会帮你解决一切😇😇😇
| 1 | 原理
通过查询gpt
,我们获得以下信息
Fuzzing,中文一般翻译为“模糊测试”,是一种自动化测试技术,主要用于发现程序中的漏洞和异常行为。它的核心思想就像是:
「嘿
我往你的程序里灌各种奇怪、乱七八糟的输入,看看你会不会崩溃(*≧▽≦)ノシ)」
比如你有个程序需要用户输入用户名,那Fuzzer就会试着输入:
- 超长字符串(比如上万字节的“AAAAAAAA…”)
- 特殊字符(比如
"; DROP TABLE users; --
) - 随机的二进制数据
- 不合规范的结构化数据(比如错误格式的PNG、PDF)
通过不断“瞎试”,来诱发程序中的各种bug,比如:
- 崩溃(Crash)
- 越界访问
- Use-After-Free(UAF)
- 内存泄露
- 未处理的异常
当我们完全看不懂数据结构的内部逻辑时,我们可以通过灌入大量数据,通过python捕捉uaf
,overflow
等错误,以此我们可以找到达成漏洞利用的具体手段,作为一个模块用来稳定获得一个漏洞,再在这个漏洞上不断利用,最后getshell
| 2 | ACTF–让我们解开R树的秘密
题目链接: actf 2022
注: 本题的libc版本为2.27
2.1 几乎没有意义的逆向分析
我们使用IDA打开elf文件后,可以看到这是个完整的菜单题
往左边的函数栏一瞟,被吓了一跳:每个操作函数函数(insert
,delet
,edit
,show
和query
)都由大量子函数进行数据结构的维护
这导致如果我们从IDA分析,逆向难度会非常大,通过题目名字和几个维护函数我们也能猜出来本次管理堆块的数据结构是R树
那么R树
是什么呢?
R树的介绍
R树(Rectangle-tree 或 Region-tree) 是一种**用来存储多维空间数据(比如矩形、坐标点、地理位置)**的数据结构。它就像是空间版本的 B 树,用于高效地执行:
- 📍范围查询(range query):找出所有在某个区域内的点或图形
- 🧲邻近查询(nearest neighbor):找出离你最近的对象
- 📦插入、删除、更新空间对象
比起传统的哈希表或数组,R树在“处理空间位置”这种问题时简直不要太强!
R树是一种 高度平衡的树结构,跟 B 树有点像,但它的每个节点存的不是单个值,而是:
✅ 一个“最小包围矩形(MBR,Minimum Bounding Rectangle)”——用来包住子节点或实际对象。
举个栗子🌰:
- 叶子节点存放:真实的矩形、点等空间对象
- 非叶子节点存放:子节点的 MBR,代表这个子节点下所有数据的范围
使用css
表示为
1 | ┌────────────┐ |
不知道你们看懂多少,反正我是一点没看懂
对大概操作的理解
虽然我们基本上不可能看懂R树
,但可以简单逆向一下几个函数的功能
add函数
通过输入x
,y
确定堆块的索引可以申请
0x30
的堆块,并以某种规则插上R树
,可以且必须输入0x20
的数据delet函数
通过
x
,y
索引寻找堆块,然后对堆块进行释放,置零(我们没法简单double free)edit函数
通过
x
,y
索引寻找堆块,编辑堆块,必须输入0x20
的内容show函数
通过
x
,y
索引寻找堆块,使用write函数打印堆块中的内容query函数
接收用户输入的矩形范围(左下角和右上角),然后在 R 树中查找所有在这个区域内的元素并输出它们的名字(这不是和show函数功能重合了吗🤔)
2.2开始fuzzing!
我们先在exp中模拟好这5个交互函数
1 | def cho(num): |
现在我们要开始请”猴子先生”开始随机敲击键盘😋
但是我们也不能真的随便瞎按,我使用了一下的约束保证fuzz不会太离谱
- 只能释放已经建立的堆块
- 堆块的索引不能重复
- 将每次fuzz的结果放在一个txt中
- 自定义一个
EOF
保证fuzz只有在double free
时才会停止
我们可以使用以下脚本
1 | class TooManyElementsError(Exception): #如果树满了我们还没找到异常,就抛出这个 |
运行这个脚本,不一会儿我们就能找到符合条件的利用过程(换成小于40行了,我发现小于20行半天爆破不出来)
总之我们此时获得了一个可以double free的手段
2.3 人脑代替电脑思考-堆利用
double free的劫持
1 | add(1,1,str(10).encode()) |
这个过程可以让我们得到一个可以编辑且已经被释放的堆块(1,3)
此时我们可以泄露堆地址,然后任意地址写(既然只知道堆地址,也就是堆上任意地址写)
我们劫持什么呢🤔,首先我们先梳理一下目前我们的状态
- 我们有一次任意地址写0x20字符,且必须写满0x20
- 基于fuzz的堆风水已经让堆很乱了,再次堆风水创造一个
double free
对我而言基本上不可能
也就是我们这次劫持的地址必须能让我们拥有持续利用堆的能力
teache_struct
teachebins
从libc2.26引入,用于管理较小堆块,其中负责管理的数据结构称为teache_struct
1 | typedef struct tcache_perthread_struct {//TCACHE_MAX_BINS=0x40 |
这个结构体由一个int8/int16数组和一个指针数组组成
其中
- int8/int16数组负责记录每种teachebins的数量
- 指针数组负责记录表头的next指针
回到堆利用,如果我们能劫持这个结构,就能实现任意地址写,而且本题的edit是不限次数的,也就是说我们想申请什么就申请什么
此时我们使用gdb观察劫持前的bins情况
这个是我们可以利用的double free chunk
由于每次申请的堆块太小了,我们想要劫持teache_struct
需要分两次:number数组
,指针数组
但是目前我们只能劫持一次,所以我们可以
- 先劫持指针数组
- 使用指针数组为我们分配一个
number数组
- 再劫持
number数组
,此时相当于控制了teache_struct
1 | edit(1,3,p64(heap+0x10+0x48)) |
保持(4,2)中不小于等于0,在(4,3)中写入想要劫持的地址
泄露libc
每次我们只能建立一个0x30
的堆块,很明显放不进unsortbins🤔
但是我们可以任意堆地址写,我们人为把堆的head中的size改一下不就行了嘛😋
随便一找,找到几个总大小为0x220的堆块,且不直接接触top_chunk
修改后的堆如下
顺便在(4,2)中把0x220的teachebins数量改为7,这样就会直接进入unsortbins
中了
成功让libc地址出现在heap中,最后让query在一个比较大的范围内打印,找找就能看到libc信息
1 | head=heap+0x580 |
最后一步,劫持__free_hook
做到这里基本上就相当于打完了.有了堆,libc地址,可以任意分配堆块–我们从一个小小的uaf里挖掘出这么多的资产
1 | fhook=base+libc.sym.__free_hook |
| 3 | EXP+fuzz
1 | from pwn import * |
1 | from pwn import * |
| 3 | UIUCTF 2024RUST
这是一道rust编写的堆菜单,我看不懂rust,所以IDA也用不上了,对着gdb和shell盲打
create可以选择创建rules和notes两个不同的堆块
通过gdb调试发现,rule堆块在创建后立马被释放,而notes堆块会保存下来
delet,read,edit都要指清楚堆块的类型和编号
其中edit需要我们输入64字节数据
**Make a law **直接给了libc地址,libc为2.31
可能造成堆利用的步骤分别是create,delet和edit
那我们的fuzzer就要包含这三个操作,并添加简单的约束
- 只能delet,edit已经创建的堆块
- 时刻记录两种堆块分别的数量
- 用尽量少的步骤(<=20),最后加上exit退出程序
1 | import random |
同时使用fuzz脚本运行
1 | !/bin/bash |
跑一会儿就出现异常退出,此时检查poc,可以看到具体的操作
进行简单包装后使用gdb调试
1 | ch(1) |
发现了很明显的uaf漏洞
我们掌握了libc地址,那就劫持**__free_hook**完事了
还需要把poc中的操作仔细看一遍,把chunk的编号标记清除
最终得到exp
1 | from pwn import * |
参考链接: