这道题目是关于heap overflow的。之前没有接触过这方面。通过阅读http://winesap.logdown.com/posts/258859-0ctf-2015-freenode-write-up, http://winesap.logdown.com/posts/258859-0ctf-2015-freenode-write-up这两篇writeup,基本上明白了原理,在此记录。
此外,关于glibc malloc的基本知识,可以参考https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/
首先,创建4个大小为1的note(大小会被自动近似到128),内容为"a"
heap从0x603000
开始。我们具体地去看那些chunks:
分给4个128 bytes的notes的chunks从0x604820
开始,其内容均为我们提供的"a"=0x61
。而打印main_arena
,可以看到0x604a60
那里就是top chunk
这些chunk的大小都足够大,不会用到fastbin。而到目前为止还没有free
,bins里也是没有chunks的:
然后,我们free
掉第0个note,位于0x604820
:
可以看到,0x604820
出现在了bins
的第1个和第2个,应该是传说中的unsorted bin了,这里chunks是双向链表:
然后,我们free
掉第2个note,位于0x604940
:
这里bins[0]
是刚刚free
掉的0x604940
,而bins[1]
是最开始free
掉的0x604820
。free chunks的链表如下:
于是,0x604820
那里的bk
,即位于0x604838
的0x604940
,指向的就是note2的chunk的地址。那么接下来,我们再创建一个同样大小的note(长度为8, 被近似到128进行malloc
),那么就会被分配到最开始free
的0x604820
那里(因为bins是FIFO)。我们提供的8 bytes的内容之后,紧跟着的就是bk
。由此。通过打印这个刚刚创建的note的内容,就可以在我们提供的8 bytes后面得到某个chunk的地址,进而得到heap的地址。
下面是我们free
掉note2后再创建新note的情况:
可以看到,0x604820
被新创建的note用掉了,所以被从bins双向链表里去掉了:
查看这个新note的内容:
看到我们提供的内容"12345678"=0x3837363534333231
之后就是bk
指针,没有被破坏掉,我们通过打印note内容就可以得到这一地址。
通过villoc,我们可以将刚才的过程用图片显示出来:
有了heap的地址,我们就可以创建一个伪chunk,这个chunk是已经free的,其fd
和bk
指向heap上的某处,具体地,fd->bk=bk->fd=ptr
。这里我们先创建note0,其内容中包含我们构造的伪chunk;然后创建note1,内容是/bin/sh
用来后面调用system
;然后再创建note2,其内容中包含多个构造的chunk
具体地,我们创建完这3个note后,真正的chunks如下
但如果从伪chunks来看,其布局是这样的:
0x604830
处的伪chunk是已经free的了,可以从0x6049d8
处看出;而fd=0x603018
,bk=0x603020
,满足fd->bk = bk->fd = ptr = 0x604830
。这是因为0x604830
作为note字符串的地址,被存在heap上了:
这也是我们第一步需要heap的基址的原因。
然后,我们删除掉note3。因为第一次创建note3所分配的chunk恰好就是0x6049d0
,在第一次删除note3的时候这个地址没有被更改;然后我们的伪chunk也在0x6049d0
,于是double free。而现在0x6049d0
这个伪chunk的内容完全是我们控制的。具体地,这个chunk不是free的(0x604a68
),前面的是已经free的0x604830
,后面是0x604a60
,也不是free的(0x604af8
)。于是这两个chunk会被合并,具体地,0x604830
会从双向链表上去掉,与0x6049d0
合并之后,插入到unsorted bin。在0x604830
从双向链表上去掉时发生unlink
,造成修改。
删除掉note3,我们看到合并之后的0x604830
,大小是0x1a0+0x90=0x230
,被放到了bins[0],即unsorted bin:
在unlink
过程中,我们把在heap上的之前存note0字符串地址的地方改了:
于是接下来,我们如果修改note0,就相当于修改了那个所有note结构数组,即可以将note0的字符串地址改为free@got
。由此,我们就可以通过读、写note的内容,做到读、写free@got
。最后是这样将free@got
改为system
,那么在删除note2的时候,就发生了system("/bin/sh")
。
下面是最后的代码