复盘:一次"二进制文件提取失误"的经验总结

事件:通过RDP远程桌面从比赛Windows服务器提取恶意样本 systemutil.exe(809,472 字节)
失误:在命令行用记事本打开隐藏文件,然后另存为 .exe
后果:文件被"去 NUL"破坏,0x00 全变空格;表面上完好,实则已废;第三层载荷与 C2 永久丢失


0. 一句话版本(可直接转发)

别用记事本打开 exe 再"另存为"。 记事本会把文件当文本处理,而文本通道装不下 0x00,于是 NUL 被一对一写成空格——文件大小看着没变,但内容已经废了。
取文件只做两件事:命令行 copy/robocopy 复制 + 两端哈希对齐。非要过文本通道,就先 certutil -encode 成 Base64 再搬。


1. 事件经过

时间线 动作 评价
T0 文件在服务器上是隐藏属性 正常(可疑样本常隐藏)
T1 在命令行执行 notepad systemutil.exe 打开 ❌ 用文本编辑器打开二进制
T2 在记事本里"另存为 systemutil.exe" ❌ 走文本通道写回
T3 文件被拷到本地,扩展名 .exe 正常、大小 809,472 也"正常" ⚠️ 无人校验 → 隐患潜伏
T4 上机分析:e_lfanew 读出来是 0x20202080,PE 一堆"空格" 才发现文件是坏的

关键点:T2 和 T4 之间没有任何校验环节,所以这个错误安静地穿过了整条流程。


2. 根因分析

2.1 技术机理

  • 文本通道天生装不下 0x00:C 字符串以 NUL 作结束符;CF_TEXT / CF_UNICODETEXT 也是;记事本的编辑控件同理。
  • 这一层会把"不可打印字符"做净化,把 0x00 一对一替换成 0x20(空格)。
  • 因为是一对一替换,文件长度完全不变,所以肉眼、dir、资源管理器属性都看不出异常。

2.2 本次损坏的精确画像(已实测)

检查项 结果 结论
文件长度 809,472 → 809,472 没增没减 → 不是 UTF-8(会加 BOM)/ UTF-16(会翻倍)重编码
起始字节 仍是 4D 5A(MZ) 开头没坏、没加 BOM
除 NUL 外 逐字节原样 例:PNG 的 gAMA 值 0x0000B18F 里高位字节 B1 8F 原样保留,逆变换后 CRC 仍命中
被改的 0x00 → 0x20,共 39,095 处 0x0D/0x0A、0x01~0x1F(IL 操作码、元数据长度)全部正常

→ 说明那台机器的记事本是单字节编码(ANSI/CP1252 之类)往返,只坏了 NUL、没坏高位字节。

2.3 根本原因(不是技术,是流程)

  1. 把"取证/分析对象"当普通文档看:为了看内容而用编辑器打开样本;
  2. 对"复制成功"的盲目信任:没有哈希、没有自检;
  3. 没有明确的操作规范:隐藏文件该怎么取、只能过终端时该怎么办,没人定过。

3. 为什么这么难发现

四个"看起来正常"让人放松警惕:

  1. 扩展名还是 .exe,图标照旧;
  2. 大小一模一样(1:1 替换的欺骗性);
  3. 复制/另存过程没有任何报错,还弹了"保存成功";
  4. 文件甚至还能被某些工具打开、显示出"像字符串"的内容。

教训:没有校验,就等于没有提取。 文件"能打开/能复制"绝不等于"是完整的"。


4. 代价盘点

项目 情况
可救回 PE 头 / 段表 / CLI 头 / .NET 元数据表(受格式约束,可用结构约束反解)→ 整个加载器逻辑、IL 反汇编都还原了
不可逆 压缩/加密数据。本例承载第三层载荷的 PNG(DAKO,deflate 流)彻底损坏
直接损失 第三层加载器 + 最终 C2 拿不到,任务卡死
时间成本 为了绕开这个坑,额外花了两小时写逆变换 + 结构修复;而正确操作只需 copy 一下,5 秒
真实场景风险 若是应急响应/司法取证,这属于证据污染,可能让整条证据链失效

5. 六条硬规则(贴在显示器上那种)

  1. 二进制永远不走文本通道:犯禁名单 —— notepad 另存为、type x > y、cat/tee、Get-Content|Set-Content、FTP ASCII 模式、串口/终端 canonical 模式、把内容复制进聊天框/表单。
  2. 取完必须做两件事:0x00 计数 > 0、两端哈希一致(certutil -hashfile / sha256sum)。
  3. 隐藏属性不影响复制:copy / robocopy 照样能取,完全不需要"用编辑器打开把内容拿出来"。
  4. 只读原则:任何时候都不要在原文件上"另存为"——可能直接覆盖掉唯一原件,且不可逆。
  5. 只能过文本通道时:先 certutil -encode(或 base64 -w0 / xxd -p)编码再搬,大文件分块。
  6. 留痕:把取文件的命令、时间、两端哈希记下来(可复现、可追溯、可甩锅定位)。

6. 标准作业流程 SOP(Windows 远程主机 + 隐藏文件)

:: ① 先确认服务器上原件还在不在("另存为"通常新建文件,原件很可能没被覆盖)
dir /a "C:\path\systemutil.exe"
certutil -hashfile "C:\path\systemutil.exe" SHA256

:: ② 正确复制(命令行复制是二进制安全的;隐藏属性不拦复制)
copy /b "C:\path\systemutil.exe" "D:\out\systemutil.exe"
robocopy "C:\path" "D:\out" "systemutil.exe" /COPY:DAT /R:2 /W:1
attrib -h -s "C:\path\systemutil.exe"        :: 想去掉隐藏再去,非必须

:: ③ 只给终端/剪贴板时:先编码,只搬纯文本
certutil -encode "C:\path\systemutil.exe" "C:\tmp\x.b64"
::   —— 记事本此刻只当"文本搬运工",开 x.b64 复制是安全的
::   本地: certutil -decode x.b64 systemutil.exe

:: ④ 两端哈希对齐 + 本地自检
certutil -hashfile "D:\out\systemutil.exe" SHA256
python mangled_repair.py systemutil.exe --check

7. 判据速查表(出问题 3 秒定位)

现象 含义 处置
0x00 计数 = 0 一定被"去 NUL"(正常 PE 含大量 0x00) 判定为提取事故,重取
e_lfanew = 0x20202080 这类大值 PE 头失效 同上
大小和原件一致但内容不对 1:1 替换(NUL→空格) 同上;注意这是最隐蔽的一种
大小变小 NUL 被丢弃(bash x=$(cat f)、文本模式截断) 同上
大小变大/带 EF BB BF 被当 UTF-8/UTF-16 重编码 同上
本地哈希 ≠ 服务器哈希 传输/导出环节出问题 重取,别修

自检命令:python mangled_repair.py <file> --check
(会输出 0x00/0x20/0x0D 0x0A 计数、e_lfanew、常见成因与对策)


8. 给自己的提醒(可直接转发)

取样本别用记事本开!exe 是二进制,记事本"另存为"会把里面所有 0x00 写成空格,大小不变但文件已经废了(我们已经因此丢掉了第三层载荷和 C2)。
以后取文件就两条:
1)命令行 copy /b 或 robocopy 复制(隐藏属性不影响复制);
2)传完两端 certutil -hashfile xx.exe SHA256 对一下,一样才叫取到了。
只能走聊天/终端时,先在服务器上 certutil -encode 成 base64 再传,本地 certutil -decode 还原。


9. 本次的补救路径(存档)

  1. mangled_repair.py:逆变换 0x20→0x00 + 用 BSJB 锚点与 CLI 头反解被 0x20 破坏的字段
    (SectionAlignment / BaseOfCode / .text VA / CLI 目录 RVA / MetaData RVA),输出与手工修复逐字节一致;
  2. 从修复后的 PE 里还原出第二层 DLL OptiMax.dll(藏在 SR1 位图像素里),加载链完整;
  3. 但第三层(藏在 DAKO PNG 里)不可逆 → 必须拿到服务器上的干净原件才能继续。
  4. 结论:先 dir /a 看原件还在不在——大概率还在,重取一次即可。
posted @ 2026-09-24 15:15  夏了茶糜  阅读(6)  评论(0)    收藏  举报