跳转到帖子

逆向木马远控实战:从 DLL 侧加载到 C2 配置提取与主机修复

精选回复

发布于

背景

在一次授权内网排查中,EDR 发现某终端上的签名程序 agent.exe 每隔约 60 秒向外发起一次 TLS 连接,同时加载了同目录下无签名的 version.dll。进程本身位于 C:\ProgramData\WinCache,而不是软件原始安装目录。

这种现象通常需要同时验证三个问题:version.dll 是否利用 DLL 搜索顺序完成侧加载、内存中是否存在远控主体、主机上是否还残留计划任务或注册表启动项。分析过程应在隔离虚拟机中进行,不要直接运行样本连接公网。

现场保全与问题定位

首先隔离主机网络,但不要立即删除文件。直接删除可能破坏文件时间、备用数据流、父子进程关系和持久化证据。建议先保存样本、哈希、Sysmon 日志及相关目录元数据。

$case='D:\IR\case-042'
New-Item -ItemType Directory -Force $case
Copy-Item 'C:\ProgramData\WinCache\agent.exe' $case
Copy-Item 'C:\ProgramData\WinCache\version.dll' $case
Get-FileHash $case\agent.exe -Algorithm SHA256
Get-FileHash $case\version.dll -Algorithm SHA256
Get-AuthenticodeSignature $case\agent.exe | Format-List
Get-AuthenticodeSignature $case\version.dll | Format-List
sigcheck64.exe -accepteula -h -i -a $case\agent.exe
sigcheck64.exe -accepteula -h -i -a $case\version.dll
wevtutil epl Microsoft-Windows-Sysmon/Operational D:\IR\case-042\sysmon.evtx

重点关联以下 Sysmon 事件:

  • 事件 1:agent.exe 的父进程、命令行和登录会话。
  • 事件 3:目标地址、端口、发起连接的进程 GUID。
  • 事件 7:agent.exe 加载 version.dll 的实际路径。
  • 事件 11:样本释放的文件以及后续配置更新。
  • 事件 13:Run 键、服务参数等注册表修改。
  • 事件 22:域名解析记录,可与网络连接时间交叉验证。
Get-WinEvent -Path D:\IR\case-042\sysmon.evtx |
  Where-Object { $_.Id -in 1,3,7,11,13,22 } |
  Where-Object { $_.Message -match 'agent.exe|version.dll|WinCache' } |
  Select-Object TimeCreated,Id,Message |
  Format-List

静态逆向:确认 DLL 侧加载链路

分析 agent.exe 的导入表可以看到其依赖 version.dll。Windows 默认 DLL 搜索顺序会优先检查程序目录,因此攻击者将恶意 version.dll 放在 agent.exe 同目录,即可借助已签名程序完成加载。签名只能证明 agent.exe 未被修改,不能证明其加载的邻近 DLL 可信。

在 Ghidra、IDA 或 PE-bear 中检查恶意 DLL 时,应关注以下位置:

  • 导出表是否伪造 GetFileVersionInfoW、VerQueryValueW、GetFileVersionInfoSizeW 等系统接口。
  • 入口点和 TLS Callback 是否创建线程、注册回调或解密内存。
  • 是否通过 GetSystemDirectoryW 拼接真正的 C:\Windows\System32\version.dll,并转发正常调用。
  • 是否出现 VirtualAlloc、VirtualProtect、CreateThread、WinHttpOpen、WinHttpSendRequest 等组合。
  • .rdata 或自定义节区是否包含固定标记、加密配置、休眠时间及 URI。

本例中,version.dll 的 DllMain 只启动一个工作线程,实际逻辑位于线程入口。线程先加载系统原版 version.dll,再读取自身 .rdata 中以 CFG1 开头的数据块。数据块包含长度、单字节种子和逐字节变换后的 JSON 配置。随后程序使用 WinHTTP 建立连接。

不要只依赖 strings 输出。域名、User-Agent 和互斥体经常在运行时解密;应从配置读取函数向前追踪数据来源,并核对长度检查和解密循环。

配置提取

确认反汇编中的数据布局为“4 字节标记、4 字节小端长度、1 字节种子、密文”后,可以编写离线提取脚本。下面代码仅针对这一布局,并设置了长度上限,避免把损坏样本当作配置解析。

from pathlib import Path
import struct
import json
import sys

buf = Path(sys.argv[1]).read_bytes()
off = buf.find(b'CFG1')
if off < 0:
    raise SystemExit('CFG1 marker not found')

size = struct.unpack_from('<I', buf, off + 4)[0]
if size == 0 or size > 0x4000:
    raise SystemExit(f'invalid config size: {size}')

key = buf[off + 8]
enc = buf[off + 9:off + 9 + size]
if len(enc) != size:
    raise SystemExit('truncated config')

plain = bytes(v ^ ((key + i) & 0xff) for i, v in enumerate(enc))
print(plain.decode('utf-8', errors='replace'))

try:
    obj = json.loads(plain)
    print(json.dumps(obj, indent=2, ensure_ascii=False))
except json.JSONDecodeError:
    pass

提取结果中应重点记录 C2 主机、端口、URI、休眠时间、抖动比例、主机标识生成方式和备用地址。配置字段必须与动态流量相互验证,不能因为文件中出现某个域名就直接认定它被实际使用。

受控动态分析

动态分析环境应使用快照虚拟机和 Host-only 网络,通过 FakeNet-NG 或 INetSim 接收连接。不要配置可访问生产网或公网的默认路由。Procmon 可设置以下过滤条件:

  • Process Name is agent.exe 或 version.dll 所在宿主进程。
  • Operation is Load Image、RegSetValue、CreateFile、Process Create。
  • Path contains WinCache、CurrentVersion\Run 或 System32\Tasks。

在 x64dbg 中可对 LoadLibraryExW、VirtualAlloc、VirtualProtect、CreateThread、WinHttpConnect 和 WinHttpSendRequest 设置断点。若观察到内存页由 RW 改为 RX,应在 VirtualProtect 返回后记录地址、大小和调用栈,再转储对应区域。若出现 OpenProcess、VirtualAllocEx、WriteProcessMemory、CreateRemoteThread 连续调用,则需要继续检查被注入进程,而不能只分析 agent.exe。

网络侧可从抓包中提取 DNS、TLS SNI、证书和 HTTP 特征:

tshark -r capture.pcapng -Y 'dns.flags.response == 0' \
  -T fields -e frame.time -e ip.src -e dns.qry.name

tshark -r capture.pcapng -Y 'tls.handshake.type == 1' \
  -T fields -e frame.time -e ip.dst \
  -e tcp.dstport -e tls.handshake.extensions_server_name

tshark -r capture.pcapng -Y 'http.request' \
  -T fields -e ip.dst -e http.host -e http.request.uri -e http.user_agent

本例的稳定特征不是单一 IP,而是固定 URI、近似 60 秒的回连周期、无 SNI 的 TLS ClientHello,以及配置中生成的特定 User-Agent。IP 和证书可能快速更换,因此检测规则应组合进程路径、模块加载和网络行为。

样本检测规则

可以针对样本家族编写 YARA 规则,但应优先使用结构性特征,避免只匹配常见 API。下面规则将伪造的 version.dll 导出、配置标记和网络相关字符串组合在一起:

import "pe"

rule RAT_Version_DLL_Sideload_CFG1
{
    meta:
        description = "Detects the investigated version.dll sideload sample"
        scope = "incident-specific"

    strings:
        $cfg = { 43 46 47 31 }
        $s1 = "WinHttpSendRequest" ascii wide
        $s2 = "WinHttpConnect" ascii wide
        $s3 = "GetSystemDirectoryW" ascii wide

    condition:
        uint16(0) == 0x5a4d and
        pe.is_dll() and
        pe.exports("GetFileVersionInfoW") and
        $cfg and 2 of ($s*)
}

部署前应使用正常 Windows DLL、软件安装包和历史文件库进行误报测试。若样本改用动态 API 哈希,明文函数名可能消失,此时应补充节区熵、导出组合、代码相似度或 EDR 行为规则。

持久化与权限边界排查

远控主体被加载后,常见持久化位置包括用户 Run 键、计划任务、服务和 WMI 永久事件订阅。排查时要区分当前用户权限与 SYSTEM 权限:普通用户创建的 HKCU Run 项不等于系统权限失陷,但如果样本能写入服务目录、HKLM 或由 SYSTEM 运行的任务,则应继续调查提权来源。

reg query HKCU\Software\Microsoft\Windows\CurrentVersion\Run
reg query HKLM\Software\Microsoft\Windows\CurrentVersion\Run
schtasks /query /fo LIST /v
sc.exe query type= service state= all
Get-CimInstance Win32_Service |
  Where-Object { $_.PathName -match 'WinCache|ProgramData' } |
  Select-Object Name,StartName,State,PathName

Get-CimInstance -Namespace root\subscription -Class __EventFilter
Get-CimInstance -Namespace root\subscription -Class CommandLineEventConsumer
Get-CimInstance -Namespace root\subscription -Class __FilterToConsumerBinding

还要检查 agent.exe 所在目录 ACL。如果普通用户对某个高权限服务或计划任务的程序目录拥有写权限,即使本次入口已清理,攻击者仍可再次替换 DLL。

icacls C:\ProgramData\WinCache
Get-ScheduledTask | Where-Object {
  $_.Actions.Execute -match 'WinCache|agent.exe'
} | Format-List TaskName,TaskPath,State,Actions,Principal

风险处置与修复建议

  • 先隔离终端并导出证据,再停止进程、禁用计划任务或服务。不要只删除 version.dll。
  • 按进程 GUID 和时间线检索同一 C2、文件哈希、父进程及 DLL 路径,确认是否存在横向扩散。
  • 如果远控进程接触过浏览器、密码管理器、LSASS、RDP 会话或部署凭据,应执行凭据轮换,而不是仅做文件清理。
  • 从可信安装介质重新部署 agent.exe 及其依赖项,删除攻击者可写目录中的副本。
  • 收紧程序目录 ACL,普通用户仅保留读取和执行权限;高权限任务不要从用户可写目录加载程序或 DLL。
  • 通过 WDAC 或 AppLocker 限制 ProgramData、Temp 和用户目录中的未知 DLL;评估兼容性后再逐步启用阻断。
  • 在 Sysmon 中保留进程创建、网络连接、映像加载、文件创建和注册表修改事件,并将日志转发到集中平台,避免终端本地日志被覆盖。
  • 建立“已签名进程加载同目录无签名 DLL”的检测规则,同时加入路径、签名状态和网络连接条件以降低误报。

总结

这类远控样本的关键不在于某个固定域名或哈希,而在于完整链路:签名程序从可写目录启动、侧加载伪造系统 DLL、解密内置配置、周期性回连,并通过任务或注册表保持驻留。有效分析需要把静态逆向、内存行为、网络流量和主机日志放到同一时间线上。

修复时也应围绕执行链处理:清除持久化、替换受污染文件、修正目录权限、轮换可能泄露的凭据,并部署能够发现异常模块加载的日志与控制策略。只封禁 IP 或删除单个 DLL,通常无法证明风险已经消除。

逆向分析与样本观察示意图
逆向分析、样本行为与调试流程示意
网络流量与边界分析示意图
网络请求、日志与边界流量分析示意

这类 DLL 侧加载样本,建议把“合法 EXE → 实际加载的 DLL → C2 配置 → 持久化入口”串起来看。只删恶意 DLL 容易漏掉负责回写文件的计划任务;只看 EXE 的有效签名,也不能说明整个加载链可信。

主机还在的话,先隔离网络,再保留进程路径、命令行、连接和文件哈希。下面这些命令需要管理员权限,输出只代表当前快照,不能替代历史日志:

# 将 1234 替换为可疑进程 PID
$targetPid = 1234

Get-CimInstance Win32_Process -Filter "ProcessId=$targetPid" |
    Select-Object ProcessId, ParentProcessId, ExecutablePath, CommandLine

Get-Process -Id $targetPid -Module |
    Select-Object ModuleName, FileName

Get-NetTCPConnection -OwningProcess $targetPid |
    Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort, State

Get-FileHash -Algorithm SHA256 -LiteralPath 'C:\suspect\example.dll'
Get-AuthenticodeSignature -LiteralPath 'C:\suspect\example.dll' |
    Select-Object Status, SignerCertificate

逆向侧加载链时,优先确认宿主导入了哪些 DLL、恶意 DLL 是否通过导出转发维持宿主运行,以及初始化是否落在 DllMain、TLS 回调或某个导出函数里。静态分析拿不准实际加载路径时,可在隔离虚拟机用 Procmon 按宿主进程过滤 Load Image,别在受害主机上重新运行验证。

C2 提取则不要停在“解出一个域名”:沿配置解码函数的交叉引用,确认地址、端口、协议、URI 和备用节点分别流向哪里。尤其要区分上线地址、下载地址和探测连通性的公共域名,避免把正常服务误列为 C2。验证尽量使用模拟网络,不直接连接真实控制端。

修复前保留样本和相关日志,再检查服务、计划任务、启动项与 WMI 永久事件订阅,确认是否还有落地器。合法宿主也建议从可信安装包恢复。若样本具备远程执行、凭据窃取能力,删除文件不足以恢复信任:应从干净设备轮换受影响凭据,必要时重装系统。

创建帐户或登录后发表意见

最近浏览 0

  • 没有会员查看此页面。