大家好,我们是 NOP Team,在之前的应急响应相关的文章中,我们提到了服务的隐藏与排查,还有如何验证可执行文件是可靠的,在 《Windows 应急响应手册》中我们给出了进阶性排查的脚本,大概的排查方式就是将所有的服务对应的命令中的启动程序拿出来,检查该程序的签名是否为微软官方的有效签名。
在后期应急响应过程中,发现一个问题: Windows Server 2016 中这个服务对应命令字符串格式比较宽泛,但开发者的使用过程中并不一定遵守什么规范,就导致可能出现一些奇怪的格式,例如存在被双引号包裹的命令,也有没有被双引号包裹的,这部分在之前的脚本中已经特别处理了,真正遇到的问题是文件名称里带空格的,举个例子
这还只是文件名存在空格,更有甚者如下
遇到这类的服务命令,之前的脚本就会报错,今天这篇文章就是测试一下服务的具体执行情况,完善脚本
之前的脚本如下
`$microsoftCNS = @('Microsoft Corporation', 'Microsoft Windows', 'Microsoft Windows Hardware Compatibility Publisher', 'Microsoft Update', 'Microsoft Windows Publisher') # 定义函数来进行签名校验 function Verify-FileSignature { param ( [Parameter(Mandatory=$true)] [ValidateScript({Test-Path $_ -PathType Leaf})] [string]$FilePath ) if (Test-Path -Path $FilePath -PathType Leaf) { $signature = Get-AuthenticodeSignature -FilePath $FilePath if ($signature.Status -eq 'Valid') { $publisher = $signature.SignerCertificate.Subject # 解析发布者信息以提取 CN 字段的值 $cnValues = @(($publisher -split ', ' | Where-Object { $_ -like 'CN=*' }).Substring(3)) if ($cnValues.Count -eq 1) { $cnValue = $cnValues[0] # Write-Output "CN 字段的值: $cnValue" # 判断 CN 字段是否为微软官方 if ($microsoftCNS -contains $cnValue) { # Write-Output "CN 字段值为微软官方。" return "Valid" } } } return "Invalid" } return "File Not Found" } $services = Get-WmiObject -Class Win32_Service | Where-Object { $_.PathName -ne $null } foreach ($service in $services) { $executablePath = $service.PathName if ($executablePath.StartsWith('"')) { $executablePath = $executablePath.Split('"')[1] } else { $executablePath = $executablePath.Split(' ')[0] } if ($executablePath -ne $null) { $fileInfo = Get-Item -LiteralPath $executablePath $result = Verify-FileSignature -FilePath $executablePath if ($result -ne 'Valid') { Write-Host "---------------------------------------" Write-Host "Service Name: $($service.Name)" Write-Host "Executable Path: $executablePath" Write-Host "Signature Status: $result" Write-Host "" } } } `
我们创建一个服务,服务对应的程序名称里放置一个空格,看看效果怎么样?
`sc create BindService binPath= "C:\Users\helper\Desktop\bi nd s.exe" `
其中 bi nd s.exe
程序为 Metasploit Framework 生成的木马,传到测试服务器上并重命名为 bi nd s.exe
`msfvenom -p windows/meterpreter/bind_tcp lport=4477 -f exe-service -o bind.exe `
当程序以服务的形式启动的时候,会自动监听 4477 端口
当前服务未启动,所以此时没有监听相关端口,现在启动该服务
服务启动后,我们的木马程序成功启动,监听 4477 端口,所以 Windows 是可以正确识别 C:\Users\helper\Desktop\bi nd s.exe
这种字符串的,并且可以正确找到要执行的程序
我们现在看一下原来的脚本检查过程中会发生什么?
可以看到,创建新的服务后,检查脚本会报错,报错为 Get-Item : 找不到路径“C:\Users\helper\Desktop\bi”,因为该路径不存在。
这显然是我们的脚本写的时候没有想到会有开发者设置服务的时候,文件名竟然会有空格,所以导致报错,我们直接修改一下,让其匹配完整路径就好了
想让脚本正确匹配到完整路径并不容易,服务对应的命令字符串可能是如下格式
`C:\Windows\system32\lsass.exe C:\Windows\system32\svchost.exe -k netsvcs C:\Program Files (x86)\Parallels\Parallels Tools\Services\prl_tools_service.exe C:\Users\helper\Desktop\bi nd s.exe `
之前我们对路径做了处理,所以主要问题出现在文件名称以及参数上,如果是单文件名,那么很好办,直接用就可以了;如果遇到第二个这种带参数的,就比较难办了,所以之前是用空格做分隔,取第一个值,但现在出现了第四个这种,名称里带空格的,这种方法明显就有错误了,怎么办呢?
要不遇到空格就分组,之后不断拼接,直到找到程序?
例如下面的顺序寻找 C:\Users\helper\Desktop\bi nd s.exe
,直到找到一个有效可执行文件
C:\Users\helper\Desktop\bi
C:\Users\helper\Desktop\bi nd
C:\Users\helper\Desktop\bi nd s.exe
但这有一个问题,如果要执行的是 bi nd s.exe
,好巧不巧,该路径下刚好有一个叫做 bi
的文件,这种情况下是不是就会导致错误匹配,导致检查不准确呢?
等等,Windows 本身是如何识别包容性这么高的命令字符串的呢?我应该和 Windows 保持一致啊!
说到 Windows 路径解析,大家可能一下子就想到了解析路径导致 DLL 劫持相关的内容,之后大家可能会想起 Windows 路径解析规则,但是我还是喜欢手动测试一遍
测试过程就不给大家展示了,直接说结论吧
当服务路径为 C:\Users\helper\Desktop\bi nd s.exe
时,Windows 服务查找的顺序为
C:\Users\helper\Desktop\bi
C:\Users\helper\Desktop\bi.exe
C:\Users\helper\Desktop\bi nd
C:\Users\helper\Desktop\bi nd.exe
C:\Users\helper\Desktop\bi nd s.exe
C:\Users\helper\Desktop\bi nd s.exe.exe
大家没有看错,Windows 并不是直接找完整路径,而是一点一点来的,而且对于服务程序来说,不需要后缀为 exe 也是可以执行的,而且 Windows 还会自动在文件名称后面加入 .exe
来匹配,也就是上面的 C:\Users\helper\Desktop\bi.exe
、 C:\Users\helper\Desktop\bi nd.exe
、C:\Users\helper\Desktop\bi nd s.exe.exe
当服务路径为 C:\Users\helper\Desktop\t m p\bi nd s.exe
时的查找顺序如下:
C:\Users\helper\Desktop\t
C:\Users\helper\Desktop\t.exe
C:\Users\helper\Desktop\t m
C:\Users\helper\Desktop\t m.exe
C:\Users\helper\Desktop\t m p\bi
C:\Users\helper\Desktop\t m p\bi.exe
C:\Users\helper\Desktop\t m p\bi nd
C:\Users\helper\Desktop\t m p\bi nd.exe
C:\Users\helper\Desktop\t m p\bi nd s.exe
C:\Users\helper\Desktop\t m p\bi nd s.exe.exe
当目录中存在空格时,解析规则也是类似的,只不过目录名字以及目录名字.exe 不会解析
对于常规来说,上面的情况已经足够了,但是我们面对的是攻击者,一群想方设法逃避检测的人,我们需要再多探究一些,如果路径空格不只一个会怎么样?
以 C:\Users\helper\Desktop\bi nd s.exe
为例,这其中都是三个空格,首先看看能不能创建成功
看来创建没有问题,如果放置一个 bi nd s.exe
能够正常启动吗?
能够正常启动,功能没问题,现在问题来了,现在匹配的时候最先匹配的是 bi
还是 bi
,Windows图形化修改文件名称最后面添加空格是没用的,试试能不能通过命令行修改
通过命令行 ren 命令无法在文件名称后面加空格,试试 PowerShell
发现 ren 和 PowerShell 修改也没有用,在 Windows 中似乎文件名后面可以随便加空格
看起来这样似乎就与之前单空格的时候一样了,是这样吗?
还是有不少场景需要我们测试一下的
C:\Users\helper\Desktop\bi
和 C:\Users\helper\Desktop\bi.exe
是否可以执行?
经过测试,可以执行,顺序与之前一致
C:\Users\helper\Desktop\bi .exe
、 C:\Users\helper\Desktop\bi .exe
、 C:\Users\helper\Desktop\bi .exe
是否可以执行?
经过测试,均不可以执行
C:\Users\helper\Desktop\bi nd
、 C:\Users\helper\Desktop\bi nd
、 C:\Users\helper\Desktop\bi nd
是否可以执行?
经过测试,只有 C:\Users\helper\Desktop\bi nd
(三个空格) 可以执行
最终测试查找顺序如下
C:\Users\helper\Desktop\bi
C:\Users\helper\Desktop\bi.exe
C:\Users\helper\Desktop\bi nd
C:\Users\helper\Desktop\bi nd.exe
C:\Users\helper\Desktop\bi nd s.exe
C:\Users\helper\Desktop\bi nd s.exe.exe
与一个空格顺序基本一致
经过测试,我们基本上已经搞清楚了 Windows 服务路径解析顺序,我们修改脚本,新的脚本如下:
`$microsoftCNS = @('Microsoft Corporation', 'Microsoft Windows', 'Microsoft Windows Hardware Compatibility Publisher', 'Microsoft Update', 'Microsoft Windows Publisher') # 定义函数来进行签名校验 function Verify-FileSignature { param ( [Parameter(Mandatory=$true)] [string]$FilePath ) if (Test-Path -Path $FilePath -PathType Leaf) { $signature = Get-AuthenticodeSignature -FilePath $FilePath if ($signature.Status -eq 'Valid') { $publisher = $signature.SignerCertificate.Subject # 解析发布者信息以提取 CN 字段的值 $cnValues = @(($publisher -split ', ' | Where-Object { $_ -like 'CN=*' }).Substring(3)) if ($cnValues.Count -eq 1) { $cnValue = $cnValues[0] # 判断 CN 字段是否为微软官方 if ($microsoftCNS -contains $cnValue) { return "Valid" } } } return "Invalid" } return "File Not Found" } # 获取所有服务 $services = Get-WmiObject -Class Win32_Service | Where-Object { $_.PathName -ne $null } foreach ($service in $services) { $path = $service.PathName # 去掉双引号 if ($path.StartsWith('"') -and $path.EndsWith('"')) { $path = $path.Trim('"') } # 处理路径中包含空格的情况 $potentialPaths = @() $parts = $path -split ' ' for ($i = 0; $i -lt $parts.Length; $i++) { $potentialPath = ($parts[0..$i] -join ' ') if (Test-Path -Path $potentialPath -PathType Leaf) { $potentialPaths += $potentialPath } elseif (Test-Path -Path "$potentialPath.exe" -PathType Leaf) { $potentialPaths += "$potentialPath.exe" } if ($potentialPaths.Length -ge 1) { break } } # 逐个验证找到的可执行文件路径 foreach ($potentialPath in $potentialPaths) { $result = Verify-FileSignature -FilePath $potentialPath if ($result -ne 'Valid') { Write-Host "---------------------------------------" Write-Host "Service Name: $($service.Name)" Write-Host "Executable Path: $potentialPath" Write-Host "Signature Status: $result" Write-Host "" } } } `
新的脚本可以成功帮我们找到 Windows 服务解析顺序下的第一个文件,并检测其签名
当然,脚本检测没问题不代表真的没问题,毕竟可以白加黑嘛,如果攻击者利用微软签名的程序进行白加黑,那么检测签名也不会检测出来,而且微软官方的 DLL 文件也没有签名,不是很容易判断,如果后续有其他好方法,会再次完善脚本
有态度,不苟同