konstantinavet/project-csec476-group2

GitHub: konstantinavet/project-csec476-group2

该项目详细记录了对包含 Metasploit 反向 HTTPS 载荷的 PDF 样本进行从静态拆解到动态行为还原的完整逆向分析过程。

Stars: 0 | Forks: 0

# 恶意软件分析报告 - 第 2 组 包含对真实世界样本的静态分析和动态分析的恶意软件分析项目,附带详细发现、逆向工程和网络行为调查。 ## 团队成员 - Konstantin Avetisian – 405006442 - Ayush Gowda – 393000595 - Nikita Astionov – 386004799 - Youssef Elgayar – 772003595 ## 目录 - 技术摘要 - 静态分析 - 动态分析 - 高级分析 - 结论 ## 技术摘要 (恶意软件功能的 1 页摘要) ## 静态分析 在静态分析中,恶意软件样本在不运行的情况下进行分析,并从二进制文件中推导出识别特征、结构属性和行为意图。对于 `group2.exe`,静态分析足以完全识别该样本,映射其命令与控制(C2)基础设施,生成样本在运行时将进行的所有 Windows API 调用,并将样本归因于特定的攻击性安全框架及其特定的构建时选项,而无需模拟或执行任何指令。 本部分将分为基础静态分析和高级静态分析。基础静态分析用于识别文件类型、结构和表层指标;高级静态分析则通过反汇编、控制流分析和与已知威胁参与者技术交叉引用来重现恶意软件的运行时行为。每个截图都包含组标识符和系统时间戳。 ### 基础静态分析 #### 初始样本识别 分配给第 2 组的样本以单个文件形式提供,名为 `group2.pdf`,总计 295.04 KiB。对于恶意软件而言,PDF 是一种不常见的容器, 因为该格式本身不可执行 — PDF 是由阅读器应用程序 (Adobe Reader、Foxit、内置 Windows 查看器或浏览器) 呈现,而不是由操作系统直接呈现。当恶意软件以 PDF 形式交付时, 几乎总是出于以下三种原因之一:利用 PDF 阅读器本身的漏洞, 调用触发二次下载的嵌入式 JavaScript,或者作为被动交付容器, 保存有效载荷供用户手动提取。因此,我们首先确定 PDF 的真实文件类型,并检查其指标,以判断样本属于这三类中的哪一种。 Detect It Easy (DIE) 版本 3.10 是一个 Windows GUI 工具,结合了 PE 识别、加壳程序检测和熵分析,用作第一步 识别。DIE 确认该文件是真正的 PDF 版本 1.7 (“with binary data”注释表示文件包含非文本流 — 强烈暗示存在嵌入式二进制内容,如字体、图像或 附件),并报告 PDF 层本身没有加壳程序、混淆器或基于签名的异常。 ![Detect It Easy identifying group2.pdf as PDF 1.7 with binary data](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/f9e1522d99032850.png) *图 1: Detect It Easy 将 `group2.pdf` 识别为合法的 PDF 文件 (格式 `PDF(1.7)[with binary data]`)。* 在继续之前确定真实文件类型是静态分析的标准做法, 因为文件扩展名可以被伪造,文件开头的实际魔数(`%PDF-1.7`) 决定了接下来应用哪些工具。 #### PDF 结构分类 Didier Stevens 的 `pdfid.py` 是快速 PDF 分类的事实标准工具。 它解析原始 PDF 对象流并计算每一个 常被滥用来触发恶意行为的关键字的出现次数 — 特别是 Adobe 和第三方 PDF 阅读器历史上允许调用代码或 检索外部资源的元素。如果以下任何关键字出现非零计数: `/JavaScript`、`/JS`、`/OpenAction`、`/AA`、`/Launch`、`/RichMedia`、 `/JBIG2Decode` 或 `/EmbeddedFile`,则是一个需要进一步调查的标志。 如果所有这些关键字都不存在,则通常表示是良性 PDF。 对 `group2.pdf` 运行 `pdfid.py` 产生的诊断配置文件大大 缩小了威胁模型。 ![pdfid output showing /EmbeddedFile = 1 and all other suspicious keywords at zero](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/35f7f4b06a032852.png) *图 2: `pdfid.py` 解析 PDF 并计算易被滥用的关键字。在 `group2.pdf` 中,仅出现一个此类关键字:`/EmbeddedFile = 1`。每个 脚本执行和自动操作关键字 — `/JavaScript`、`/JS`、 `/OpenAction`、`/AA`、`/Launch` — 均为零。* 这告诉我们 PDF 不包含嵌入式 JavaScript,没有自动执行触发器,也没有启动操作。 其唯一功能是携带单个嵌入式文件作为附件。 #### 定位嵌入式文件引用 确认只有一个 `/EmbeddedFile` 后,下一步是确定哪个 PDF 对象携带嵌入式有效载荷以及与其关联的文件名。PDF 中的嵌入式文件遵循特定的对象结构。`/Filespec` 字典描述文件(名称、MIME 类型、关系),而 Filespec 内部的 `/EF` (嵌入式文件)字典引用包含实际文件字节的单独流对象。因此,识别 `/Filespec` 对象 是提取的入口点。 `pdf-parser.py --search Filespec` 遍历对象图并返回文档中的每个 `/Filespec` 字典。 ![pdf-parser --search Filespec showing object 4 references /F (group2.exe)](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/0876984d4b032853.png) *图 3: `pdf-parser.py` 定位对象 `4 0`,其类型为 `/Type /Filespec`。关键行是 `/F (group2.exe)` — 嵌入式附件的文件名, 存储为 ASCII 字面量。并行的 `/UF` 字段将相同的文件名编码为 UTF-16 以支持 Unicode(转义序列 `\x00g\x00r\x00o\x00u\x00p \x00 2\x002 \x00.\x00e\x00x\x00e` 是 `group2.exe` 的 UTF-16LE 编码,带有前导 字节顺序标记)。`/EF /F 3 0 R` 行引用对象 `3 0` 作为实际文件内容的容器。* #### 检查嵌入式文件对象 在提取有效载荷之前,我们直接检查对象 `3 0` 以验证其 结构属性,特别是 PDF 规范对流应用了何种压缩或过滤。PDF 流可以声明任何组合 的 `/Filter` 值 — 常见的有 `/FlateDecode`(zlib deflate,与 ZIP 中使用的压缩相同)、`/ASCIIHexDecode`、`/ASCII85Decode`、 `/LZWDecode` 和 `/Crypt`。要恢复原始文件,我们必须按顺序 逆应用每个过滤器。 ![pdf-parser --object 3 showing /Type /EmbeddedFile, /Subtype /application/octet-stream, /Filter /FlateDecode](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/1b0fa1f83f032854.png) *图 4: 对象 `3 0` 确认为 `/Type /EmbeddedFile`,具有 `/Subtype /application/octet-stream` 和 `/Filter /FlateDecode`。子类型 `application/octet-stream` 是“任意二进制数据”的 MIME 类型 — PDF 故意不承诺有效载荷是可执行文件、 文档还是其他任何内容。`/FlateDecode` 过滤器意味着磁盘上的流字节是 zlib 压缩的;必须对其进行解压缩以恢复原始文件。* #### 提取有效载荷 确定了对象并了解了其压缩方式后,提取工作就 变得很直接。`pdf-parser.py --object 3 --filter --dump ` 按顺序应用 每个声明的流过滤器,并将结果字节写入指定文件。我们将恢复的字节写入 FLARE-VM 分析系统 下载目录中的 `group2.exe`。 #### 对恢复的二进制文件进行哈希计算和识别 一旦 PE 作为独立文件存在于磁盘上,就应用了标准识别 程序。 ![PowerShell Get-FileHash computing SHA-256 of the extracted group2.exe](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/f8bd939783032856.png) *图 5: 提取的二进制文件的 SHA-256* 哈希值有两个用途。首先,它是样本的规范标识符 — 本报告中的所有后续发现都归因于此特定 SHA-256,任何读者都可以通过重新计算其自己提取的文件副本上的哈希值来独立验证。其次,可以针对 公共威胁情报数据库(如 VirusTotal、MalwareBazaar 和 Hybrid Analysis)搜索哈希值,以确定该样本之前是否在野外被观察到。 提取的二进制文件的完整元数据如下: | 属性 | 值 | |---|---| | 文件类型 | PE32+ MS Windows 可执行文件,x86-64 | | 文件大小 | 7.50 KiB (7680 字节) | | MD5 | `0ce70f0f07c21bf4290a1c0308fc4f46` | | SHA-1 | `1562f662214044749c1fa5601f52332ed347e011` | | SHA-256 | `89dfbfeda4ec1d4f6d28ab376cc28468f42f98ccc694cd8e9a5033a34c2f7a7b` | | ssdeep | `24:eFGSGj30pFLknehtht6dp506WcNYKan2DOIRwQa/FlGVKbuqiksvckOp:iGb0onehthEdc2GJCO1Qa9QQqilk` | 二进制文件的小尺寸(小于 8 KB)本身就是一个行为指标。一个 功能齐全的恶意应用程序 — 例如银行木马、勒索软件或 后门 — 一旦计入其功能、配置和(通常)静态链接的依赖项, 通常跨越数百千字节到数兆字节。7.5 KB 的 Windows PE 只能包含少量的实际代码。这种尺寸是 **Stager(引导程序)** 的特征:一个最小的第一 阶段有效载荷,其唯一目的是从攻击者的基础设施下载更大的第二阶段有效载荷 并在内存中执行它。高级静态分析下面测试并确认了这一假设。 #### 威胁情报查询 ![VirusTotal search returns no matching results for the extracted SHA-256](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/7f855214e5032857.png) *图 6: 截至分析日期,VirusTotal 对提取的 SHA-256 未返回匹配结果。* 该样本之前未被任何 贡献给 VirusTotal 的杀毒引擎观察到,也没有被社区 评论系统发现。这种否定结果本身就是一个发现:无论此样本是什么,它要么是真正新颖的,要么是专门为此交付物新生成的,因此没有先前的指纹。无论哪种情况,我们都不能依赖 外部分类,必须通过我们自己的分析来识别其家族。 #### 使用 Detect It Easy 进行二进制识别 然后将 DIE 应用于提取的可执行文件,以确定编译器、链接器、 架构以及任何高级指标(如加壳程序或保护程序)。 ![Detect It Easy on group2.exe reporting PE64 AMD64 GUI, MSVC 19.36.35207, Visual Studio 2022 v17.6, and a packer heuristic](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/f525f4b0d4032858.png) *图 7: DIE 报告 `group2.exe` 为 64 位 GUI 子系统 PE(`PE64`、 `AMD64`、`GUI`),使用 Microsoft Visual C/C++ 版本 19.36.35207 构建,并使用 Microsoft Linker 14.36.35207 链接(Visual Studio 2022,v17.6)。启发式行 `(Heur)Packer: Compressed or packed data [Last section EP]` 是 此步骤中最有趣的发现。* DIE 的加壳启发式标记 PE 的入口点不位于二进制文件的第一个节(`.text`)中,而是位于**最后一个**节中。正常的 MSVC 输出将可执行代码放在 `.text` 中,这始终是第一个代码节。位于尾部节的入口点是加壳程序(在运行时解压缩原始代码并将控制权转移给它)或自定义加载器 (准备环境并跳转到嵌入式 shellcode)的诊断标志。结合 小文件尺寸,后一种解释的可能性更大。 #### PE 结构分析 使用两个互补工具检查了提取的二进制文件的可移植可执行(PE)结构:PE-bear 用于交互式探索和节 级可视化,CFF Explorer 用于详细的节特征 检查。 ![PE-bear tree view showing DOS Header, NT Headers, Section Headers, and five sections ending with .glav containing the entry point](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/db51520213032859.png) *图 8: PE-bear 对 `group2.exe` 的树形视图。* 节树显示了五个节:`.text`、`.rdata`、`.data`、`.pdata` 和 `.glav`。前四个 是 64 位 PE 的 Microsoft 链接器的标准输出(`.text` 用于 可执行代码,`.rdata` 用于只读数据,`.data` 用于可变数据,`.pdata` 用于 x64 调用约定 所需的异常处理元数据)。第五个,`.glav`,是**非标准的**。Microsoft 链接器 在任何默认配置下都不会发出具有此名称的节。入口点注释 `EP = 1A00` 进一步确认二文件的 执行始于 `.glav` 内部 — 最后加载的节。这是 手工制作的 PE 加载器带有嵌入式 shellcode 的 结构签名,而不是正常的编译应用程序。 ![CFF Explorer section table showing .glav with virtual size 0x375, characteristics 0xE0000020](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/2fdfd321bf032900.png) *图 9: CFF Explorer VIII 节特征表。`.glav` 节具有虚拟大小 `0x375`、虚拟地址 `0x5000`、原始大小 `0x400` 和 **特征 `0xE0000020`**。该值解码为四个 PE 节标志的按位或: `IMAGE_SCN_CNT_CODE (0x00000020)`、 `IMAGE_SCN_MEM_EXECUTE (0x20000000)`、`IMAGE_SCN_MEM_READ (0x40000000)` 和 `IMAGE_SCN_MEM_WRITE (0x80000000)`。* 组合 `MEM_EXECUTE | MEM_READ | MEM_WRITE` — 通常缩写为 RWX — 在合法软件中极其罕见。现代编译器和操作系统 通过构建时节标志和通过数据执行保护功能(DEP)的运行时执行来强制执行 W^X 原则(页面应该是可写或可执行的,但不能两者兼有)。在构建时标记为 RWX 的 PE 节绕过了此防御的第一层,并强烈指示 存在 shellcode 容器:代码必须是可写的,因为它在执行期间会修改自身,并且是可执行的,因为最终会将控制权转移到它。 `.glav` 命名、RWX 权限、尾部节中的入口点以及最少的 `.text` 内容的组合表明 `group2.exe` 在结构上是一个加载器,而不是传统应用程序。其余的节(`.text`、`.rdata`、`.data`、`.pdata`)的存在主要是为了满足 Windows 加载器对格式良好的 PE 的期望;实际的恶意有效载荷驻留在 `.glav` 中。 #### 熵分析 识别非标准节后的标准后续步骤是检查其内容的熵。香农熵以每字节位数为单位测量,范围从 0 到 8,指示区域中字节值的分布均匀程度。熵值与内容类别有意义的对应关系:接近零表示重复数据(长连续相同的字节),大约 4-5 表示 ASCII 文本,5.5-6.5 表示 x86/x64 机器代码,高于 7.5 表示压缩或加密数据。 ![DIE entropy analysis of group2.exe showing per-section entropy values and overall profile](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/468178652a032902.png) *图 10: Detect It Easy 对 `group2.exe` 的熵分析。* 尽管 DIE 的启发式扫描器在图 7 中将二进制文件标记为可能已加壳,但熵分析器报告的最终结论是“未加壳(21%)”。两个子系统测量的内容不同 — 启发式标志是**结构性的**(EP 位置、节布局、IAT 形状),而熵分析是**统计性的**(字节值分布)。真正的加壳程序会同时触发这两个信号:结构异常加上包含压缩有效载荷的节具有高熵(通常为 7.5+ 位/字节)。在这里,每节熵为 `.rdata` = 2.87、`.data` = 0.03、`.pdata` = 0.10 和 `.glav` = 5.76,整体文件熵为 1.72。`.glav` 中的 5.76 值相对于文件的其余部分有所升高,与图中可见的约 6.6 的局部峰值一致,但这完全符合密集的手工 x64 机器代码(5.5–6.5 位/字节)的预期范围,远低于指示压缩的 ~7.5 阈值或指示加密的 ~7.9 阈值。因此,两个 DIE 发现的调和很清楚:`group2.exe` 具有**已加壳二进制文件的结构**(入口点位于尾部的自定义节),而没有其**内容**(该节中没有压缩或加密的 blob)。这是 shellcode 加载器而不是真正的加壳程序的结构签名 — `.glav` 中的字节是准备执行的 x64 指令,而不是解压缩然后运行的存根。对逆向工程的实际含义在下面的高级静态分析部分得到了确认:`.glav` 中的 shellcode 可以直接反汇编,而无需剥离任何加密或压缩包装。 #### 导入地址表 PE 的导入地址表(IAT)是二进制文件声明它在加载时需要的外部 DLL 函数列表。Windows 加载器在将控制权交给程序的入口点之前,将 IAT 中的每个条目解析为函数指针。对于静态分析人员来说,IAT 通常是行为信息最丰富的来源之一:导入 `CreateFileA`、`WriteFile` 和 `CloseHandle` 的应用程序显然是在进行文件 I/O;导入 `InternetOpenUrlA` 和 `HttpSendRequestA` 的应用程序是在进行 HTTP;导入 `CryptAcquireContextA`、`CryptHashData` 和 `CryptEncrypt` 的应用程序是在进行加密。二进制文件的 IAT 往往是其功能的完整目录。 ![PE-bear imports tab showing a single entry: KERNEL32.dll!VirtualProtect](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/d5f201a892032903.png) *图 11: `group2.exe` 的完整导入地址表。* 在所有 DLL 中恰好只有**一个**导入的函数:`KERNEL32.dll!VirtualProtect`。 这是非同寻常的。Windows GUI 应用程序 — PE 头声明此二进制文件为 GUI 应用程序 — 通常至少从 `user32.dll` 和 `gdi32.dll` 导入窗口创建、消息泵和 GDI 函数,加上从 `kernel32.dll` 导入的数十个实用程序函数。单函数 IAT 不仅小,而且是最小的可能有用的 IAT。 解释很清楚,并遵循有据可查的恶意软件技术: **二进制文件没有静态声明其依赖项,因为它在运行时动态解析它们**。恶意软件不是让 Windows 加载器将指向 WinINet、内核和网络函数的指针填充到 IAT 中,而是通过在运行时遍历进程环境块(PEB)、枚举已加载的模块、使用轻量级哈希函数对导出的函数名称进行哈希处理,并将结果与嵌入在 shellcode 中的预计算哈希常量进行比较,从而执行自己的解析。这种技术称为 **API 哈希**,由安全研究员 Stephen Fewer 在 2000 年代中期通过他公开发布的 `block_api` shellcode 推广。它为攻击者提供了两个防御目的。首先,恶意软件的意图对于只检查 IAT 的静态分析人员来说是不可见的 — 实际的 API 表面隐藏在不透明的 32 位哈希值后面。其次,不存在对例如 `InternetConnectA` 的 IAT 引用意味着恶意软件规避了简单的签名规则,这些规则将“任何导入 WinINet 函数的二进制文件标记为可疑”。 存在的唯一导入 `VirtualProtect` 与此解释一致:运行时加载器需要在写入代码页后使其可执行,而 `VirtualProtect` 是用于更改已分配区域的内存保护标志的标准 Windows API。由于加载器可以通过 IAT 合法地获取 `VirtualProtect` 函数指针,并且此单个函数没有签名级别的可疑之处,攻击者已经从这个起点引导了其余的 API 解析。 #### 静态字符串提取 从二进制文件中提取的字符串通常显示硬编码的 URL、文件名、 错误消息和配置数据。除了使用较简单的 `strings` 实用程序外,还使用了 FLOSS(FireEye Labs 混淆字符串求解器),因为 FLOSS 对二进制文件的代码应用控制流分析,可以恢复在运行时组装或解码的字符串,而不是存储为静态数据的字符串。 ![FLOSS static strings output showing section names, PAYLOAD marker, shellcode fragments, wininet, User-Agent, 212.22.1.3, and a 120-character URI](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/c667c240c9032904.png) *图 12: FLOSS 静态字符串输出 — 较丰富的一半。* 值得注意的提取字符串包括:PE 节和目录标签(`.text$mn`、`.rdata`、 `.idata$5`、`.xdata`、`.idata$2`、`.idata$3`、`.idata$4`、`.idata$6`、 `.data`、`.pdata`、`.glav`);导入表条目(`VirtualProtect`、 `KERNEL32.dll`);一个 `PAYLOAD:` 字面量(可能是嵌入在 shellcode 中的标记字符串);短 ASCII 片段(`AQAPRH1`、`rPM1`、`JJH1`、`R AQ`、 `AX^YZAXAYAZH`、`XAYZH`、`YSZM1`、`SZAXM1`、`PSSI`、`SYj@ZI`);字符串 `wininet`;完整的 Mozilla/Chrome HTTP User-Agent (`Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36`);一个 IPv4 地址 `212.22.1.3`;以及一个 以 `/DoSaKUGGHJcVXRRffO9-...` 开头的 120 个字符的类似 URI 的 blob。 短 ASCII 片段(`AQAPRH1`、`AX^YZAXAYAZH`、`SZAXM1` 等)不是有意义的文本。它们是**恰好落在可打印 ASCII 范围内的 x64 指令操作码字节**,因此被字符串提取器捕获。例如,`A` 是 `0x41`,这是 x64 中常用的 REX.B 前缀;`Q` 是 `0x51`,即 `push rcx`;`P` 是 `0x50`,即 `push rax`;`R` 是 `0x52`,即 `push rdx`;`Z` 是 `0x5A`,即 `pop rdx`;`H` 是 `0x48`,即使指令变为 64 位的 REX.W 前缀;`1` 是 `0x31`,当与 ModR/M 字节结合时,它是 `xor r/m, r` 的操作码。诸如 `AX^YZAXAYAZH` 之类的序列解码为一系列 push/pop/xor 指令,这些指令混洗寄存器值 — 这种寄存器混换发生在手工编写的 shellcode 的函数序言和结尾中。因此,这些片段不是通常意义上的“字符串”,而是被文本提取器扫除的 shellcode 指令流的指纹。 其余值得注意的字符串各自对应特定的恶意软件行为: - `wininet` 是 Windows HTTP 客户端库的名称。它作为字符串出现,加上 `wininet.dll` 函数在 IAT 中完全不存在,意味着该库是通过 `LoadLibraryA` 在运行时加载的,而不是声明为静态依赖项。 - Mozilla/Chrome User-Agent 是恶意软件将在其 HTTP 请求的 `User-Agent` 头中呈现给 C2 服务器的字符串值。冒充主流浏览器是针对标记异常 User-Agents 的网络层检测系统的标准规避技术。 - `212.22.1.3` 是一个硬编码的 IPv4 地址,极有可能是命令与控制服务器。硬编码 IP 对攻击者来说很脆弱(一旦任何网络防御者观察到该地址,它就成为一个不可变的 IOC),但这是 msfvenom 的默认行为,并且与训练样本一致。 - 120 个字符的 `/DoSaKU…MZra` blob 是最独特的字符串。其字符集(`A–Z`、`a–z`、`0–9`、`-`、`/`)类似于将 `+` 符号替换为 `-` 的 URL 安全 base64 编码。这个字符串成为下面的高级静态分析的重点。 ![FLOSS stack and decoded strings output showing only wininet recovered](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/3c6d5cdf78032905.png) *图 13: FLOSS 栈字符串和解码字符串输出。* 除了已经显示的静态字符串外,FLOSS 报告恰好一个栈字符串(`wininet`)和恰好一个解码字符串(也是 `wininet`)。栈字符串是在运行时在栈上逐字节构建的 — 这是一种混淆技术,字符串永远不会作为连续数据存在于二进制文件中。解码字符串是 FLOSS 确定由运行时解码例程产生的字符串。FLOSS 从两个来源恢复 `wininet` 这一事实意味着二进制文件在运行时在栈上组装这个特定字符串,而不是从 `.rdata` 加载它。其他所有内容 — User-Agent、IP 地址、URI、节名称 — 似乎是静态存在的。这完全符合我们的预期,如果 shellcode 在其执行早期使用字符串 `wininet` 一次来调用 `LoadLibraryA`。 #### 自动化能力提取 Mandiant 的 `capa` 工具通过将数千条规则库与二进制文件的反汇编、导入和字符串进行匹配,自动检测恶意软件能力。它以自然语言生成输出(“通过哈希解析函数”、“连接到 HTTP 服务器”、“包含混淆的栈字符串”等),并且是手动分析的最快健全检查方法之一。然而,对于 `group2.exe`,结果很不寻常。 ![capa output reporting "no capabilities found" for group2.exe](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/8e17eef250032906.png) *图 14: `capa` 报告 `group2.exe` “未发现任何能力”。* 这是一个空结果:capa 的规则引擎没有将其数千个能力规则中的任何一个与二进制文件匹配。结果不是错误 — 它是二进制文件结构的直接后果。capa 的规则在很大程度上依赖于特征性的 IAT 导入和标准反汇编模式。由于恶意软件在运行时解析每个 API,因此没有常规的导入引用可供 capa 规则匹配。矛盾的是,capa 在这里的失败本身就是 API 哈希的有力证据:在一个 99.9% 的现实恶意软件至少触发少数 capa 规则的生态系统中,零能力结果是高级规避的诊断,并指引分析人员转向高级静态分析部分中使用的手动技术。 #### Authenticode 签名检查 Windows 可执行文件可以使用 Microsoft 的 Authenticode 方案由其发布者进行数字签名。有效的签名链接到受信任的证书颁发机构并确认发布者的身份。恶意软件通常未签名,尽管高投入威胁有时会使用被盗的签名证书或通过虚假手段获得的证书。开源实用程序 `osslsigncode` 用于检查任何签名。 ![osslsigncode verify output reporting "No signature found" and an invalid PE checksum warning](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/6e9b3ee211032908.png) *图 15: `osslsigncode verify group2.exe` 报告“未找到签名”。* 该二进制文件未签名 — 这是 Metasploit 生成的样本所预期的。 额外的警告 `invalid PE checksum` 很有趣:`osslsigncode` 报告 PE 声明的校验和(`0x000098A5`)与它从二进制文件内容计算出的值(`0x00009A0D`)不匹配。不匹配表明链接器的 `/RELEASE` 选项未使用(在这种情况下,根本不写入校验和,并且该字段默认为零 — 但此二进制文件的校验和非零),或者二进制文件在链接后进行了后处理。对于 Metasploit 样本,第二种解释是可能的:`msfvenom` 获取编译的 PE 模板并在其中修补 stager shellcode,这会使任何先前计算的校验和无效。因此,不匹配是指向 msfvenom 生成的二进制文件的另一个结构指纹。 #### 使用 rabin2 进行二次 PE 调查 作为基础静态分析的最后一个步骤,使用 `rabin2`(radare2 套件的 二进制信息工具)独立确认 Windows 原生工具的发现。 ![rabin2 -I, -S, -i, -zz output showing file properties, sections, imports, and strings](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/865b70ff4f032909.png) *图 16: `rabin2` 交叉验证。* `rabin2 -I` 确认 PE 级属性(机器 `AMD64`、子系统 `Windows GUI`、`nx: true`、`signed: false`、`stripped: false`)。`rabin2 -S` 打印五节布局,明确显示权限字符串:`.text` 为 `-r-x`, `.rdata` 为 `-r--`、`.data` 为 `-rw-`、`.pdata` 为 `-r--`、`.glav` 为 `-rwx`。`rabin2 -i` 确认单条目 IAT(`KERNEL32.dll / VirtualProtect`)。`rabin2 -zz` 打印所有字符串,包括 `!This program cannot be run in DOS mode.` DOS 存根消息。Windows 端工具的每个发现都得到 Linux 端工具的证实,从而对基础静态分析结论充满信心。 #### 基础静态分析发现摘要 在基础静态分析结束时,我们确立了以下内容: 该样本是一个从 PDF 附件中提取的小型(7.5 KiB)64 位 Windows PE;PE 是使用 MSVC 2022 构建的,但经过了后处理,使校验和无效;入口点位于名为 `.glav` 的非标准、读+写+执行节中;导入表仅声明单个函数(`VirtualProtect`);因此运行时行为依赖于通过某种替代机制进行 API 解析;从二进制文件中提取的字符串包括硬编码的 C2 IPv4 地址(`212.22.1.3`)、Chrome User-Agent、对 `wininet` HTTP 库的引用以及以 `/DoSaKU` 开头的独特 120 个字符的 ASCII blob;`capa` 未检测到常规能力,因为 API 引用都是动态解析的;该二进制文件未签名,之前未在 VirusTotal 上被发现。 这些发现共同指向一个作为 shellcode 加载器构建的极简 HTTP(S) 下载程序。下面的高级静态分析确认了这一假设,并识别了用于生成样本的特定框架、变体和构建时选项。 **基础静态分析中使用的工具:** Detect It Easy (DIE)、Didier Stevens 套件(`pdfid.py`、`pdf-parser.py`)、PowerShell `Get-FileHash`、VirusTotal、PE-bear、CFF Explorer VIII、FLOSS、capa、osslsigncode、rabin2(radare2 套件)。 ### 高级静态分析 基础静态分析确立了 `group2.exe` 是一个具有自定义 RWX 节、动态 API 解析和多个硬编码运行时参数的 shellcode 加载器。高级静态分析旨在通过反汇编 shellcode 本身、识别 API 解析算法、枚举恶意软件在运行时将调用的每个 Windows 函数、恢复 120 个字符 `/DoSaKU…` blob 的含义以及将样本归因于特定的恶意软件家族或工具包来完善这一情况。 #### Ghidra 反汇编 — 接触 stager 的第一种方法 Ghidra 被用作主要的静态反汇编程序。在基础静态分析中将 120 个字符的类似 URI 的字符串识别为独特的产物后,我们在 Ghidra 定义的字符串中搜索它,并跟随其交叉引用到代码中。该字符串在标记为 `FUN_140005191` 的函数中内联引用,紧跟在 `call` 指令之后。 ![Ghidra listing at FUN_140005191 showing argument-preparation instructions and the call FUN_14000522E followed by the inline /DoSaKU... URI](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/277f07c4f4032910.png) *图 17: Ghidra 对 `FUN_140005191` 的反汇编。* 该函数为 WinINet 调用准备参数 — `mov r8, 0x1f92` 设置一个 4 字节的立即数(后来被识别为端口号),`xor r9, r9` 清除一个寄存器以用作 NULL 参数,`push rbx` 和 `push 0x3` 将调用(服务 `0x3` = `INTERNET_SERVICE_HTTP`)的栈参数放置,以及 `mov r10, 0xc69f8957` 加载一个 32 位常量,该常量将证明是 `InternetConnectA` 的 ROR13 哈希。位于 `0x1400051ae` 的 `call rbp` 调度此 API 调用。紧随其后,位于 `0x1400051b0` 的 `call FUN_14000522E` 将控制权转移到 stager 链中的下一个函数 — 而 `0x1400051b5` 处的下一个字节是 `0x2F`(`/`),即 `/DoSaKU…` URI 的第一个字符,Ghidra 的自动分析器正确地将其保留为未定义(`??`),因为这些字节位于代码节中间而不是数据区域。 ASCII 字符串紧接在 `call` 指令**之后**这一事实是关键的结构观察。x86-64 `call` 指令在将控制权转移给被调用者之前,将下一条指令的地址作为返回地址压入栈。如果被调用者以从栈 `pop` 寄存器开始,它将接收一个指向恰好跟随 `call` 的任何字节的指针。这种习惯用法称为 **CALL/POP 技术**,是位置无关 shellcode 引用嵌入在其自己的代码流中的字符串数据的标准方式,无需数据节或对其自身加载地址的任何了解。 ![Ghidra listing at FUN_14000522E showing the pop rdx consuming the URI pointer and subsequent argument setup](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/8654deb22f032911.png) *图 18: Ghidra 对 `FUN_14000522E` 的反汇编。* 该函数以 `mov rcx, rax`(加载前一个 `InternetConnectA` 调用返回的连接句柄)开始,然后是 `push rbx / pop rdx` — 栈对齐混排 — 然后是 `pop r8`,它消耗前一个 `call` 压入栈的返回地址。由于位于 `0x1400051b0` 的 `call FUN_14000522E` 压入的地址是 `0x1400051b5`(内联 `/DoSaKU…` URI 的第一个字符),`r8` 现在保存指向该 URI 字符串的指针。随后的 `xor r9, r9`、栈压入和 `mov rax, 0x84a83200` 为下一个调用设置剩余参数 — 常量 `0x84A83200` 是 WinINet 请求标志的位掩码,包括 `INTERNET_FLAG_SECURE (0x00800000)`,将请求识别为 HTTPS 而不是纯 HTTP。 这解决了 `/DoSaKU…` 字符串的谜团。它未编码。它未压缩。它是纯文本 ASCII 数据,存储在代码节中间而不是 `.rdata` 中,并通过 x86-64 调用约定的自动返回地址压入进行访问。恶意软件在执行流第一次经过关联的 `call` 时,从其自己的指令流中读取 URI。 #### URI 校验和 — 特定于 Metasploit 的指标 将 URI 识别为 WinINet 调用的纯文本参数后,下一个问题是 URI 本身是否编码任何有意义的信息。简短的熵分析排除了 URI 中存在 base64 编码有效载荷的可能性 — 5.43 位/字符的测量值本质上是随机的,并且没有字母排列可生成可读文本。 不同的调查路线被证明更有成果。在 Metasploit 的反向 HTTP 分阶段有效载荷架构中,处理程序通过对请求的 URI 应用校验和来区分合法的 stager 连接和随机 HTTP 流量。具体而言,Metasploit 框架计算 `sum(URI 的 ASCII 字节) mod 256` 并将结果与一个小魔术常量表进行比较。如果校验和匹配,则该请求被视为 stager 握手,并在回复中提供适当的第二阶段有效载荷。这些常量在 Metasploit 源文件 `lib/rex/payloads/meterpreter/uri_checksum.rb` 中定义: | 常量 | 值 | 含义 | |---|---|---| | `URI_CHECKSUM_INITP` | 80 | Python stager 握手 | | `URI_CHECKSUM_INITJ` | 88 | Java stager 握手 | | `URI_CHECKSUM_INITW` | 92 | 32 位 Windows stager 握手 | | `URI_CHECKSUM_INITW_X64` | **139** | **64 位 Windows stager 握手** | 计算恢复的 URI 的校验和得到 `sum(ord(c) for c in "/DoSaKUGGHJcVXRRffO9-ggcg2uPT9Oxuy8xGfQfirY7yO23UxNc4jDSyqGoZ7c040azjJqAMGe4nUjWYYXyEajzPQIC5LT9OUMP4ysU35sPczVGyXNyMZra") % 256 = 139`。 与 139(`0x8B`)匹配是对该样本作为 64 位 Windows Metasploit stager 的**决定性**识别。校验和方案记录在 Metasploit 源代码中,随该框架的每个副本一起提供,并且不会在非 Metasploit 软件中偶然重现。从分析中的这一点开始,我们将归因视为既定,并使用 Metasploit 上游源代码作为权威参考来验证后续发现。 #### 使用 radare2 发现 Shellcode 入口点 确立了框架身份后,我们转向 shellcode 本身。 Metasploit x64 Windows shellcode 块都共享一个规范的入口序言,以字节序列 `fc 48 83 e4 f0` 开始 — 这解码为 `cld; and rsp, 0xfffffffffffffff0`,它清除方向标志并根据 x64 调用约定的要求将栈对齐到 16 字节边界。使用 radare2 在 `group2.exe` 中定位此序言。 ![r2 search /x fc4883 returning single hit at 0x140005000 followed by disassembly block_api prologue](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/c4827cd850032913.png) *图 19: radare2 识别 shellcode 入口点。* 命令 `/x fc4883` 搜索字节模式 `fc 48 83` 并恰好返回一个匹配项,位于虚拟地址 `0x140005000`。跳转到该地址并反汇编会显示完整的 Stephen Fewer `block_api` 解析器序言:`cld`(清除方向标志)、`and rsp, 0xfffffffffffffff0`(栈对齐)、`call sub.fcn.1400050d6`(转移到解析器主体),然后是特征寄存器保存压入序列 `push r9 / push r8 / push rdx / xor rdx, rdx / push rcx`。下一条指令 `mov rdx, gs:[rdx+0x60]` 是规范的 x64 PEB 访问:`gs:[0x60]` 是进程环境块指针的线程环境块偏移量。随后的指令(`mov rdx, [rdx+0x18]` 和 `mov rdx, [rdx+0x20]`)遍历 `PEB.Ldr` 和 `Ldr.InMemoryOrderModuleList`,遍历已加载模块的链接列表。radare2 将此函数入口标记为 `entry0`、`hit9_0`(来自我们的搜索)和 `rip` — 所有这些都位于 `0x140005000`,确认 PE 入口点*就是* shellcode 入口点,没有单独的 PE 包装器。节横幅确认该节是 `.glav` 并标记为 `-rwx`。 从 `0x140005000` 开始的指令序列不仅与 Metasploit 的 `block_api.asm` 相似;它与上游源 `metasploit-framework/external/source/shellcode/windows/x64/src/block/block_api.asm` 中的每个字节都完全相同。 序言的每个字节、每个寄存器选择、每个栈操作都完全对应。这相当于匹配指纹。 #### 枚举所有 API 调用 通过解析器进行的每个 API 调用都遵循相同的结构:shellcode 将目标函数名称的 32 位 ROR13 哈希加载到 `r10` 中,将其他参数放置在平台标准位置(`rcx`、`rdx`、`r8`、`r9` 和栈),然后 `call rbp` 调用解析器,解析器返回时已调用已解析的函数,并将其返回值放在 `rax` 中。解析器的地址在 shellcode 的生命周期中保存在 `rbp` 中。 为了枚举每个 API 调用,我们搜索字节模式 `ff d5` — `call rbp` 的 x64 编码 — 遍历整个二进制文件。 ![r2 session showing pd -3 at final call sites displaying Sleep, VirtualAlloc, InternetReadFile, ExitProcess hash constants and argument setup](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/64b6c829fa032914.png) *图 20: radare2 在 shellcode 中最后四个 `call rbp` 站点提取哈希常量和参数。* 在 `0x1400052a2`,前面的指令 `jne 0x1400052b0 / mov rcx, 0x1388 / movabs r10, 0xe035f044` 将调用识别为 `Sleep(5000)` — `0x1388` 是十进制 5000,确认了 5 秒睡眠。在 `0x1400052cc`,序列 `shl edx, 0x10 / mov r8, 0x1000 / movabs r10, 0xe553a458` 将调用识别为 `VirtualAlloc`。`shl edx, 0x10` 是从 `0x40`(同时也是 `PAGE_EXECUTE_READWRITE`)生成 `0x400000`(4 MiB)的紧凑方法;`r8 = 0x1000` 是 `MEM_COMMIT`。净调用是 `VirtualAlloc(NULL, 0x400000, MEM_COMMIT, PAGE_EXECUTE_READWRITE)`。在 `0x1400052ef`,`mov r8, 0x2000 / mov r9, rdi / movabs r10, 0xe2899612` 将调用识别为 `InternetReadFile`,块大小为 8 KB(`0x2000`)。在 `0x140005310`,`push 0 / pop rcx / mov r10, 0x56a2b5f0` 将最终调用识别为 `ExitProcess(0)`。 将此提取与 Ghidra 反汇编中可见的早期哈希(图 17 和 18)以及在其余调用站点的额外 radare2 搜索相结合,产生了完整的 API 调用清单: | # | 调用站点 | ROR13 哈希 | 解析的 API | 目的 | |---|---------------|--------------|----------------------|----------------------------------------| | 0 | `0x1400050f1` | `0x0726774C` | `LoadLibraryA` | 加载 `wininet.dll` | | 1 | `0x14000517f` | `0xA779563A` | `InternetOpenA` | 创建 WinINet 会话;设置 User-Agent | | 2 | `0x1400051ae` | `0xC69F8957` | `InternetConnectA` | TCP 连接到 `212.22.1.3` | | 3 | `0x14000524d` | `0x3B2E55EB` | `HttpOpenRequestA` | 准备带有内联 URI 的 HTTPS GET | | 4 | `0x140005272` | `0x869E4675` | `InternetSetOptionA` | 禁用证书验证 | | 5 | `0x14000528b` | `0x7B18062D` | `HttpSendRequestA` | 发送 HTTPS 请求 | | 6 | `0x1400052a2` | `0xE035F044` | `Sleep` | 失败发送重试之间 5000 毫秒 | | 7 | `0x1400052cc` | `0xE553A458` | `VirtualAlloc` | 分配 4 MiB RWX 第二阶段缓冲区 | | 8 | `0x1400052ef` | `0xE2899612` | `InternetReadFile` | 以 8 KB 块下载第二阶段 | | 9 | `0x140005310` | `0x56A2B5F0` | `ExitProcess` | 失败时退出代码 0 | 此表中的每个哈希都与 Metasploit 框架源中记录的相应 Windows API 的规范 Stephen Fewer ROR13 哈希匹配。 对应关系完整且精确:没有哈希是未解释的,没有哈希不匹配,也不存在额外的哈希。这与上游 `block_reverse_https.asm` 源的十比十匹配。 #### 解释 — 完整的运行时行为 结合 API 清单、参数值和 URI 校验和结果,可以在不执行 `group2.exe` 的情况下得出完整的行为描述: 1. 启动时,Windows 加载器将 PE 映射到内存中。入口点位于 `.glav` 节(`0x140005000`)的开头,该节标记为 RWX。控制权直接转移到那里。 2. 第一条指令(`cld`)清除方向标志;第二条指令(`and rsp, -16`)对齐栈。这些是任何 C 约定调用之前所需的内务操作。 3. `call` 进入 API 解析器存根,将 `rbp` 初始化为指向解析器。从此时起,每个 WinINet/Kernel32 调用都通过 `rbp` 使用在 `r10` 中传递的 32 位 ROR13 哈希进行调度。 4. `LoadLibraryA("wininet")` 将 HTTP 库加载到进程中。 5. `InternetOpenA` 打开一个 WinINet 会话,注册硬编码的 `Mozilla/5.0 ... Chrome/131.0.0.0` User-Agent。 6. `InternetConnectA` 使用服务类型 `INTERNET_SERVICE_HTTP` 连接到 C2 主机 `212.22.1.3`。端口从参数准备指令中的立即数 `0x01BB`(十进制 443)加载。 7. `HttpOpenRequestA` 使用内联 URI 作为对象名称准备 HTTP GET 请求。请求标志(`0x84A83200`)包括 `INTERNET_FLAG_SECURE`,将连接识别为 HTTPS。 8. 使用选项 31(`INTERNET_OPTION_SECURITY_FLAGS`)和值 `0x3380` 调用 `InternetSetOptionA`,禁用所有证书验证。这是必要的,因为 Metasploit C2 服务器的 TLS 证书是自签名的。 9. `HttpSendRequestA` 发送请求。失败时,调用 `Sleep(5000)` 并重试发送;十次失败后,控制权转移到 `ExitProcess(0)`。 10. 发送成功后,`VirtualAlloc` 分配一个 4 MiB RWX 缓冲区以保存下载的第二阶段有效载荷。 11. `InternetReadFile` 以 8 KB 块读取响应主体,直到响应耗尽,将每个块附加到 RWX 缓冲区。 12. 控制权通过 `pop rax; ret` 习惯用法转移到下载缓冲区的开头,执行第二阶段有效载荷 — 根据框架设计,这是 Meterpreter 反射 DLL。 此描述 — 重试策略、缓冲区大小、证书验证绕过、块大小、退出行为 — 与 Metasploit 发布的 `block_reverse_https.asm` 源文件逐行对应,确认了框架和特定的 stager 变体。 #### 与原始 msfvenom 输出的定量相似性 为了提供进一步的独立证据,并缩小用于生成样本的确切 msfvenom 构建选项,使用推断的参数生成了一个参考二进制文件:payload `windows/x64/meterpreter/reverse_https`,LHOST `212.22.1.3`,LPORT `443`,输出格式 `exe`。使用 ssdeep 模糊哈希算法将此参考文件与 `group2.exe` 进行比较,该算法基于上下文触发分块哈希产生 0 到 100 的相似性分数 — 一种对比较文件中的小编辑和插入具有鲁棒性的技术。 ![msfvenom generating reference.exe followed by ssdeep -d comparing group2.exe against reference.exe, returning similarity score 38](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/8c4cb2f32f032915.png) *图 21: 生成原始 Metasploit 参考二进制文件和 ssdeep 相似性比较。* 参考 `reference.exe` 为 7168 字节。`group2.exe` 和 `reference.exe` 之间的相似性得分为 38/100。并排检查两个 ssdeep 哈希显示两端都有公共子字符串 — 两个哈希都以 `eFGS` 开头,尾部都包含 `qqilk` — 表明两个二进制文件的大区域在等效偏移处结构等效。38/100 的分数量化了包括不同 PE 包装器在内的文件的整体相似性,哈希开头和结尾的较高局部相似性反映了共享的 PE 头约定和共享的 shellcode 结尾。 #### 高级静态分析发现摘要 `group2.exe` 是一个 Metasploit Framework `windows/x64/meterpreter/reverse_https` stager,使用 `EXITFUNC=process` 构建,配置为连接回 `https://212.22.1.3:443/DoSaKUGGHJcVXRRffO9-ggcg2uPT9Oxuy8xGfQfirY7yO23UxNc4jDSyqGoZ7c040azjJqAMGe4nUjWYYXyEajzPQIC5LT9OUMP4ysU35sPczVGyXNyMZra`。 stager 采用三种规避技术:Stephen Fewer 的 API 哈希解析器(用于从静态检查中隐藏其导入)、自签名 TLS 证书绕过(使其 C2 通道看起来像普通的 HTTPS)和 URI 校验和握手(使其初始 C2 请求对不知道校验和方案的防御者来说与随机 Web 流量无法区分)。 成功连接后,stager 下载 4 MiB 第二阶段有效载荷 — 根据框架约定,这是一个反射加载的 Meterpreter DLL — 并将控制权转移到它。归因得到 URI 校验和结果(139 = `URI_CHECKSUM_INITW_X64`)、shellcode 序言与 Metasploit 上游 `block_api.asm` 的逐字节完全匹配、所有 API ROR13 哈希与 Metasploit 规范哈希表的十比十匹配、匹配的标志常量和重策略以及与本地生成的原始参考相比的 ssdeep 相似性得分 38 的支持。 **高级静态分析中使用的工具:** Ghidra(反汇编和交叉引用导航)、radare2(`-AAA` 自动分析、`/x` 字节模式搜索、`pd` 反汇编、`s` 定位)、Python(URI 校验和计算)、`msfvenom`(参考二进制文件生成)、`ssdeep`(模糊哈希相似性比较)、Rapid7 Metasploit Framework 源代码(`external/source/shellcode/windows/x64/src/block/block_api.asm`、`external/source/shellcode/windows/x64/src/block/block_reverse_https.asm`、`lib/rex/payloads/meterpreter/uri_checksum.rb` — 用作 ROR13 哈希值、标志常量和 URI 校验和常量的权威参考)。 ## 动态分析 ### 基础动态分析 - 行为 - 网络流量 - 进程 ### 高级动态分析 - 调试 - 内存分析 - 持久化机制 ## 发现 - C2 通信(Telegram 等) - 数据渗漏 - 威胁指标 ## 结论 (恶意软件功能、风险级别、关键见解) ## 团队贡献 - 谁做了什么
标签:AI合规, APT, CMS检测, DAST, PDF 陷阱, Wayback Machine, Windows API, 二进制分析, 云安全监控, 云安全运维, 云资产清单, 反汇编, 命令与控制, 威胁情报, 实战演练, 开发者工具, 恶意代码, 恶意样本, 恶意软件分析, 控制流分析, 数字取证, 红队框架, 网络安全, 网络行为分析, 自动化脚本, 逆向工具, 逆向工程, 隐私保护, 静态分析, 高级持续威胁