简介 :
这题目之前就做完了。。因为中秋实在是太懒了。一直没有写文章,今天好好悔过,忏悔一下,重新做了一遍,现在开始写。这题接着上一题的unsortbin attack。
详解 :
checksec:
1 | ➜ zerostorage checksec ./zerostorage |
全开了。
分析一下程序功能:
bss段存了三个部分:
- 申请堆块个数计数
- 随机数
- 堆块结构体 :固定数字1(表示正在使用),申请大小,申请到的地址和随机数异或后得到的地址。这三部分构成了堆块的结构体。
insert :
- 逐一查看 chunk_struct 数组,查找第一个未使用的元素,但是这个数组最大也就是32。
- 读取 chunk 元素所需要存储内容的长度。
- 如果长度不大于0,直接退出;
- 否则如果申请的字节数小于128,那就设置为128;
- 否则,如果申请的字节数不大于4096,那就设置为对应的数值;
- 否则,设置为4096。
- 使用 calloc 分配指定长度,注意 calloc 会初始化 chunk 为0。
- 将 calloc 分配的内存地址与 bss 段的随机数进行抑或,得到一个新的内存地址。
- 根据读取的chunk的大小来读入内容。
- 将对应的chunk的大小以及存储内容的地址保存到对应的chunk_struct元素中,并标记该元素处于可用状态。但是,需要注意的是,这里记录的storage的大小是自己输入的大小!!!
- 递增num的数量。
update :
- 如果没有任何存储,就直接返回。
- 读入要更新的chunk_struct元素的id,如果id大于31或者目前处于不处于使用状态,说明不对,直接返回。
- 读取更新后chunk所需要存储内容的长度。
- 如果长度不大于0,直接退出;
- 否则如果申请的字节数小于128,那就设置为128;
- 否则,如果申请的字节数不大于4096,那就设置为对应的数值;
- 否则,设置为4096。
- 根据 bss 段对应的随机数获取原先chunk存储内容的地址,
- 如果更新后所需的长度不等于更新前的长度,就使用realloc为其重新分配内存。
- 再次读取数据,同时更新chunk_struct元素。
merge :
- 如果正在使用的元素不大于1个,那么无法合并,直接退出即可。
- 判断chunk_struct是否已经满了,如果不满,找出空闲的那一块。
- 分别读取merge_from的id以及merge_to的id号,并进行相应大小以及使用状态的检测。
- 根据最初用户输入的大小来计算两个 merge 到一起后所需要的空间,如果不大于128,那就不会申请新的空间,否则就申请相应大小的新的空间。
- 依次将merge_to与merge_from的内容拷贝到相对应的位置。
- 最后存储merge_from内容的内存地址被释放了,但并没有被置为NULL。同时,存放merge_to内容的内存地址并没有被释放,相应的storage的抑或后的地址只是被置为了NULL。
- 但是需要注意的是,,在merge的时候,并没有检测两个storage的ID是否相同。
最重要的也就是上面三个功能了,后面的delete,view,list都是普通的功能。
思路 :
程序读入了from ID与to ID后,完成一个合并的操作,然后将from ID指向的那个堆内存free。那么如果merge时输入的2个ID相同,在完成合并后那块内容指向的chunk将被free,但是我们依然可以读写那块chunk,造成use after free.之后直接view这块内容,即可leak出libc的地址。
在上述操作后,chunk被放入unsorted bin中,此时如果修改这个chunk的bk指针并重新malloc这个chunk,就能触发unsortedbin attack。进而去修改global_max_fast的值。这个变量用于控制最大的Fast chunk的大小,将这里改写为unsorted bin的地址(一般来说是一个很大的正数),就能使之后的chunk都被当作fast chunk,即可进行Fast bin attack。
由于unsorted bin在改写操作后即被破坏,我们需要事先布置好内存的布局。在改写global_max_fast之后,我们再进行一次merge的操作,这次chunk将进入’Fast bin’(实际它的index并不在正常的Fast bin数组内,但没有关系),然后改写fd指针指向程序管理内容的数组,我们需要事先在数组上insert一个大小为144的块作为Fast chunk的size以通过检查,然后将fd指到这里。
之后下下次的malloc即可取得程序bss上的指针,注意分配过来的时候需要读入对应的大小,我们需要故意让这段区域跨过这个块自己,因为程序在读入数据之后还会将其元数据回填,这样我们就能通过view来得到异或之后的地址,随即计算出key的值。然后update这个块,修改某一个指针为realloc_hook的地址异或key的值,接着update对应的块,将system的地址填入realloc_hook。最后扩大事先布置好的存有
/bin/sh
的块,即可得到shell。
这里我们也可以覆盖修改free_hook等等的值。
还有一个注意点:
由于本机是ubuntu16,但是程序开了PIE,且在ubuntu16中libc基址和程序基址不存在固定的偏移,所以该程序在本机中只有在关闭了aslr的情况下才能够getshell。
详细的就不说了,仔细查看exp。
EXP :
1 | from pwn import * |