从0开始学Linux内核之android内核栈溢出ROP利用
最近在研究一个最简单的android内核的栈溢出利用方法,网上的资料很少,就算有也是旧版内核的,新版的内核有了很大的不同,如果放在x86上本应该是很简单的东西,但是arm指令集有很大的不同,所以踩了很多坑
把上一篇改了一下名字,换成了从0开始学Linux内核,毕竟不是专业搞开发的,所以驱动开发没必要学那么深,只要会用,能看懂代码基本就够用了。本篇开始学Linux kernel pwn了,而内核能搞的也就是提权,而提权比较多人搞的就是x86和arm指令集的Linux系统提权了,arm指令集的基本都是安卓root和iOS越狱,而mips指令集的几乎没啥人在搞,感觉是应用场景少。
环境准备
android内核编译
下载相关源码依赖
android内核源码使用的是goldfish[1],直接clone下来,又大又慢又久,在git目录下编译也麻烦,所以想搞那个版本的直接下那个分支的压缩包就好了
本文使用的工具的下载地址:
- 源码:https://android.googlesource.com/kernel/goldfish/+archive/android-goldfish-3.10.tar.gz
- 交叉编译工具:https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.6
- 一键编译脚本:https://android.googlesource.com/platform/prebuilts/qemu-kernel/+/master
PS:git clone速度慢的话可以使用国内镜像加速:s/android.googlesource.com/aosp.tuna.tsinghua.edu.cn/
1 | # 下载源码 |
修改内核
学android kernel pwn最初看的是Github上的一个项目[3],不过依赖的是旧内核,估计是android 3.4以下的内核,在3.10以上的有各种问题,所以我自己做了些修改,也开了一个Github源:https://github.com/Hcamael/android_kernel_pwn
对kernel源码有两点需要修改:
- 添加调试符号
首先需要知道自己要编译那个版本的,我编译的是32位Android内核,使用的是goldfish_armv7
,配置文件在: arch/arm/configs/goldfish_armv7_defconfig
但是不知道为啥3.10里没有该配置文件,不过用ranchu也一样:
给内核添加调试符号,只需要在上面的这个配置文件中添加:CONFIG_DEBUG_INFO=y
,如果是goldfish就需要自己添加,ranchu默认配置已经有了,所以不需要更改。
- 添加包含漏洞的驱动
目的是研究Android提权利用方法,所以是自己添加一个包含栈溢出的驱动,该步骤就是学习如何添加自己写的驱动
上面给了一个我的Github项目,把该项目中的vulnerabilities/
目录复制到内核源码的驱动目录中:
1 | $ cp vulnerabilities/ goldfish/drivers/ |
修改Makefile:
1 | $ echo "obj-y += vulnerabilities/" >> drivers/Makefile |
导入环境变量后,使用一键编译脚本进行编译:
1 | $ export PATH=/root/arm-linux-androideabi-4.6/bin/:$PATH |
PS: 在docker中复现环境的时候遇到一个问题,可以参考:https://stackoverflow.com/questions/42895145/cross-compile-the-kernel
编译好后的内核在/tmp/qemu-kernel
目录下,有两个文件,一个zImage,内核启动镜像,一个vmlinux是kernel的binary文件,丢ida里面分析内核,或者给gdb提供符号信息
Android模拟环境准备
内核编译好后,就是搞Android环境了,可以直接使用Android Studio[2]一把梭,但是如果不搞开发的话,感觉Studio太臃肿了,下载也要下半天,不过还好,官方提供了命令行工具,觉得Studio太大的可以只下这个
PS: 记得装java,最新版的java 11不能用,我用的是java 8
建一个目录,然后把下载的tools放到这个目录中
1 | $ mkdir android_sdk |
首先需要使用tools/bin/sdkmanager
装一些工具
1 | # 用来编译android binary(exp)的,如果直接用arm-liunx-gcc交叉编译工具会缺一些依赖,解决依赖太麻烦了,还是用ndk一把梭方便 |
PS:因为是32位的,所以选择的是armeabi-v7a
PSS: 我一共测试过19, 24, 25,发现在24,25中,自己写的包含漏洞的驱动只有特权用户能访问,没去仔细研究为啥,就先使用低版本的android-19了
创建安卓虚拟设备:
1 | ./bin/avdmanager create avd -k "system-images;android-19;google_apis;armeabi-v7a" -d 5 -n "kernel_test" |
启动:
1 | $ export kernel_path=ranchu_3.10_zImage |
去测试下我写的exp:
1 | $ cd ~/goldfish/drivers/vulnerabilities/stack_buffer_overflow/solution |
编译好了之后运行,记得要用普通用户运行:
1 | shell@generic:/ $ id |
Android 内核提权研究
环境能跑通以后,就来说说我的exp是怎么写出来的。
首先说一下,我的环境都是来源于AndroidKernelExploitationPlayground项目[3],但是实际测试的发现,该项目中依赖的估计是3.4的内核,但是现在的emulator
要求内核版本大于等于3.10
从内核3.4到3.10有许多变化,首先,对内核的一些函数做了删减修改,所以需要改改驱动的代码,其次就是3.4的内核没有开PXN保护,在内核态可以跳转到用户态的内存空间去执行代码,所以该项目中给的exp是使用shellcode,但是在3.10内核中却开启了PXN保护,无法执行用户态内存中的shellcode
提权思路
搞内核Pwn基本都是一个目的——提权。那么在Linux在怎么把权限从普通用户变成特权用户呢?
一般提权的shellcode长这样:
1 | asm |
这个shellcode提权的思路有三步:
- prepare_kernel_cred(0) 创建一个特权用户cred
- commit_creds(prepare_kernel_cred(0)); 把当前用户cred设置为该特权cred
- MSR CPSR_c,R3 从内核态切换回用户态(详情自己百度这句指令和CPSR寄存器)
切换回用户态后,当前程序的权限已经变为root,这时候就可以执行/bin/sh
再继续深入研究,就涉及到内核的三个结构体:
1 | $ cat ./arch/arm/include/asm/thread_info.h |
每个进程都有一个单独thread_info
结构体,我们来看看内核是怎么获取到每个进程的thread_info
结构体的信息的:
1 |
|