分析一下2015 9447ctf search_engine这道题目。CTF-wiki中2015-9447-ctf-search-engine对于漏洞利用已经讲的十分清楚,我对于程序的功能分析进行一下补充,然后对exp做一下简单的注释,便于理解一些。
1.泄露stack_addr: 首先利用输入长度为48的非数字,而程序不会为字符串设置截断符,可以泄露栈区信息。(可能要输入多次“a”*48,因为有可能恰好在stack区域中,字符串结尾恰好是“\00”,导致不能泄露stack信息,而由于这个stack是递归的,每一次都会导致stack下移,因为多输入几次,即可成功泄露stack信息。)
2.泄露libc: 然后是泄露libc,本文提供了一种新的思路,如何控制node的内容:申请一个存放sentence的chunk,大小与node的大小一样,然后free sentence,然后继而在add一个sentence,并且sentence_chunk_size != node_size,那么node就会被分配到我们前面用来存放sentence的chunk中,即将此chunk加入到了node_list中去。然后我们可以通过比较“\00”使最近添加的node_chunk删除(即删除前面的node的sentence,对应到当前的node),从而又将node_chunk放到了fastbin中,然后我们可以申请一个sentence_chunk_size与之对应sentence,那么我们后面就可以通过控制sentence来控制这个node了,从而实现任意地址读取。泄露libc
3.后面通过double free以及fastbin_attack改写fd,分配fake_chunk到stack区域,然后覆盖ret(没有复现成功以及理解)
首先是泄露stack的信息:
在输入选项1或者2或者3的时候,他有一个48字节的缓冲区,当输入非数字字符长度为48的时候,他并不会在字符串末尾设置截断符,并且由于strol,会将栈区信息泄露出来。
这题目算是本菜鸡做pwn以来读的最难懂的程序了,程序的内容以及数据结构花了比较长的时间才弄清楚,下面分析一下这个题目的功能。
程序开始之后,对输出缓冲区做了限制,但是没有对输入缓冲区进行限制,因此程序开始之后会先申请一段heap空间作为输入缓冲区。
下面进入功能函数,我对某些变量做了标注,就如图中展示的一样。
get_numbe()函数用来接受输入,根据输入的数字来执行相应的功能。
get_numbe()函数用来接受输入,根据输入的数字来执行相应的功能。
get_numbe()函数用来接受输入,根据输入的数字来执行相应的功能。
程序只有两个功能,说实话,我看到的时候很迷。。
首先介绍index_sentence()的功能,功能的名字叫做索引句子。有点难懂啊,索引句子到底是怎么个索引方式。
为了完成索引,在你输入的句子中需要有一个空格" ",比如“aaa b”这样,当然还要输入size = 6。输入完成之后可以根据“aaa”或者"b"来找到输入的句子并且选择是否删除。
输入一个新的sentence之后创建两个数据结构来存储信息。
struct node
{
char * word; 索引字符串的起始地址
int word_size; 索引字符串的大小
char * sentence; 句子的其实地址
int sentence_size; 句子的大小
void * pre_node; 上一个节点
}
看一下测试的结果加深一下理解,在index功能下输入size = 7 ,sentence="aaa b "效果如下:
pwndbg> x 0x6020b8
0x6020b8: 0x00603470
pwndbg> x/20xg 0x603410
0x603410: 0x0000000000000000 0x0000000000000021
0x603420: 0x000a206220616161 0x0000000000000000 字符串“aaa b ”
0x603430: 0x0000000000000000 0x0000000000000031
0x603440: 0x0000000000603420 0x0000000000000003 "aaa"的起始地址以及size=3
0x603450: 0x0000000000603420 0x0000000000000006
0x603460: 0x0000000000000000 0x0000000000000031 也就是说添加一个sentence会生成两个结构node
0x603470: 0x0000000000603424 0x0000000000000002 "b"的起始地址以及size = 1
0x603480: 0x0000000000603420 0x0000000000000006
0x603490: 0x0000000000603440 0x0000000000020b71 上一个节点是0
0x6034a0: 0x0000000000000000 0x0000000000000000
pwndbg> x 0x6020b8
0x6020b8: 0x00603470
pwndbg> x/20xg 0x603410
0x603410: 0x0000000000000000 0x0000000000000021
0x603420: 0x000a206220616161 0x0000000000000000 字符串“aaa b ”
0x603430: 0x0000000000000000 0x0000000000000031
0x603440: 0x0000000000603420 0x0000000000000003 "aaa"的起始地址以及size=3
0x603450: 0x0000000000603420 0x0000000000000006
0x603460: 0x0000000000000000 0x0000000000000031 也就是说添加一个sentence会生成两个结构node
0x603470: 0x0000000000603424 0x0000000000000002 "b"的起始地址以及size = 1
0x603480: 0x0000000000603420 0x0000000000000006
0x603490: 0x0000000000603440 0x0000000000020b71 上一个节点是0
0x6034a0: 0x0000000000000000 0x0000000000000000
search_with_word的时候输入"aaa"或者"b"的时候都可以找到这个句子。
下面看一下search_with_word功能。
我也做了相应的注释,就是取bss段中存储的最新添加进去的节点,然后依次判断有没有条件相符的,并且首先要保证node中的sentence的内容不为空。其实是最低字节不能为空**(_BYTE **)(i+16)
找到符合条件以后,如果选择删除之后,那么会先对sengtence置0并且free掉,但是并没有设置NULL而且node中的sentence的内容仍然存在,存在UAF漏洞,可以继续使用。
此外get_input函数中还有一个漏洞,如果输入的字符串长度恰好的读取长度相同,在结尾值没有设置结束符"\00"。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)