CVE-2019-0808内核漏洞利用分析

2019-05-17 约 63 字 预计阅读 1 分钟

声明:本文 【CVE-2019-0808内核漏洞利用分析】 由作者 kgsdy 于 2019-05-17 10:00:00 首发 先知社区 曾经 浏览数 214 次

感谢 kgsdy 的辛苦付出!

漏洞概述

CVE-2019-0808是微软在2019年3月修补的内核漏洞,该漏洞只影响Windows 7和Windows Server 2008,漏洞允许攻击者提升权限并在内核模式下执行任意代码。在谷歌威胁分析团队的报告中发现该漏洞用于进行Chrome沙箱逃逸,和CVE-2019-5786 Chrome 远程代码执行漏洞配合使用。

补丁分析

通过对Win7上3月份的补丁进行对比可以知道问题出现在xxxMNFindWindowFromPoint函数中,这次的补丁只要对xxxSendMessage函数的返回值进行了检查,如果返回的不是菜单窗口就失败,还检查了tagPOPUPMENU和tagPOPUPMENU中的spmenu是否为空,为空则失败。所以导致漏洞的原因很可能就是tagPOPUPMENU或者tagPOPUPMENU中的spmenu为空。通过对xxxMNFindWindowFromPoint、NtUserMNDragOver和MNGetpItemFromIndex函数进行引用分析,知道可以通过拖动菜单项来触发相关漏洞函数。漏洞成因可以参考360的报告,这里不做详细分析。

漏洞利用

可利用性分析
通过分析漏洞触发流程知道xxxMNUpdateDraggingInfo函数获得窗口对象后,会通过MNGetpItem函数访问其成员tagPOPUPMENU对象,MNGetpItem函数又会继续访问tagPOPUPMENU对象的spmenu成员,从而造成零指针解引用漏洞。在MNGetpItem中会调用函数MNGetpItemFromIndex,该函数接受菜单对象指针和请求的菜单项索引为参数,根据菜单对象提供的信息返回一个菜单项。
在MNGetpItem中调用函数MNGetpItemFromIndex,而MNGetpItemFromIndex参数a1就是传入的tagPOPUPMENU对象的spmenu成员,spmenu成员是一个tagMENU结构。在漏洞触发的情况下spmenu成员为空,所以MNGetpItemFromIndex中的*(_DWORD *)(a1 + 52)会触发零指针解引用漏洞。在Windows7 32位的系统中还未引入零页内存保护机制,所以在Windows7 32位的系统中可以通过申请零地址并赋值来通过MNGetpItemFromIndex代码中的取值以及后续代码取值和校验。通过申请零地址可以对零地址的值进行控制,也就可以控制*(_DWORD *)(a1 + 52)的值,a2的值也可以通过全局的消息钩子函数来获取或者修改值,达到控制MNGetpItemFromIndex返回任意值的目的。对于申请零地址,常规的内存申请函数像VirtualAlloc是不允许在小于0x00001000的地址分配内存,只有使用函数NtAllocateVirtualMemory来完成对零地址的分配。通过上面的分析知道MNGetpItemFromIndex返回值result是可控的,而返回的值又是一个菜单项,所以result是一个指向菜单项结构的指针。如果在后续的代码中能找到修改菜单项数据的代码,就可以先通过修改零地址的值来控制result指向任意地址,再通过后续代码修改任意地址的数据,来达到任意代码写的目的。
对MNGetpItemFromIndex后续的代码进行分析,MNGetpItemFromIndex返回后回到MNGetpItem函数,在MNGetpItem函数中未对返回值进行任何操作直接返回。MNGetpItem函数返回后回到xxxMNUpdateDraggingInfo函数,对xxxMNUpdateDraggingInfo函数进行分析,只发现有对result指针进行取值判断的代码,没有发现对result指向数据进行修改的代码。虽然没有直接修改的代码,但是分析发现在xxxMNUpdateDraggingInfo函数的结尾会调用xxxMNSetGapState函数两次,在第二次会传入pMenuWnd的指针和*(v3 + 0x3C),而*(v3 + 0x3C)也是上图调用MNGetpItem函数的参数。在xxxMNSetGapState函数中会又会多次调用MNGetpItem函数,传入的第一个参数和xxxMNUpdateDraggingInfo函数中调用MNGetpItem函数时传入的第一个参数一样,都是同样的一个tagPOPUPMENU结构指针。通过对代码进行分析知道MNGetpItem的第二个参数是一个菜单项索引,在xxxMNSetGapState中的三次MNGetpItem函数调用,第一次与xxxMNUpdateDraggingInfo中调用MNGetpItem的索引一致,第二次索引加1,第三次索引减1。通过代码分析发现在第二次调用的时候会把MNGetpItem返回值偏移4的数据与0x80000000进行或运算,第三次调用的时候会把MNGetpItem返回值偏移4的数据与0x40000000进行或运算。以第二次调用为例,就找到了可以把任意地址的数据与0x80000000进行或运算的代码,0和0x80000000或运算得到0x80000000。走到这里已经可以把任意地址的数据与0x80000000进行或运算,但是由于前面的代码还有很多的验证,所以要想代码执行到这里还需要通过设置一些数据使验证通过,比如xxxMNSetGapState的第三个参数必须为6才能执行到与0x80000000进行或运算的代码,而6又是通过对xxxMNUpdateDraggingInfo中MNGetpItem返回值的数据进行判断得到2,再与4进行或运算得到的。还有一些零地址偏移的值需要去设置,比如零地址偏移0x20和0x28地址的值。这些值会用来进行判断,判断通过后才能顺利到达与0x80000000进行或运算的代码,这里就不一一进行说明,可以通过调试去验证后在去设置相应的值即可。现在知道了如何修改数据,现在分析如何通过代码来准确的控制result的值。通过MNGetpItemFromIndex函数知道result的值是由两个变量运算后相加得到,而这两个变量都可以控制,其中a1是菜单对象指针,a2是菜单项的索引。简单的想法就是直接把a2设置为0,直接把任意地址赋给*(a1 + 0x34),因为a1为零地址所以*(a1 + 0x34)的可以修改为任意值。但是在实际的测试中发现这样做并不行,因为后面会对*(a1 + 0x34)当成菜单项指针获取数据用于验证,若*(a1 + 0x34)为任意地址则附近的数据不可控,这样可能导致后面的验证不通过。最好的办法就是让*(a1 + 0x34)在零页内存上,这样可以控制*(a1 + 0x34)的数据用于后续的验证。假设任意地址的值为addr,可以按如下来设置a1和a2的值。设置a1和a2的值后,还需要对后续一些验证地址的数据进行调整,在xxxMNSetGapState中调用MNGetpItem时传入的是a2+1,要注意计算a2的值。最后就能顺利执行到与0x80000000进行或运算的代码。
任意地址读写
上文的代码可以实现任意地址与0x80000000进行或运算,直接看可能意义不大,但是可以把这个值转到其它数据结构中去看,比如可以用来修改其它结构表示大小的字段,这样或许就能使该结构覆盖其它数据,再通过一些结构的函数就可以完成任意读写。
在这里使用tagWND结构体的cbWNDExtra成员,该成员8字节表示窗口附加数据的大小,默认情况下cbWNDExtra大小为0。在之前需要获取cbWNDExtra成员在内核中的偏移。为了获取cbWNDExtra成员的偏移,可以创建了两个窗口WindowA和WindowB,将这两个窗口的窗口类的cbWndExtra成员设置为不同的值。而窗口类的cbWNDExtra正好对应tagWND的 cbWNDExtra成员,创建窗口之后就是获取cbWNDExtra成员在tagWND结构体中的偏移。首先就要获取tagWND结构的地址,获取tagWND可以使用HMValidateHandle函数,这个函数在很多内核漏洞利用代码中都会使用,这里就不单独进行说明。在获取tagWND的地址后,通过扫描WindowA和WindowB的cbWNDExtra成员的值来获取cbWNDExtra成员的偏移。
获取了cbWNDExtra的偏移,还需要获取保存额外数据的偏移。可以使用SetWindowLong函数向窗口写入额外数据,在使用与获取cbWNDExtra偏移相同的方法来扫描写入的额外数据,进而获取额外数据的偏移。知道这两个重要的偏移后,就可以开始后面的利用过程。为了达到覆盖数据的目的,可以先创建两个窗口,一个作为触发漏洞窗口,一个作为利用窗口,cbWNDExtra使用默认的值0。创建好后就可以通过触发漏洞可以修改触发漏洞窗口cbWNDExtra的大小。因为是任意地址异或0x80000000,这里修改cbWNDExtra的大小为0x00800000。修改了触发漏洞窗口cbWNDExtra的大小后,调用SetWindowLong函数对漏洞窗口的附加数据进行写操作。通过前面计算的偏移,可以准确的修改利用窗口的数据。为了读取写入地址上的值,可以将要读取的地址写入利用窗口的tagWND的spwndParent成员,在调用GetAncestor函数,该函数实际调用内核态函数NtUserGetAncestor,当函数的gaFlags参数为GA_PARENT时,该函数将读取tagWND结构体的spwndParent成员。为了后续利用还需要写操作,对写操作可以使用tagWND结构中的strName成员,在strName中有一个buffer可以使用SetWindowText函数进行数据写入。这儿可以通过前面的漏洞窗口修改buffer地址为任意地址,在调用SetWindowText函数就可以进行任意地址写。提权
有了任意地址读写就可以获取system进程的Token,再使用system进程的Token覆盖当前进程的Token,完成提权操作。有了任意读写的能力后替换Token的办法有很多,可以覆盖ntoskrnl!HalDispatchTable表中第二项的hal!HaliQuerySystemInformation() 函数指针后,调用NtQueryIntervalProfile函数执行shellcode,在shellcode中去完成提权。也可以通过tagWND对象来一层层去获取EPROCESS,在EPROCESS中就能获取ActiveProcessLinks,UniqueProcessId,Token,就可以遍历进程获取Token,通过写操作完成提权。

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now