| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 网络协议 -> 网络安全之内核提权漏洞深入分析 -> 正文阅读 |
|
[网络协议]网络安全之内核提权漏洞深入分析 |
背景知识本节内容描述了创建窗口时需要用到的结构体及函数:
窗口类拥有如下属性结构,此处仅列出比较重要的结构:
在用户态创建窗口时,需要调用RegisterClass注册窗口类,每个窗口类有自己的名字,调用CreateWindow创建窗口时传入类的名字,即可创建对应的窗口实例。
tagWNDK结构体,需要重点关注此结构体:
当WNDCLASSEXW 中的cbWndExtra值不为0时,创建窗口时内核会回调到用户态函数USER32!_xxxClientAllocWindowClassExtraBytes申请一块cbWndExtra大小的内存区域,并且将返回地址保存在tagWNDK结构体的pExtraBytes变量中。
使用函数SetWindowLong和GetWindowLong,可对窗口扩展内存进行读写,进入内核后调用栈如下:
SetWindowLong函数形式如下: 第二个参数为index,含义为设置扩展内存偏移index处的内容。 137行代码判断index+4如果大于cbWndServerExtra+ cbWndExtra,表明越界,一般情况下cbWndServerExtra为0,如果越界,会跳转到117行LABEL_34,设置v18为1413,跳转到LABEL_55,调用UserSetLastError设置错误值,我们可以在cmd下查看此错误值的含义: 如果没有越界的话,接下来会根据不同的模式来使用pExtraBytes,如下: 在xxxSetWindowLong函数中: 正常情况下cbWndServerExtra为0,157行如果index+4< cbWndServerExtra,那么修改的是窗口的保留属性,例如GWL_WNDPROC对应-4,含义为设置窗口的回调函数地址。我们需要设置的是窗口扩展内存,所以进入165行的代码区域。 在167行会判断dwExtraFlag属性是否包含0x800,如果包含,那么168行代码destAddress=pExtraBytes+index+内核桌面堆基址,此处pExtraBytes作为相对内核桌面堆基址的相对偏移量,(QWORD)(pTagWnd->field_18+128)为内核桌面堆基地址 ,对应的汇编代码为 在171行处,dwExtraFlag属性不包含0x800,此时destAddress=index+pExtraBytes,此处pExtraBytes作为用户态申请的一块内存区域地址。 dwExtraFlag的含义: dwExtraFlag&0x800 != 0时,代表当前窗口是控制台窗口。调用AllocConsole申请控制台窗口时,调用程序会与conhost程序通信,conhost去创建控制台窗口,调用栈如下: conhost获取到窗口句柄后,调用NtUserConsoleControl修改窗口为控制台类型,调用栈如下: dwExtraFlag&0x800 ==0时,代表当前窗口是GUI窗口,调用CreateWindow时窗口就是GUI窗口。
漏洞成因此漏洞是对CVE-2021-1732漏洞的绕过,此处简要介绍下CVE-2021-1732漏洞: 用户调用CreateWindow时,在对应的内核态函数中检查到窗口的cbWndExtra不为0,通过xxxCreateWindowEx-> xxxClientAllocWindowClassExtraBytes->调用回调表第123项用户态函数申请用户态空间, 1027行会调用USER32!_xxxClientAllocWindowClassExtraBytes,EXP在回调函数中调用NtUserConsoleControl修改窗口的dwExtraFlag和pExtraBytes,修改窗口类型为控制台。 Windows修复代码在1039行,检查pExtraBytes是否被修改,此处查看汇编代码更为清晰 rdi+0x140-0x118 = rdi+0x28,得到tagWNDK,偏移0x128得到pExtraBytes,判断是否不等于0,如果不等于0,1045行代码会跳转,最终释放窗口,漏洞利用失败。 也就是说:CVE-2021-1732的修复方法是在调用xxxClientAllocWindowClassExtraBytes函数后,在父函数CreateWindowEx中判断漏洞是否被利用了,这个修补方法之前是没有问题的。 但是在后续代码更新后,有了新的路径来触发xxxClientAllocWindowClassExtraBytes函数: 在xxxSwitchWndProc函数中调用xxxClientAllocWindowClassExtraBytes后也有检查pExtraBytes是否为0,如果不为0,那么就复制pExtraBytes内存数据到新申请的内存地址中,没有检查dwExtraFlag是否被修改。
利用漏洞的流程本节介绍了漏洞触发的流程,并介绍了触发漏洞及利用漏洞需要的各个知识点。 漏洞触发利用的流程: 要利用这个漏洞,需要以下背景知识: 6.1 触发用户态回调本节描述如何触发用户态回调,使内核回调到USER32!_xxxClientAllocWindowClassExtraBytes。 在IDA中查看xxxClientAllocWindowClassExtraBytes的引用,有多处地方调用到了此函数, 查看xxxSwitchWndProc代码如下: 98行代码有cbWndServerExtra变量赋值,而在调用SetWindowLong时会使用index-cbWndServerExtra,所以我们真正想设置内存区域偏移index位置的变量时,参数2应该传入index+cbWndServerExtra。 103行代码调用xxxClientAllocWindowClassExtraBytes返回值赋值给了v20变量。 111行代码检查原来的pExtraBytes是否为0,如果不为0,那么就复制内存的数据,还会释放原来的pExtraBytes。 117、123行代码都会将v20变量赋值给pExtraBytes。 而xxxSwitchWndProc函数是可以通过win32u! NtUserMessageCall函数来触发的,在用户态调用NtUserMessageCall函数会触发内核态函数xxxClientAllocWindowClassExtraBytes,函数调用栈如下:
在内核态的win32kfull!xxxClientAllocWindowClassExtraBytes函数中,会调用用户态的xxxClientAllocWindowClassExtraBytes函数。 KernelCallbackTable第123项对应_xxxClientAllocWindowClassExtraBytes函数,使用IDA查看函数内容: 此函数中调用RtlAllocateHeap函数来申请*(a1)大小的内存,内存地址保存在addr变量中,然后调用NtCallbackReturn函数返回到内核态,返回的数据为addr变量的地址,对应在上面win32kfull!xxxClientAllocWindowClassExtraBytes函数中的v7变量,v7为addr变量的地址,*v7即为上图中的addr。
6.2 HOOK回调函数上一小节讲了触发到USER32!_xxxClientAllocWindowClassExtraBytes函数的流程,我们还需要hook此回调函数,在回调函数中触发漏洞。下面代码可以将回调函数表项第123、124分别修改为MyxxxClientAllocWindowClassExtraBytes、MyxxxClientFreeWindowClassExtraBytes。 6.3 修改窗口模式为模式1上一小节讲了如何进入到用户态自定义的函数,本节讲述在自定义的函数中通过用户态未公开函数NtUserConsoleControl修改窗口模式为模式1,本节对NtUserConsoleControl函数进行逆向分析。 函数win32u! NtUserConsoleControl可以设置模式为内核桌面堆相对寻址模式,此函数有三个参数,第一个参数为功能号,第二个参数为一个结构体的地址,结构体内存中第一个QWORD为窗口句柄,第三个参数为结构体的大小。 NtUserConsoleControl函数会调用到内核态win32kfull模块的NtUserConsoleControl函数,调用栈如下:
win32kfull模块NtUserConsoleControl判断参数,然后调用xxxConsoleControl如下: 17行判断参数index不大于6 22行判断参数length小于0x18 26行判断参数2指针不为空且length不为0 以上条件满足时会调用xxxConsoleControl函数,传入参数为index、变量的地址,传入数据的长度, xxxConsoleControl函数会对index及len进行判断: 110行代码可知,index必须为6,113行代码可知len必须为0x10,115行到119行代码可知,传入参数地址指向的第一个QWORD数据必须为一个合法的窗口句柄,否则此函数会返回。 134、136行判断是否包含0x800属性,如果包含,v23赋值为内核桌面堆基地址+偏移量pExtraBytes,得到的v23为内核地址。 140行代码,如果不包含0x800属性,那么调用DesktopAlloc申请一段cbWndExtra大小的内存保存在v23中。 149到156行代码判断原来的pExtraBytes指针不为空,就拷贝数据到刚申请的内存中,并调用xxxClientFreeWindowClassExtraBytes->USER32!_xxxClientFreeWindowClassExtraBy释放内存。 159、160行代码使用内核地址v23减去内核桌面堆基址得到偏移量v21,将v21赋值给pExtraBytes变量。 使用如下代码可以修改窗口模式为模式1:
6.4 回调返回伪造偏移量在_xxxClientAllocWindowClassExtraBytes 函数中调用NtCallBackReturn回调函数可以返回到内核态: 伪造一个合适的偏移量Offset,然后应该取Offset地址传给NtCallbackReturn函数,可以将offset赋值给pExtraBytes变量。 由于之前已经切换窗口为模式1,pExtraBytes含义为相对于内核桌面堆基址的偏移,再查看tagWNDK结构体,关注以下字段:
OffsetToDesktopHeap为窗口本身地址tagWNDK相对于内核桌面堆基址的偏移,可以使用如下方法来伪造合适的偏移量:
6.5 泄露内核窗口数据结构上一小节中我们在用户态中要返回窗口0的OffsetToDesktopHeap到内核态,OffsetToDesktopHeap是内核态的数据,要想获取这个数据还需要一些工作。 调用CreateWindow只能返回一个窗口句柄,用户态无法直接看到内核数据,但是系统把tagWNDK的数据在用户态映射了一份只读数据,只需要调用函数HMValidateHandle即可,动态库中没有导出此函数,需要通过IsMenu函数来定位: 定位USER32!HMValidateHandle的代码如下: 定位到USER32!HMValidateHandle函数地址后,传入hwnd即可获取tagWNDK数据地址。
6.6 如何布局内存通过上面的知识,我们可以通过窗口2修改窗口0的tagWNDK结构体数据,本节描述如何布局内存,构造写原语。 应该通过NtUserConsoleControl修改窗口0切换到模式1,这样对窗口0调用SetWindowLong即可修改内核数据,但是调用SetWindowLong时index有范围限制,所以通过窗口2将窗口0的tagWNDK. cbWndExtra修改为0xFFFFFFFF,扩大窗口0可读写的范围。 现在我们开始内存布局: 创建窗口0,窗口0切换到模式1,pExtraBytes为扩展内存相对内核桌面堆基址的偏移量 窗口2触发回调后,回调函数中对窗口2调用NtUserConsoleControl,所以窗口2也处于模式1,pExtraBytes为扩展内存相对内核桌面堆基址的偏移量。 回调函数中返回窗口0的OffsetToDesktopHeap,此时内存如下: 图中红色线条,此时窗口2的pExtraBytes为窗口0的OffsetToDesktopHeap,指向了窗口0的结构体地址,此时对窗口2调用SetWindowLong即可修改窗口0的内核数据结构 通过窗口2修改窗口0的cbWndExtra SetWindowsLong(窗口2句柄, 0xC8(此处还有一个偏移量),0xFFFFFFFF),即可修改窗口0的cbWndExtra为极大值,且此时窗口0处于模式1,如果传入一个较大的index且不大于0xFFFFFFFF,那么就可以越界修改到内存处于高地址处的其他窗口的数据。 再次创建一个窗口1,窗口1处于模式2,不用修改模式 窗口1刚开始pExtraBytes指向用户态地址,使用模式2直接寻址。 由于此时窗口1处于模式1直接寻址,且我们可以设置窗口1扩展内存地址pExtraBytes为任意地址,所以对窗口1调用SetWindowLong即可向任意内核地址写入数据。
EXP分析调试首先动态定位多个函数地址,接下来需要调用
调用函数RegisterClassEx创建两个窗口类: 类名为NormalClass的窗口,窗口的cbWndExtra大小为0x20。 类名为MagicClass的窗口,窗口的cbWndExtra大小为0x1337,使用MagicClass类创建的窗口会利用漏洞构造一个内核相对偏移量。 内存布局的代码如下: 第241行到244行,创建了菜单,之后创建窗口使用此菜单。 第245行到250行,使用NormalClass类名创建了50个窗口存放在g_hWnd数组中,然后销毁后面的48个窗口,这样是为了后面创建窗口时可以占用被销毁窗口的区域,缩短窗口之间的间距,此时g_hWnd[0]和g_hWnd[1]存放句柄,将这两个窗口称为窗口0和窗口1,其中247行调用HMValidateHandle函数传入句柄得到对应窗口在用户态映射的tagWNDK数据内存地址保存在g_pWndK数组中。 第245行到255行,调用NtUserConsoleControl函数设置窗口0由用户态直接寻址切换为内核态相对偏移寻址,并且窗口0的pExtraBytes是相对于内核桌面堆基址的偏移。 第257行到258行,使用MagicClass类名创建窗口2保存在g_hWnd[2]中,称为窗口2,然后调用HMValidateHandle获得窗口2的tagWNDK数据映射地址保存在g_pWndK[2]中。 第260和278行代码判断内存布局是否成功,此时窗口0处于内核模式,所以窗口0的pExtraBytes为申请的内核内存空间(不是窗口内核对象地址)相对于内核桌面堆基地址的偏移,窗口1和窗口2为用户态模式,OffsetToDesktopHeap为窗口内核对象地址相对于内核桌面堆基地址的偏移,内存布局必须满足: 窗口0的pExtraBytes小于窗口1的OffsetToDesktopHeap,计算差值extra_to_wnd1_offset,为正数。 窗口0的pExtraBytes小于窗口2的OffsetToDesktopHeap,计算差值extra_to_wnd2_offset,为正数。 如果布局失败,那就销毁窗口继续布局,如果最后一次布局失败,就退出。 布局完成后,程序运行到此处: 程序在虚拟机中运行到DebugBreak()函数时,如果有内核调试器,调试器会自动中断: 此时指令位于DebugBreak函数中,输入k,栈回溯只显示了地址,没有显示符号表,输入
.reload /user会自动加载用户态符号,pdb文件位于本地对应目录,再次输入k,显示栈回溯,可以看到显示正常。 在调用NtUserMessageCall之前,窗口0处于模式1,窗口1和2处于模式2。 |
|
网络协议 最新文章 |
使用Easyswoole 搭建简单的Websoket服务 |
常见的数据通信方式有哪些? |
Openssl 1024bit RSA算法---公私钥获取和处 |
HTTPS协议的密钥交换流程 |
《小白WEB安全入门》03. 漏洞篇 |
HttpRunner4.x 安装与使用 |
2021-07-04 |
手写RPC学习笔记 |
K8S高可用版本部署 |
mySQL计算IP地址范围 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 | -2024/12/30 3:43:18- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |