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

漏洞概述

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,通过写操作完成提权。

在这里插入图片描述

本内容来自先知社区,仅作分享,侵删~

漏洞原理
最后推荐几个漏洞原理的学习内容:

1、【奇虎360】stagefright漏洞 <<< 点击访问

2、系统组件常见漏洞原理 <<< 点击访问

3、安全开发漏洞原理 <<< 点击访问

4、Kali实战-漏洞扫描 <<< 点击访问

部分内容需要付费购买,安全牛课堂的会员目前在打折,截止到5月26号,还有3天,想把这块学好的同学可以买个会员,这些课程都能看,非常划算。活动咨询 牛油果果 vx 1472301220

在主机发现、端口发现、服务扫描得到目标系统开放端口的服务、服务的的软件版本信息,继而可以在软件的官网上查看版本信息以及补丁列表,而高危漏洞则可以直接控制目标系统。

在此基础上对目标系统存在哪些漏洞弱点进行一个基本的判断,但是这样做会有很多弊端,扫描的很多端口上跑着各种各样的服务,你会忙于在各大网站上去查询官方文档,阅读大量的文章,导致你非常疲惫,这种思路成本比较高,速度比较慢。

我们可以去各个库去搜漏洞,然后逐个验证,这样也是一种方法,但是效率不高,不同的语言写出来的程序你不一定都在行,所以这个时候,对于渗透测试人员的要求就比较高,难度比较大,有一个简便的方法,使用弱点扫描器,这些扫描器调用漏洞利用代码朝向你指定的目标进行各种探测。

展开阅读全文

没有更多推荐了,返回首页