windbg分析dmp蓝屏文件_手把手教你分析漏洞 : CVE-2018-8120

3046a3928943d9016123cd15a575ba8e.png

实验环境

64fef0067eca07bd02a4fedd85cc8b6d.png

双虚拟机调试

这里我把漏洞机和调试机都放在了虚拟机里,采用虚拟机调试虚拟机的好处就是可以实时保存快照,有空的时候在接着工作(其实是为了更好的加班,开个玩笑~~

1.漏洞机的设置

在虚拟机里安装后系统后,必须进行断网设置 ,因为我在测试调试的时候,运行poc几次后都是成功的,而后就不行了,排除了原因很久,IDA发现,它自动打了补丁(以前内核调试的时候,都没有遇到这种问题)。断网设置如下:

5f25775bf38a838d0db6c04094774d4a.png

右键漏洞机-->虚拟机设置,这里首先把“打印机 ”给删除了(调试机也一样), 然后点击添加--> "串行端口",并根据截图填写下列信息就好:

315bbc74685d0d692a705c90a6e51b40.png

进入漏洞机,以管理员打开cmd,分别输入下列命令:

9088390c21090af9acf8411489d3f604.png

a4674971c6fb4cea6eed9795c917e029.png

这里写了个自动的WKD.bat脚本,管理员运行它即可:

46744c6dbeba6b948a04880377989fda.png
(原图查看地址:https://imgchr.com/i/uVCxX9)

2. 调试机的设置

调试机的虚拟机设置

1f8a68c4cad92624ddd337d457d7aba6.png

在调试机里安装后windbg,设置调试符号,计算机--> 属性 --> 高级系统设置 --> 环境变量

e8239407ab98b77f60e9f52943d55c6a.png

管理员打开windbg,并进行下列nel ebug --> COM

5c22fc70b5309fa034e9dcf7982efaf9.png

注意:

1. win7的com端口是默认安装的,在系统安装到虚拟机的时候,自己可以查看设备管理器验证一下,都是COM1的。
2. 首先必须删除掉打印机,再进行串口的添加,这样才能保证漏洞机和调试机两边串口名的一致性,两边才能正常通信,不然windbg是附加不上的。比如在虚拟机设置--硬件--设备栏里,漏洞机的串口名为"串行端口2", 而调试机的串口名为"串行端口1",两者就不能通信。在都删除了打印机的情况下,漏洞机和调试机的串口名应该都为"串行端口",这样才能通信。

现在漏洞机和调试机都设置好了,管理员打开windbg,然后在重新打开漏洞机,选择MyDebug; 等待一段时间,windbg出现如图所示,即表示双机调试环境搭建成功。

e890b3c0fb9a340333990cf9a072bf20.png

漏洞原因

81f277c191ccf5b8a8dde20af6ab2fa0.png

原因是在win32k.sys组件中,SetImeInfoEx函数没有对指针进行安全验证; 当指针指向零地址后,在接下的代码又对该区域进行访问,就会引发访问违规,触发蓝屏。所以该漏洞属于空指针引用漏洞。而 SetImelnfoEx 函数只有NtUserSetImeInfoEx调用它,我们就从 NtUserSetImeInfoEx函数开始分析起吧。

e761416dea0b9bbf1fafc1e1cbedb94b.png

be2b4014be4f47cef1f1bd4eb5796f02.png

8baf4f4d820ef021c1d593a2685ae948.png

在win32k.sys中, NtUserSetImeInfoEx函数是用于将用户进程定义的输入法扩展信息对象设置到与当前进程关联的窗口站中。从上图可以看到NtUserSetImeInfoEx函数只有一个参数a1,而a1参数的类型其实是一个tagIMEINFOEX结构体,这个结构就是输入法的扩展信息结构。值得注意的是NtUserSetImeInfoEx函数里有调用了GetProcessWindowStation来获取当前进程所在窗口站的句柄。而与GetProcessWindowStation函数相对应的函数是在用户层使用CreateWindowStationW创建了一个窗口站,并通过SetProcessWindowStation将窗口站与当前进程相关联。而CreateWindowStationW返回的是一个tagWINDOWSTATION 句柄。

814984de769c02ba7405b77231b1fde3.png

下面我们就先弄明白tagWINDOWSTATION是什么,现在根据github上的代码改写一下poc。

1b34ec641f7ed1ec1170db9f6dd98d4b.png

vs2013-->文件-->新建--项目-->Visual C++ -->Win32项目-->控制台应用程序-->空项目。新建好工程后,这里就要设置一下属性了。

1570c7b8a17d1c1a30a3829cccf0054e.png

11df08695f4f27fdae8ac369ca765747.png

编译运行后,得到crash.exe并把它赋值到漏洞机里,然后点击运行crash.exe; 调试机里windbg断下, 并切换到crash.exe进程。

e0b4c6b91b3817f23728f2a203a992e9.png

可以看到没有执行CreateWindowStationW函数时,tagWINDOWSTATION->spklList时是有值的。

b4a698391470686e4aac0741663c7006.png

执行CreateWindowStationW函数后,tagWINDOWSTATION->spklList默认为空值了。

24832824cc2b7e0756fe8242576aa836.png

这个SetProcessWindowStation函数,如果大家有兴趣可以逆向一下,因为在我们的重点不是在这里。

b9b3aa05128a8e491a07dccb06dcc172.png

那什么是tagWINDOWSTATION呢?窗口站是和当前进程和会话(session)相关联的一个内核对象,tagWINDOWSTATION 包含了剪贴板(clipboard)、原子表、一个或多个桌面(desktop)对象等。

4896d4ae89119851d38b3e348ec1c925.png

而且这个 NtSetUserImeInfoEx 函数的参数 tagIMEINFOEX a1 是我们可以构造的,是可控的。到这里我们反汇编NtUserSetImeInfoEx函数,并在 94410030 地址处下个断点。

41ee13e2f350cb9b1dd08e4b87d2fc56.png

然后g一下,kb看下堆栈的情况,可以看到通过系统调用进入内核;

e5612fcbd687b4fc6e0e2f92e1c595ff.png

单步步过win32k!_GetProcessWindowStation;证明该函数返回的是窗口站句柄的内核地址。

3e9f9df24ffe924886e75b3ba4569282.png

到目前为止,我们知道了如下图所示,现在我们单步进入win32k!SetImeInfoEx漏洞函数分析一下:

7221ab867b6a9cda977094fe870fa395.png

在比较处发生异常访问。

e621ed1165ff3f7458d7f7003f19bb65.png
(原图查看地址:https://imgchr.com/i/uVAOsg)

101331a710673f542ca4c748677de371.png

214b70443c639b90f74b5201f91d6141.png
(原图查看地址:https://imgchr.com/i/uVVlhq)

其实SetImeInfoE就做下面这两个步骤而已,只不过在第一步骤就发生了零页内存访问异常了而已

e7d3ec303e74e926cd9305f495e73f64.png

由上可知,tagWINDOWSTATION窗口站对象的spklList成员,是关联的键盘布局tagKL(KeyBoad Latout)对象链表的头指针, SetImeInfoE函数会遍历spklList链表,直到节点对象pklNext成员回到头指针对象为止。其中判断每个被遍历的节点对象的hkl成员是否与piiex->hkl相等。如果相等会退出循环,否则继续循环遍历。接下来判断键盘布局对象的piiex成员是否为空,且成员变量的fLoadFlag值是否等于零,如果是, 则会把用户自定义的tagIMEINFOEX数据(可由我们自定义构造的)拷贝到键盘布局对象的piiex成员中。

总结—— 用户进程调用CreateWindowStation创建窗口站时, 窗口站对象splList成员会初始值为0,在调用系统服务 NtUserSetImeInfoEx 设置输入法扩展信息的时候,内核函数 SetImeInfoEx 将会访问splList指向位于用户进程地址空间中的零页内存;如果当前进程零页未被映射,函数的操作将引发缺页异常,导致系统 BSOD 的发生。到此漏洞原因分析完毕。

漏洞利用

首先,解决零页地址访问异常的问题,在未开启零页保护的系统下,我们可以通过 NtAllocateVirtualMemory申请0地址内存,来使得零页内存也是可访问的,并构造位于零页中的伪造tagKL键盘布局对象,使得tagKL->hkl也是可控的 (即在[0+14h]处存放我们构造的tagKL->hkl值)。

df168bdeb8425dba8493e884df660b82.png
(原图查看地址:https://imgchr.com/i/uVVcuD)

fc8dd0db22b739d5018427e5141e5d35.png

其次,tagIMEINFOEX结构体是我们可控的,因为NtUserSetImeInfoEx(tagIMEINFOEX a1)函数的a1参数也是我们可以构造的。

现在我们需要shellcode有ring0的权限去执行,那么我们可以修改一个具有ring0权限的函数指针为shellcode指针即可实现ring0权限执行shellcode。内核函数选择hal!HaliQuerySystemInformation函数,因为有一个调用它的ring3函数(NtQueryIntervalProfile函数)是一个未文档化的函数,也就是一个不常用的函数; 这样我们覆盖它的函数指针后对于整个程序执行造成的影响会小一些,相对来说安全些。而且NtQueryIntervalProfile函数是在ntdll.dll中导出的未公开的系统调用,可以直接在Ring3调用 。也就是Ring3调用NtQueryIntervalProfile,其内部会调用内核态nt!HalDispatchTable+0x4处的函数,而我们把nt!HalDispatchTable+0x4的地址,替换为我们的shellcode地址,就可以达到shellcode在内核态上正常执行并提取利用了。

17b672f5de157bb644cbbae0cc293735.png

Bitmap GDI 技术

那么我们如何把用户态shellcode的地址替换掉内核态nt!HalDispatchTable+0x4的地址呢?Bitmap GDI 技术可以实现用户态对内核态任意地址读/写(即通过CreateBitmap/GetBitmapBits/SetBitmaps这3个函数实现).

eb1d5eb4f2b9f586717fc3fce14d92ae.png
1.把gWorker.pvScan0的地址构造到tagIMEINFOEX结构体上,然后通过漏洞把gManger.pvScan0地址里存储的值替换为gWorker.pvScan0地址
2.gManger利用SetBitmapBits将gWorker.pScan0地址里存储的值设置为HalDisptchTable+4地址
3.gWorker利用GetBitmapBits获取HalDispatchTable+4地址里存储的值(也就是hal!HaliQuerySystemInformation), 存储到&pOrg里去(保存一份hal!HaliQuerySystemInformation,为后面恢复还原做准备)
4.gWorker利用SetBitmapBits将HalDispatchTable+4所指内存的值替换为shellcode的地址
5.调用NtQuerySystemInformation执行shellcode
6.gWorker利用SetBitmapBits将HalDispatchTable+4所指内存的值还原为&pOrg存放的值(即第3步骤设置的值)

1.把gManger.pvScan0的值改gWorker.pvScan0的地址

调试github上的exp,在 HANDLE gManger = CreateBitmap(0x60, 1, 1, 32, bbuf); 处下断点

83cfd2a4deeb3ed00eb4ec0a98f1a92a.png

下面是exp通过构造tagKL和tagIMEINFOEX来把gManger.pvScan0的值改为gWorker.pvScan0的地址。

1be7e29d371bcfef1afb0b55e377ae61.png

9a0fdcf5e9b87710c54cf275bf62d945.png
(原图查看地址:https://imgchr.com/i/uVk81O)

下面是零页内存上的值

b96891c3c316bc300583ac84ad885066.png

下面是完整的tagIMEINFOEX构造的逆向过程

2bc8dba34da0636a35d737839fd6e658.png

(原图查看地址:https://imgchr.com/i/uVSse0)

现在我们跟入NtUserSetImeInfoEx函数,看它是如何把gManger.pvScan0的值改为gWorker.pvScan0的地址的。

0d6f90b98e70b9d25fe24ebc3b6f4447.png

50ed27231be9214505a3081db2f64336.png

134de90f78fb91264124e9afffa02c55.png
(原图查看地址:https://imgchr.com/i/uVZZP1)

继续跟入NtUserSetImeInfoEx漏洞函数

b72064e7a1ff009a358a01f5e1555410.png
(原图查看地址:https://imgchr.com/i/uVZWsU)

执行 qmemcpy 后

b88957f4043f0b94f4b48f854f5b5336.png
(原图查看地址:https://imgchr.com/i/uVZxdH)

由上可知, 我们已经把gManger.pvScan0的值改为gWorker.pvScan0的地址,上面的图您可能看着有点奇怪,其实也不用纠结,它只不过是布局tagIMEINFOEX结构体,然后通过SetImeInfoEx漏洞函数,来实现把gManger.pvScan0的值改为gWorker.pvScan0的地址而已。至于为什么赋值给mpv-4(fdb2c75c), 而不是直接赋值给mpv(fdb2c760),是由 _SURFOBJ 结构体的特性决定的。

2. SURFOBJ 结构体

surfobj结构体在《windows Graphics Programming Win32 GDI and DirectDraw》由 Feng Yuan著是有一点讲解的,中文本叫《Windows 图形编程》 2002年出版。

d78bd609f13298a4b18f5704fa60658f.png

mpv-4 其实就是SURFOBJ->pvBits ,mpv为SURFOBJ->pvScan0;在底层它会将 pvBits 的值更新到 pvScan0

c515fea9d8c38e36ca238c5c2f0c065b.png

那么这个SURFOBJ结构体是在哪里呢?当我们调用CreateBitmap函数来创建一个bitmap时,SURFOBJ结构就会被附加到进程PEB的GdiSharedHandleTable成员中。GdiSharedHandleTable是一个GDICELL结构体数组的指针(感兴趣的可以调试一下gdi32!CreateBitmap函数,这里我就不逆给你们看了)

b9dfc737d10c1a3fa108b54000e68e0f.png
(原图查看地址:https://imgchr.com/i/uVeYTJ)

gdicell地址的计算公式为:

db86257fa3c392d77d64ae2c5dff0195.png

92a80aba9ae6277985d727397f4c56bc.png
(原图查看地址:https://imgchr.com/i/uVeB6K)

通过资料和计算,我们知道gdicell->pKernelAddress刚好指向BASEOBJECT结构,在32位机中,BASEOBJECT+0x10的偏移就是SURFOBJ结构,SURFOBJ结构的pvScan0成员也就是漏洞利用的关键,pvScan0让我们能把用户态的shellcode地址,暂时存放到内核态,提供了一个很狭小的4字节的内核空间,让我们在诸多系统保护机制下,有了可乘之机。

2dafaf3b7831fdebf93cd2b56c439f4b.png

3. gManger利用SetBitmapBits将gWorker.pScan0地址里存储的值设置为HalDisptchTable+4地址

到现在为止,总算完成了第一步骤,现在我们继续往下走,进入第二步骤。

653920973934b72d64eec5e2cfa6e663.png

逆向一下 SetBitmapBits((HBITMAP)gManger, sizeof(PVOID), &oaddr); 函数

e09fa62688671f0e663d4f95694d5e55.png

58ae7999faf8e6321c904069cdfe1070.png

NtGdiSetBitmapBits把我们的参数继续往下传

d5bb95cd5c378979fa70259d64e8cc6b.png
(原图查看地址:https://imgchr.com/i/uVnXLT)

跟入GreSetBitmapBits函数,先判断一下size是否为空值,然后通过SURFREF::SURFREF函数获取pkernelAddress的地址。

723ab640127b07873c1a2d962430a485.png

可以看到 SetBitmapBits 调用 bDoGetSetBitmapBits 时,a3的值默认为零;GetBitmapBits调用时,a3为1.

267fdd335573ce960054e7fdf647b01c.png

3d3c672a2657a6853d285bb9a17d7336.png

跟入bDoGetSetBitmapBits ,memcpy把gWorker.pScan0里的值替换为 nt!HalDispatchTable+0x4 的地址。

7bc1b771e0dbc0a499daf8ed17462436.png
(原图查看地址:https://imgchr.com/i/uVueTe)

81aa282470b0f640c9468364c6004868.png
(原图查看地址:https://imgchr.com/i/uVK9HS)

到目前为止, 我们通过SetBitmapBits->NtGdiSetBitmapBits->GreSetBitmapBits->bDoGetSetBitmapBits->memcpy的连续调用,完整了第二部分的数据交换。

4. 通过GetBitmapBits把HalDispatchTable+4地址里存储的值,保存一份到&pOrg里去

b720d7128cad13d8f8cba0b986953e02.png

我们直接来到GetBitmapBits->NtGdiGetBitmapBits->GreGetBitmapBits下的bDoGetSetBitmapBits

1cd2f1567aeb1b742ee6667eac7e795b.png

41b00dc5813ad8a2df6d1dfa3af9eb26.png
(图片查看地址:https://imgchr.com/i/uVQQp9)

最后进行数据替换,如下图

2eabc0eb1746f346c5fd9ac07be1d84f.png
(原图查看地址:https://imgchr.com/i/uVQ3Ox)

6f592a5ef7f66f78d540a9dd57312678.png

19b72192e7e67ce7fb0c5d83b05d8230.png

5. 通过SetBitmapBits将HalDispatchTable+4所指内存的值替换为shellcode的地址

2ce2fdca06c2b9f7af8374f73521e145.png

拷贝后的值:

ce5fd1273661f1fc1f2cb2292fcbf9ad.png

6. 调用NtQuerySystemInformation执行shellcode

通过NtQueryIntervalProfile调用nt!HalDispatchTable+0x4,也就是被我们用shellcode地址替换掉的hal!HaliQuerySystemInformation地址。我们可以在nt!KeQueryIntervalProfile+0x23下个断点

37a81023d8412be21e35ea1883024d54.png

0e6422f1ec3bc03b9732bd5744427dc4.png

这里的shellcode思路很简单就是把系统的Token替换掉我们当前进程的Token,那么当前进程就具备了管理员的权限,从而达到提权的目的。

f8badd8a95bc6f4c518e443dee78a664.png

4779747c233688362b689c9b964aa5c1.png

263a53bc0f73b82d8a2cc31ee81db217.png

7. 恢复HalDisptchTable+4的地址处的值

通过 SetBitmapBits((HBITMAP)gWorker, sizeof(PVOID), &pOrg); 把shellcode替换回hal!HaliQuerySystemInformation.

9bf622fbdb110ae8765f3f6ae2fc9ea2.png

1e9aeafba99b2da73e5ee9f18c49d244.png

到这里所有的调试和利用就完成了,最后看下效果

4f00ff22a7bdcffc53746a2438acff69.png

心得

windows内核漏洞利用,如果内核函数不明白的,就是逆向它的逻辑,你会更加比别人理解这种利用方式;比如CreateBitmap这个函数,对于其他漏洞来说,就需要用到它来做池风水布局,而网上的公式您单看是看不明白的(如果您是追求原理本质,那就只能上手逆了,不然分析漏洞时,疑惑是很多的)。

本人的分析水平是有限的,只能尽量做到这样,所以文中很多错误的地方是有待指正和修改的。

最后,来点小菜鸡的心声(me), 想要飞的高,就请忘记地平线。全剧终...

01fa0e35deea4302696adc858f4f18bb.png

bbb46aa68f76a3ed59e828fe3e81a3f5.png

4a921f1bb8e7ee06dbb5b7d98257f869.png

1ad8b1a7f4ee185a93614eb86d6a0243.png

4379d6a815b46588df32d779dd2fbe99.png

5cf5f8bb65544c2723b588a51d239bb1.png

d6f239cacce5af0e05047e56f230aa87.png

7ce79f5a74ba851ca0a6c5bf196b2f0e.png

f6ffb99e2c87d3af75f671a8631927bd.png

4515ff11824b259a3529c0c7bad61cdc.png

b956777408b8f74c9e8102d196ef171f.png

9358b570fc8de0ddf9616f42b24cdbfc.png

11658fa9e504ae025e13d9dfa1909e2f.png

1f34fb7ede50e2e36a026c3149b31323.png

f8af06ad86aa54616cc79d3fa59c0cb4.png

75359d44f2de1de78f04cb7f21dabd55.png

d624fa36fbc3e7274a0cabc82f63fd95.png

原创文章申明:

本文为“苏州极光无限信息技术有限公司”原创,未经许可,禁止转载!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值