MacOS系统上的堆介绍及利用

分享一下之前总结的一些MacOS系统的堆介绍及利用思路。

MacOS下的堆介绍

MacOS高版本系统使用Magazine Allocator进行堆分配,低版本使用Scalable Allocator,详细结构这里不做介绍,它在分配时按照申请大小将堆分为三类tiny,small,large
其中tiny&small用一个叫做 **Quantum ( Q )**的单位管理

  • tiny (Q = 16) ( tiny < 1009B )
  • small (Q = 512) ( 1008B small < 127KB )
  • large ( 127KB large )

每个magazine有个cache区域可以用来快速分配释放堆

堆的元数据(metadata)

MacOS的堆分配方式和其他系统不同,没有采用Linked List方式的分配,堆的前后并没有带堆的元数据,而是将元数据存放在了其他地方,并且做了一系列措施方式防止堆溢出修改元数据。


每个进程包含3个区域,分别为tiny rack, small rack, large allocations

tiny racksmall racklarge allocationsmagazinemagazinemagazinemagazinemagazinemagazine……magazinemagazine

每个区域包含了多个活动可变的magazine区域
magazine中有n多个"Region"
这个叫"Region"的区域大小在tiny rack和small rack中是不同的,
“Region” in Tiny rack = 1MB
“Region” in Small rack = 8MB

"Region"中包含三样东西,一个是以Q为单位的内存block, 还有个是负责将各个"Region"关联起来的trailer另外一个就是记录chunk信息的metadata

large allocations保存在cache中,直接记录地址和大小,除非是分割严重,否则一般不会被unmmap

堆的释放 - chunk本身的变化

tiny堆

tiny堆在释放时,将该chunk挂在freelist上,这里和Linux类似

比较有意思的一点是,tiny堆在释放时,会在chunk上写入元数据,我们值得关心的就是这一点

这里有两个pointer和Linux上chunk的头极其相似,同样的,它们的作用也一样,在freelist上获取chunk时将会用这个pointer来进行链表的操作,还有chunk在free时,会进行合并检查,然后用这两个pointer进行unlink操作。
***但是***这里如果按照Linux的方式去攻击堆时,就会发现这里的checksum会阻止堆的元数据被溢出修改。后面会大致介绍这里的checksum

关于tiny堆释放时的需要注意的另外一个点:

small堆

small堆与tiny堆不同,释放后会先移动到cache中,等到下一个small堆被free时,当前的才会被移动到freelist中

堆的释放 - chunk元数据(metadata)的变化

mag_free_list

这里便是要讲上文提到的freelist,mag_free_list是个负责存放地址的列表,一共包含32个元素,各个元素处储存着已经free的对应Q值的chunk地址,前31个分别是从1Q~31Q的chunk freelist,第32个存放比31Q还要大的chunk freelist。


当新的chunk被free时,将按照chunk的大小,存放在对应Q值的freelist上,并按照双向链表设置好checksum(prev_pointer), checksum(next_pointer) {参照Linux的freelist}

mag_free_bit_map

这个则如名字所示,按位来标记Q(n)是否具有freelist

堆的释放 - checksum

程序在运行时,都会随机生成一个cookie,这个cookie会pointer进行下面的计算生成一个checksum, 然后将(checksum << 56 ) | (pointer >> 4)运算后将checksum保存在高位上,以便检测堆的元数据是否被溢出破坏

magazine_t

这个则包含了上述介绍过的各种数据,比如chunk cache, 以及mag_free_bit_map, mag_free_list, 以及最后一个被使用的region, 以及所有region的链表

堆的申请

整个申请流程是首先从cache中寻找是否有对应的堆,如果没有接着从freelist中寻找,没找到再从region中去申请

题目攻击思路

首先题目保护全开,具有PIE,再分析程序流程。
程序整个流程就是以下面的结构体进行堆数据操作。

  • 溢出

发现在update()更新mem时,可以随意设定当前mem->nameSize的大小,导致修改name时,可溢出修改name后的下一块mem的数据。
但是修改的size发现做了限制,导致数据溢出最大只能修改到mem结构的前三项
mem->StyleTableIndex
mem->ShapeTableIndex
mem->Time

MacOS系统上的堆介绍及利用

1.png988×82 14.3 KB

  • leak

在show()显示时,可以用StyleTable[offset/8]来leak数据

因为有PIE的存在,程序每次运行堆栈地址都会随机,所以整个利用思路就是先leak libsystem_c.dylib的地址,接着利用heap操作产生的漏洞去将包含的execv(’/bin/sh’)代码运行地址写入可以劫持到程序流程的地方。

利用MacOS堆的特性leak libsystem_c.dylib

查看程序运行时的vmmap,可以看到程序下方有个Malloc metadata的region,这里开头存放的就是DefaultZone

MacOS系统上的堆介绍及利用

2.png1077×181 12.5 KB


我们可以看下libmalloc的源代码

值得我们仔细关注的是这里的
struct malloc_introspection_t * MALLOC_INTROSPECT_TBL_PTR(introspect);

继续查看源代码

用之前介绍过的堆资料,可以知道
所以DefaultZone->introspect->enumerator这里储存了enumerator对应的函数
szone_ptr_in_use_enumerator的地址

libsystem_malloc.dylib地址

所以
libsystem_malloc.dylib的地址 = leak出的
szone_ptr_in_use_enumerator地址 - sznoe偏移量(0x0000000000013D68)

libsystem_c.dylib地址

这里有个很有趣的现象,就是MacOS的PIE会保证程序每次运行时都会随机堆栈以及加载地址,但是引入的动态库地址不会产生变化,似乎只会在开机时变化。
所以可以看下vmmap,确定下libsystem_c.dylib与libsystem_malloc.dylib加载地址,得到偏移量。
libsystem_c.dylib = libsystem_malloc.dylib - 偏移量(0x161000)

OneGadget RCE

分析了libsystem_c.dylib,发现了与Linux libc中同样的execv(’/bin/sh’)代码片段


onegadget rce = libsystem_c.dylib + 0x0000000000025D94

MacOS系统上的堆介绍及利用

3.png1520×200 46.1 KB

劫持程序流 - 前置

这里利用MachO的Lazy Bind机制,复写libsystem_c.dylib的la_symbol_ptr表中的函数存放地址(不写原程序的原因是无法leak原程序加载地址)
查看一周发现最优的选择为exit_la_symbol_ptr
我们可以在add()函数阶段输入不被认可的Size,可让程序执行exit()进而执行我们写入的地址。

这里发现libsystem_c.dylib的TEXT和DATA region地址相差较大,不像原程序紧挨在一起,所以这里还需要再leak一次libsystem_c.dylibd的DATA region地址。

libsystem_c.dylib DATA

分析原程序时发现在.got内有个FILE **__stdinp_ptr
可以看到开头的_p指向了某块内存的地址,这样就可以利用这个来完成leak DATA地址,这里buffer与DATA起始地址的偏移量分析下就可以得到

libsystem_c_DATA = libsystem_c_stdinptr - 0x4110

劫持程序流 - 核心

根据前面堆的申请介绍,我们可以构造一些tiny堆,让再次申请堆时保证从freelist上获取,然后完成
tiny_malloc_from_free_list(),使内部的unlink操作完成next->previous = ptr->previous任意数据写任意地址的操作

但是这里有个问题,就是在unlink前,会有个unchecksum的检查,因为程序每次运行时,都会对当前的zone生成随机的cookie,导致这里无法绕过去

但万幸的是MacOS在对生成的cookie和pointer进行checksum后,只使用了4个有效位来保存checksum值,所以可以设定个checksum进行爆破,让程序生成的cookie在与我们的pointer在checksum后恰好等于我们自己设定的值。

value = p64(((libsystem_c_exit_la_symbol_ptr >> 4) | int(checksum, 16)))

getshell

下面是完整的exp

MacOS系统上的堆介绍及利用

4.png2980×1832 823 KB


分享到:


相關文章: