“cerber”敲诈者对CVE-2016-7255漏洞利用分析

0x1 前言

360互联网安全中心近日捕获到一款“ceber”敲诈者木马变种,该变种与其他“ceber”敲诈者木马变种在代码执行流程上并没有太大区别。唯一值得注意的是,该木马利用CVE-2016-7255权限提升漏洞对自身进行提权。本文将分析该敲诈者对CVE-2016-7255权限提升漏洞的利用过程。

 

0x2 漏洞细节

出问题的代码位于win32k!xxxNextWindow中,由于缺少必要的检查直接将tagWND+0xC0成员偏移0x28对应地址中的值与4进行或操作,而tagWND+0xC0又是可控的,从而导致了任意地址写。存在漏洞的代码如下所示。

图1 存在漏洞的代码

        图中v12表示的就是tagWND结构体,该结构体如下所示(省略掉部分)。

图2 tagWND结构体

        从上图可以看出,tagWND+0xC0对应的是spmenu成员,如果存在用户态函数可以对该成员进行赋值,即可触发任意地址写。对于32位系统而言,可以直接调用SetWindowLong函数,SetWindowLong函数会调用内核态函数NtUserSetWindowLong完成此功能;对于64位系统而言,不存在可以利用的用户态函数,但是可以使用syscall的方式调用内核态函数NtUserSetWindowLong或函数NtUserSetWindowLongPtr来完成这项工作。(以下介绍的是NtUserSetWindowLong函数,NtUserSetWindowLongPtr函数执行流程相同)。

NtUserSetWindowLong函数只是一层外壳,它会将参数传递给xxxSetWindowLong并调用它,该函数如下所示。

图3 xxxSetWindowLong函数

        在该函数中会对传入的nIndex进行判断,并根据nIndex的值执行对应的操作。对于nIndex的值为-16,-20,-12,-21的情况,会调用xxxSetWindowData函数进行处理。如下所示。

图4 调用xxxSetWindowData函数进行处理

        该函数接收xxxSetWindowLong的参数,当nIndex参数为-12(GWL_ID),且所操作窗口的style为WS_CHILD或WS_CHILDWINDOW(0x40000000)时,会将所操作窗口tagWND结构体的spmenu成员的值设为dwNewLong。如下图所示。

图5 触发漏洞的位置

        由于dwNewLong是调用NtUserSetWindowLong函数时传递的参数,用户态进程可以利用syscall随意控制它。而win32k!xxxNextWindow函数会对spmenu+0x28的成员与4进行或操作,因此触发了任意地址写。

 

0x3 漏洞利用分析

从漏洞细节中可以看出,用户态进程拥有对tagWND结构体的spmenu成员的修改权,该成员是个tagMENU结构体,结构体定义如下所示。

图6 tagMENU结构体

        不难看出,xxxNextWindow函数修改的值就是spmenu的fFlags成员(偏移0x28),由于对该成员进行与0x4的或操作,因此该漏洞只能修改1bit大小的区域。

只能修改1bit表面上看起来貌似没有多大价值,不过该木马变种并非只把目光集中在这1bit上,而是转移到了tagWND结构体的cbWNDExtra成员,该成员表示的是窗口附加数据的大小。如果能够通过修改窗口附加数据的大小来覆盖关键地址,之后再利用其他方式写入数据,就可达到完美利用。

那么要完成对cbWNDExtra成员的写操作,就必须获取cbWNDExtra成员的地址或者是cbWNDExtra成员相对于某个已知地址的偏移。除外还必须获取附加数据的地址或者是相对于某个已知地址的偏移,以便之后进行计算与写入。对于获取cbWNDExtra成员的地址,该木马创建了两个窗口“ExtraWnd1”和“ExtraWND2”,而这两个窗口的不同之处在于其窗口类的cbWndExtra成员,该成员正好对应tagWND的 cbWNDExtra成员。程序将两个窗口类的cbWndExtra成员分别赋值为0x118和0x130,如下图所示。

图7 创建两个窗口

        创建窗口之后就是获取cbWNDExtra成员在tagWND结构体中的偏移,使用的是HMValidateHandle函数。该函数并未在用户态中导出,不过有个用户态函数IsMenu调用了它。木马通过判断IsMenu中相关字节码的位置获取HMValidateHandle的地址。

图8 利用字节码定位函数

        HMValidateHandle函数会泄露tagWND结构体的内容,因此木马很容易就能定位cbWNDExtra成员在tagWND结构体中的偏移。为了保险起见,木马判断两个窗口tagWND结构体的cbWNDExtra成员的偏移,当两个cbWNDExtra为注册窗口类时设置的值(0x118和0x130)且偏移相同时才说明该偏移有效。

图9 确定cbWndExtra成员的偏移

        在获取cbWNDExtra成员的偏移之后,该获取附加数据的偏移了。附加数据在内核态中是紧跟在tagWND结构体之后的,而SetWindowLong函数可以向窗口写入附加数据,nIndex表示的是写入的初始偏移。该木马调用SetWindowLong函数向附加数据区域的开头写入0x31323334,之后再次调用HMValidateHandle函数并通过比较偏移的内容是否为0x31323334来确定附加数据的偏移。

图10 确定附加数据的偏移

        确定关键的两处偏移之后,就该进行漏洞的触发了。由于触发漏洞的窗口必须设置为WS_CHILD或WS_CHILDWINDOW(0x40000000)类型。因此此处调用SetWindowLong函数设置窗口的style。

图11 设置窗口style

        然后就是对tagWND结构体的spmenu成员赋值,此时调用的是以GWL_ID做为nIndex参数的NtUserSetWindowLong函数(或NtUserSetWindowLongPtr函数)。该木马会首先确定程序所处的系统环境,如果是32位系统则直接调用SetWindowLong函数完成工作;如果是64位系统,则在用户态中使用syscall并赋予特殊的ID调用NtUserSetWindowLongPtr函数。32位系统环境下和64位系统环境下调用方式如图所示。

图12 32位系统下通过调用SetWindowLong函数调用NtUserSetWindowLong

图13 64位系统使用syscall方式调用NtUserSetWindowLongPtr函数

        本文以64位系统为例,syscall ID在不同版本中各不相同,木马在执行漏洞利用之前会先判断操作系统版本并初始化存放不同操作系统版本syscall ID的数组。在Win7 x64下,该ID为0x133a。

图14 判断系统版本并初始化syscall ID

        之前已经获得cbWNDExtra成员在tagWND结构体中的偏移,现在需要获得所操作窗口的tagWND结构体在内核中的地址。幸运的是,tagWND.head.pSelf成员(head成员偏移0x20处)泄露了tagWND结构体在内核中的地址,可以首先通过HMValidateHandle函数获取tagWND结构体的内容进而获取tagWND.head.pSelf成员,从而取得tagWND结构体在内核中的地址。之后对tagWND结构体成员内核地址的获取都是先使用该方法获取tagWND结构体的内核地址再加上相应的偏移。

图15 获取tagWND地址

        tagWND的地址加上cbWNDExtra成员的偏移即可得cbWNDExtra成员在内核中的地址。由于xxxNextWindow函数操作的是spmenu成员中的值加上0x28后的结果,而木马的目的是修改cbWNDExtra的值,因此木马将cbWNDExtra成员(大小为4字节)在内核中的地址减去0x28后加上3字节,将该值传递给spmenu,让cbWNDExtra的最高字节与0x4进行或操作。这样原本大小为0的cbWNDExtra就变成了0x4000000。此时窗口的附加数据长度就变为了0x4000000。

完成附加数据长度的增长之后,需要往附加数据中写入特定的值以寻求覆盖一些关键的标志位达到权限提升。该木马使用的方法是再次创建一个窗口,并确保该窗口的tagWND的地址与触发漏洞的窗口的tagWND的地址距离足够小,以保证触发漏洞的窗口的附加数据可以覆盖到该窗口的tagWND。为此,木马在完成该步骤之前创建了100个窗口,并选取两个tagWND结构体地址相差小于0x3fd00的窗口,一个作为触发漏洞的窗口,一个作为利用的窗口。

图16 创建两个窗口进行漏洞触发和利用

        选取好两个窗口之后调用xxxNextWindow函数触发漏洞。在用户态中可以模拟按键VK_MENU来触发xxxNextWindow函数的调用。漏洞触发后,窗口1的附加数据长度变为0x4000000,覆盖了窗口2的tagWND结构体。

图17 模拟VK_MENU触发xxxNextWindow函数的调用

        木马利用该漏洞的目的是提升权限,因此木马必须对一些重要的标志位,例如进程令牌进行修改。幸运的是,通过检索tagWND结构体的一些成员就能找到可以进行操作的地方。TagWND.head.pti -> ppi -> Process-> ActiveProcessLinks保存所有进程的EPROCESS对象,木马可以通过检索这些对象获得系统高权限进程的相关信息。

那么木马该如何获得这些信息?由于窗口1的附加数据覆盖了窗口2的tagWND结构体,而且可以调用NtUserSetWindowLongPtr函数(32位下可以调用SetWindowLong函数)对窗口的附加数据进行写操作,这意味着可以操作窗口2的tagWND结构体。如果将某个内核地址写入窗口2的tagWND结构体中的某个成员,再利用其它函数读取该成员的值,就能够读取该内核地址中的值。木马选取的是tagWND的spwndParent成员。该成员偏移为0x58。

图18 利用spwndParent任意读

        木马通过syscall调用NtUserSetWindowLongPtr函数写窗口1的附加数据,而数据操作的起始偏移为窗口2tagWND. spwndParent的内核地址-窗口1附加数据区的起始地址,也就是窗口1附加数据覆盖到窗口2 tagWND结构体spwndParent成员的位置。木马将之前提到的ActiveProcessLinks链表的内核地址写入窗口2 tagWND结构体的spwndParent成员。然后调用GetAncestor函数,该函数实际调用内核态函数NtUserGetAncestor,当gaFlags参数为GA_PARENT(1)时,该函数将读取tagWND结构体的spwndParent成员。

图19 gaFlags参数为1时读取的值

        如此一来,木马就能够读取ActiveProcessLinks链表的内核地址,并且可以遍历该链表获取其它进程的EPROCESS对象。木马遍历ActiveProcessLinks链表中每个进程的PID,以获得当前进程的EPROCESS对象。

图20 获得当前进程的EPROCESS对象

        之后还会继续遍历链表,获得system进程(PID=4)的EPROCESS对象。

图21 获得system进程的EPROCESS对象

        然后木马读取system进程EPROCESS对象偏移0x208的值,也就是token成员,同时也获取当前进程的token成员的内核地址。之后它将用system进程的token替换掉当前进程的token,以达到提权的目的。

图22 读取system进程EPROCESS对象的token成员

图23 读取当前进程EPROCESS对象的token成员

        在完成必要的读取操作之后,木马需要将token写入当前进程的EPROCESS对象的相应偏移中,而对于写操作,木马选择了SetWindowText函数。SetWindowText函数会将数据写入tagWND.strname.Buffer,如果可以将tagWND.strname.Buffer修改为当前进程token的内核地址,就可以对当前进程的token进行修改。和之前的原理一样,可以利用syscall调用NtUserSetWindowLongPtr,通过写入数据到窗口1的附加数据区域以覆盖窗口2的tagWND.strname.Buffer成员,即可修改窗口2的tagWND.strname.Buffer为当前进程EPROCESS对象的token成员的内核地址。

图24 获取tagWND.strname.Buffer

        之后调用SetWindowText将system进程的token写入到tagWND.strname.Buffer的地址中,也就是用system进程的token代替当前进程的token,从而使当前进程拥有system权限。至此进程提权成功。

图25 cerber敲诈者木马提权成功,/p>

 

0x4 缓解机制

通过微软的补丁可以发现,在对win32k!xxxNextWindow的调用时进行了限制。如下图所示。

图26 缓解机制

        在对spmenu.fFlags进行或操作之前会首先判断tagWND的style成员是否为WS_CHILD或WS_CHILDWINDOW(0x40000000),也就是所操作窗口的类型是否为WS_CHILD或WS_CHILDWINDOW,如果窗口类型为WS_CHILD或WS_CHILDWINDOW则跳过此处的操作,不会对spmenu.fFlags进行或操作。由于调用以GWL_ID做为nIndex参数的NtUserSetWindowLongPtr函数的时候,只有当所操作的窗口类型为WS_CHILD或WS_CHILDWINDOW(0x40000000)的情况下才会将dwNewLong参数中的值赋予tagWND.spmenu(见图5),而此处限制了窗口的类型,从而对整个漏洞触发的过程进行了限制。

 

0x5 总结

如今越来越多的恶意程序使用nDay漏洞进行攻击或者辅助自身的攻击,而对此最好的解决方法就是及时打上补丁以阻止恶意攻击。对于用户而言,提高安全意识,不运行安全性未知的程序,为系统及时打上补丁,这些对于防范恶意程序攻击都是非常有必要的。

 

 

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注