最喜欢的高版本堆题,挺有意思的.所以到底叫上海大师杯还是中华武数杯
randomHeap
glibc2.35堆,保护全开
逆向
程序实现了这样的堆管理结构.初始化时,分配了16个堆管理结构和16个大小为0x28字节的堆块,放入chunk_list和chunk_manager_list.正常情况下这两个结构应该是用户不可见的.
用户的对堆块的操作是通过user_chunk_manager_list,每次add会从chunk_manager_list取出一个堆管理结构的指针放到user_manager_list中,不涉及malloc的操作.
这三个list之间的id是随机产生的,没有对应关系.且堆块和堆管理结构的分配顺序也是随机的.
在show的时候没有对idx的判断,可以将IO_2_1_stdin_结构作为伪造的堆管理结构,泄露出libc地址.在edit的时候也没有对offset的判断.可以使offset为负数来修改某个堆块上方的堆管理结构,由于堆块和管理结构产生顺序随机,这里需要爆破一下,之后就可以完成任意地址读写.
exp
1 | lg = lambda x, y: log.success(f'{x}: {hex(y)}') |
预期解
init的时候分配了这样的大堆块,可以部分覆写chunk_manager的指针来指向这些大块,释放进largebin再泄露地址.
预期解爆破挺不容易的,要爆堆布局和爆一位ASLR.可以用扫描所有堆结构尝试泄露地址的方式来减少堆布局的爆破.
1 | lg = lambda x, y: log.success(f'{x}: {hex(y)}') |
Shortestpath
2.35堆,保护全开
逆向
是一个求图中两点间最短路径的程序,算法大概是从起点开始广搜然后比较所有能到达终点的路径长度(一点算法不懂的表示很难逆).
程序的图是用如下结构来表示的.
可以无限制的创建结点和边,输入函数有个offbynull,且可以绕开’\0’的截断,由此可以将tcache和unsortedbin中的堆拿出来,泄露堆和libc地址.
现在就差任意写了,看看最短路径函数.
变量命名有点逆天,因为我是按照刚学的算法逆的,逆完发现就是个广搜.
该函数使用如下的结构.算法首先创建了一个PathInfo的数组dist(存储路径信息)和一个S_set集合(记录一个结点是否已经以其为源点进行过广搜).
manager中的bottom_idx和top_idx,用作dist数组的索引.可以理解为双指针(形成的队列).
每搜到一个结点,无论是否已经由其他点出发搜到过
,将该路径信息存到dist[top_idx]中.top_idx++.每以一个结点为源点开始搜索,将dist[bottom_idx]取出存到栈上的path,进行计算操作,bottom_idx++.这样top_idx和bottom_idx之间的PathInfo,就是已搜到但还未以其为源点搜索的结点(其实是路径信息).直到top_idx==bottom_idx,完成广搜.
不过有一个问题,dist数组只分配了node_count+1个PathInfo,而算法是每搜到一个结点,无论是否已经由其他点出发搜到过
,将该路径信息存到dist数组中,所以存在溢出.构造一个图,其中一个结点有很多条入边,可以Poc出这个漏洞.
但我没有往这方面走,因为有另一个洞更吸引我注意.这个洞在逆向过程中很容易发现:啊啊啊这两个__int64到底是啥啊也没初始化啊啊.嗯哼,manager结构没有初始化.我们可以提前布置一个堆块伪造manager结构并释放,在short的时候取出,将其作为manager,由于tcache取出时对key的清0,bottom_idx的初始值一定为0.我们仅能控制top_idx.
看看怎么利用.可以发现在进入循环之前的深搜初始化工作,将起点存到dist[top_idx]中,其中src_key是我们可控的,于是便有了dist+top_idx*0x18+8地址处的8字节任意写.
别高兴太早,这样破坏内存的行为很容易导致程序崩溃.
橙色框中是容易导致崩溃的地方(还有内循环的store_dist函数).尝试通过黄色框中的条件绕过:第一个比较是无符号比较,无法通过负数绕过.第二个比较的cur_key不可控,无法绕过.gg
于是为了避免崩溃,我们的top_idx只能为一个较小数,只能完成在堆上的近似任意写(近似是因为有0x18倍数的要求).那就劫持tcache然后改stderr,再利用offbynull清空topchunk触发malloc_assert.
exp
唉,最喜欢的调堆环节.
我知道exp里结点编号很乱…调完风水懒得改了
1 |
|