前言
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 群里有不同层次但都热爱这项技术的人,可以互相学习、交流 提升自己的技能知识哦。