发布于4小时前4小时 背景 在一次授权内网排查中,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,通常无法证明风险已经消除。 逆向分析、样本行为与调试流程示意 网络请求、日志与边界流量分析示意
3小时前3小时 这类 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 永久事件订阅,确认是否还有落地器。合法宿主也建议从可信安装包恢复。若样本具备远程执行、凭据窃取能力,删除文件不足以恢复信任:应从干净设备轮换受影响凭据,必要时重装系统。
创建帐户或登录后发表意见