Unlink - 2014 HITCON stkof
CTF-Wiki中看到的一个案例,利用方法是Unlink。
由于CTF-Wiki中写的相当简略,忽略了很多细节,所以写这篇文章对其进行补充。
checksec
1 | life@ubuntu:~/ctf/heap/2014_hitcon_stkof$ checksec stkof |
64位程序,开启了Canary和NX保护,Partial RELRO说明我们可以对GOT进行改写。
功能
拖入IDA分析,该程序会根据输入的数字来进行对应的操作。
数字1:进入alloc函数,malloc指定大小的chunk,保存malloc的指针到globals[]数组里,位于bss段。
数字2:进入fill函数,根据id,从globals数组中选择相应的chunk,写入size长度的数据,造成堆溢出。
数字3:根据id,free对应的chunk,并把globals[id]清空。
Unlink 利用条件
- 需要一块small bin大小的chunk,用于触发unlink。
- 绕过Unlink的__builtin_expect检查。
- 通过堆溢出修改下一个chunk的inuse位为0。
步骤1、分配chunk
先在exp中定义一个alloc,用来申请size大小的chunk。
1 | def alloc(size): |
然后申请三个chunk,并查看内存布局。
1 | alloc(0x100) |
chunk1会夹杂在fgets和printf的缓冲区之中。
所以我们不需要chunk1,只利用chunk2和chunk3即可。
这是chunk2和chunk3:
我们后面就会用这两个chunk进行unlink。
步骤2、进行unlink
由于已经分配了3个chunk,现在查看一下globals数组:
数组中保存是chunk的mem地址,分别对应分配的三个chunk。
unlink的思路是:
- 通过堆溢出,在chunk2上创造一个fake_chunk。
- 那么fake_chunk的P地址也就是0xe05940。
- 然后需要绕过unlink的检查机制,要求FD->bk要等于P,BK-fd也得等于P,否则就会报错。
- 因为P是0xe05940,我们可以利用globals中的第2个数组,因为这里本来存储的就是chunk2的mem。
- 最后再通过溢出,将chunk3的size的inuse位改成0,表示fake_chunk已经是空闲状态,可以进行合并。
然后定义数字2的函数,用于向堆中写数据,进行堆溢出:
1 | def edit(ID,size,content): |
fake_chunk:
1 | fake_chunk = p64(0x0) |
向chunk2中写入fake_chunk:
1 | edit(2,len(fake_chunk),fake_chunk) |
此时chunk2的布局如下:
1 | pwndbg> x /20xg 0xe05930 |
1 | def free(ID): |
然后我们free第3个chunk,程序就会进入unlink函数,检查FD->bk和BK->fd是不是等于0xe05940。
我们在fd上写的0x602138,它的bk就是+0x18,是0x602150,所以绕过了检查。BK位置同理。
最后的效果:
FD->bk = BK;
- *(0x602138+0x18) = 0x602140
BK->fd = FD;
- *(0x602140+0x10) = 0x602138
globals[2]的位置变成了 0x602138
改了它的好处就是,0x602138现在就是chunk2的指针了,以后再向chunk2写数据,就相当于写到了globals数组中。
步骤3、泄露libc地址
我们可以将globals[1]的位置改为free@got地址,然后调用edit向chunk1写数据,就相当于向free@got中写。
我们将free@got改成puts@plt,这样下次再调用free的时候就会puts出地址。
1 | payload = p64(0x0) |
现在globals[1]是free@got,将其写入puts@plt。
1 | edit(1,len(p64(0x400760)),p64(0x400760)) #puts@plt |
再次调用free,相当于调用了puts,打印了puts@got中的libc地址。
1 | free(2) |
步骤4、system
接收puts的libc地址:
1 | puts_addr = p.recvuntil('\nOK\n',drop=True) |
有了libc地址就可以执行system了。
计算system的地址:
1 | libc_base = puts_addr - libc.symbols['puts'] |
将atoi@got.plt改成system地址,下次需要输入的时候,直接输入/bin/sh即可。
1 | edit(3,len(p64(system_addr)),p64(system_addr)) |
总结
- 先分配三个chunk。
- 利用edit在chunk2中写入fake_chunk,用来触发unlink。
- 通过unlink将globals[2]的内容改为&globals[2]-0x18,也就是globals[-1]。
- 将globals[1]改为,free@got
- 将globals[2]改为,puts@got
- 将globals[3]改为,atoi@got
- 修改free@got的地址为puts@plt
- 再次调用free,相当于调用了puts,打印出libc地址。
- 利用得到的libc地址计算出system地址。
- 将atoi@got改为system地址。
- 下次要输入的时候,直接输入/bin/sh即可。
完整exp:
1 | from pwn import * |