首页
社区
课程
招聘
[原创]BCTF 2018 House of Atum分析
发表于: 2021-8-27 18:51 11861

[原创]BCTF 2018 House of Atum分析

2021-8-27 18:51
11861

这道题主要考察tcache bin、double free、伪造chunk并利用、glibc中chunk分配与释放机制等知识点熟练掌握和运用程度,比较典型,值得深入研究。
一、house of Atum结构分析
二、Double Free 利用
(一)分析基础
(二)泄露heap地址
(三)伪造chunk
(四)泄露libc地址
三、劫持_free_hook函数
四、总结

house of Atum共有4个功能,分别为新建(new)、编辑(edit)、删除(delete)、显示(show),其中alloc()函数最多只能分配2个堆块(0x48字节),漏洞函数存在于del()函数中,当clear操作选择'n'时,不会将notes数组上的指针置0,存在UAF漏洞。
关键函数部分代码如下:

notes[i]指针指向分配的chunk数据区,readn函数直接读入0x48个字符串,没有对字符串末尾进行截断处理,只对字符串末尾添加'\0a',导致存在泄露内存地址的可能。

选择删除(delete)时,如果在clear选项后面选择'n',notes[v1]将不会置0,导致存在uaf漏洞。

以上就是有用的信息,我们所能执行的操作就限定在以上范围之中。

给定一个这样的题目,怎们办?根据已有的知识,存在uaf漏洞,那么就可以触发double free,double free后,位于tcache bin中的chunk的next指针将指向next所在位置,即指向自己。从这点出发,我们可以动态得到next指针的值。

我们的目标是最终执行system('/bin/sh'),而且是通过堆来完成。上一篇《HITB CTF 2018 gundam分析》中,我们通过动态获得libc基地址,进而计算出_free_hook地址和system地址,想方设法在_free_hook地址处写入system地址,再创建1个内容为'/bin/sh'的chunk,然后释放,就可以触发_free_hook,最终执行system('/bin/sh')。这道题同样可以采取这种思路。1.要动态获得libc基地址,就要用到unsorted bin,在tcache的count为7的情况下,将符合unsorted bin大小的chunk释放到unsorted bin中。该chunk前向指针fd和后向指针bk的值就是要泄露的地址(详细分析见上一篇《HITB CTF 2018 gundam分析》)。

2.题目中创建的house of Atum chunk的大小为0x51。显然,这样chunk释放后只能进入tcache bin和fast bin,要想使其进入unsorted bin,就得改变指定chunk的大小。

3.要在_free_hook地址处写入system地址,就得构建1个以_free_hook地址为数据区地址的chunk,即_free_hook-0x10为起始地址的chunk,以system地址作为内容参数。
以下分析和调试过程基于以上3点考虑,通过对堆块chunk的灵活操作,成功执行获得shell。

我们知道,创建house of atum chunk时,系统分配的chunk大小为0x51,如果只创建1个chunk,size为0x51的chunk后面紧跟着top chunk。而我们后面还要改写指定chunk的size大于fastbin的最大值0x80,显然只申请1个大小为0x51的chunk,并在这唯一一个chunk上来回操作是不能满足要求的。

考虑到2个chunk的大小加起来为0xa1,后面需要紧跟着1个大小为0x11的chunk来标识前一chunk的prev_inuse状态,故后续操作中将指定chunk的大小由0x51改为0x91。开始先申请2个chunk,chunk0的内容为一个字符‘A’,chunk1的内容为p64(0)*7+p64(0x11),即56个字节的0,加上1个长度为8个字节的0x11,把chunk0、chunk1看作1个大的chunk正好满足后面将chunk0的大小改为0x91的要求。
图片描述
这里需要特别注意的是:chunk1最后8个字节必须是p64(0x11),而不能是p64(0x10)。如果是p64(0x10),后面释放7次大小为0x91的chunk0时,chunk0能够正常释放到tcache中,但是第8次再释放该chunk0时,会出现错误,而错误的原因就是把0x11写成了0x10。通过查看glibc的源代码,在_int_free()函数中,存在以下代码:

在chunk被释放到unsorted bin之前系统会通过下一个chunk的prev_inuse标志位来检查该chunk是否已经被释放。写成0x10之后,glibc检测发现prev_inuse为0,就会触发异常"double free or corruption (!prev)",而在放入tcachebin之前是没有这种检查机制的。

以上分析是以下所有chunk操作的起点。

1.先将chunk1释放到0x51大小的tcache中;
2.连续将chunk0释放6次,chunk0将放入0x51大小的tcache中,同时chunk1将从tcache中移除,chunk0的next指针将指向自身next地址,通过show('0'),可以获得chunk0的内容,即next指针地址,定义为heap_addr。
图片描述
图片描述

动态获得了heap_addr,即chunk0的next指针,后面该如何利用?

根据前面分析,要获得libc的基地址,就要改变chunk0的大小为0x91。chunk0的size域位于chunk0头部16个字节的后半部分中,可以考虑创建1个以chunk0-0x10为起始地址的chunk1,将chunk0的新的size值(0x91)作为chunk1的内容。要从tcachebin中分配chunk1时,前提是chunk1在tcachebin中,有两种方式可以使chunk1(chunk0-0x10为起始地址)进入tcachebin:一种是释放已经存在的chunk1到tcache中,另一种是将chunk1链接到fastbin中某个chunk后面,这样当chunk被从fastbin中分配时,其后面的chunk1就会被移到tcache中。显然前一种方式不现实,我们采用第二种方式。

通过前面7次释放chunk,tcachebin 已有7个chunk,再释放1次chunk0,chunk0将进入fastbin。这时,chunk0同时存在于fastbin和tcachebin中。

图片描述

chunk0进入fastbin后,我们继续按上面的思路走。创建chunk,chunk0将从tcachebin中分配,将p64(heap_addr-0x20)作为参数写入chunk0数据区,fastbin中的chunk0后面链接着以heap_addr-0x20为起始地址的chunk。此时tcache中count变成了6,并且tcache为空。
图片描述

从上图可以看到,fastbin中增加了1个chunk(从heap_addr-0x20开始)。
此时,如果我们再申请创建1个chunk,即chunk1,该chunk将会从fastbin中分配,同时fastbin中的剩余chunk将会移到tcachebin中。

由于tcachebin中next指针指向chunk的数据区,fastbin中next指针指向chunk起始地址(两者相差0x10),因此写入tcache的entries的值为heap_addr-0x10,即chunk0起始地址。

图片描述

再释放掉chunk1,chunk1会进入fastbin中。

图片描述

到目前为止,我们已经成功实现伪造chunk(heap_addr-0x20作为起始地址),并使其进入tcachebin。如果我们再次申请创建chunk,chunk1将会从tcache中分配。在这里,我们把p64(0)+p64(0x91)作为参数创建chunk,chunk1(从heap_addr-0x20开始)的数据区heap_addr-0x10处将会写入0x91,heap_addr-0x10正是chunk0的起始地址,成功改变chunk0的大小为x91。

图片描述

连续释放chunk0 7次,将会使chunk0进入0x91大小的tcachebin中,再释放1次,chunk0将会进入unsortedbin。

图片描述

根据上一篇《HITB CTF 2018 gundam分析》,chunk0+0x10处的以7f开头的地址就是泄露的地址。

图片描述

而libc的基地址如上图标红处所示,为0x7fe3b0f45000,由此可以计算出libc基地址与泄露的指向unsortedbin的fd指针之间的偏移为:
泄露地址0x00007fe3b12f0c78-libc基地址0x7fe3b0f45000=0x3abc78,进而可以计算出__free_hook函数地址和system函数地址。

第一部分第3点我们谈到,要在_free_hook地址处写入system地址,就得构建1个以_free_hook地址为数据区地址的chunk,即_free_hook-0x10为起始地址的chunk,以system地址作为内容参数。

我们知道fastbin中chunk被分配后,该chunk后面链接的chunk会被移到tcachebin中,后面再分配chunk时,移到tcache中的chunk会被分配。我们继续按这个思路分配和释放chunk。

此时我们完成了libc基地址的获取,需要将chunk0的大小从0x91改回到0x51,以便创建大小为0x51的house of atum chunk。

从上图可以看到fastbin中chunk0后面链接的是指向unsortedbin的指针,如果我们将链接的指针修改为__free_hook-0x10,那么再创建大小为0x51的chunk时,glibc会从fastbin中分配chunk0,同时将_free_hook地址写入tcache。

如何将chunk0大小改回到0x51并修改chunk0后面的链接指针?我们继续使用前面伪造的chunk1(从heap_addr-0x20开始),利用house of Atum 程序的edit功能,将chunk1的内容编辑为p64(0)+p64(51)+_free_hook-0x10,成功实现修改chunk0大小和chunk0后面链接指针的目的。

图片描述

继续创建大小为0x51的chunk,chunk0将从fastbin中分配,同时_free_hook地址被写入0x51大小的tcache中。

图片描述

释放chunk0,chunk0被释放到fastbin中,再次申请创建chunk0, 将从tcache中分配chunk0(_free_hook-0x10开头),以system函数地址为参数,把system函数地址写入_free_hook地址,实现_free_hook函数与system函数的绑定。
图片描述

图片描述

释放伪造的chunk1,再次创建chunk1,chunk1将从fastbin中分配,以‘/bin/sh\x00’为参数,字符串‘/bin/sh\x00’被写入chunk1数据区。

图片描述

再次释放chunk1,实际上执行del操作的前两步,触发_free_hook函数,最终执行system(‘/bin/sh\x00’),成功得到shell。
图片描述

本题的解题思路是从题目本身出发,分析题目本身能够直接获取的信息,然后思考如何建立与解题目标(成功获得shell)之间的联系,包括如何利用double free后得到泄露的heap_addr?为什么以及如何伪造以heap_addr-0x20开头的chunk?如何从伪造的chunk出发获得泄露的libc基地址?弄清这些内在联系对于逆向解题至关重要。网上流传较多的版本是最后通过one_gadget获得系统控制权,本文采用执行system函数的方式成功实现这一目的。

int alloc()
{
  int i; // [rsp+Ch] [rbp-4h]
 
  for ( i = 0; i <= 1 && notes[i]; ++i )
    ;
  if ( i == 2 )
    return puts("Too many notes!");
  printf("Input the content:");
  notes[i] = malloc(0x48uLL);
  readn((void *)notes[i], 0x48LL);
  return puts("Done!");
}
int alloc()
{
  int i; // [rsp+Ch] [rbp-4h]
 
  for ( i = 0; i <= 1 && notes[i]; ++i )
    ;
  if ( i == 2 )
    return puts("Too many notes!");
  printf("Input the content:");
  notes[i] = malloc(0x48uLL);
  readn((void *)notes[i], 0x48LL);
  return puts("Done!");
}
unsigned __int64 del()
{
  int v1; // [rsp+0h] [rbp-10h]
  char v2[2]; // [rsp+6h] [rbp-Ah] BYREF
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]
 
  v3 = __readfsqword(0x28u);
  printf("Input the idx:");
  v1 = getint();
  if ( v1 >= 0 && v1 <= 1 && notes[v1] )
  {
    free((void *)notes[v1]);
    printf("Clear?(y/n):");
    readn(v2, 2uLL);
    if ( v2[0] == 'y' )
      notes[v1] = 0LL;
    puts("Done!");
  }
  else
  {
    puts("No such note!");
  }
  return __readfsqword(0x28u) ^ v3;
}
unsigned __int64 del()
{
  int v1; // [rsp+0h] [rbp-10h]

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-8-29 00:06 被dolphindiv编辑 ,原因:
收藏
免费 2
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//