CVE-2017-16995 Ubuntu16.04漏洞复现

漏洞概述

Ubuntu 16.04 本地提权漏洞,该漏洞存在于Linux内核带有的eBPF bpf(2)系统调用中,当用户提供恶意BPF程序使eBPF验证器模块产生计算错误,导致任意内存读写问题。

影响版本:

Linux Kernel Version 4.4 ~ 4.14
Ubuntu版本:16.04.01~ 16.04.04

复现环境:

ubuntu-16.04.2-desktop-amd64.iso

Linux ubuntu 4.8.0-36-generic #36~16.04.1-Ubuntu SMP

原exploit代码
改进版exploit

漏洞原理

众所周知,linux的用户层和内核层是隔离的,想让内核执行用户的代码,正常是需要编写内核模块,当然内核模块只能root用户才能加载。而BPF则相当于是内核给用户开的一个绿色通道:BPF(Berkeley Packet Filter)提供了一个用户和内核之间代码和数据传输的桥梁。用户可以用eBPF指令字节码的形式向内核输送代码,并通过事件(如往socket写数据)来触发内核执行用户提供的代码;同时以==map==(key,value)的形式来和内核共享数据,用户层向map中写数据,内核层从map中取数据,反之亦然。BPF设计初衷是用来在底层对网络进行过滤,后续由于他可以方便的向内核注入代码,并且还提供了一套完整的安全措施来对内核进行保护,被广泛用于抓包、内核probe、性能监控等领域。
Bpf指令的校验是在函数do_check中,代码路径为kernel/bpf/verifier.c。do_check通过一个无限循环来遍历我们提供的bpf指令。

简言之,eBPF提供了一种用户往内核传递代码并进行数据交互的途径。当然,内核会对用户注入的BPF指令进行安全校验(类似于虚拟执行)。本漏洞即利用一个符号扩展的bug,导致一个本应永真的布尔表达式会在实际执行时判断为false。因而,如果把shellcode放在这个所谓的“false”分支就可以跳过eBPF的安全检查(不会检查永假的分支),从而达到往内核注入任意代码的目的,提权就水到渠成了。

1
2
3
4
if <false_true_expression> //检查时为true,实际执行为false  : <(u32)0xFFFFFFFF == -1>
exit(0);
//shellcode
...

exploit详解

根据上节,相当于通过一个漏洞,我们可以通过eDPF向内核注入任意代码,并且可以在内核和用户态间传递信息。
那么如何利用这些达成一次exploit呢。这里我们的目标是本地提权:

思路:

  1. 获取到==内核的栈地址==(:shellcode获取内核栈地址+读取到用户态)
  2. ==current的地址==(:常规操作)
  3. ==task_struct的地址==(读current结构体的第一个成员:需要一个对指定地址的读操作+传到用户态)
  4. 然后定位到==cred的地址==,然后定位到==uid的地址==(:常规操作)
  5. 然后直接将uid的值改为0(:一个针对指定地址的写操作),
  6. 然后启动/bin/bash(已经提权,直接进行)

shellcode(eBPF代码)

那么由上可知,我们要求shellcode做到:

  1. 与用用户态进行信息交互(通过map实现)
  2. 指令A:==读取==内核栈地址,传给map
  3. 指令B:从map读取地址,==读取==该地址的值,传给map
  4. 指令C:从map读取一个值和地址,将该值==写==到该地址
  5. 根据用户态程序,选择性执行指令A或B、C

用户态漏洞程序

同时我们构造这样的用户程序:

  1. 将shellcode通过eBPF送入内核
  2. 执行shellcode进行指令A,得到内核栈地址
  3. 计算current的地址address_c
  4. 传入address_c,执行shellcode进行指令B,得到task_struct的地址
  5. 计算cred的地址,uid的地址address_uid
  6. 传入address_uid和值0,执行shellcode进行指令C,提权成功!
  7. root下启动/bin/bash

参考链接: