首页
社区
课程
招聘
[原创]cve-2020-0624 win32k漏洞分析笔记
2020-7-10 18:34 9959

[原创]cve-2020-0624 win32k漏洞分析笔记

2020-7-10 18:34
9959

目录

前言

当Win32k组件无法正确处理内存中的对象时,Windows即存在一个特权提升漏洞,我对他做了简单的分析.因为感觉作者的poc有一点小问题,所以文末会给出我的魔改版poc.
原作者poc: https://github.com/james0x40/CVE-2020-0624

漏洞影响范围

Microsoft Windows 10 1903
Microsoft Windows Server 1903
Microsoft Windows 10 1909

配置漏洞触发环境

[+] win10 x64 1903
[+] windbg preview 1.0.2001.02001

图片描述

BSOD分析

tip:因为KALSR的关系,我们分析起来会很麻烦,不过我们可以在调试之前先保存一个快照,这样我们调试的时候就可以先不考虑KALSR.

 

崩溃之后我们使用!analyze v来分析一下.首先查看一下错误类型

1: kd> !analyze v
ERROR: FindPlugIns 8007007b
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

BAD_POOL_CALLER (c2)
The current thread is making a bad pool request.  Typically this is at a bad IRQL level or double freeing the same allocation, etc.
Arguments:
Arg1: 0000000000000046, Attempt to free an invalid pool address
Arg2: ffffc29800880000, Starting address
Arg3: 0000000000000000, 0
Arg4: 0000000000000000, 0

错误是BAD_POOL_CALLER (c2),出现异常的地址为ffffc29800880000,错误提示是我们释放了错误的内存.没什么头绪,看看堆栈的内容

 

图片描述

 

看起来很像是子函数NewCCI2进行了错误的操作,但其实要稍微复杂一些,后面分析poc源码的时候再说.这里我们只关注win32kfull!Win32FreePoolImpl就好,正是这个函数导致了异常的发生,本来我是想直接给这个函数打断点,但是这个函数会被频繁调用,很不方便.查阅资料发现该函数释放的内存后七位固定为0880000,所以我们可以通过cx寄存器来判断释放的内存是否为一场内存.条件断点如下:

ba e1 win32kfull!Win32FreePoolImpl+0x46 "r rcx;.if(cx == 0){.echo 1}.else{.echo 2;g}"

又或者你干脆已经知道了错误内存的地址为ffffc29800880000,那么直接设置rcx=ffffc29800880000也是ok的.

 

图片描述

 

经历了漫长的条件判断之后我们终于断下来了,现在rcx的值为ffffc29800880000,正是我们的错误内存.检测一下属性

 

图片描述

 

解析不出来,直接查看数据

 

图片描述

 

连Header都没有,这根本就不是Kernel Pool,我们需要继续追踪这块奇怪的内存,从堆栈看一下调用关系

 

图片描述

 

上层函数是Win32FreePool,静态分析一下

 

图片描述

 

Win32FreePool函数仅仅是将参数传递给Win32FreePoolImpl函数而已,再看看上层函数xxxDestroyThreadInfo

 

图片描述

 

不同于以前的win7,在win10上无法查看tagTHREADINFO结构.所以无法得知tagTHREADINFO+0x2c8代表什么,以及是什么函数设置了它的内容,我们尝试继续下断点.

ba e1 win32kbase!xxxDestroyThreadInfo+0x94 "r rsi;.if(poi(rsi+0x2C8) != 0){.echo 1}.else{g}"

当rsi+0x2C8不为零的时候断下来,查看rsi+0x2c8是否为触发异常的内存.

 

图片描述

 

还是那个熟悉的数字,看来这个地址就是关键,某个函数设置了它的值,并且最终交给xxxDestroyThreadInfo函数来释放他所指向的内存,我们只要一步一步追溯就可以追溯到事发源头.但其实有更方便的法子,我们可以修改一下poc的源码,在一切都发生之前加入一个DebugBreak()断下来,接着对tagTHREADINFO+0x2c8下一个内存访问断点,这样windbg就会自动帮我们找到凶手了.

 

但是tagTHREADINFO的值每次都会发生变化,所以我们需要再保存一个快照,就在DebugBreak()函数断下来的时候.接着重新找出tagTHREADINFO的值,和刚刚一样:

 

图片描述

 

现在我们恢复到刚刚保存的快照.重新断在DebugBreak()之后,接着我们对ffffc298061d48a0+2c8下一个内存访问断点并运行

 

图片描述

 

断下来之后我们就可以看到修改ffffc298061d48a0+2c8的地方,看一下堆栈里面的调用关系

 

图片描述

 

就是win32kfull!xxxSBTrackInit这个邪恶的函数将错误的地址写入了ffffc298061d48a0+2c8.我们在ida里面查看一下

 

图片描述

 

上面这个名字长的一批的函数返回了一个指向tagSBTrack结构的指针,之后这个指针将会被写入tagTHREADINFO+0x2c8处,即tagTHREADINFO->pSBTrack.这块内存是由nt!MmCommitSessionMappedView函数分配的,而ExFreePool函数只能释放由ExAllocatePool,ExAllocatePoolWithTag,ExAllocatePoolWithQuota或ExAllocatePoolWithQuotaTag分配的内存,自然会触发异常从而导致BSOD.

poc源码分析

因为作者给出了源代码,所以我们接着看一下poc的源代码,我分成几个小部分来一一分析.

    /* 获取指向TEB和PEB的指针 */
    DWORD OldProtect{};    

    /* 获取指向TEB和PEB的指针 */
    PTEB teb = NtCurrentTeb();
    PPEB peb = teb->ProcessEnvironmentBlock;

OldProtect只是用来保存内存被修改前的访问保护值,teb和peb则分别保存线程环境块和进程环境块.

PVOID pCCI2 = &((PVOID*)peb->KernelCallbackTable)[2];

进程环境块中的KernelCallbackTable保存着函数指针表的副本,KeUserModeCallback通过参数ApiNumber作为索引来选择函数指针表中相应的函数.但是为什么是[2],我们可以在这两句代码之前下一个断点

0: kd> dt nt!_PEB @$peb +0x058
......
   +0x058 KernelCallbackTable : 0x00007ff5`6ad80028 Void
......
0: kd> dps poi($peb+58)
00007ffa`2bdb6330  00007ffa`2bd35150 USER32!_fnCOPYDATA
00007ffa`2bdb6338  00007ffa`2bdae720 USER32!_fnCOPYGLOBALDATA
00007ffa`2bdb6340  00007ffa`2bd52cd0 USER32!_fnDWORD
00007ffa`2bdb6348  00007ffa`2bd56780 USER32!_fnNCDESTROY
00007ffa`2bdb6350  00007ffa`2bd5cd50 USER32!_fnDWORDOPTINLPMSG
......

peb+58的地址就是KernelCallbackTable的地址,这里的[2]是USER32!_fnDWORD.如果我们向滚动条子控件发送WM_LBUTTONDOWN,消息时,会调用到win32kfull!xxxSBTrackInit()函数,该函数首先会创建一个Session Pool,用来保存 tagSBTrack结构.所以后面我们会特意营造这种情景来调用这个回调函数.

/*
    BOOL VirtualProtect(                // 此函数更改对调用进程的虚拟地址空间中的已提交页面区域的保护
        LPVOID lpAddress,                // 要更改其访问保护属性的页面区域的起始页面
        SIZE_T dwSize,                    // 要更改其访问保护属性的区域的大小
        DWORD  flNewProtect,            // 内存保护选项,PAGE_EXECUTE_READWRITE为可读可写可执行权限
        PDWORD lpflOldProtect            // 指向变量的指针,该变量接收页面的指定区域中第一页的先前访问保护值
    );

    PVOID InterlockedExchangePointer(    // 此函数原子交换一对地址
        PVOID volatile *Target,            // 目标地址
        PVOID          Value            // 与目标函数交换的地址
    );
    */

    if (!VirtualProtect(pCCI2, sizeof(PVOID), PAGE_EXECUTE_READWRITE, &OldProtect))
        return 0;
    OrgCCI2 = (PFNUSER32CALLBACK)InterlockedExchangePointer((PVOID*)pCCI2, &NewCCI2);

我们在上一步已经得到了指向(peb->KernelCallbackTable)[2]和(peb->KernelCallbackTable)[3]地址的指针,接着我们只要直接赋值就可以hook这两个函数了

 

图片描述

 

OrgCCI2保存原先的函数指针以使用正常的功能,这样我们的hook函数既可以执行我们自定义的操作,还不影响原本的功能.

    hChild = CreateWindow(
        L"ScrollBar", 
        L"Vul", 
        WS_OVERLAPPEDWINDOW | WS_VISIBLE, 
        CW_USEDEFAULT, 
        CW_USEDEFAULT, 
        10, 
        10, 
        NULL, 
        NULL, 
        NULL, 
        NULL
    );

scrollbar的窗口是可见的, 设置WM_VISIBLE,这样才能成功触发.至此,回调函数也hook完了,窗口也已经创建了,我们可以开始考虑调用我们hook的函数了,具体实现如下

NTSTATUS NTAPI NewCCI2(PVOID Param)
{
    if (Flag)
    {
        ExitThread(0);
    }
    return OrgCCI2(Param);
}

因为被我们hook的两个函数有可能会被其他部分调用,所以我们设置了Flag1和Flag2来跳过我们hook的内容,而去执行OrgCCI2和OrgCCI3,这两个指针保存的正是hook之前的函数指针,这样,其他部分调用hook之后的函数也不会发生异常.

    Flag = TRUE;
    SendMessage(hVul, WM_LBUTTONDOWN, 0, 0);

在NewCCI2中,因为Flag已经被置1,所以我们会调用if语句之内的内容,也就是ExitThread(0).接着win32kfull!Win32FreePoolImpl就会调用nt!ExFreePool来释放tagSBTrack,导致BSOD.

 

大概流程是这样:

[+] HOOK KernelCallbackTable->fnDWORD
[+] 创建一个可视的滚动条窗口SrollBar并发送WM_LBUTTONDOWN消息
[+] 系统处理消息初始化SBTrack结构并开始循环,接着触发fnDWORD回调
[+] 由于KernelCallbackTable->fnDWORD已经被我们修改,所以程序转去执行NewCCI2函数
[+] win32kfull!xxxSBTrackInit()函数已经将tagSBTrack结构写入了tagTHREADINFO+2c8处,退出线程时会触发BSOD

参考文章

晏子霜师傅本人和博客都有很大帮助,我偷了很多思路和技巧:http://www.whsgwl.net/
wjllz师傅,同样是偷思路和技巧:https://xz.aliyun.com/u/12604
其他:
https://www.anquanke.com/post/id/97498
https://pediy.com/kssd/pediy11/104918.html

一些疑问

遗憾的是,我没能完成利用.因为我对于类型隔离中分配的这块内存实在是没有办法了,问了一位师傅得到的答复是这是一个利用的可能性微乎其微的漏洞,但微软官方给出的确实是权限提升的通告,所以论坛的师傅们如果有思路的话请分享一下,不胜感激!!!

 

博客有我的联系方式,欢迎大家来玩,地址:https://www.0x2l.cn


阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

最后于 2020-8-7 10:44 被0x2l编辑 ,原因: 修改
上传的附件:
收藏
点赞5
打赏
分享
最新回复 (7)
雪    币: 3981
活跃值: (5619)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
badboyl 2 2020-7-11 21:23
2
0
好文
雪    币: 7
活跃值: (4331)
能力值: (RANK:270 )
在线值:
发帖
回帖
粉丝
0x2l 3 2020-7-12 00:51
3
0
blck四 好文
参考了很多其他师傅的优秀文章
雪    币: 9301
活跃值: (6179)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
王cb 9 2020-7-12 13:24
4
0
雪    币: 292
活跃值: (680)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
Keoyo 2 2020-7-13 19:15
5
0
感谢楼主分享,看了下补丁,微软把原先调用Win32FreePool的地方改成了TypeIsolation的释放方法,调用NSInstrumentation::CTypeIsolation<28672,112>::Free来释放threadinfo+2c8位置,也就是pSBTrack对象。

if ( v42 )
  {
    HMAssignmentUnlock(v42 + 16);
    HMAssignmentUnlock(*((_QWORD *)v0 + 89) + 24i64);
    HMAssignmentUnlock(*((_QWORD *)v0 + 89) + 8i64);
    v43 = (_QWORD *)*((_QWORD *)gpUserTypeIsolation + 5);
    if ( v43 )
      NSInstrumentation::CTypeIsolation<28672,112>::Free(v43, *((_QWORD *)v0 + 0x59));
    *((_QWORD *)v0 + 89) = 0i64;
  }

这个洞还是挺有意思的,应该属于一个win32k的历史遗留问题,在采用typeisolation之前都是按照常规的kernel pool的分配方法,微软改用类型隔离之后这个kernel pool的位置没有改回去,老版本中想利用这个洞还是几乎不可能的,按照原作者的描述说这个洞是个UAF实际上并不是一个UAF,严格意义上来说可以算是类型混淆,但实际上SBTrack的typesolation allocator在这里并没有涉及释放的问题,当然也无从谈起占位的问题。
另外其实关于typeisolation在allocate的时候的操作很好理解,有点像lfh会从一个很大的section中划出一片你对象需要的内存,当然这里就是sbtrack对象,然后在allocator manager结构中有一个bitmap专门管理这片section,使用状态就是1,未使用就是0,当然free的操作也很简单,就是将bitmap对应的比特置0即可。
最后还是感谢楼主分享(题外话:因为我最开始看到作者说UAF,楼主说是因为释放了非kernel pool的内存是对楼主的分析感到质疑,认为微软不会有这么低级的操作,会不会是在其他操作,比如destroywindow的时候sbtrack对象被释放掉未置位,导致最后destorythreadinfo的时候导致double free呢,没想到楼主是正确的,微软还是延续了此类作风2333)
雪    币: 27
活跃值: (532)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Concord 2020-7-13 19:19
6
0

赞 感谢楼主分享

最后于 2020-7-13 19:20 被Concord编辑 ,原因:
雪    币: 7
活跃值: (4331)
能力值: (RANK:270 )
在线值:
发帖
回帖
粉丝
0x2l 3 2020-7-13 19:29
7
0
Keoyo 感谢楼主分享,看了下补丁,微软把原先调用Win32FreePool的地方改成了TypeIsolation的释放方法,调用NSInstrumentation::CTypeIsolation::Free来 ...
感谢师傅的指点。我最近看到一个很类似的漏洞,就是cve-2018-8453。漏洞成因虽然不相同,但是那个漏洞也是对SBTrack进行利用,师傅有兴趣的话可以看看晏子霜师傅对于那个漏洞的分析:http://www.whsgwl.net/blog/CVE-2018-8453_1.html
雪    币: 364
活跃值: (1416)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
wujimaa 1 2020-7-29 11:49
8
1
好文
游客
登录 | 注册 方可回帖
返回