吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 15753|回复: 91
收起左侧

[系统底层] 从零开始的Linux堆利用(一)——House of Force

    [复制链接]
呆毛王与咖喱棒 发表于 2021-3-22 19:35
本帖最后由 呆毛王与咖喱棒 于 2021-11-7 17:33 编辑

感觉栈相关简单的漏洞基本原理学的差不多了,准备学一学堆相关的;

这个主要是学习Linux Heap Exploitation时的笔记,具体的课可以去Udemy上看,感觉讲的蛮不错的;

然后内容都是自己的博客,原文在https://hack1s.fun/,欢迎大家去看

introduction

Glibc

ldd是list dymic dependencies,可以显示出二进制程序运行时需要加载的动态链接库

libc是linux中最基本的动态链接库,绝大多数程序都需要用到libc,如果删除libc的链接,关机都会关不掉;

malloc

堆是程序在执行中可以使用malloc向内核请求一段连续的内存空间;

I/O、文件读写等等都是通过堆来实现的;

堆与malloc

首先需要对堆有一个基本的理解,堆通过malloc分配chunk,通过free来释放chunk

首先是一个demo例子用于理解堆

在pwndbg中执行

set context-sections code

这样可以让之后每一次显示context只显示源代码部分

image-20210217212638040

这个程序主要做的事情就是调用了几次malloc,之后return

vmmap可以显示出进程当前的内存空间,在这一句malloc还没有执行的时候,程序内存空间不存在堆的区域

image-20210217212950029

当第一次malloc执行之后,查看vmmap会发现多出来了一个堆的空间

image-20210217213700269

在pwndbg中的命令vis_heap_chunk简写为vis可以查看堆的chunk分布

image-20210217214128093

我们虽然是执行了malloc(9),申请了9的空间,但是实际上给了我们3*8=24字节的空间(蓝色部分的第一个8字节是头部,不是用户可以用的)

也就是说malloc(9)分配给了24字节的user data以及8个字节的meta data,这个chunk一共占了32字节;

malloc分配的最小chunk就是这样0x20的大小,即24字节的user data和8个字节的meta data

即使执行的是malloc(0)malloc(1)仍然会分配一个0x20大小的chunk

image-20210217214720913

图中几个chunk分别是malloc(9)malloc(1)malloc(0)malloc(24)分配的;

可以看到实际上内容都是一样的占了0x20字节;

但是也可以注意到,其中meta data部分并不是存储了0x20,而是0x21;

这是因为meta data这里两个字段,一是chunk size,表示整个chunk(包含user和meta两部分)的大小,另外由于chunk分配时是按照16字节对齐的,最低位就可以用来表示其他信息;这个字段就是previous_inuse,用来表示这一个chunk相邻的前一个chunk是否是在使用的状态,如果是就为1,否则为0;

下面如果继续执行malloc(25)会分配一个0x30的空间

image-20210217215150684

虽然最后16个字节只用到了1个字节,但还是按照16字节对齐进行分配的。

最后就是Top chunk,可以看到在我们自己申请的Chunk之后有一个Top Chunk的meta data;

并且随着一次次的申请新的堆空间,这个Top Chunk的大小会发生变化。

这是因为内核在分配堆的内存空间时是创建一块大的Top Chunk,每一次用户执行的malloc就是压缩top chunk分配给用户,直到Top Chunk的空间不足以分配,就会再向下拓展Top Chunk

image-20210217221525951

在Top Chunk中有一个值得注意的地方是,在Glibc的很多版本中,Top Chunk的Size字段都是没有完整性检查的,这就是The House of Force的基本原理

在2005年,第一次出现了一篇名为The Malloc Maleficarum的论文,其中写了5种堆利用的技巧;

  1. Houses of Prime
  2. Houses of Mind
  3. Houses of Force
  4. Houses of Lore
  5. Houses of Spirit

从此之后的堆利用技巧也因此都叫"house of  XX"这样的形式

House of Force

原理

house of force的原理就是前面提到的,没有对Top Chunk的size字段进行完整性检查;

这导致在分配了一个比较小的chunk后,如果输入的内容大于chunk的大小,进而溢出到top chunk的size 字段,就可以伪造控制top chunk的大小;

之后再一次使用malloc分配chunk,可以达到一个任意地址写的效果,运用得当也可以实现RCE的效果;

漏洞程序本身

程序本身是一个类似CTF中堆题的结构

image-20210226160340641

为了方便学习漏洞本身,程序运行前输出了puts的地址以及heap开始的地址

选项1是malloc,之后可以输入要申请的大小以及输入的内容

例如上面申请了24,但是输入了24个a以及7个b最后和一个\n

这是我们<C-c>后回到pwndbg,可以用vis看到现在的堆

image-20210226160534387

可以看到由于溢出了7个字节的b和1个字节的\x0a,top chunk处的size已经被覆盖了。

在GDB中使用vmmap libc可以查看程序调用的libc信息

这个程序由于增加了Runpath,连接的是特定的libc

image-20210226193254435

这里使用的是没有开启tcache的程序,但是实际上house of force是可以在tcache存在的libc使用的;

这里使用这样没有开启tcache的libc是为了在还没有学过tcache机制的情况下就可以了解如何使用这个漏洞利用方式

任意地址写

程序的第二个选项可以输出一个变量target

正常来说这个变量的值是一串X

image-20210226162944191

在pwndbg中可以使用dq &target以四个字为单位查看这个变量附近的值

image-20210226163057701

dq的全称是dump qwords,另外也有类似的dwdddb

堆起始地址是0x603000,但是可以看到这里其实target的位置是在堆的上方的0x602010

使用malloc只能继续往高地址申请空间,没有办法摸到target

所以我们需要溢出top chunk

这边由于虚拟机的环境有写问题,换了一台虚拟机

首先申请24的空间,然后输入b"Y"*24+b"\xff"*8

这样可以把top chunk覆盖为0xffffffff,在python脚本里面用gdb调试

这里给出的脚本中有几个函数是可以直接辅助在VIM中运行的,在vim输入

:!./% GDB

这个功能的实现是通过这一块代码

gs = '''
continue
'''

def start():
  if args.GDB:
    return gdb.debug(elf.path,gdbscript=gs)
  else:
    return process(elf.path)

相当于直接启动GDB附加这个程序,使用vis看到

image-20210304153132039

top chunk已经是全f了

第二步就是申请一个特别大的chunk,正好到target前面一点点的位置;

这个程序前面输出了heap的地址,在pwntools中读取之后,计算差值

需要分配的是(0xffffffff-0x603000)+0x602010-0x20-0x20这么大的内容

malloc之后用vis查看

image-20210304155216574

可以看到这时正好在0x602010上方

这之后再malloc申请内存覆盖的就是target的地方,再申请20的空间,在里面随便输入一些内容

发现vis后0x602010处的值就已经不再是XXXXXX了

image-20210304155430523

在菜单里面输出target发现值也变了

image-20210304155406422

这就实现了一个任意地址写的效果

任意代码执行

通过一个任意地址写转换成代码执行的利用有这样几个思路:

  1. 修改栈,但是这个程序中栈采用了ASLR;
  2. 修改Binary段,修改PLT中的项或修改fini_array,程序中的每一个函数在退出时会运行这个fini_array中的,但是这个程序开启了full-RELRO,在binary加载完成之后原本的二进制区段会变成只读,无法对其进行修改;
  3. 修改堆,但是这个程序中除了我们自己的数据,没有影响控制流的数据,所以没用;
  4. 修改libc,__exit_funcstls_dtors_list这两个指针会在特定情况下调用,比较类似于PLT,但是都被指针完整性保护,并且在这个程序中没有可以触发的地方,所以难以实现;
  5. 修改__malloc_hook,在GLIBC中的数据段,修改__malloc_hook可以使程序在调用malloc时调用这里被修改的函数;

这里面修改__malloc_hook是可行的,我们首先将top chunk溢出为全f

之后申请一个空间,从堆中目前top chunk所在的位置到libc的__malloc_hook这么长;

由于libc的区段在堆的下面,不需要像获取任意地址写那样滚一圈内存空间了;

distance = libc.sym.__malloc_hook - 0x20 - (heap + 0x20)

调试的时候使用:!./% GDB NOASLR暂时关掉ASLR

分配之后查看__malloc_hook的位置

image-20210308212201801

由于分配时减了0x20,这里看一下__malloc_hook - 2

image-20210308212444377

运行top_chunk看到top_chunk的位置就在这上方

image-20210308212604069

所以我们接下来再用malloc申请一段空间,将__malloc_hook这里的函数指针修改为指向system函数的地址

libc.sym['system']

这时用p __malloc_hook看一下可以发现这个函数指针已经变成了__ libc_system

image-20210308213641372

下面就是想办法执行system("/bin/bash")

由于system的参数是一个指向字符串的指针,我们可以在前面几轮malloc填充数据时就直接填充/bin/bash在这里填写当时分配出来的地址

image-20210308214212027

比如把第二轮的malloc中填充的字符串改成/bin/bash

这样最后调用时要填写的字符串地址就是最初的heap+0x30

这次再执行就不需要后面的GDB NOASLR了,直接运行就可以拿到shell

image-20210308214407682

总结一下,这里的house_of_force只对2.28以下的GLIBC有效,再新的GLIBC就增加了top chunk的完整性保护了;



house_of_force.zip

567.13 KB, 下载次数: 90, 下载积分: 吾爱币 -1 CB

二进制程序和脚本模板

免费评分

参与人数 42吾爱币 +35 热心值 +37 收起 理由
Xin9527 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
jijimao123 + 1 + 1 谢谢@Thanks!
pianopiece + 1 谢谢@Thanks!
云随风2013 + 1 + 1 谢谢@Thanks!
520xYZ + 1 热心回复!
zhenkaixin852 + 1 + 1 谢谢@Thanks!
Zoroaaa + 1 我很赞同!
prontosil + 1 热心回复!
红烧排骨 + 1 我很赞同!
longestusername + 1 + 1 谢谢@Thanks!
夺笋 + 1 + 1 谢谢@Thanks!
abc星河 + 1 + 1 谢谢@Thanks!
yzhappyzsx + 1 + 1 热心回复!
逸飞兮 + 1 热心回复!
404undefined + 1 + 1 我很赞同!
lxmly0623 + 1 + 1 谢谢@Thanks!
laiyuou + 1 + 1 我很赞同!
AlanSilence + 1 + 1 谢谢@Thanks!
it_harry + 1 + 1 谢谢@Thanks!
夜灯 + 1 谢谢@Thanks!
Ivonzhang + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
疯狂の马甲 + 1 + 1 我很赞同!
21MyCode + 1 + 1 热心回复!
lookerJ + 1 + 1 谢谢@Thanks!
chuxia12 + 1 + 1 热心回复!
wws天池 + 1 + 1 我很赞同!
pdcba + 1 + 1 我很赞同!
棺灵lcl + 1 谢谢@Thanks!
lizhenqiang1990 + 1 用心讨论,共获提升!
joeyli + 1 谢谢 @Thanks!
刀小锋 + 1 谢谢@Thanks!
不知天在水 + 1 用心讨论,共获提升!
爱飞的猫 + 2 + 1 用心讨论,共获提升!
小脚jio + 1 + 1 我很赞同!
tandz + 1 谢谢@Thanks!
soga + 1 + 1 我很赞同!
victos + 1 + 1 谢谢@Thanks!
azcolf + 1 + 1 热心回复!
ttao88 + 1 热心回复!
bullsh1tlie + 1 + 1 谢谢@Thanks!
fengbolee + 2 + 1 用心讨论,共获提升!
gxkyrftx + 1 + 1 这可对新人太友好了

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| 呆毛王与咖喱棒 发表于 2021-12-29 13:03
longestusername 发表于 2021-12-2 14:01
大神能不能讲一下linux的malloc请求极大内存的时候,分配内存为啥会wrap around从VA的最开始重新分配呢?
...

不好意思看到问题比较晚,我也是边学边记,看到您的问题后看了一下ptmalloc的源码

malloc总体的流程大概是首先判断free的bins里面有没有符合条件的,都不行的话会将unsortedbin排序后看unsortedbin里面有没有可以满足申请需要的空间;最后才会考虑top chunk

下面这部分是使用top chunk分配时的代码,这里判断的条件发现top chunk大于申请的size之后进入了第一个if分支,没有经过什么检查就直接将这部分空间划分出去了。

会发生wrap的原因应该就是只保留后64位导致的,实际上应该也不算只保留后64位吧,因为这个top字段的长度就是64位,更多的内容也不会保留下来

[C] 纯文本查看 复制代码
use_top:
      /*
         If large enough, split off the chunk bordering the end of memory
         (held in av->top). Note that this is in accord with the best-fit
         search rule.  In effect, av->top is treated as larger (and thus
         less well fitting) than any other available chunk since it can
         be extended to be as large as necessary (up to system
         limitations).

         We require that av->top always exists (i.e., has size >=
         MINSIZE) after initialization, so if it would otherwise be
         exhausted by current request, it is replenished. (The main
         reason for ensuring it exists is that we may need MINSIZE space
         to put in fenceposts in sysmalloc.)
       */

      victim = av->top;
      size = chunksize (victim);

      if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
        {
          remainder_size = size - nb;
          remainder = chunk_at_offset (victim, nb);
          av->top = remainder;
          set_head (victim, nb | PREV_INUSE |
                    (av != &main_arena ? NON_MAIN_ARENA : 0));
          set_head (remainder, remainder_size | PREV_INUSE);

          check_malloced_chunk (av, victim, nb);
          void *p = chunk2mem (victim);
          alloc_perturb (p, bytes);
          return p;
        }

      /* When we are using atomic ops to free fast chunks we can get
         here for all block sizes.  */
      else if (have_fastchunks (av))
        {
          malloc_consolidate (av);
          /* restore original bin index */
          if (in_smallbin_range (nb))
            idx = smallbin_index (nb);
          else
            idx = largebin_index (nb);
        }

      /*
         Otherwise, relay to handle system-dependent cases
       */
      else
        {
          void *p = sysmalloc (nb, av);
          if (p != NULL)
            alloc_perturb (p, bytes);
          return p;
        }
longestusername 发表于 2021-12-2 14:01
本帖最后由 longestusername 于 2021-12-2 15:03 编辑

大神能不能讲一下linux的malloc请求极大内存的时候,分配内存为啥会wrap around从VA的最开始重新分配呢?
也就是“需要分配的是(0xffffffff-0x603000)+0x602010-0x20-0x20这么大的内容” 您文章中这一个计算方式是怎么得来的呢
---明白了。应该是malloc时,检查top_chunk_size > request_size 之后计算分配内存的尾地址,
尾地址为  current_chunk_top_position + request_size 。
也就是 (0x603000+0x8+0x20) + request_size ; 如果request_size接近0xffff ffff ffff ffff 那么地址结果超过64位,发生整数溢出,只截断保留后64位地址。也就导致了malloc分配极大内存时会wrap around了。

简单讲:malloc时根据chunk_size和request_size判定是否内存足够分配,计算尾地址发生了一次整数溢出

自己理解的没有看malloc源码,不晓得是不是正确
莎莎啦啦 发表于 2021-3-23 19:34
syz17213 发表于 2021-3-23 20:10
感谢分享,学习了
bullsh1tlie 发表于 2021-3-23 21:23
感谢楼主分享
tan567421 发表于 2021-3-23 23:18
GOOD GOOD GOOD。非常牛。收下了
jncsw 发表于 2021-3-24 08:04
支持楼主,很好的教程,感谢分享。
ttao88 发表于 2021-3-24 10:53
支持,感谢,学习了。
小脚jio 发表于 2021-3-24 12:06
感谢楼主,看一下。。
ExtremeDanger 发表于 2021-3-24 14:33
是时候该好好学习了!感谢楼主分享
飘动的云 发表于 2021-3-24 18:28
作为新人 ,该好好学习了!谢楼主分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 01:14

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表