HQY

×

税务软件里藏了个木马,我花了一整天把它揪出来

hqy hqy 发表于2026-08-11 17:07:44 浏览421 评论0

抢沙发发表评论

税务软件里藏了个木马,我花了一整天把它揪出来(附完整排查脚本)

昨天给客户的电脑处理了一个棘手的恶意软件问题。从发现到彻底清除,前前后后花了整整一天,写了十几个 PowerShell 脚本,踩了无数坑,最终成功定位并清除了元凶。
整个过程像侦探破案一样——层层剥茧,柳暗花明。把完整的排查过程和所有用到的脚本命令记录下来,希望能帮到遇到类似问题的朋友。


一、症状:每次开机都"复活"的文件

客户的电脑(Windows 10系统),开机后发现临时目录里多了一个 hx.exe 文件:


路径:C:\Users\Tina\AppData\Local\Temp\hx.exe大小:98,304 字节
而且这个hx.exe被Cortex XDR报警了
图片
我试过把hx.exe删掉,然后重启之后它又回来了。更诡异的是,这个文件虽然一直存在,但从来不会被系统作为进程运行。它就静静地躺在那里,像个定时炸弹的引信。这不是普通的流氓软件行为,这是持久化驻留——有什么东西在每次登录时把它放进来。

二、第一回合:云沙箱分析

打开浏览器输入https://s.threatbook.com/回车,依次上传文件鼠标选择hx.exe然后开始分析
图片
图片
报告显示,hx.exe 运行后,立刻向一个名为 digitalsoft.etfapiao.com 的域名发起了网络请求。这个域名伪装得很像“电子发票”相关的网站,极具迷惑性。但从IP地址(121.22.232.153 和 101.200.238.210)来看,它们都是普通的云服务器,绝非国家税务总局或任何正规机构的官方服务器。释放病毒文件 (释放文件):它成功连接服务器后,在客户的电脑里释放了多个恶意文件:
diskperfa.dat:虽然名字像数据文件,但报告显示它是一个可执行的控制台程序,很可能是后续的恶意模块。
mailx.exe:这是一个可执行文件,被多个杀毒引擎报毒。
zserv.exe:这是一个可执行文件,同样被报毒。
zzz.zip:一个压缩包,里面很可能也包含恶意工具。
0.7z.dll:一个动态链接库文件。
图片

图片

可以看到在C盘programs 里都有这些可疑的文件夹,先打开任务管理器,进程,依次把这些进程全部结束掉。结束掉后,再把这些可疑的文件夹都删除了。删除完后重启电脑。重启好后,diskperfa.dat、mailx.exe、zzz.zip、0.7z.dll这四个已经没有了。但是hx.exe和zserv.exe依旧存在。当时在想,会不会还有一些隐藏的服务联网又自动下载了,得先把网络给隔离开,我在防火墙里把域名digitalsoft.etfapiao.com 以及两个IP 121.22.232.153、101.200.238.210全部添加到黑名单了。
图片
添加完后,随即测试了下,在浏览器上输入digitalsoft.etfapiao.com回车,显示无法访问此网站。
图片
然后再次删除hx.exe和zserv.exe,重启电脑。重启后,发现hx.exe和zserv.exe这两个exe依旧存在。然后在运行里输入services.msc回车,检查了下服务,发现有一个LocalProtService,这个服务比较可疑,停用这个服务后,再次删除重启。重启后发现zserv.exe已经没有了,但是hx.exe依旧存在。


三、第二回合:开启审计日志,踩了两个大坑

要找到谁在释放 hx.exe,最正统的办法是开启 Windows 的安全审计日志,让系统自己记录下来。需要开启两项审计:
命令如下:

auditpol /set /subcategory:”File System” /success:enable /failure:enable
在 Tina 的中文 Windows 上直接报错:


设置审计策略时出错 - 参数不正确。错误代码: 0x00000057

折腾了一圈才发现,中文 Windows 的 auditpol 需要用 GUID 而不是英文名称。正确的写法:


文件系统审计 GUID(与语言无关)$guidFileSystem = ”{0CCE921D-69AE-11D9-BED3-505054503030}”# 进程创建审计 GUID$guidProcessCreation = ”{0CCE922B-69AE-11D9-BED3-505054503030}” # 用 GUID 启用审计策略auditpol /set /subcategory:$guidFileSystem /success:enable /failure:enableauditpol /set /subcategory:$guidProcessCreation /success:enable /failure:enable # 验证auditpol /get /subcategory:$guidFileSystemauditpol /get /subcategory:$guidProcessCreation
这次成功了。完整的审计配置脚本 detect_hx_v2.ps1:

detect_hx_v2.ps1 - 中文 Windows 审计配置# 必须用 GUID,不能用英文名$guidFileSystem = ”{0CCE921D-69AE-11D9-BED3-505054503030}”$guidProcessCreation = ”{0CCE922B-69AE-11D9-BED3-505054503030}” # 启用审计策略$r1 = auditpol /set /subcategory:$guidFileSystem /success:enable /failure:enable 2>&1Write-Host ”文件系统审计: $r1”$r2 = auditpol /set /subcategory:$guidProcessCreation /success:enable /failure:enable 2>&1Write-Host ”进程创建审计: $r2” # 验证auditpol /get /subcategory:$guidFileSystem 2>&1 | ForEach-Object { Write-Host ” $” }auditpol /get /subcategory:$guidProcessCreation 2>&1 | ForEach-Object { Write-Host ” $” } # 尝试设置 SACL(文件系统审计需要)$tempDir = ”C:\Users\Tina\AppData\Local\Temp”$acl = Get-Acl $tempDir -Audit$auditRule = New-Object System.Security.AccessControl.FileSystemAuditRule( ”Everyone”, ”Write”, ”ContainerInherit, ObjectInherit”, ”None”, ”Success”)$acl.AddAuditRule($auditRule)Set-Acl $tempDir -AclObject $acl # 验证 SACL$verifyAcl = Get-Acl $tempDir -AuditWrite-Host ”SACL 条目数: $($verifyAcl.AuditRules.Count)”
进程创建审计(Event 4688)成功开启了,但文件系统审计还需要设置 SACL(系统审计控制列表)。上面的脚本运行后,Set-Acl 没有报错,但验证时发现 AuditRules.Count = 0——SACL 根本没设上。
于是不信邪,又写了 fix_sacl.ps1,用了三种方法尝试:

fix_sacl.ps1 - 三种方法设置 SACL $tempDir = ”C:\Users\Tina\AppData\Local\Temp” # 方法1:用 SID 代替用户名(S-1-1-0 = Everyone)Write-Host ”[1/3] 用 SID S-1-1-0 设置 SACL...”$everyoneSid = New-Object System.Security.Principal.SecurityIdentifier(”S-1-1-0”)$auditRule = New-Object System.Security.AccessControl.FileSystemAuditRule( $everyoneSid, ”Write”, ”ContainerInherit, ObjectInherit”, ”None”, ”Success”)$acl = Get-Acl $tempDir -Audit$acl.AddAuditRule($auditRule)Set-Acl $tempDir -AclObject $acl$verifyAcl = Get-Acl $tempDir -AuditWrite-Host ” AuditRules 条目数: $($verifyAcl.AuditRules.Count)”# 结果:0。失败了。 # 方法2:用 icacls 命令行工具Write-Host ”[2/3] 用 icacls 设置...”$result = & cmd /c ”icacls ”<span class="code-snippet__variable">$tempDir</span>” /audit Everyone:(W) /T /C” 2>&1Write-Host ” icacls 输出: $result”# 结果:icacls 不支持 /audit 参数(”无效参数”)。失败了。 # 方法3:用 auditpol /resourceSACLWrite-Host ”[3/3] 用 auditpol /resourceSACL...”$resourceSacl = auditpol /resourceSACL /get 2>&1$resourceSacl | ForEach-Object { Write-Host ” $_” }# 结果:参数错误。也失败了。
三种方法全部失败。最后意识到——Tina 的电脑装了火绒安全软件,进程 HipsDaemon 和 HipsTray 一直在运行。火绒会静默拦截 SACL 的修改操作——不报错、不提示,就是悄悄让你的设置不生效。装了安全软件,反而阻碍了我们通过安全审计来追踪恶意软件。既然 SACL 设不上,Event 4663(文件系统审计)就彻底没戏了。只能靠 Event 4688(进程创建审计)来追踪。


四、第三回合:文本解析又翻车

Event 4688 的审计日志有了,接下来是解析日志内容。写了 search_hx_v3.ps1,用正则表达式匹配事件消息中的字段:

search_hx_v3.ps1 - 解析 Event 4688 $today = (Get-Date).Date$events4688 = Get-WinEvent -FilterHashtable @{ LogName=”Security”; Id=4688; StartTime=$today} -MaxEvents 1000 -ErrorAction SilentlyContinue | Sort-Object TimeCreated foreach ($evt in $events4688) { $msg = $evt.Message $newProcName = ”” $creatorProcName = ”” $cmdLine = ””  # 先尝试中文字段名 if ($msg -match ”新进程名称:\s+(.+)”) { $newProcName = $Matches[1].Trim() } if ($msg -match ”创建者进程名称:\s+(.+)”) { $creatorProcName = $Matches[1].Trim() } if ($msg -match ”进程命令行:\s+(.+)”) { $cmdLine = $Matches[1].Trim() }  # 英文回退 if (-not $newProcName -and $msg -match ”New Process Name:\s+(.+)”) { $newProcName = $Matches[1].Trim() } if (-not $creatorProcName -and $msg -match ”Creator Process Name:\s+(.+)”) { $creatorProcName = $Matches[1].Trim() }  $time = $evt.TimeCreated.ToString(”HH:mm:ss”) $name = Split-Path $newProcName -Leaf -ErrorAction SilentlyContinue $creator = Split-Path $creatorProcName -Leaf -ErrorAction SilentlyContinue Write-Host ” $time | $name <- $creator”}
结果呢?进程名全部为空。中文正则匹配不上,英文也匹配不上。先导出原始消息看看格式:

导出原始 Event 4688 消息$events = Get-WinEvent -FilterHashtable @{ LogName=”Security”; Id=4688} -MaxEvents 3 -ErrorAction SilentlyContinue foreach ($evt in $events) { Write-Host ”=== $($evt.TimeCreated) ===” Write-Host $evt.Message Write-Host ””}
输出确认了中文字段名确实存在("新进程名称:"),但正则就是匹配不到。可能是编码问题,也可能是消息格式里有不可见字符。文本解析这条路,又走不通了。


五、终局之战:结构化数据一击必杀

文本解析反复翻车,决定彻底换个思路——不再解析日志消息的文本,而是直接读取事件日志的结构化属性集合(Win32_EventLogRecord.Properties)。
先导出前 5 条事件的全部属性,看看每个索引对应什么数据:

导出 Event 4688 的 Properties 集合$today = (Get-Date).Date$events = Get-WinEvent -FilterHashtable @{ LogName=”Security”; Id=4688; StartTime=$today} -MaxEvents 5 -ErrorAction SilentlyContinue | Sort-Object TimeCreated foreach ($evt in $events) { Write-Host ”--- Event at $($evt.TimeCreated.ToString('HH:mm:ss')) ---” Write-Host ”Properties count: $($evt.Properties.Count)” for ($j = 0; $j -lt $evt.Properties.Count; $j++) { $val = $evt.Properties[$j].Value if ($val -is [byte[]]) { $val = [System.BitConverter]::ToString($val).Replace(”-”,” ”) } Write-Host ” [$j] = $val” } Write-Host ””}
输出结果一目了然:

















--- Event at 18:51:38 ---Properties count: 15 [0] = S-1-5-18 [1] = MMK-TINA$ [2] = WORKGROUP [3] = 999 [4] = 17212 ← 新进程 PID [5] = C:\...\MicrosoftEdgeUpdate.exe ← 新进程路径 [6] = %%1936 [7] = 2656 [8] = [9] = S-1-0-0 [10] = - [11] = - [12] = 0 [13] = C:\Windows\System32\svchost.exe ← 创建者进程路径 [14] = S-1-16-16384
属性索引映射确认:
接下来写自动检测索引的逻辑,然后遍历所有进程事件,按时间排列,找出 hx.exe 的创建者。完整的 final_search.ps1 核心代码:

读取所有 Event 4688$allEvents = Get-WinEvent -FilterHashtable @{ LogName=”Security”; Id=4688; StartTime=$today} -MaxEvents 1000 -ErrorAction SilentlyContinue | Sort-Object TimeCreated # 从第一条事件自动检测属性索引$sampleEvt = $allEvents[0]$procPathIdx = -1$creatorPathIdx = -1 for ($j = 0; $j -lt $sampleEvt.Properties.Count; $j++) { $val = ”$($sampleEvt.Properties[$j].Value)” if ($val -match ”^C:\..exe$” -and $procPathIdx -eq -1) { $procPathIdx = $j } elseif ($val -match ”^C:\..exe$” -and $procPathIdx -ne -1 -and $creatorPathIdx -eq -1) { $creatorPathIdx = $j }} Write-Host ”检测到索引: ProcessPath=[$procPathIdx] CreatorPath=[$creatorPathIdx]” # 遍历所有进程事件,输出时间线foreach ($evt in $allEvents) { $time = $evt.TimeCreated.ToString(”HH:mm:ss”) $procPath = ”$($evt.Properties[$procPathIdx].Value)” $creatorPath = ”$($evt.Properties[$creatorPathIdx].Value)” $procName = Split-Path $procPath -Leaf -ErrorAction SilentlyContinue $creatorName = Split-Path $creatorPath -Leaf -ErrorAction SilentlyContinue Write-Host ” $time | $procName <- $creatorName”}
这一次,日志终于开口说话了。在 1000 条进程事件中,清晰地看到了:






18:41:20 | UKAO.exe <- explorer.exe18:41:23 | UKAO.exe <- runonce.exe18:41:24 | hx.exe <- UKAO.exe ← 抓到了!18:44:44 | UKAO.exe <- explorer.exe18:44:48 | UKAO.exe <- runonce.exe18:44:48 | hx.exe <- UKAO.exe ← 再次确认!
完整的启动链条浮出水面:





runonce.exe / explorer.exe ↓ UKAO.exe ↓ hx.exe(释放到 Temp 目录)

折腾了一圈才发现,中文 Windows 的 auditpol 需要用 GUID 而不是英文名称。正确的写法:


文件系统审计 GUID(与语言无关)$guidFileSystem = ”{0CCE921D-69AE-11D9-BED3-505054503030}”# 进程创建审计 GUID$guidProcessCreation = ”{0CCE922B-69AE-11D9-BED3-505054503030}” # 用 GUID 启用审计策略auditpol /set /subcategory:$guidFileSystem /success:enable /failure:enableauditpol /set /subcategory:$guidProcessCreation /success:enable /failure:enable # 验证auditpol /get /subcategory:$guidFileSystemauditpol /get /subcategory:$guidProcessCreation
这次成功了。完整的审计配置脚本 detect_hx_v2.ps1:

detect_hx_v2.ps1 - 中文 Windows 审计配置# 必须用 GUID,不能用英文名$guidFileSystem = ”{0CCE921D-69AE-11D9-BED3-505054503030}”$guidProcessCreation = ”{0CCE922B-69AE-11D9-BED3-505054503030}” # 启用审计策略$r1 = auditpol /set /subcategory:$guidFileSystem /success:enable /failure:enable 2>&1Write-Host ”文件系统审计: $r1”$r2 = auditpol /set /subcategory:$guidProcessCreation /success:enable /failure:enable 2>&1Write-Host ”进程创建审计: $r2” # 验证auditpol /get /subcategory:$guidFileSystem 2>&1 | ForEach-Object { Write-Host ” $” }auditpol /get /subcategory:$guidProcessCreation 2>&1 | ForEach-Object { Write-Host ” $” } # 尝试设置 SACL(文件系统审计需要)$tempDir = ”C:\Users\Tina\AppData\Local\Temp”$acl = Get-Acl $tempDir -Audit$auditRule = New-Object System.Security.AccessControl.FileSystemAuditRule( ”Everyone”, ”Write”, ”ContainerInherit, ObjectInherit”, ”None”, ”Success”)$acl.AddAuditRule($auditRule)Set-Acl $tempDir -AclObject $acl # 验证 SACL$verifyAcl = Get-Acl $tempDir -AuditWrite-Host ”SACL 条目数: $($verifyAcl.AuditRules.Count)”
进程创建审计(Event 4688)成功开启了,但文件系统审计还需要设置 SACL(系统审计控制列表)。上面的脚本运行后,Set-Acl 没有报错,但验证时发现 AuditRules.Count = 0——SACL 根本没设上。
于是不信邪,又写了 fix_sacl.ps1,用了三种方法尝试:

fix_sacl.ps1 - 三种方法设置 SACL $tempDir = ”C:\Users\Tina\AppData\Local\Temp” # 方法1:用 SID 代替用户名(S-1-1-0 = Everyone)Write-Host ”[1/3] 用 SID S-1-1-0 设置 SACL...”$everyoneSid = New-Object System.Security.Principal.SecurityIdentifier(”S-1-1-0”)$auditRule = New-Object System.Security.AccessControl.FileSystemAuditRule( $everyoneSid, ”Write”, ”ContainerInherit, ObjectInherit”, ”None”, ”Success”)$acl = Get-Acl $tempDir -Audit$acl.AddAuditRule($auditRule)Set-Acl $tempDir -AclObject $acl$verifyAcl = Get-Acl $tempDir -AuditWrite-Host ” AuditRules 条目数: $($verifyAcl.AuditRules.Count)”# 结果:0。失败了。 # 方法2:用 icacls 命令行工具Write-Host ”[2/3] 用 icacls 设置...”$result = & cmd /c ”icacls ”<span class="code-snippet__variable">$tempDir</span>” /audit Everyone:(W) /T /C” 2>&1Write-Host ” icacls 输出: $result”# 结果:icacls 不支持 /audit 参数(”无效参数”)。失败了。 # 方法3:用 auditpol /resourceSACLWrite-Host ”[3/3] 用 auditpol /resourceSACL...”$resourceSacl = auditpol /resourceSACL /get 2>&1$resourceSacl | ForEach-Object { Write-Host ” $_” }# 结果:参数错误。也失败了。
三种方法全部失败。最后意识到——Tina 的电脑装了火绒安全软件,进程 HipsDaemon 和 HipsTray 一直在运行。火绒会静默拦截 SACL 的修改操作——不报错、不提示,就是悄悄让你的设置不生效。装了安全软件,反而阻碍了我们通过安全审计来追踪恶意软件。既然 SACL 设不上,Event 4663(文件系统审计)就彻底没戏了。只能靠 Event 4688(进程创建审计)来追踪。


四、第三回合:文本解析又翻车

Event 4688 的审计日志有了,接下来是解析日志内容。写了 search_hx_v3.ps1,用正则表达式匹配事件消息中的字段:

search_hx_v3.ps1 - 解析 Event 4688 $today = (Get-Date).Date$events4688 = Get-WinEvent -FilterHashtable @{ LogName=”Security”; Id=4688; StartTime=$today} -MaxEvents 1000 -ErrorAction SilentlyContinue | Sort-Object TimeCreated foreach ($evt in $events4688) { $msg = $evt.Message $newProcName = ”” $creatorProcName = ”” $cmdLine = ””  # 先尝试中文字段名 if ($msg -match ”新进程名称:\s+(.+)”) { $newProcName = $Matches[1].Trim() } if ($msg -match ”创建者进程名称:\s+(.+)”) { $creatorProcName = $Matches[1].Trim() } if ($msg -match ”进程命令行:\s+(.+)”) { $cmdLine = $Matches[1].Trim() }  # 英文回退 if (-not $newProcName -and $msg -match ”New Process Name:\s+(.+)”) { $newProcName = $Matches[1].Trim() } if (-not $creatorProcName -and $msg -match ”Creator Process Name:\s+(.+)”) { $creatorProcName = $Matches[1].Trim() }  $time = $evt.TimeCreated.ToString(”HH:mm:ss”) $name = Split-Path $newProcName -Leaf -ErrorAction SilentlyContinue $creator = Split-Path $creatorProcName -Leaf -ErrorAction SilentlyContinue Write-Host ” $time | $name <- $creator”}
结果呢?进程名全部为空。中文正则匹配不上,英文也匹配不上。先导出原始消息看看格式:

导出原始 Event 4688 消息$events = Get-WinEvent -FilterHashtable @{ LogName=”Security”; Id=4688} -MaxEvents 3 -ErrorAction SilentlyContinue foreach ($evt in $events) { Write-Host ”=== $($evt.TimeCreated) ===” Write-Host $evt.Message Write-Host ””}
输出确认了中文字段名确实存在("新进程名称:"),但正则就是匹配不到。可能是编码问题,也可能是消息格式里有不可见字符。文本解析这条路,又走不通了。


五、终局之战:结构化数据一击必杀

文本解析反复翻车,决定彻底换个思路——不再解析日志消息的文本,而是直接读取事件日志的结构化属性集合(Win32_EventLogRecord.Properties)。
先导出前 5 条事件的全部属性,看看每个索引对应什么数据:

导出 Event 4688 的 Properties 集合$today = (Get-Date).Date$events = Get-WinEvent -FilterHashtable @{ LogName=”Security”; Id=4688; StartTime=$today} -MaxEvents 5 -ErrorAction SilentlyContinue | Sort-Object TimeCreated foreach ($evt in $events) { Write-Host ”--- Event at $($evt.TimeCreated.ToString('HH:mm:ss')) ---” Write-Host ”Properties count: $($evt.Properties.Count)” for ($j = 0; $j -lt $evt.Properties.Count; $j++) { $val = $evt.Properties[$j].Value if ($val -is [byte[]]) { $val = [System.BitConverter]::ToString($val).Replace(”-”,” ”) } Write-Host ” [$j] = $val” } Write-Host ””}
输出结果一目了然:

















--- Event at 18:51:38 ---Properties count: 15 [0] = S-1-5-18 [1] = MMK-TINA$ [2] = WORKGROUP [3] = 999 [4] = 17212 ← 新进程 PID [5] = C:\...\MicrosoftEdgeUpdate.exe ← 新进程路径 [6] = %%1936 [7] = 2656 [8] = [9] = S-1-0-0 [10] = - [11] = - [12] = 0 [13] = C:\Windows\System32\svchost.exe ← 创建者进程路径 [14] = S-1-16-16384
属性索引映射确认:
接下来写自动检测索引的逻辑,然后遍历所有进程事件,按时间排列,找出 hx.exe 的创建者。完整的 final_search.ps1 核心代码:

读取所有 Event 4688$allEvents = Get-WinEvent -FilterHashtable @{ LogName=”Security”; Id=4688; StartTime=$today} -MaxEvents 1000 -ErrorAction SilentlyContinue | Sort-Object TimeCreated # 从第一条事件自动检测属性索引$sampleEvt = $allEvents[0]$procPathIdx = -1$creatorPathIdx = -1 for ($j = 0; $j -lt $sampleEvt.Properties.Count; $j++) { $val = ”$($sampleEvt.Properties[$j].Value)” if ($val -match ”^C:\..exe$” -and $procPathIdx -eq -1) { $procPathIdx = $j } elseif ($val -match ”^C:\..exe$” -and $procPathIdx -ne -1 -and $creatorPathIdx -eq -1) { $creatorPathIdx = $j }} Write-Host ”检测到索引: ProcessPath=[$procPathIdx] CreatorPath=[$creatorPathIdx]” # 遍历所有进程事件,输出时间线foreach ($evt in $allEvents) { $time = $evt.TimeCreated.ToString(”HH:mm:ss”) $procPath = ”$($evt.Properties[$procPathIdx].Value)” $creatorPath = ”$($evt.Properties[$creatorPathIdx].Value)” $procName = Split-Path $procPath -Leaf -ErrorAction SilentlyContinue $creatorName = Split-Path $creatorPath -Leaf -ErrorAction SilentlyContinue Write-Host ” $time | $procName <- $creatorName”}
这一次,日志终于开口说话了。在 1000 条进程事件中,清晰地看到了:






18:41:20 | UKAO.exe <- explorer.exe18:41:23 | UKAO.exe <- runonce.exe18:41:24 | hx.exe <- UKAO.exe ← 抓到了!18:44:44 | UKAO.exe <- explorer.exe18:44:48 | UKAO.exe <- runonce.exe18:44:48 | hx.exe <- UKAO.exe ← 再次确认!
完整的启动链条浮出水面:





runonce.exe / explorer.exe ↓ UKAO.exe ↓ hx.exe(释放到 Temp 目录)


打赏

本文链接:https://www.kinber.cn/post/6755.html 转载需授权!

分享到:


推荐本站淘宝优惠价购买喜欢的宝贝:

image.png

 您阅读本篇文章共花了: 

群贤毕至

访客