首页
社区
课程
招聘
[原创]CVE-2019-1458分析
2020-6-23 17:22 12459

[原创]CVE-2019-1458分析

2020-6-23 17:22
12459

目录

CVE-2019-1458分析笔记

文章本该以倒叙的方式,从漏洞函数到调用漏洞的函数,类似于栈回溯的方式开始编写。

 

但是为了方便大家理解,我准备以exp的执行流程为主线开始讲解

 

调试过程中用到的exp地址
https://github.com/unamer/CVE-2019-1458

漏洞分析

漏洞存在于函数为xxxPaintSwitchWindow

 

其交叉引用如下

 

xxxWrapSwitchWndProc->xxxSwitchWndProc->xxxPaintSwitchWindow

 

要想调用xxxWrapSwitchWndProc函数,我们需要调用NtUserMessageCall

 

该函数原型如下

 

NtUserMessageCall(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType)

 

下面我们从NtUserMessageCall开始分析,以及怎么经过层层bypass最终调用漏洞函数

 

可以看到NtUserMessageCall内部会调用gapfnMessageCall表

 

 

通过控制其参数NtUserMessageCall将会调用NtUserfnINLPCREATESTRUCT,该函数内部会通过gpsi,调用gapfnMessageCall表从而调用触发漏洞的函数xxxWrapSwitchWndProc

 

我们可以看到该表的调用跟dwType有关系

 


因此可以通过控制传入dwType的值为0xE0,调用xxxWrapSwitchWndProc函数(公式:(gpsi+ 8 * ((dwType + 6) & 0x1F)+ 16))

 

xxxSwitchWndProc函数分析

 

红色部分是第一次判断,我们新创建的窗口期,fnid为0,可以顺利进入判断内部

 

黄色部分是第二次判断,在这里前面已经不符合要求,因此我们要进行绕过,绕过的方法为 不满足 cbwndExtra+0x128 < [gpsi+0x154]

 

蓝色部分是第三次判断,在这里我们可以直接将消息设置为WM_CREATE即可绕过

 

我们要对NtUserMessageCall进行两次调用,第一次调用目的修改wnd.fnid为2a0(过掉红色和黄色部分,蓝色部分)

 

黑色部分是我们最终想要得到的结果,令wnd->fnid为 0x2a0,这样我们才能走到下面的switch语句

 

 

第二次调用是目的是为了调用xxxPaintSwitchWindow函数,我们只需要让msg为0x14 或者0x3a

 

在对应的 Window Messages中,可以查询到

 

#define WM_ERASEBKGND 0x0014

 

xxxPaintSwitchWindow函数分析

 

如下图,要进行第一次bypass

 

我们可以通过创建一个特殊的窗口,则可以让(gpsi+0x154) = 0x130,只要事先把窗口的cbwndExtra=0x8即可bypass

    printf("[*] Creating switch window #32771, this has a result of setting (gpsi+0x154) = 0x130\n");
    HWND switchWnd = CreateWindowEx(0, (LPCWSTR)0x8003, L"", 0, 0, 0, 0, 0, NULL, NULL, hself, NULL);

走到这里,离胜利已经非常近了。我们可以看到,wndExtra的值是可以通过
SetWindowLongPtr设置的,这样我们便可以实现一个任意内存破坏的漏洞了

 

EXP调试分析笔记

第一次调用NtUserMessageCall

 

第一次判断

 

[rcx+0x42]为fnid ,判断其是否为0x2a0

 

 

第二次判断 rcx为[gpsi+0x154],rax为wnd->cbwndExtra + 0x128

 


成功将wnd.fnid赋值为0x2a0

 

通过SetWindowLongPtr更改wndExtra值

 

红色框会设置前后,蓝色框为要设置的值

 

第二次调用NtUserMessageCall,在过掉几层判断之后

 

可以看到,实现了任意内存的破坏,成功让tagcls.cbClsExtra变成了一个比较大的数

 

 

此时我们已经通过xxxSetClassLongPtr函数进行越界写

 

要写的地址为pclsBase[1].pclsNext,等价于 pclsBase+sizeof_tagCLS==tagCLS+sizeof_tagCLS

 

该漏洞的利用方式为更改tagWND的StrName指针,搭配InternalGetWindowText 和 NtUserDefSetText即可实现任意读写,实现token的替换

 

因此在此处控制offset为

 

offset=tagWND - tagCLS - sizeof_tagCLS + off_tagWND_strName

 

最后的要写的地址

 

dwDestAddr=tagCLS+sizeof_tagCLS+offset

 

dwDestAddr=tagCLS+sizeof_tagCLS+tagWND - tagCLS - sizeof_tagCLS + off_tagWND_strName

 

dwDestAddr=tagWND + off_tagWND_strName

 

通过InternalGetWindowText读取

 

通过NtUserDefSetText实现写

 

exp中有封装好的函数,我们只需要根据不同的系统,构造不同的offset即可

 

有了任意读写,替换token已经很简单了,不再阐述

 

值得注意的是我们在破坏内存的时候不仅仅破坏了pcls的cbClsExtra的字段,因此要对被破坏的字段进行修复

 

总体来讲,该漏洞的利用还是比较简单的

移植到windows 2016R2的讨论

exp中并没有针对2016 r2的提权。我们下面对2016 R2进行分析

 

左面为2016 右面 win7 的xxxSwitchWndProc 函数对比,可以看到除了偏移不一样,都可以让Fnid设置为2a0

 

左面为2016 右面 win7 的xxxPaintSwitchWindow函数对比

 

可以看到2016的会获取tagwnd 0x168偏移的位置,这里其实就是wndExtra。理论上来讲也是存在漏洞的

 

但是2016和win7的xxxSetWindowLongPtr函数是有区别的

 

2016对tagWnd的某个标志位进行了检测

 

只有当tagWnd的偏移0x2a的bit位为1时,才会走win7所走的流程,才会使得 tagwnd的0x168大小偏移处写上我们的数据

动态验证,将0x2a的bit位,置1 ,可以看到返回值会是tagWnd的内核地址

 

 

2016的tagWND微软已经不公开了,但是前边跟win7是一样的,可以参考如下

 

那么现在的关键问题就来了

 

是否可以在tagwnd.fnid位为0的情况下,又使bDialogWindow位为1呢?


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞3
打赏
分享
最新回复 (5)
雪    币: 1474
活跃值: (955)
能力值: (RANK:860 )
在线值:
发帖
回帖
粉丝
仙果 19 2020-6-24 16:13
2
0
楼主努力下,你这篇分享没有写完,继续补充下吧
雪    币: 2170
活跃值: (2354)
能力值: ( LV12,RANK:270 )
在线值:
发帖
回帖
粉丝
Kalendsi 6 2020-6-24 16:26
3
0
仙果 楼主努力下,你这篇分享没有写完,继续补充下吧
已经分析完了,只不过后边跟了一个希望能移植到2016R2的讨论,前边是分析和利用,后边是想等等看雪的大佬,看看是否能做到这样。没人发过2016R2的利用,不确定2016R2是否能利用
雪    币: 8152
活跃值: (5833)
能力值: ( LV12,RANK:430 )
在线值:
发帖
回帖
粉丝
王cb 8 2020-6-24 21:23
4
0
不错
雪    币: 2170
活跃值: (2354)
能力值: ( LV12,RANK:270 )
在线值:
发帖
回帖
粉丝
Kalendsi 6 2020-6-24 21:28
5
0
王cb 不错
师傅好
雪    币: 871
活跃值: (1018)
能力值: ( LV7,RANK:101 )
在线值:
发帖
回帖
粉丝
brucy 2021-9-13 16:40
6
0
师傅,创建一个类名为#32771的窗口就会使得(gpsi+0x154) = 0x130是怎么发现的?
游客
登录 | 注册 方可回帖
返回