/advent

10 - Got Root? r2k

Welcome once again to the Advent of Radare2!

Today we’ll dive deep into Linux Kernel hacking, exploring the intricate world of kernel memory manipulation and task structure internals.

To do this we’ll leverage r2k, the kernel plugin for radare2 which interacts with the radare2 kernel module for Linux (but bear in mind that it’s also available for macOS and Windows), to directly interact with kernel memory spaces while bypassing traditional debugging restrictions.

You’ll learn how to build and load custom kernel modules, navigate through physical and virtual memory spaces, and understand the relationships between process memory layouts.

We’ll also explore techniques to manipulate the task_struct data structures, modify process credentials like effective user IDs (EUID), and bypass common anti-debugging mechanisms. Using a bash shell as our test subject, we’ll demonstrate how to elevate privileges by directly modifying kernel memory structures, all while avoiding detection from security monitoring tools.

Ring Zero

Nowadays, modern kernels come with several safety protections that enforce a strict separation between kernel space (Ring 0) and user space (Ring 3). This separation is a fundamental security principle where applications run in a restricted user mode, while the kernel operates with full hardware privileges in kernel mode. This architecture prevents user applications from directly accessing hardware or critical memory areas, reducing the risk of system crashes and security breaches.

When a userland process needs to perform privileged operations (like reading a file or creating a network connection), it must use system calls (syscalls) to request the kernel’s services. Syscalls are executed through specific CPU instructions (like SYSCALL on x86_64) that trigger a controlled transition from Ring 3 to Ring 0. During this transition, the CPU switches to a separate kernel stack and validates the syscall parameters.

To maintain memory safety, data passed between userland and kernel space must be carefully copied rather than directly accessed. The kernel uses functions like copy_from_user() and copy_to_user() to safely transfer data across this boundary, ensuring that userland processes can’t trick the kernel into accessing invalid or malicious memory addresses. Additionally, the kernel verifies all memory addresses provided by userland applications before accessing them, preventing potential buffer overflows or other memory-based attacks.

Safety Considerations

This introduction should get you started with r2k. Remember to always exercise caution when working with kernel-level tools, as mistakes can lead to system instability or crashes.

Installing the Plugin

r2k is a kernel module plugin for radare2 that allows direct access to kernel memory and hardware devices. It enables users to analyze and debug kernel-level code, inspect kernel memory spaces, and read/write any process or physical memory at will.

r2k consists of two main components:

While this guide focuses on Linux, r2k also supports Android, Windows and macOS/iOS. Each platform has its specific building requirements and limitations.

$ sudo r2pm -ci r2k-linux

Under the hood, the r2pm package will download the radare2-extras repository, change to r2k/linux and run make to compile and insert the module in the kernel.

$ make
make -C /lib/modules/6.8.0-49-generic/build M=/home/pancake/prg/radare2-extras/r2k/linux modules
make[1]: Entering directory '/usr/src/linux-headers-6.8.0-49-generic'
warning: the compiler differs from the one used to build the kernel
  The kernel was built by: x86_64-linux-gnu-gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0
  You are using:           gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0
  CC [M]  /home/pancake/prg/radare2-extras/r2k/linux/r2k.o
  LD [M]  /home/pancake/prg/radare2-extras/r2k/linux/r2kmod.o
  MODPOST /home/pancake/prg/radare2-extras/r2k/linux/Module.symvers
  CC [M]  /home/pancake/prg/radare2-extras/r2k/linux/r2kmod.mod.o
  LD [M]  /home/pancake/prg/radare2-extras/r2k/linux/r2kmod.ko
  BTF [M] /home/pancake/prg/radare2-extras/r2k/linux/r2kmod.ko
Skipping BTF generation for /home/pancake/prg/radare2-extras/r2k/linux/r2kmod.ko due to unavailability of vmlinux
make[1]: Leaving directory '/usr/src/linux-headers-6.8.0-49-generic'

Several things can go wrong here, even if r2k have enough ifdefs to be able to compile in many versions of the Linux kernel you need the linux-headers and disable some security measures to make insmod to work.

If we have the r2kmod.ko in that directory we are one step closer to achieve our plan!

Troubleshooting

Inserting a module shouldn’t be more than just insmod r2kmod.ko. If that fails we probably need to change these two things in the BIOS and GRUB/UEFI:

Once installed this new device should be created, note that permissions only allow r2k to be used from root. But if we want to use it from any other user we can just chmod’it!

$ dmesg | tail -n 2
[118301.185120] r2k: loading driver
[118301.185198] r2k: /dev/r2k created

$ ls -l /dev/r2k
crw------- 1 root root 234, 0 dic  2 15:30 /dev/r2k

The r2k’s io plugin

The way to communicate with the kernel from radare2 is via the r2k io plugin. This plugin is built and shipped by default in all the builds for Linux, macOS or Windows. Because there are kernel modules for all those 3 operating systems (iOS and Android are also part of the list)

Finding the Task struct

The task_struct is the kernel’s process descriptor in Linux - a data structure that contains all information about a specific process. It’s defined in <linux/sched.h> and it includes:

Some links for reference:

struct cred {
    atomic_long_t   usage;
    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 */
...
        struct rcu_head rcu;        /* RCU deletion hook */
    };
} __randomize_layout;

NOTE that __randomize_layout is a Linux kernel security feature (a GCC attribute) that randomizes the layout of sensitive kernel structures in memory during compilation. This helps prevent attacks that rely on knowing the exact layout or offset of structure members. It’s part of the kernel’s defense against exploitation techniques like buffer overflows and information leaks.

$ sudo r2 r2k://
 -- Seek at relative offsets with 's +<offset>' or 's -<offset>'
[0x00000000]> :?
Usage: :[MprRw][lpP] [args...]
:dm              Print kernel memory map (or process if r2k.io==1)
:dr              Print control registers
:dR              Print control registers in detailed mode
:dp [pid]        Print current selected pid or change it
:e r2k.io=[012]  Read/Write from 0: Linear, 1: Process, 2: Physical addresses

Old Commands: (deprecated)
:M                      Print kernel memory map
:b      beid [pid]      Change r2k backend. pid is required when beid is 1.
                         0: linear address; 1: process address; 2: physical address
:p      pid             Print process information
:rl     addr len        Read from linear address
:rp     pid addr len    Read from process address
:rP     addr len        Read physical address
:R[p]                   Print control registers. Use :Rp for detailed description
:wl[x]  addr input      Write at linear address. Use :wlx for input in hex
:wp[x]  pid addr input  Write at process address. Use :wpx for input in hex
:wP[x]  addr input      Write at physical address. Use :wPx for input in hex
:W      1|0             Honor arch write protect (1 enable WP, 0 disable WP)
[0x00000000]> 

Let’s open a new terminal and get the PID for the current shell:

$ ps
    PID TTY          TIME CMD
  62711 pts/4    00:00:00 bash
  62725 pts/4    00:00:00 ps
$

To find the address of the task struct in the kernel for a specific PID we can use the :dp command to select the process id, and then we will switch the IO mode with :e r2k.io=1, this way we can read and write into the process memory without using /proc/pid/mem or ptrace.

At this point we must check the dmesg:

[0x00000000]> :dp 62711
[0x00000000]> :e io.r2k = 1
[0x00000000]> !sudo dmesg | grep Task | tail
[119007.797853] Task UID 1000 Comm bash
[119007.797858] Task 62711 EUID 0xffff8da6de2233d8 Cred 0xffff8da6de2233c0
[119007.797861] Task 0xffff8da581668000 + 2992

The kernel usually makes those structs hard to find without symbols structs, but r2k is pleasant enough to provide us this information. So let’s make use of it by switching back to the linear addressing mode.

[0x00000000]> :e r2k.io=0
[0x00000000]> 0xffff8da6de2233d8
[0xffff8da6de2233d8]> x 32
- offset -          D8D9 DADB DCDD DEDF E0E1 E2E3 E4E5 E6E7  89ABCDEF01234567
0xffff8da6de2233d8  e803 0000 e803 0000 e803 0000 e803 0000  ................
0xffff8da6de2233e8  0000 0000 0000 0000 0000 0000 0000 0000  ................
[0xffff8da6de2233d8]> 

We have some numbers there, but do we know the meaning of e803? Let’s use pv4 to swap the endianness and the use base10:

[0xffff8da6de2233d8]> pv4
0x000003e8
[0xffff8da6de2233d8]> pv4d
1000
[0xffff8da6de2233d8]> 

Exactly! It’s the UID, EUID, GID and EGID!

The ‘e’ in EUID, EGID, etc. stands for “effective,” and it’s part of Linux’s security model for process credentials.

Each Linux process has four types of IDs: real (from process creator), effective (current permissions), saved (backup of effective), and filesystem (for file access). This enables features like setuid programs, where processes can temporarily gain elevated permissions while maintaining their original user identity.

The real ID (RUID/RGID) represents the user/group from the user who started the process, the effective ID (EUID/EGID) determines the actual permissions for the process during execution.

The kernel prevents direct access to protected memory regions. r2k bypasses these protections by exploiting kernel functions to perform privileged memory operations, allowing arbitrary read/write access to normally restricted memory addresses, including the cred struct.

So now we can just patch it:

[0xffff8da6de2233d8]> wv4 0

And observe what happens if we run whoami in the pwned shell again:

pancake@pnuc:~$ whoami
pancake
pancake@pnuc:~$ whoami
root
pancake@pnuc:~$ 

Challenge

The challenge for today involves working with r2k, a kernel module designed for memory manipulation at ring zero (kernel level) privileges. Follow the steps described in the post to

While technically straightforward, it requires attention to detail and proper execution of each step to ensure the r2k module is correctly installed and configured. The exercise serves as a practical introduction to kernel-level memory analysis tools.

Closing

If there’s any problem during the process feel free to submit your questions, pull requests, fill tickets or whatever needed to improve the state of the Kernel support for radare2!

See you tomorrow in another advent post of radare2!

–pancake