【技术分享】Android内核漏洞利用技术实战:环境搭建&栈溢出实战题

前言

Android的内核采用的是 Linux 内核,所以在Android 内核中进行漏洞利用其实和在 一般的 x86平台下的linux内核中进行利用差不多。主要区别在于Android下使用的是arm汇编以及环境的搭建方面。本文对我最近的实践做一个分享,其实很简单。

内核调试环境搭建

搭建平台: ubuntu 16.04

这里使用android模拟器来进行内核调试。首先下载内核代码

git clone https://aosp.tuna.tsinghua.edu.cn/kernel/goldfish.git

然后下载github上的一个安卓漏洞利用的项目,

git clone https://github.com/Fuzion24/AndroidKernelExploitationPlayground.git kernel_exploit_challenges

然后使用项目中的patch文件把 patch 内核编译配置,来把项目中的带漏洞的模块编译进linux内核

git am --signoff < …/kernel_exploit_challenges/kernel_build/debug_symbols_and_challenges.patch &&

cd … && ln -s $(pwd)/kernel_exploit_challenges/ goldfish/drivers/vulnerabilities

这里注意: goldfish目录和 kernel_exploit_challenges目录要在同一目录下

然后下载 arm-linux-androideabi-4.6交叉编译工具链 。下载完成后把它解压后,然后把它加到环境变量中

tar xvf arm-linux-androideabi-4.6.tar.bz2

export PATH=(pwd)/arm−linux−androideabi−4.6/bin/:(pwd)/arm-linux-androideabi-4.6/bin/:(pwd)/arm−linux−androideabi−4.6/bin/:PATH

然后进入 goldfish目录,开始编译

make goldfish_armv7_defconfig && make -j8

编译完成后,就会有两个主要的文件:goldfish/vmlinux 和 goldfish/arch/arm/boot/zImage。前面那个用于在调试时gdb加载,后面的用于在安卓模拟器启动时加载。

下面下载 安卓sdk, 用来下载和运行 安卓模拟器。

sdk下载地址: http://dl.google.com/android/android-sdk_r24.4.1-linux.tgz

然后把 sdk解压

tar xvf android-sdk_r24.4.1-linux.tgz

把 android-sdk-linux/tools 加入环境变量,把下面的命令添加到 ~/.bashrc 的末尾<把命令中的目录改成你的目录>

export PATH=/home/haclh/hacktools/android-sdk-linux/tools:$PATH

然后重新打开一个shell, 使用下面的命令 <要先下载jdk ,并且设置好环境变量>

android

然后把下面标注的两个下载下来

下载完后。首先查看下载的镜像文件

然后创建 模拟器

android create avd --force -t “android-19” -n kernel_challenges

然后进入 goldfish 目录,使用下面的命令来使用我们的内核来运行模拟器,并在 1234 端口起一个 gdbserver 来方便进行 内核调试

emulator -show-kernel -kernel arch/arm/boot/zImage -avd kernel_challenges -no-boot-anim -no-skin -no-audio -no-window -qemu -monitor unix:/tmp/qemuSocket,server,nowait -s

第一次运行有类似的结果:

$ emulator -show-kernel -kernel arch/arm/boot/zImage -avd kernel_challenges -no-boot-anim -no-skin -no-audio -no-window -qemu -monitor unix:/tmp/qemuSocket,server,nowait -s

WARNING: userdata image already in use, changes will not persist!

Creating filesystem with parameters:

Size: 576716800

Block size: 4096

Blocks per group: 32768

Inodes per group: 7040

Inode size: 256

Journal blocks: 2200

Label:

Blocks: 140800

Block groups: 5

Reserved block group size: 39

Created filesystem with 11/35200 inodes and 4536/140800 blocks

WARNING: cache image already in use, changes will not persist!

Creating filesystem with parameters:

Size: 69206016

Block size: 4096

Blocks per group: 32768

Inodes per group: 4224

Inode size: 256

Journal blocks: 1024

Label:

Blocks: 16896

Block groups: 1

Reserved block group size: 7

Created filesystem with 11/4224 inodes and 1302/16896 blocks

为了便于后面的操作我们需要把 交叉编译工具链 添加到环境变量里。把下面的命令添加到 ~/.bashrc 的末尾<把命令中的目录改成你的目录>

export

PATH=/home/haclh/hacktools/arm-linux-androideabi-4.6/bin/:$PATH

然后重新开个 shell, 进入到 goldfish 目录,加载 vmlinux 以便调试内核

arm-linux-androideabi-gdb vmlinux

如果一切正常,应该可以得到下面的类似输出

然后连接 模拟器里面的 调试端口

如果能看到这样的输出说明已经可以正常进行内核调试了。

内核栈溢出漏洞利用

首先看看漏洞代码, kernel_exploit_challenges/challenges/stack_buffer_overflow/module/stack_buffer_overflow.c:

上述代码会创建/proc/stack_buffer_overflow 设备文件 ,当向该设备文件调用 write 系统调用时会调用 proc_entry_write 函数进行处理。漏洞显而易见,在 proc_entry_write 函数中 定义了一个 64 字节大小的栈缓冲区 buf, 然后使用 copy_from_user(&buf, ubuf, count) 从用户空间 拷贝数据到 buf ,数据大小和内容均用户可控。于是当我们输入超过64字节时我们能够覆盖其他的数据,比如返回地址等,进而劫持程序执行流到我们的 shellcode 中 进行提权。

首先我们来试试触发漏洞。先把模拟器打开,然后 adb shell 进入模拟器,使用 echo 命令向 /proc/stack_buffer_overflow 设备输入72字节的数据。

echo AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA > /proc/stack_buffer_overflow

可以看到 pc 寄存器的值 为 0x41414141 成功劫持。测试时该内核没开 pxn ,所以我们可以在用户态编写shellcode让内核去执行。提取的方式很简单,内核态调用 commit_creds(prepare_kernel_cred(0)); 提升权限为 root, 然后返回 用户态 执行 execl("/system/bin/sh", “sh”, NULL); 起一个 root 权限的 shell, 完成提权。下面先获取 prepare_kernel_cred 和 commit_creds 函数的地址。在 /proc/kallsyms 文件中保存着所有的内核符号的名称和它在内存中的位置。不过在最近的内核版本中,为了使利用内核漏洞变得更加困难,linux内核目前禁止一般用户获取符号。具体可以看这里。

当启用 kptr_restrict 是我们不能获取内核符号地址的。

root@generic:/ # cat /proc/kallsyms | grep commit_creds

00000000 T commit_creds

在本文中,把它禁用掉,不管他。

root@generic:/ # echo 0 > /proc/sys/kernel/kptr_restrict

root@generic:/ # cat /proc/kallsyms | grep commit_creds

c0039834 T commit_creds

root@generic:/ # cat /proc/kallsyms | grep prepare_kernel_cred

c0039d34 T prepare_kernel_cred

禁用掉之后,我们就可以通过 /proc/kallsyms 获取 commit_creds 和 prepare_kernel_cred的地址。

至此,提权的问题解决了,下面就是要回到用户态,在x86平台有 iret指令可以回到用户态,在arm下返回用户态就更简单了。在arm下 cpsr 寄存器的 M[4:0] 位用来表示 处理器的运行模式,具体可以看这个。所以我们把 cpsr 寄存器的 M[4:0] 位设置为 10000后就表示 处理器进入了用户模式。

所以现在的利用思路是

1.调用 commit_creds(prepare_kernel_cred(0)) 提升权限

2.调用 mov r3, #0x40000010; MSR CPSR_c,R3; 设置 cpsr寄存器,使cpu进入用户模式

3.然后执行 execl("/system/bin/sh", “sh”, NULL); 起一个 root 权限的 shell

最后的 exp :

不管你是安卓逆向的初学者还是有一定基础的人,我们都需要时刻的进步,不停地去学习,大家可以加QQ群:321255184

或者直接点击链接:https://jq.qq.com/?_wv=1027&k=5VTVnLe 群里有不同层次但都热爱这项技术的人,可以互相学习、交流 提升自己的技能知识哦。

你可能感兴趣的:(【技术分享】Android内核漏洞利用技术实战:环境搭建&栈溢出实战题)