CVE-2016-0095提权漏洞分析

1 前言

瞻仰了k0shl和鹏哥 的漏洞分析,感慨万千,任重而道远。

2 系统环境和工具

  • windows 7 32旗舰版

  • windbg

3 poc

3.1poc复现

首先k0shl大佬给出的poc():

注意 这些地方可能需要稍作修改:

  • 首先是对NtGdiSetBitmapAttributes的重构定义中使用的W32KAPI:

  • 其次是KiFastSystemCall没有提供地址:

生成并在 目标系统中运行,产生如下异常:

1568897875975

3.2 poc分析

下面是poc的触发函数:

简述分析:

这个漏洞和BitMap有关,触发漏洞所在流程:创建 bitmap--> 创建HDC设备-->绑定(选中)一个bitmap-->创建画刷,矩形-->使用FillRgn填充

应该是前面几个步骤中的不当行为,在调用FillRgn的时候触发了漏洞.

4 漏洞分析

4.1 查看异常上下文

首先查看 寄存器环境:

1568902987053

分析:

可以看到 eax=0,导致了崩溃语句中 eax+24h是一个非法地址.

栈回溯分析: 1568903006134

看到几个关键函数以及偏移,接下来在IDA静态分析。

4.2 使用IDA分析该模块

通过IDA 加载win32k.sys 分析(注意加载符号文件):

1568902909087

看到和参数2有关,而参数二 是 EBRUSHOBJ * ,而且我们可以根据附近的上下文获得下面的关系式:

[ [ [arg_4] +34h ] +1ch] + 24h ]

arg_4 = ebp +0xc

关系式还可以写成如下:

[ [ [ ebp +0xc ] + 34h ] + 1ch] + 24h ]

所以目前我们需要知道 EBRUSHOBJ:0x34 是什么类型的数据

由于EBRUSHOBJ 是未公开的结构体,所以不好找缘由,只有追溯ERUSHOBJ* a2的来源:

  • 查看伪代码:可以看到下图中的伪代码界面,异常点和a2有关,a2参数 是一个EBRUSHOBJ* 类型数据。继续往上找a2 的来源。

    1569244793292

  • 追溯上一层分析:发现是刚才的a2 是 pvGetEngBrush(struct _BRUSHOBJ * a1) 调用时传入的参数 a1.

    1569244902159

  • 继续追溯上一层:

    1569245040647

    在本层查看局部变量v13的数据来源: 可以看到是 调用 EngBitBlt的时候传入的第9个参数 struct _BRUSHOBJ *a9

    1569245104601

  • 继续追溯上层:发现是 EngPaint() 的第三个参数 struct _BRUSHOBJ *a3

    1569245331220

  • 继续向上追溯:这里 我是分析完了再截的图,这里的 v5 是分析了vInitBrush() 才确定的关系,但是在这里我们可以看到 v18 是前面我们分析的 struct _BRUSHOBJ *a3;往上一看,有一个初始化v18的函数,这里就是关键处了。接下来我们查看vInitBrush()

    1569481806047

    vInitBrush():通过查看vInitBrush() ,可以看到 这里的this (前面的 v18 ), v18[13] 即 我们崩溃触发点的 a2[13] (前面回溯流程第一张图里)。那么奔溃数据源 就转移到了 vInitBrush() 的第6个参数 a6; 接下来返回上一层 查看 vInitBrush()的第6个参数数据。

    1569247692181

  • 可以看到 a6刚好 就是我们的 v5,而 v5 在这儿是 int类型所以 +16 在这儿就是简单的+16=10h. 而 根据最开始的分析异常崩溃处是 [a2[13]+7] ,这里 a2[13] 是一个指针,所以 +7 应该是数值上的+ 28 即 1ch, 而 a2[13] = 前面的a6 = v5 ,而 v5 +10h是一个 _SURFOBJ 结构体指针,所以 奔溃处的 [a2[13]+7] = v5 +1ch = _SUROBJ:0ch 。

    1569245519175

    而_SUROBJ 是一个公开的结构体:

    可以看到 这个结构体中0ch 偏移处是 HDEV hdev.

4.3 结论

_SURFOBJ 结构体中 HDEV字段 hdev 为 0 导致了这个漏洞的产生,也即[EBRUSHOBJ:0x34]:0x1c 处为0 导致了这个漏洞

所以可以得出结论是 hdev 没有正确得到初始化, 造成的这个漏洞的触发; 由于目前水平有限这里对内核函数(HBITMAP)NtGdiSetBitmapAttributes((HBITMAP)hBitmap1, (DWORD)0x8f9);不明白其dwflag = 0x89f 的意义。

5 漏洞利用EXP

5.1 分析提取可利用点

由于这里win7 32/64 可以直接调用NtAllocateVirtualMemory申请零页内存,那么这里可以调用此函数申请内存,然后在特定位置放上我们的代码等着它call 就行了。

那么哪里可利用点在哪里呢?

梳理一下: 在异常之前,那些都是正常运行过来了,所以这里我们可以在异常崩溃所处的函数内部找那些 和 a2 (崩溃处的a2 --IDA中标识第二个参--即是 引发崩溃的数据来源)有关的 call, 这些call 即为可利用点。

继续使用IDA按f5查看和a2相关的call,发现如下可利用点:

1569507144839

查看汇编层:

1569510207824

但是注意,前面有几个条件跳转语句,为了是执行流正常来到这里,我们必须将前面几个跳转语句过掉:

整理之后发现有三处:

  1. if(! v23) 处不能为真

    1569508775049

    分析:

    这里可以看到 v20 = a2, v23 = *((WORD*)V20 + 712); 因为 v20 被强转成 WORD * 型, 所以在 V20 + 712 = (byte* )a2 + 712 *2 = a2 +590h.

    结论:

    word ptr [(byte *)a2 + 590h] != 0

  2. if(!*24) 处不能为真

    1569509050320

    分析:

    v24 = (WORD *)((char *)V20 +1426); 这里 V20 被强转成 char * 类型,所以 v24 = (char *) v20 + 1426 = (byte *)a2 + 592h;

    结论:

    word ptr [(byte *)a2 + 592h] != 0

  3. if(v26) 处必须为真

    1569509446624

    分析:

    在 v26 = a2[466] ; 因为 a2 是一个指针,所以 a2[466] = dword ptr [(byte *)a2 + 466*4] = dword ptr[ (byte*) a2 +748h];

    结论:

    所以 dword ptr[(byte*)a2 = 748h] !=0

综上分析

在以下条件处成立,即可利用 此处。

word ptr [(byte *)a2 + 590h] != 0

word ptr [(byte *)a2 + 592h] != 0

dword ptr[(byte*)a2 = 748h] !=0

5.2 利用原理

win7 32/64 可以使用 NtAllocateVirtualMemory() 任意申请 0零页内存,而且还没有 SMEP(这里的知识是安全客上学的,还有HEVD), SMEP有点像DEP,是内核的一种缓解措施,我们可以将它简单的理解成禁止在内核态下执行用户空间的代码). 所以直接就能在 RING0 执行 RING3 的代码完成提权。

这里提权的方式是将当前进程的Token 安全令牌换成系统的,即能实现切换到system 最高权限。

5.3 利用实现

(有些代码参考k0shl大佬的,因为原理流程是那样)

  1. 使用NtAllocateVirutalMemory() 申请0页内存,并设置前面分析的跳转条件通过

  2. 获取系统 token 换到当前进程

5.4 在提权后弹一个shell 用以验证

5.5 完整提权EXP

6 查看提权效果

1569571159023

7 升级为 64位 提权 EXP

这里64位分析过程和32位类似,篇幅限制就不一一赘述了。

7.1 解决vs64位汇编不支持内联的问题

点击项目属性,按照下图索引

1569571529068

然后勾选上 masm ;即可 编译asm 文件,虽然不能支持内联,但是也能使用asm 中创建api 调用asm api 的方式替换。

1569571602049

7.2 win7-64位 分析 KiSystemCall64

这里系统调用KifastCallentryshi32位中的的调用,在64位发生了改变,使用syscall ,KiSystemCall64:

R10和R11是呼叫密集型暂存器。用asm编写的透明包装函数可以将它们用于暂存空间,而不会干扰RCX,RDX,R8,R9中的任何args,并且无需在任何地方保存/恢复保留调用的寄存器。

R12..R15是调用保留的寄存器,您可以将其用于任何需要的寄存器,只要在返回之前保存/恢复它们即可。

R10和R11还是x86-64 SysV中的非参数传递调用对象寄存器。有趣的事实2:syscall 破坏 R11(和RCX),因此Linux使用R10而不是RCX来将参数传递给系统调用,但否则使用与用户空间函数调用相同的register-arg传递约定

1569574656234

还是查看一下 KiSystemCall64 满足以下好奇心:

syscall是AMD CPU下的sysenter,以此进入内核层,由于64位下没有nt!KiFastCallEntry,而改用的是nt!KiSystemCall64,在64位系统下启用了四个新的MSR寄存器,有不同的作用,其中MSR_LSTAR保存的是rip的相关信息,可以通过rdmsr c0000082的方法查看到syscall跳转地址。这个地址正是nt!KiSystemCall64的入口地址。

32位与64位call地址的区别见:加密解密4 543页

前置: ntdll!NtCreateDebugObject: 00000000`76d70680 4c8bd1 mov r10,rcx 00000000`76d70683 b890000000 mov eax,90h 00000000`76d70688 0f05 syscall 00000000`76d7068a c3 ret

syscall { rcx = rip; /* save rip for syscall return / r11 = rflags; / save rflags to r11 / ... jmp MSR_LSTAR; / MSR_LSTAR = __readmsr(0xC0000082); */ }

  • 所以分析kiSystemcall64 (参看看雪论坛):

注:这里不知道具体怎么系统调用的,所以我看了以下gdi32.dll的源码如下:

可以看到在SetBitmapAttributes中调用了NtGdiBitmapAttributes,那我们直接分析SetBitmapAttributes。

1569590740489

还说分析一下的,结果直接加载符号用IDA 查看 就出来了:

1569590378756

7.3 升级EXP

1569572386450

调整后的asm 文件如下:(这里使用 https://www.vergiliusproject.com/kernels 查看的结构体偏移,eprocess链接: https://www.vergiliusproject.com/kernels/x64/Windows%207%20%7C%202008R2/RTM/_EPROCESS

8 查看64位下提权结果

1569587643803


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值