Android ROP利用技术

出发点

早期的缓冲区溢出攻击,因为当时终端都未使用ALSR缓解技术,攻击者通常会把“邪恶代码”部署在栈中,劫持PC指针后指向栈中的部署好的代码进行执行。后来为了应对这种缓冲区溢出攻击,操作系统安全厂商开发了防止数据执行(XN)缓解技术,部分弥补了冯·诺依曼计算机体系结构中数据和代码在内存未进行区分的问题,使进程虚拟空间中的数据区、栈区和堆栈区不能再执行机器代码,
这里写图片描述

从而有效的缓解了早期的缓冲区溢出攻击,但攻防双方就像军备竞赛一样不停的进步,攻击方很快的找到了对抗XN的技术。XN技术使进程的用户空间难以再找到满足条件部署“邪恶代码”的内存位置,这迫使攻击者转换了攻击思路—不再部署自己的“邪恶代码”,而是使用进程空间现存的代码来替代。例如使用进程空间中加载的各种动态链接库中的函数,更进一步在内存空间中找到“邪恶代码”所需的指令,通过一种方法串联起来执行,其实就达到最终的目的。

Ret2Lib

首先被提出突破XN机制的技术叫Return-to-library technique,简称“Ret2Lib”。该技术的核心思想:虽然堆栈中不能再部署“邪恶代码”,但进程虚拟空间中加载了各种各样的动态链接库,包括程序自己的第三方库以及操作系统平台提供的C运行时库,这些库中充斥着大量实现了各种功能的函数,攻击者可以使用这些函数来替代“邪恶代码”的功能,或者调用一些系统函数来关闭操作系统的缓解机制后再部署“邪恶代码”。

这里以溢出后调用Glibc库中的system为例,简单的描述一些Ret2Lib的过程。当缓冲区溢出后,攻击者可以控制PC指针的指向和栈的布局,那么调用system的流程如下:

  1. 定位system函数在内存中的入口地址。这里假设系统未开启ALSR机制,Glibc库在进程的虚拟内存空间加载的地址是固定的,通过Glibc库加载的位置和system函数相对于Glibc库的偏移即可计算出入口地址。

  2. 在x86平台函数的参数是通过栈进行传递,攻击者需要重新部署栈来完善调用system函数的栈帧。

  3. 劫持PC指向system入口开始执行。

这里写图片描述

由于在x64平台以及ARM平台上参数通过寄存器传递,Ret2Lib技术只能调用无参函数,大大降低了该技术的灵活性,目前该技术已经被淘汰,故不再对细节展开。

ROP

Ret2Lib有很多局限性,比如,在x64平台以及ARM平台上由于参数通过寄存器传递,无法通过布局栈来传递参数;可能进程空间找不到合适的函数完成“邪恶代码”等等。攻击者又发明了另外一种更为强大、灵活的攻击技术—Retrun-Oriented-Programming,简称“ROP”。ROP技术的核心思想:在进程的内存空间中的充斥着各种各样的机器指令,攻击者只需要找到“邪恶代码”所需要的指令集合,并串起来执行即可达到目的。

“邪恶代码”所需的指令在进程中并不能保证是连续存在的,常见的是几条指令分散在内存各个位置,这就需要一种方法把这些指令串起来。因为缓冲区溢出后,攻击者可以控制PC和栈的布局,所以一种可行的方法是找到以跳转指令结尾的指令片段(通常称为gadget),这样的指令片段需要栈来提供PC的指向,故能让攻击者能够持续的控制PC指针指向下一个代码片段,最终串联起来执行。
这里写图片描述

Android下的ROP

从ROP的原理中可以看到其关键就是在内存中找到以跳转指令结尾的gadget,由于每个平台的ABI(应用二进制接口)不同,调用函数以及函数返回的方式也不尽相同,这里主要介绍一下ARM平台下的可用gadget的特点。

这里不得不提一下ARM平台下调用子函数的特点。同是32位CPU却和x86平台不同,

  • 返回地址存储。和x86平台返回地址存放在栈中不同,ARM平台下子函数的返回地址存储在特殊的链接寄存器(ARM模式下的R14,Thumb 下的LR)中,这是通过BL或BLX指令调用函数时,把下一条指令的地址存入的LR,然后才开始直接被调函数。
  • 函数返回方式。函数返回时,在x86中PC指针不可以通过类似MOV指令设置,只能通过特殊的ret指令设置。ARM平台下却没有限制,可以通过MOV PC , LR 和 BX LR来恢复PC指针。

但在实际逆向过程中,很少见到以MOV PC , LR方式恢复PC,这是因为ARM平台的另一个特点。ARM处理器支持两种执行模式:ARM和Thumb模式。当前处理器处于那种模式,可以通过程序状态寄存器(CPSR)中的第五个比特位(也成为T-bit)进行判断,1表示Thumb,0表示ARM。当处理器在两种模式下切换时,比如,程序调用Thumb模式下的函数,那么BL和BLX指令会将LR寄存器的最低有效位设置1,反之亦然。那么MOV PC , LR和BX LR在ARM处理器上就会有明显的却别,MOV PC , LR指令只适用于ARM模式下函数间调用,BX LR却可以满足两种模式间函数调用,且两个指令的性能也相同。所以在当前编译器上只会为函数返回生成BX LR指令。故两种模式下的子函数例程代码如下:

  • ARM指令调用子函数
stmia sp! , {r11 , lr}     ;保存LR寄存器到栈上以便函数调用子函数,保存R11环境变量
...
bl subroutine              ;调用子函数
...
ldmia sp! , {r11 , lr}     ;恢复环境变量和LR寄存器值
bx lr                      ;返回母函数
  • Thumb指令调用子函数
push {r11 , lr}
...
bl subroutine
...
pop {r11, pc}        

所以,在Android 的ARM平台上,任何以

ldmia sp! , {... , lr}
bx lr
或
pop {..., pc}  

结尾的指令序列就是很好的gadget选择,因为LR是从栈上恢复的CPU将要执行的下一条指令地址,缓冲区溢出后通过堆栈进行布局就可以使这个地址指向下一个gadget的地址。

如果下一个gadget是Thumb模式指令,栈在提供指向地址时需要将最低比特位设置为1.

另外一个有用的gadget是只以

bx lr

结尾的gadget,由于本身没有恢复LR的指令配合,在执行完成后存在很多不确定性,可能导致LR不再指向下一个gadget的地址,所以需要一个专门调整LR的gadget配合使用,

pop {... , lr}
bx lr
加上
pop {pc}

上面几个gadget串起来的样子,

这里写图片描述

上图只是展示gadget的类型,串联起来没有现实意义。

识别gadget

可以通过IDA等工具自己在程序或so中自己寻找合适的gadget,但为了效率还是推荐ROPGadget工具,

root@Tangxx:~# ROPgadget --binary=./DlmallocTest --thumb | grep "ldr r0"
0x000085da : ldr r0, [r0] ; ldr r0, [r0] ; ldr r1, [sp, #0x84] ; subs r0, r0, r1 ; cmp r0, #0 ; bne #0x8600 ; push {r4} ; pop {r0} ; add sp, #0x88 ; pop {r4, r6, r7, pc}
0x000085dc : ldr r0, [r0] ; ldr r1, [sp, #0x84] ; subs r0, r0, r1 ; cmp r0, #0 ; bne #0x85fe ; push {r4} ; pop {r0} ; add sp, #0x88 ; pop {r4, r6, r7, pc}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值