0x00 前言
本例为
CTF-WIKI
上面用来练习Unlink
的例题。题目下载链接:下载链接
参考链接:
0x01 分析
1 | Arch: amd64-64-little |
- alloc:输入 size,分配 size 大小的内存,并在 bss 段记录对应 chunk 的指针,假设其为 head
- read:根据指定索引,向分配的内存处读入数据,数据长度可控,这里存在堆溢出的情况
- free:根据指定索引,释放已经分配的内存块
- ????:这个功能没啥用
- 在
2. read
函数里面存在漏洞, 由于程序采用如下读取输入的方式,并不会对读取长度进行限制,故会造成堆溢出。
1 | for ( i = fread(ptr, 1uLL, n, stdin); i > 0; i = fread(ptr, 1uLL, n, stdin) ) |
- 这里存在一个有趣的现象:
值得注意的是,由于程序本身没有进行 setbuf 操作,所以在执行输入输出操作的时候会申请缓冲区。这里经过测试,会申请两个缓冲区,分别大小为 1024 和 1024。
所以我们可以在前面先分配一个 chunk 来把缓冲区分配完毕,以免影响之后的操作。
还有一点值得注意: 注意看
1. alloc函数
。
- 这里采用的
::s[++dword_602100] = v2;
,也就是说,第一个chunk指针是存在s[1]的位置的。
0x02 思路
- 通过
unlink
漏洞,修改free@got
为puts@plt
,想办法输出puts
函数的真实地址,计算libcbase
,从而得到system地址
.修改atoi@got
为system
, 输入/bin/sh的地址, 执行system('/bin/sh')
获得shell。
0x03 Let’s do it!
前期模板
1 | from pwn import * |
这里注意一下
edit
函数,由于程序读入字符串的方式比较特殊,这里选取c.send(cont)
。具体来讲就是,这个程序虽然不会对输入的字符串长度进行限制,但是这种读入方法不同于
gets
,gets
读到换行符就会结束,但是这个程序读到换行符会把换行符也当做一种输入,然后继续等待接下来的输入,这显然不符合我们的需求。而sendline
在末尾会有换行符,所以我们采用末尾不带有换行符而是相当于带有\x00
的send
。
申请内存
前面的分析里已经讲到,需要申请先申请一个chunk把缓冲区分配掉。
其次还要注意他们的下标,下标是从1开始的。
1
2
3alloc(0x100) #1
alloc(0x30) #2
alloc(0x80) #3分配好以后,来看一下我们的
head
,也就是IDA里面显示的s
,这也证明了下标是从1开始。
1 | pwndbg> x/10gx 0x602140 |
伪造chunk
由于
unlink
能够实现*target = target-0x18
(下面会在例子中讲)我们选取
target = head + 16
,也就是head[2]
的地址。因为接下来我们是需要通过free掉chunk2的nextchunk
,也就是free(head[3])
来触发unlink
,而unlink
的验证则要求fd->bk == P && bk->fd == P
。
- 上面如果看着有点晕, 那么简单地说就是需要
target = 我们伪造的chunk所在的chunk的地址
。
1 | target = head + 16 |
- 执行以后我们就能在chunk2里面伪造(套娃)一个chunk,然后覆盖掉
chunk3
的prev_size
和size里的prev_inuse标志位
。
unlink
- 先看一下unlink前我们
head
附近的内存
1 | pwndbg> x/10gx 0x602140 |
- 当程序unlink时,
1 |
|
- 再来查看一下head附近的内存。
1 | pwndbg> x/10gx 0x602140 |
- chunk3被
free
掉并置NULL
了,chunk2的指针变成head - 0x8
了。
偷梁换柱得shell
1 | payload = 'a'*8 #padding |
- 执行以后,head附近的内存应该是这样。
1 | pwndbg> x/10gx 0x602140 - 0x10 |
- 接下来我们把
free_got
改成puts_plt
,那么我们执行free(1)
的话就相当于执行puts(puts_got)
。
1 | #[free@got] = puts@plt |
- 那么可以通过
free(1)
获取puts
的真实地址puts_addr
,进而获得libcbase
和system
了。
1 | free(1) |
- 然后我们有了system,就可以把
atoi_got
换成system
了,从而获得shell。
1 | #[atoi@got] = system |
0x04 完整exp
1 | # -*- coding: utf-8 -*- |