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.
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.
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.
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:
r2k.ko
) that provides the low-level
accessWhile 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!
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:
module.sig_enforce=0
in the
/boot/grub.cfg cmdline)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 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)
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; /* real UID of the task */
kuid_t uid; /* real GID of the task */
kgid_t gid; /* saved UID of the task */
kuid_t suid; /* saved GID of the task */
kgid_t sgid; /* effective UID of the task */
kuid_t euid...
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:~$
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.
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