这是一个mmap动态分配的题目,也是我第一次见到,人生有很多第一次。这次也不例外;)
看了这道题也很长时间,如果单纯从做题来说的话,其实也没必要。但是还是想看一下具体是怎么作者是怎么分的。用mmap 代替brk来管理内存。mmap也被分成了一个个chunk的块,当然了它并没有有malloc那样复杂的内存管理。
chunk的结构与malloc里面的chunk有一点不太一样,chunk是相对空闲状态的内存来说的。
1 | struct chunk{ |
你会发现这个chunk稍微有一点点特别,同样是个双向链表,但是prev 和 next在chunk最后面。链表的头是用一个全局变量,这里暂且叫它top。同样chunk块存在动态的释放和申请。从top开始遍历,当内存不足时,用mmap向系统申请。同时top指向这个新的内存页。用prev保存之前的内存页。
top指向内存大于等于需要申请的内存时,也分两种情况剩余内存大于0x18时,则从该内存空间切割出来所需内存即可,同时改变prev->next 和 next->prev的指向,这里空闲状态是发生变化的,所以这里需要改变引用这个chunk的所有指针。
小于0x18时全部全部分配出去。这里为什么是0x18,因为这里申请的内存大小最小是0x10,再加上一个uint size位,而且这里注意到同样有size位的flag,size|1
表示改chunk处于使用状态,size|2
表示处于连续chunk的中间。我想他这个意思应该是希望来优化内存碎片的。起初我并没有在意这个点。这就是alloc的过程
下面来看free的过程。首先判断size&1
确保这是一个使用中的chunk的,free过程出现了分支,如果该chunk处于连续chunk中间即size&2 == 1
,则去判断该chunk内存位置上紧靠着的nextchunk是否处于未使用的状态,是的化则进行合并,这里没有合并的话会直接扔到top上,如果产生了合并,改合并的nextchunk必须等于top才会把合并之后的chunk扔到top上。
这里有一个存在一个小问题,你会发现合并之后的size位上只去掉了flag_2.并没有去掉flag_1.那么这里就是一个double_free。
这个点怎么用呢?如果这里double_free 会有什么效果呢?首先得保证第一次free的时候 size&3 == 0
,第一次会向前合并,那么第二次flag_2清掉以后,这里的机制会把释放掉的chunk直接扔到top上,同时old_chunk->prev
等于释放的chunk。这里就造成了free_chunk链上存在同时指向相同的位置chunk。这里造成了
old_chunk == the chunk to be free
(the chunk to be free) -> next=the chunk to be free
我们的目标可能是想通过这个机制看能不能拿到 任意地址的引用。我们得想办法改变next的指向。如果现在alloc(0x30)
,这就直接可以控制next的指向,这里虽然不能改变next,但能改变next的next。
但是这里利用条件比较苛刻,为什么这样说呢,我们能很容易轻易的控制流程到next->next上,但是呢next->next指向的结构有一定要求,首先size的大小肯定要大于0x30,并且分割以后如果prev 和 next不为NULL,那还要写prev->next 和next -> prev ,这里还要想想写的时候能不能写。一般来看最好的情况就是prev 和 next 都为0。再来想想要拿到什么地址附近的引用呢? got表? 不行,got附近的没有合适的size,都是很大,导致prev和next的寻址会出现问题。malloc_hook 也不存在 这里用的是mmap
结合题意,它给了一个栈上的地址,那么我们能不能拿到相应函数返回值附近的地址引用呢?这个地方我找了好久,一般都是size合适,但对应prev 和 next 都不为NULL,会导致写出问题,但是附近恰好有一个合适的size 0xa00
且对应的prev 和 next是NULL,这里我们可以拿到ret的引用的,那么这里怎么getshell呢?rop? NOOOOOOO没必要,这里mmap如果你vmmap话发现是可以执行的,所以直接在mmap上申请一块保存shellcode的chunk,再用ret打过去即可。这里本地成功,远程失败,应该是权限问题。我不知道为什么。由于比赛也还行进行,但是这一份不能用的exp贴出来也没事 :)))))))xi 思路最重要 逃)
1 | from pwn_debug import * |