fuzzing?fuzzing!()
| 0 | 前言
如果你也遇到过:IDA反编译出来的数据结构充满了各种交叉引用与奇奇怪怪的数据结构,别说逆向分析了,就算是用gdb调试出来都是越看越晕✋别担心,fuzzing会帮你解决一切😇😇😇
| 1 | 原理
通过查询gpt,我们获得以下信息
Fuzzing,中文一般翻译为“模糊测试”,是一种自动化测试技术,主要用于发现程序中的漏洞和异常行为。它的核心思想就像是:
「嘿我往你的程序里灌各种奇怪、乱七八糟的输入,看看你会不会崩溃(*≧▽≦)ノシ)」
比如你有个程序需要用户输入用户名,那Fuzzer就会试着输入:
超长字符串(比如上万字节的“AAAAAAAA…”)
特殊字符(比如 “; DROP TABLE users; – )
随机的二进制数据
不合规范的结构化数据(比如错误格式的PNG、PDF)
通过不断“瞎试”,来诱发程序中的各种bug,比如:
崩溃(Crash)
越界访问
Use-After-FreeUAF
内存泄露
未处理的异常
当我们完全看不懂数据结构的内部逻辑时,我们可以通过灌入大量数据,通过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): #如果树满了我们还没找到异常,就抛出这个 |
这个结构体由一个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 * |
同时使用fuzz脚本运行
1 | !/bin/bash |
跑一会儿就出现异常退出,此时检查poc,可以看到具体的操作
进行简单包装后使用gdb调试
1 | ch(1) |
发现了很明显的uaf漏洞
我们掌握了libc地址,那就劫持**__free_hook**完事了
还需要把poc中的操作仔细看一遍,把chunk的编号标记清除
最终得到exp
1 | from pwn import * |
参考链接: