前言
昨天刚刚结束的湾区杯
全场只有5解的pwn题,差点拿到3血(其实差了不少: / )
失去了AI的辅助,全身心投入逆向分析,看起来也不是件坏事
附件分析
附件可以到看雪上下载: 下载链接
本题的数据传输采取了类似protobuf的序列化/反序列化数据包传输指令
所以我们在进行堆利用前要将自定义的vm操作梳理清楚
数据包逆向
在主逻辑前,先是进行了对时间种子的设置
经典的伪随机数

随后是个循环读入
注意看这里读入的循环判断,是当前读入的字节(char)为负数
字符串与数字的转化
读入后进行了dump_num操作,按照一定格式把str转化为__int64
1 | __int64 __fastcall dump_num(_QWORD *size, __int64 ptr, unsigned __int64 n9_1, unsigned __int64 *len) |
可以看到在循环中,将字符转化为数字,并使用”|“运算和”>7
t|=0x80
num+=p8(t)
s=u8(num[-1:])
num=num[:-1]
num+=p8(s-0x80)
return num
1 |
|
首先从上个函数读入的内容中开始使用dump_num取出一个数字
根据最低三比特与第四比特的bool标志进入不同的分支
分别opcode的不同字段设置内容/设置不同内容对应的标志位
从这个函数中我们也能分析出对应的opcode结构体
1 | struct __fixed opchunk // sizeof=0x38 |
其中
opcode用于指定进行什么堆操作(add,edit,free…)
Idx用于设置操作堆块的编号
size用于设置操作堆块的大小
content_ptr用于设置想要add/edit的内容
content_len用于指定content的长度
每个成员都对应一个int类型的flag位,用于标记此变量是否存在
根据每次从ptr中取出的num不同,可以一次性设置多个成员变量

Idx的获取算法
随后根据数据包中idx的数值,使用get_idx转化真实的idx
1 | __int64 __fastcall get_idx(char long2) |
可以看到idx的获取,是数据包中的Idx成员与随机数进行xor,然后高低位反转,作为最后真正的idx
据此我们可以逆向分析得到如何输入Idx
1 | def idx(Id): |
通过Idx在bss上寻址,找到对应堆块,将内容复制并置零
同样对长度有完备的检查,不存在溢出
以下结构进行edit请求
1 | def edit(Id,content): |
正常的delet操作,不存在uaf漏洞
1 | def free(Id): |
正常show
1 | def show(Id): |
出题人给了一次uaf机会,且在uaf后调用了一次可以无限栈溢出的函数overflow
以下结构请求uaf操作
1 | def uaf(Id): |
污染next指针为_IO_list_all,将其劫持到堆上的可控地址中
伪造IO结构体,并触发其中的函数表
1 | data=heap+0x340 |
经典的house of apple2,调用system(“ sh”)getshell
完整EXP
1 | from pwn import * |
后记
本道题解数少,最重要的原因是结构体与算法的逆向复杂+耗费时间,且由于ctf+awdp并发进行,pwn手只能专注于一方面的攻击
无论是在ctf还是iot的学习中,我总感觉自己的逆向能力在AI的帮助下不断退化,可能这也是不少pwn手目前的真实写照
在去AI的情况下打出这道题,真的让我很开心 : )