__ __ High Octane Triage Analysis __
|| _||______ __ __________ _____ ||
|| \||___ \__| ____/ ______/___ / ____\ ||
==||=====|| | __/ |/ \ /==| / __ \ __\===]|
'======|| | \ | | \_ _| \ ___/| | ||
||____ /__|___|__/ / | \____]| | ||
=========''====\/=========/ /==|__|=====|__|======'
\ /
\/
Binary Refinery™ 是一个 Python 脚本集合,用于实现压缩和加密等二进制数据转换。我们通常将其简称为 refinery,这也是对应软件包的名称。这些脚本被设计为仅从标准输入读取数据,并将输出写入标准输出。主要理念是每个脚本都应是一个单元,即只做_一件_工作,并且可以通过命令行上的管道操作符 | 将各个单元组合成_管道_来执行更复杂的任务。该项目的主要关注点是恶意软件分流,并试图在命令行上实现类似于 CyberChef 的功能。
创建一个 Python 虚拟环境。你需要 Python 3.10 或更高版本。如下安装 refinery:
python -m pip install -U pip
python -m pip install -U binary-refinery[extended]
使用 -h 参数运行单元以了解其用法,通过 docs 搜索,或使用 binref 命令来查找它们。如果你想看实际效果,可以观看最新视频。不过,也请阅读本 README 的其余部分。
没有固定的发布计划,但发布非常频繁,建议定期更新。GIT 之外不记录错误修复,但所有其他更改(即新功能)都会记录在更新日志中。在 Mastodon 上关注我,获取重大版本更新的通知。
当使用 -h 或 --help 开关执行单元时显示的帮助文本就是其主要文档。自动生成的文档 在顶层汇集了每个单元的输出,但也包含该工具包三个基本概念的规范:分帧、multibin 参数 和 元变量。通过提供的 binref 命令,还可以在命令行上对每个单元的描述和帮助文本进行全文搜索。考虑到参考文档可能有些枯燥,我们正在持续制作一系列教程;我非常推荐你去看看。除此之外,我在下面收集了一些额外的资源(包括第三方制作的资源)。
[!NOTE]
Refinery 仍处于 alpha 阶段,界面有时可能会发生变化, 即单元和参数可能被删除或重命名。 因此,旧视频和博文中的某些特定命令行可能不再有效。
2021/08] OALabs 很客气地让我在专题视频中演示该工具包。在视频中,我基本上完成了第一个教程的内容。2021/11] Johannes Bader 写了一篇精彩的博文,介绍如何使用 Binary Refinery 分析恶意垃圾邮件。2024/03] Malware Analysis For Hedgehogs 制作了一个使用 refinery 解包 XWorm 样本的视频。2024/11] the CyberYeti 邀请我在直播中演示 refinery。2025/06] 我再次与 the CyberYeti 一起直播,这次更偏向即兴。你在其中看到的所有 bug 均已修复。😉演示内容同样包括下方示例部分中的样本以及教程。
Binary Refinery 版权所有 (c) 2019 Jesko Hüttenhain,并根据 3-Clause BSD License 发布。本仓库还包含完整的许可证文本副本。如果你想将其用于本许可证未涵盖的用途,请随时联系作者。
refinery 至少需要 Python 3.10。建议将其安装到独立的虚拟环境中:该包可能会引入大量依赖,安装到全局 Python 环境比较容易出现版本冲突。此外,由于该工具包引入了大量新命令,某些命令在部分系统上有很大概率会发生冲突,而将它们放在独立的虚拟环境中是避免这种问题的一种方法。
如果你希望所有 refinery 命令随时在 shell 中可用(即无需切换到自定义虚拟环境),你还可以选择为安装设置一个_前缀_,该前缀将放在每个已安装命令包装脚本的前面。例如,如果你选择 r. 作为前缀,则 emit 单元将安装为命令 r.emit。额外的好处是,你可以输入 r. 并快速按两次 Tab,即可获得所有可用 refinery 命令的列表。但请注意,文档中不假设任何前缀,并且 refinery 的一个开发目标就是在大多数系统上_不_发生冲突。作者本人不使用前缀,提供此选项只是为了以防万一。
安装和更新 refinery 最直接的方式是通过 pip。请先确保你运行的是最新版本:
python -m pip install -U pip
然后只需安装 refinery 包:
pip install -U binary-refinery
如果你想为所有单元选择前缀,可以通过环境变量 REFINERY_PREFIX 来指定。例如,以下命令将在 Linux 上以 r. 前缀将 refinery 安装到当前 Python 环境中:
REFINERY_PREFIX=r. pip install -U binary-refinery
在 Windows 上,你需要运行以下命令:
set REFINERY_PREFIX=r.
pip install -U binary-refinery
指定特殊前缀 ! 将导致不创建任何 shell 命令,binary refinery 将仅作为库安装。如果你想安装当前 refinery 的 HEAD,可以重复上述所有步骤,并将此仓库指定为安装源而不是 pip 包。例如,以下命令将安装 refinery 最新的提交:
pip install -U git+git://github.com/binref/refinery.git
最后,如果你使用 REMnux,可以使用他们的 refinery docker 容器。
如果你想让你本地的恶意软件分析 claude 学会使用 binary refinery,请查看 Binary Refinery 技能。
以下是对当前各种 shell 环境支持程度的总结:
如果你使用的是其他 shell 并有反馈要分享,请告诉我!
有一些非常特定场景的单元带有(有时很大的)外部依赖。例如,stego 是一个需要图像解析库 Pillow 的单元。为了将首次用户的 refinery 安装时间保持在合理水平,某些库默认不安装。当依赖缺失时,对应单元会告诉你该怎么做:
$ emit config.png | stego RG
(13:37:00) failure in stego: dependency Pillow is missing; run pip install Pillow
然后你可以手动安装这些缺失的依赖。如果你不想被缺失依赖困扰,也不介意 refinery 安装时间较长,可以按如下方式安装该包:
pip install -U binary-refinery[all]
这将在必需依赖的基础上安装_所有_依赖。更准确地说,有以下额外类别可用:
| 名称 | 包含的依赖 |
|---|---|
default | 合理依赖的推荐选择,作者的选择 |
extended | 扩展选择,仅排除最冷门的依赖 |
all | 所有 refinery 单元的全部依赖 |
这些类别按升序列出,即 extended 会安装 default 会安装的所有内容。
或者,你可以克隆此仓库,并使用 update.sh(Linux)或 update.ps1(Windows)脚本将 refinery 包安装到本地虚拟环境中。此方法的安装和更新过程只需运行脚本即可:
binary-refinery,binary-refinery[all]。你也可以在本地生成所有文档。为此,请执行 run-pdoc3.py 脚本。除非你从已安装 binary refinery 作为 Python 包的环境中运行它,否则该脚本会失败。要运行它,你必须将虚拟环境的路径作为第一个命令行参数传递给 run-pdoc3.py,这将使脚本使用该环境的解释器重新运行自身。如果你确定要运行 run-pdoc3.py,还有一个命令行开关可以强制脚本使用当前默认的 Python 解释器运行。该脚本会安装 pdoc3 包,并使用它为 refinery 包生成 HTML 文档。生成的文档位于本 README 文件旁边的 html 子目录中。
教程是 Jupyter 笔记本,如果你的虚拟环境已安装 Jupyter,你可以直接运行和执行它们。值得指出的是,Visual Studio Code 对 Jupyter 提供了非常舒适的支持。
emit 和 dump 这两个单元扮演着特殊角色:前者用于输出数据,后者用于将数据转储到剪贴板或磁盘。例如,考虑以下管道:
emit M7EwMzVzBkI3IwNTczM3cyMg2wQA | b64 | zl | hex
这里,我们输出字符串 M7EwMzVzBkI3IwNTczM3cyMg2wQA,使用 b64 对其进行 base64 解码,使用 zl 对结果进行 zlib 解压缩,最后使用 hex 对解压后的数据进行十六进制解码。每个单元默认执行某种变换的_“解码”_操作,但其中一些也实现了反向操作。如果实现,通常是通过提供命令行开关 -R 或 --reverse 来完成的。由于 hex、zl 和 b64 都提供反向操作,你可以使用以下命令生成上述 base64 字符串:
emit "Hello World" | hex -R | zl -R | b64 -R
给定一个包含 base64 编码负载缓冲区的文件 packed.bin,以下管道将该负载提取到 payload.bin:
emit packed.bin | carve -l -t1 b64 | b64 | dump payload.bin
carve 单元可用于从输入缓冲区中雕刻出数据块,在本例中,它会查找 base64 编码的数据,按长度排序(-l),并返回第一个(-t1),从而从 packed.bin 中切出最大的、看起来像 base64 的数据块。然后对该数据进行 base64 解码,并转储到文件 payload.bin。
pack 单元会从文本缓冲区中提取所有数值表达式,并将它们转换为二进制表示。一个简单的例子是管道
emit "0xBA 0xAD 0xC0 0xFF 0xEE" | pack | hex -R
它将输出字符串 BAADC0FFEE。
从 BLOB 中提取最大的 base64 编码数据块并解码:
emit file.exe | carve -ds b64
从缓冲区中切出一个 ZIP 文件,从中选取一个 DLL,并显示其信息:
emit file.bin | carve-zip | xtzip file.dll | pemeta
列出 PE 文件节及其对应的 SHA-256 哈希:
emit file.exe | vsect [| sha256 -t | pf {} {path} ]]
递归列出当前目录中的所有文件及其各自的 SHA-256 哈希:
ef "**" [| sha256 -t | pf {} {path} ]]
从当前目录内递归枚举的所有文件中提取指标:
ef "**" [| xtp -n6 ipv4 socket url email | dedup ]]
将以网络字节序硬编码的 IP 地址 0xC0A80C2A 转换为可读格式:
emit 0xC0A80C2A | pack -EB4 | pack -R [| sep . ]
执行单字节 XOR 暴力破解,并在每次迭代中尝试提取 PE 文件负载:
emit file.bin | rep 0x100 [| xor v:index | carve-pe -R | peek | dump {name} ]
提取 RemCos C2 服务器:
emit c0019718c4d4538452affb97c70d16b7af3e4816d059010c277c4e579075c944 \
| perc SETTINGS [| put keylen cut::1 | rc4 cut::keylen | xtp socket ]
提取 AgentTesla 配置:
emit fb47a566911905d37bdb464a08ca66b9078f18f10411ce019e9d5ab747571b40 \
| dnfields [| aes x::32 --iv x::16 -T ]] \
| rex -M "((??email))\n(.*)\n(.*)\n:Zone" addr={1} pass={2} host={3}
从恶意的 XLS 宏投放器中提取 PowerShell 负载:
emit 81a1fca7a1fb97fe021a1f2cf0bf9011dd2e72a5864aad674f8fea4ef009417b [ \
| xlxtr 9.5:11.5 15.15 12.5:14.5 [ \
| scope -n 3 | chop -t 5 [| sorted -a | snip 2: | sep ] \
| pack 10 | alu --dec -sN B-S ]] \
| dump payload.cmd
并获取下一阶段的域名:
emit payload.cmd | cmdarg | ps1 | xtp -f domain
提取解包后的 HawkEye 样本的配置:
emit ee790d6f09c2292d457cbe92729937e06b3e21eb6b212bf2e32386ba7c2ff22c \
| put cfg perc[RCDATA]:c:: [\
| xtp guid | pbkdf2 48 rep[8]:h:00 | cca eat:cfg | aes -Q x::32 --iv x::16 ] \
| dnds
Warzone RAT:
emit 4537fab9de768a668ab4e72ae2cce3169b7af2dd36a1723ddab09c04d31d61a5 \
| vsect .bss | struct I{key:{}}{} [\
| rc4 eat:key | struct I{host:{}}{port:H} {host:u16}:{port} ]
从 shellcode 加载器中提取负载并切出其 c2:
emit 58ba30052d249805caae0107a0e2a5a3cb85f3000ba5479fafb7767e2a5a78f3 \
| rex yara:50607080.* [| struct LL{s:L}{} | xor -B2 rand[s]:msvc | xtp url ]
获取来自已被遗忘的时代、当时就是如此操作的恶意 VBA 宏:
emit ee103f8d64cd8fa884ff6a041db2f7aa403c502f54e26337c606044c2f205394 \
| vbamc
然后提取恶意下载器负载:
emit ee103f8d64cd8fa884ff6a041db2f7aa403c502f54e26337c606044c2f205394 \
| doctxt | repl drp:c: | carve -s b64 | rev | b64 | rev | ppjscript
从恶意 PDF 文档中提取负载 URL:
emit 066aec7b106f669e587b10b3e3c6745f11f1c116f7728002f30c072bd42d6253 \
| xt JS | csd string | csd string | url | xtp url [| urlfix ]]
从公式编辑器漏洞利用文档中提取负载 URL:
emit e850f3849ea82980cf23844ad3caadf73856b2d5b0c4179847d82ce4016e80ee \
| officecrypt | xt oleObject | xt native | rex Y:E9[] | vstack -a=x32 -w=200 | xtp
假设 data 是一个使用 256 位 AES 在 CBC 模式下加密的文件。密钥是从秘密口令 swordfish 使用 PBKDF2 密钥派生例程并以盐 s4lty 派生得到的。IV 作为前 16 个字节前缀在缓冲区中。可以使用以下管道解密:
emit data | aes --mode cbc --iv cut::16 pbkdf2[32,s4lty]:swordfish
这里,cut:0:16 和 pbkdf2[32,s4lty]:swordfish 都是使用特殊处理器的 multibin 参数。在本例中,cut:0:16 从输入数据中提取切片 0:16(即前 16 个字节)——应用该 multibin 处理器后,输入数据的前 16 个字节被移除,而参数 iv 被设置为这 16 个字节。最后一个参数指定 32 字节的加密密钥:另一方面,处理器 pbkdf2[32,s4lty] 指示 refinery 创建一个 pbkdf2 单元实例,就像给它按顺序传递了命令行参数 32 和 s4lty,并用该单元处理字节串 swordfish 一样。作为简单测试,以下管道将加密并解密一段示例文本:
emit "Once upon a time, at the foot of a great mountain ..." ^
| aes pbkdf2[32,s4lty]:swordfish --iv md5:X -R | ccp md5:X ^
| aes pbkdf2[32,s4lty]:swordfish --iv cut:0:16
| Shell | 平台 | 状态 | 备注 |
|---|
| Bash | Posix | 🔵 良好 | 作者偶尔使用。 |
| CMD | Windows | 🔵 良好 | 作者广泛使用。 |
| PowerShell | Windows | 🟡 尚可 | 只要 PowerShell 版本至少为 7.4,就能正常工作。 |
| Zsh | Posix | 🟠 小问题 | 在讨论之后,有一个修复方案。 |
| Fish | Posix | 🟠 小问题 | 参见 issue #55 和讨论 #22。 |