linux应用程序堆,从零开始的Linux堆利用(一)

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

这个主要是学习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只显示源代码部分

7ae2098f7eeec4829a97ad14143349fa.png

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

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

ee2507bd059fbc8c4f66bf6a7b431585.png

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

e9f4a222cedef8bd94943366c36b1269.png

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

d6f9bf02d62acb6a262265966dd7c9c5.png

我们虽然是执行了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

bedb0503574d889ac19cb7661330ad8f.png

图中几个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的空间

864dada80ecdeef3dab96b0c0787296b.png

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

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

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

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

919b4df5dd71199c4c26f818379c1e64.png

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

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

Houses of Prime

Houses of Mind

Houses of Force

Houses of Lore

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中堆题的结构

82cdba24dd9a907cff0518a78dcce206.png

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

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

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

这是我们后回到pwndbg,可以用vis看到现在的堆

09afbba4e9f9c7356d4bcd4844165cde.png

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

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

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

8517b5305371670f092747bcfe3e1089.png

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

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

任意地址写

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

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

cb92a622d4f8d1875f9be45c4de05e0b.png

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

cf727520f16e353f78985d20c4cc5cbc.png

dq的全称是dump qwords,另外也有类似的dw、dd、db

堆起始地址是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看到

58a4883917eba16398b6b61f3e67bdaa.png

top chunk已经是全f了

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

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

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

malloc之后用vis查看

49e74609a23e1deb7926ec4ce079c0cd.png

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

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

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

91f2a37d0be229dbdf7769bb3a961bfd.png

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

ed88350a603d838de72b34b89b044571.png

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

任意代码执行

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

修改栈,但是这个程序中栈采用了ASLR;

修改Binary段,修改PLT中的项或修改fini_array,程序中的每一个函数在退出时会运行这个fini_array中的,但是这个程序开启了full-RELRO,在binary加载完成之后原本的二进制区段会变成只读,无法对其进行修改;

修改堆,但是这个程序中除了我们自己的数据,没有影响控制流的数据,所以没用;

修改libc,__exit_funcs、tls_dtors_list这两个指针会在特定情况下调用,比较类似于PLT,但是都被指针完整性保护,并且在这个程序中没有可以触发的地方,所以难以实现;

修改__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的位置

6a3f9aa661bef0b7cfda5bd97eafa622.png

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

c22249d15edd0c43ec2415f60b2fe024.png

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

b8792261e5cfcb677954c08c061c547c.png

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

libc.sym['system']

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

351ee4eb22c98b3a1bfe8c30bd8735f7.png

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

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

ebb139b05843a445b4cfb5a2eaad5510.png

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

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

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

97680d9263a3222ab58f000b2aae65fe.png

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值