查询 43 次,耗时 0.3676 秒

从入口点到行为证据:在隔离实验室中逆向分析 Windows 可疑 PE 样本

monika

New member

monika Rep
0
0
0
Rep
0
monika Vouches
Vouches
0
2 MONTHS
2 2 MONTHS OF SERVICE
monika 获得
0
0
0
获得
0
monika 凭证
0
0
0
凭证
0
主题
15
点赞
0
HakB
2 月
在社区成长 2 月
LEVEL 1 1 XP
 
【背景与目标】

面对来源不明的 Windows PE 文件,可靠的分析流程不应从“双击运行看看”开始,而应先建立隔离边界,再逐步完成静态画像、动态观测、关键代码定位和结论验证。本文以一个本地自制的无害训练样本为对象:程序启动后读取自身目录中的 config.dat,计算其 SHA-256,尝试解析一段配置,并向本机 127.0.0.1:8080 发送固定测试字符串。这个行为组合足以覆盖文件访问、字符串处理、条件分支和网络 API 调用,同时不会触碰真实目标。

文章重点不是破解授权机制,也不提供免杀、持久化或攻击真实系统的方法,而是演示如何形成可复现的防御分析证据链:样本是什么、静态上看到了什么、运行时实际做了什么、代码路径如何对应,以及结论有哪些不确定性。

【分析原理】

PE 静态分析关注“不运行样本时能够确认的信息”,包括文件哈希、编译架构、节表、导入函数、资源、字符串和可能的壳特征。它速度快、风险较低,但容易受到字符串加密、动态解析 API、控制流混淆和加壳影响。

动态分析关注“样本运行后产生的可观测行为”,例如进程树、文件读写、注册表访问、模块加载、网络连接和异常。动态证据更贴近真实行为,但一次执行未必覆盖所有分支;样本也可能根据时间、环境、参数或调试器存在与否改变路径。

调试分析用于连接二者。通过入口点、交叉引用、断点、调用栈、寄存器和内存数据,可以解释某个行为由哪段代码触发。实践中应避免只凭一个可疑字符串下结论,也不要仅凭“沙箱里没联网”就认定没有网络能力。可信结论通常来自至少两类相互印证的证据。

【前置条件与隔离要求】

1. 使用专用虚拟机,建议 Windows 10 或 Windows 11 分析机,并在导入样本前创建干净快照。
2. 虚拟机网络优先设置为 Host-Only;如需观察网络请求,使用 FakeNet-NG、INetSim 或本机测试服务模拟响应。不要桥接到生产网络。
3. 禁用共享剪贴板、共享目录、宿主磁盘映射和不必要的 USB 自动连接。需要传递文件时,使用只读镜像或经过校验的中转包。
4. 准备 Sysinternals Process Monitor、Process Explorer、TCPView,静态工具可使用 Detect It Easy、PE-bear、FLOSS、Ghidra,调试器可使用 x64dbg。
5. 记录样本来源、获取时间、原始文件名、文件大小和哈希。真实事件中的样本还应按照组织流程加密归档并限制访问。
6. 本文所有操作仅适用于自制样本、公开授权练习样本或已获授权的防御调查。

【步骤一:建立样本身份与基线】

先不要运行文件。在 PowerShell 中计算哈希并读取基本属性:

Get-FileHash .\training-sample.exe -Algorithm SHA256
Get-Item .\training-sample.exe | Format-List Name,Length,CreationTimeUtc,LastWriteTimeUtc
Get-AuthenticodeSignature .\training-sample.exe | Format-List Status,SignerCertificate

哈希是后续笔记、检测规则和团队协作中的主标识。时间戳只能作为线索,因为 PE 头时间和文件系统时间都可能被修改。数字签名也不是绝对安全证明:有效签名只能说明签名链和文件完整性满足特定条件,不能替代行为分析。

建议建立 analysis-notes.txt,至少记录:SHA-256、分析虚拟机快照名、工具版本、网络模式、首次执行时间、命令行参数和观察结果。没有环境信息的结论往往难以复现。

【步骤二:检查 PE 结构与异常特征】

用 Detect It Easy 或 PE-bear 打开样本,检查以下项目:

1. Machine 字段确认 x86 或 x64,避免使用错误架构的调试器。
2. AddressOfEntryPoint 是否落在可执行节中。
3. 各节的名称、虚拟大小、磁盘大小和权限。正常编译产物常见 .text、.rdata、.data、.rsrc;节名异常本身不是恶意证据。
4. 是否存在同时具备写入和执行权限的节。W+X 值得关注,但某些运行时或保护器也可能合法使用。
5. 节熵是否明显偏高。高熵可能来自压缩、加密资源或壳,不能单独等同于恶意。
6. 导入表是否很小,同时包含 LoadLibrary、GetProcAddress 等动态解析函数。如果样本行为复杂但静态导入极少,应考虑运行时解析 API 或加壳。

可用 Python 的 pefile 做辅助枚举,以下代码只读取元数据:

import pefile
pe = pefile.PE('training-sample.exe', fast_load=False)
print(hex(pe.OPTIONAL_HEADER.AddressOfEntryPoint))
for section in pe.sections:
print(section.Name.rstrip(b'\x00').decode(errors='replace'),
hex(section.VirtualAddress),
section.SizeOfRawData,
round(section.get_entropy(), 2))

注意:熵值是筛选指标,不是判定规则。更稳妥的方法是把高熵、异常节权限、入口点位置、导入表规模和运行时内存变化综合起来看。

【步骤三:提取字符串与导入关系】

普通 strings 工具可能看不到经过简单编码或栈内构造的文本,可以同时使用 FLOSS:

floss.exe .\training-sample.exe > .\floss-output.txt

重点搜索文件名、互斥体名、URL、IP、User-Agent、错误信息、命令行参数和调试输出。对于训练样本,预期可能出现 config.dat、127.0.0.1、Content-Type 等线索。

随后查看导入函数。文件行为可关注 CreateFileW、ReadFile、WriteFile、GetFileAttributesW;网络行为可关注 WSAStartup、connect、send、recv、WinHttpOpen、WinHttpConnect;进程行为可关注 CreateProcessW、ShellExecuteW。导入某个 API 只代表程序可能使用它,不能证明该行为一定发生。

在 Ghidra 中导入样本,执行默认分析,然后从 Symbol Tree 的 Imports 进入目标 API,查看 References。若 CreateFileW 只有一个调用点,可以从该调用点向上追踪参数来源;若调用经由函数指针完成,则需要结合反编译结果和动态断点识别。

【步骤四:准备可控的动态观测环境】

恢复或确认干净快照,将网络设为 Host-Only。若需要接收训练样本发往 127.0.0.1:8080 的数据,可在分析机本地启动一个仅监听回环地址的服务:

python -m http.server 8080 --bind 127.0.0.1

这个简单服务不一定能正确处理样本的自定义协议,但可以验证连接是否发生。若需要更完整的协议模拟,应使用专门的假网络工具,并保存日志。

启动 Process Monitor,先设置过滤条件:

Process Name is training-sample.exe → Include
Operation is Process Create → Include
Operation is CreateFile → Include
Operation is ReadFile → Include
Operation is RegSetValue → Include

过滤的目的是降低噪声,不是隐藏其他证据。首次运行还可以保留较宽的过滤范围,确认是否产生子进程;如果发现子进程,再把其 PID 或进程名加入过滤。

运行前记录目录状态:

Get-ChildItem .\sample-dir -Force -Recurse |
Select-Object FullName,Length,LastWriteTimeUtc |
Export-Csv .\before.csv -NoTypeInformation

运行后使用同一命令生成 after.csv,再执行:

Compare-Object (Import-Csv .\before.csv) (Import-Csv .\after.csv) -Property FullName,Length,LastWriteTimeUtc

这能帮助发现新增或修改文件,但无法替代 Procmon,因为短暂创建后删除的文件可能不会出现在最终目录差异中。

【步骤五:用 x64dbg 定位文件与网络行为】

根据 PE 架构启动对应版本的 x64dbg。载入样本后,不要急于单步穿过所有运行库初始化代码。更高效的方法是在关键系统 API 上设置断点。

对于文件读取路径,可在 kernel32!CreateFileW、kernel32!ReadFile 上断。命中 CreateFileW 后,根据 Windows x64 调用约定,第一个参数通常位于 RCX;在 Dump 中跟随 RCX,可以看到 UTF-16 文件路径。x86 程序则通常从栈上传参,具体位置应结合反汇编和调用约定确认。

对于网络路径,可在 ws2_32!connect、ws2_32!send 上断点。命中 connect 时检查 sockaddr 参数,确认地址族、目标 IP 和端口。端口字段采用网络字节序,不要直接把内存中的两个字节按主机字节序解释。

建议每次断点命中时记录:

1. 当前模块和指令地址;
2. 返回到样本模块的调用栈位置;
3. 关键参数指向的字符串或缓冲区;
4. API 返回值及 GetLastError 或 WSAGetLastError;
5. 对应的 Procmon、TCPView 或假网络日志时间。

如果断点频繁命中系统组件,可以设置条件,仅在返回地址属于 training-sample.exe 模块范围时暂停。这样能减少系统噪声,也能避免把其他进程或库的行为错误归因给样本。

【步骤六:从 API 断点回溯到核心逻辑】

当 CreateFileW 断点命中并确认参数为 config.dat 后,执行到返回,再通过调用栈回到样本模块。在该调用点附近通常能看到:构造路径、检查句柄、读取长度、分配缓冲区、调用 ReadFile、解析内容等步骤。

在 Ghidra 中跳转到对应 RVA。注意 ASLR 会导致运行时虚拟地址变化,可按以下关系换算:

RVA = 运行时地址 - 模块实际加载基址
静态地址 = Ghidra 映像基址 + RVA

给函数和变量重新命名,例如 load_config、parse_config、send_local_report,比保留 FUN_140001230 更利于后续审阅。重命名应基于证据,无法确定时可使用 suspected_parse_config 之类的保守名称。

训练样本可能存在类似逻辑:读取 config.dat 后计算哈希;若配置头等于 DEMO,则组装固定字符串并调用 send;否则输出错误并退出。可以分别准备合法和非法配置进行差分运行,观察哪一个条件跳转改变路径。差分测试比随意修改跳转指令更稳妥,因为它保留了原始程序行为,也更适合防御分析。

【步骤七:验证行为而不是猜测行为】

为了验证配置分支,可以准备两个本地文件:

Set-Content .\config.dat 'DEMO' -NoNewline
Copy-Item .\config.dat .\config-valid.dat
Set-Content .\config.dat 'INVALID' -NoNewline
Copy-Item .\config.dat .\config-invalid.dat

分别恢复快照或清理环境后运行,记录是否触发 connect/send、发送缓冲区内容是否变化、退出码是否不同。若只有 DEMO 分支发送数据,可以得出“在当前环境和输入下,配置头控制本机连接行为”的结论,而不应扩大成“该样本在所有环境下一定联网”。

还可以使用 Procmon 的 PML 文件、x64dbg 日志和假网络日志建立时间线:先打开 config.dat,再读取内容,随后连接 127.0.0.1:8080,最后退出。时间顺序与代码调用关系一致时,证据强度会明显提高。

【步骤八:提取防御性检测信息】

检测信息应优先选择稳定且具有上下文的特征。文件 SHA-256 精确但只能匹配同一文件;字符串可能因重新编译而变化;行为组合通常更有韧性。

对于本训练样本,可以记录:

- 精确哈希:用于实验室样本管理;
- PE 架构、节布局和导入组合:用于聚类,不单独阻断;
- 读取同目录 config.dat 后连接回环地址 8080 的行为序列:用于实验验证;
- 固定测试报文:仅适合作为本地练习检测项。

如果编写 YARA 练习规则,应加入文件类型和字符串组合条件,避免单字符串误报。例如仅用于本地样本集的规则思路是:文件头为 MZ,文件大小处于预期范围,同时出现 config.dat 和训练报文。不要把未经大规模良性集测试的规则直接部署到生产环境。

【风险与注意事项】

1. 未知样本即使看似无害,也可能包含虚拟机逃逸、横向传播或破坏逻辑。不要在宿主机直接运行。
2. 快照不是万能隔离。共享目录、剪贴板、桥接网络和宿主集成功能都会扩大边界。
3. 不要把样本上传到公开多引擎平台,除非确认不涉及客户数据、未公开事件或保密代码。公开平台可能向第三方共享样本。
4. 调试器会改变时序、异常处理和线程调度。调试结果应与非调试运行、系统日志和网络日志交叉验证。
5. 修改二进制绕过条件分支会改变证据对象。若为理解代码而补丁,应保留原始哈希与补丁后哈希,并明确区分两次结果。
6. 不要根据编译时间、PDB 路径、语言字符串直接归因组织或个人。归因需要远超单样本逆向的证据。
7. 网络模拟服务只绑定回环或隔离网卡,不应向公网暴露,也不要转发样本产生的请求到真实域名。

【常见问题】

问:静态导入表没有 connect,是否说明样本不联网?
答:不能。样本可能使用 WinHTTP、WinINet、COM,也可能通过 LoadLibrary/GetProcAddress 动态解析 API,或者把网络逻辑放在子进程中。应结合运行时模块、API 跟踪和网络日志判断。

问:字符串里发现一个公网 IP,能否直接认定为控制服务器?
答:不能。它可能是测试数据、失效基础设施、诱饵字符串或未执行分支。至少要确认交叉引用、参数构造过程和动态连接证据。

问:程序在虚拟机中立即退出怎么办?
答:先检查缺失参数、配置文件、运行目录、架构和依赖库,再观察退出码、异常和最后一次 API 调用。不要一开始就假设存在反虚拟机逻辑。若确实发现环境检查,应记录检查对象和分支结果,而不是贸然修改系统或对抗检测。

问:x64dbg 地址与 Ghidra 地址对不上怎么办?
答:通常是 ASLR 导致。先获取运行时模块基址,计算 RVA,再映射到 Ghidra 映像基址。还要确认加载的是同一哈希文件以及架构一致。

问:Procmon 显示 NAME NOT FOUND 是否代表攻击失败?
答:未必。很多程序会主动探测多个路径或注册表项,NAME NOT FOUND 只是该次访问结果。应查看后续是否换用其他路径、是否回退到默认配置,以及程序最终控制流。

问:单次运行没有看到恶意行为,能否判定安全?
答:不能。行为可能依赖参数、时间、权限、网络响应、区域设置或特定文件。结论应限定为“在指定环境、输入和观察窗口内未发现某行为”。

【总结】

一套可复现的 PE 逆向流程,应从隔离和样本身份开始,通过 PE 结构、导入表和字符串形成静态假设,再用 Procmon、假网络与 API 断点验证真实行为,最后回到反编译器定位条件分支并做输入差分。分析者需要始终区分“能力”“意图”和“已观测行为”:导入 API 说明潜在能力,代码上下文帮助判断用途,动态日志才说明某次运行中实际发生了什么。

工程上最重要的产物不是一张反汇编截图,而是包含哈希、环境、输入、时间线、关键地址、参数、返回值和不确定性的证据包。这样得到的结论才能被复核,也更容易转化为样本归档、主机检测、网络监控和事件响应中的防御措施。
 

dead3223

New member

dead3223 Rep
0
0
0
Rep
0
dead3223 Vouches
Vouches
0
1 MONTH
1 1 MONTH OF SERVICE
dead3223 获得
0
0
0
获得
0
dead3223 凭证
0
0
0
凭证
0
主题
11
点赞
0
HakB
1 月
在社区成长 1 月
LEVEL 1 1 XP
 
建议补充“证据时间线”实践:启动 Procmon 捕获前先校准虚拟机时间,并同步保存 PML、x64dbg 日志与本地监听服务日志。命中 connect 或 WSASend 时,记录线程 ID、调用栈、目标地址及缓冲区前 64 字节,再按时间戳与 Procmon 中 config.dat 的 CreateFile、ReadFile 事件对齐。这样可验证“读取配置后发送数据”的先后关系,避免仅凭导入表或单次断点推断。若分支未触发,还应分别测试 config.dat 缺失、格式错误和有效三组输入,并为每组恢复同一快照。
 

BOSS

首领

BOSS Rep
0
0
0
Rep
0
BOSS Vouches
Vouches
0
3 YEARS
3 YEARS OF SERVICE
创始人
BOSS 获得
0
0
0
获得
0
BOSS 凭证
0
0
0
凭证
0
主题
32
点赞
18
3 年
来到社区 3 年
LEVEL 100 XP
 
还需注意几个边界条件:Host-Only 不等于绝对隔离,虚拟网卡、宿主服务及误配置路由仍可能形成出口,执行前应验证路由表、DNS 和防火墙策略。127.0.0.1 监听只能证明连接尝试,不能证明协议内容被服务端正确接收;应结合抓包、API 返回值和调用栈确认。另一个常见误区是过度依赖 API 断点:样本可能使用直接系统调用、异步 I/O、子进程或注入路径绕过观察点。快照也不能替代宿主侧防护,分析结束后应销毁运行态、核验共享能力,并将“未观察到”明确写成覆盖范围有限,而非“不存在该行为”。
 
顶部