binref/refinery
GitHub: binref/refinery
一款基于管道理念的 Python 命令行工具集,旨在提供类似 CyberChef 的二进制数据处理能力,专注于恶意软件快速分类与逆向分析。
Stars: 822 | Forks: 78
# Binary Refinery
[][docs]
[][tests]
[][codecov]
[][pypi]
```
__ __ High Octane Triage Analysis __
|| _||______ __ __________ _____ ||
|| \||___ \__| ____/ ______/___ / ____\ ||
==||=====|| | __/ |/ \ /==| / __ \ __\===]|
'======|| | \ | | \_ _| \ ___/| | ||
||____ /__|___|__/ / | \____]| | ||
=========''====\/=========/ /==|__|=====|__|======'
\ /
\/
```
Binary Refinery™ 是一系列 Python 脚本的集合,实现了对二进制数据的各种转换,例如压缩和解密。
我们通常简称为 _refinery_,这也是对应包的名称。
这些脚本被设计为仅从 stdin 读取输入,并将输出写入 stdout。
其主要理念是每个脚本都应该是一个单元,即只完成 _一_ 项工作,
而各个单元可以通过命令行中的管道操作符 `|` 组合成 _管线 (pipelines)_,以执行更复杂的任务。
该项目的主要重点是恶意软件分类 (malware triage),
旨在尝试在命令行中实现类似 [CyberChef](https://github.com/gchq/CyberChef) 的功能。
## 简短版本
创建一个 Python 虚拟环境。你需要 Python 3.8 或更高版本。像这样安装 refinery:
```
python -m pip install -U pip
python -m pip install -U binary-refinery[extended]
```
使用 `-h` 运行单元以了解其工作原理,通过 [docs][] 搜索或在命令中使用 `binref` 命令来查找它们。
如果你想看它实际运行的效果,请观看 [最近的视频][VOD3]。
此外,也请阅读本自述文件的其余部分。
## 发布计划
没有固定的发布计划,但发布非常频繁,建议定期更新。
除了 GIT 之外,错误修复通常不做记录,但所有其他更改(即新功能)都记录在 [变更日志](CHANGELOG.md) 中。
在 [Mastodon][] 上关注我,以获取关于特别重要发布的更新。
## 文档
使用 `-h` 或 `--help` 开关执行单元时显示的帮助文本是其主要文档。
[自动生成的文档][docs] 包含了顶层每个单元的输出汇总,
但也包含了该工具包三个基本概念的规范:
[帧][frame]、[多二进制参数][argformats] 和 [元变量][meta]。
通过提供的 `binref` 命令,也可以在命令行上对所有单元的描述和帮助文本进行全文搜索。
鉴于参考文档可能有些枯燥,我们正在努力制作一系列 [教程](tutorials);我非常建议你去看看。
此外,我在下面收集了额外的资源(包括一些由第三方制作的)。
- [`2021/08`] [OALabs][OA] 好心地让我 [在一个专题视频中演示该工具包][VOD1]。
在视频中,我基本上完成了
[第一个教程](tutorials/tbr-files.v0x01.netwalker.dropper.ipynb) 的内容。
- [`2021/11`] [Johannes Bader][JB] 写了一篇关于使用 binary refinery 分析恶意垃圾邮件的精彩 [博客文章][BLOG]。
- [`2024/03`] [Malware Analysis For Hedgehogs][MH] 制作了一个关于使用 refinery [解包 XWorm 样本的视频][VOD2]。
- [`2024/11`] [the CyberYeti][CY] 邀请我 [在直播中介绍 refinery][VOD3]。
- [`2025/06`] 我再次与 [the CyberYeti][CY] 进行了 [直播][VOD4],这次的内容更原生态一些。
你在这里看到的所有 bug 都已修复。 😉
演示再次包括了下面示例部分和 [教程](tutorials) 中的样本。
## 许可证
Binary Refinery 版权所有 (c) 2019 Jesko Hüttenhain,并在 [3-Clause BSD 许可证][license] 下发布。
本仓库还包含 [完整许可证文本的副本](LICENSE.md)。
如果你想做一些本许可证未涵盖的事情,请随时联系作者。
## 警告与建议
refinery 至少需要 **Python 3.8**。
建议将其安装到单独的 [虚拟环境][venv] 中:
该包可能会引入 **大量** 依赖项,
将其安装到全局 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 上将 refinery 安装到当前 Python 环境中,前缀为 `r.`:
```
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][remnux-main],你可以使用他们的 [refinery docker 容器][remnux]。
## Shell 支持
以下是当前各种 shell 环境支持程度的摘要:
| Shell | 平台 | 状态 | 注释 |
|:-----------|:--------|:---------------|:--------------------------------------------------------------|
| Bash | Posix | 🔵 良好 | 作者偶尔使用。 |
| CMD | Windows | 🔵 良好 | 作者广泛使用。 |
| PowerShell | Windows | 🟡 合理 | [只要 PowerShell 版本至少为 7.4 就能正常工作][psh1] |
| Zsh | Posix | 🟠 小问题 | 经过一次 [讨论][zsh1],有一个 [修复方案][zsh2]。 |
| Fish | Posix | 🟠 小问题 | 参见 issue [#55][fsh1] 和讨论 [#22][fsh2]。 |
如果你使用的是其他 shell 并有一些反馈要分享,请 [告诉我](https://github.com/binref/refinery/discussions)!
## 重量级依赖
有些单元具有相当重量级的依赖项。
例如,[pcap][] 是唯一需要数据包捕获文件解析库的单元。
默认情况下不安装这些库,以使首次用户的 refinery 安装时间保持在合理水平。
当缺少依赖项时,相应的单元会告诉你该怎么做:
```
$ emit archive.7z | xt7z -l
(13:37:00) failure in xt7z: dependency py7zr is missing; run pip install py7zr
```
然后你可以手动安装这些缺少的依赖项。
如果你不想被缺少的依赖项困扰,并且不介意 refinery 安装时间较长,你可以按如下方式安装该包:
```
pip install -U binary-refinery[all]
```
这将在必需依赖项之上安装 _所有_ 依赖项。
更具体地说,有以下额外的类别可用:
| 名称 | 包含的依赖项 |
|-----------:|:--------------------------------------------------------|
| `all` | 所有 refinery 单元的所有依赖项 |
| `arc` | 所有归档相关的依赖项(即 7zip 支持) |
| `default` | 推荐的合理依赖项选择,作者的选择 |
| `display` | 诸如 `colorama`、`Pygments` 和 `jsbeautifier` 之类的包 |
| `extended` | 扩展选择,仅排除最重量级的依赖项 |
| `formats` | 与解析各种文件格式相关的所有依赖项 |
| `office` | `formats` 的子集;所有与 office 相关的解析依赖项 |
| `python` | 与 Python 反编译相关的包 |
你可以指定这些类别的任意组合来安装,以便在依赖项和功能之间进行一些权衡控制。
## 前沿版本
或者,你可以克隆此仓库并使用脚本 [update.sh](update.sh)(在 Linux 上)或 [update.ps1](update.ps1)(在 Windows 上)将 refinery 包安装到本地虚拟环境中。
此方法的安装和更新过程只需运行脚本:
- 它会拉取仓库,
- 激活虚拟环境,
- 卸载 `binary-refinery`,
- 然后安装 `binary-refinery[all]`。
## 生成文档
你也可以在本地生成所有文档。
为此,请执行 [run-pdoc3.py](run-pdoc3.py) 脚本。
除非你从已将 binary refinery 作为 Python 包安装的环境中运行它,否则这将 **失败**。
要运行它,你必须将虚拟环境的路径指定为 [run-pdoc3.py](run-pdoc3.py) 的第一个命令行参数,
这将导致脚本使用该环境的解释器再次运行自身。
如果你确定要运行 [run-pdoc3.py](run-pdoc3.py),
有一个命令行开关可以强制脚本使用当前的默认 Python 解释器运行。
该脚本安装 [pdoc3 包][pdoc3] 并使用它为 `refinery` 包生成 HTML 文档。
然后可以在本自述文件旁边的 `html` 子目录中找到该文档。
[教程](tutorials) 是 Jupyter notebooks,如果你的虚拟环境 [安装了 Jupyter][jupyter],你可以直接运行和执行它们。
值得一提的是,[Visual Studio Code 对 Jupyter 提供了非常舒适的支持][jupyter-vscode]。
## 示例
### 基本示例
单元 [emit][] 和 [dump][] 扮演着特殊的角色:
前者用于输出数据,而后者用于将数据转储到剪贴板或磁盘。
举个例子,考虑以下管线:
```
emit M7EwMzVzBkI3IwNTczM3cyMg2wQA | b64 | zl | hex
```
在这里,我们输出字符串 `M7EwMzVzBkI3IwNTczM3cyMg2wQA`,
使用 [b64][] 对其进行 base64 解码,
使用 [zl][] 对结果进行 zlib 解压缩,
最后对解压缩后的数据进行 [hex][] 解码。
默认情况下,每个单元执行特定转换的 _"解码"_ 操作,但其中一些也实现了反向操作。
如果它们支持,这总是通过提供命令行开关 `-R` 或 `--reverse` 来完成的。
你可以使用以下命令生成上述 base64 字符串,因为 [hex][]、[zl][] 和 [b64][] 都提供了反向操作:
```
emit "Hello World" | hex -R | zl -R | b64 -R
```
给定一个包含 base64 编码 payload 缓冲区的文件 `packed.bin`,以下管线将该 payload 提取到 `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 文件 payload:
```
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 -Q ]] \
| rex -M "((??email))\n(.*)\n(.*)\n:Zone" addr={1} pass={2} host={3}
```
从恶意的 XLS 宏释放器中提取 PowerShell payload:
```
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.ps1
```
并获取下一阶段的域名:
```
emit payload.ps1
| carve -sd b64 | zl | deob-ps1
| carve -sd b64 | zl | deob-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 加载器中提取 payload 并切出其 c2:
```
emit 58ba30052d249805caae0107a0e2a5a3cb85f3000ba5479fafb7767e2a5a78f3 \
| rex yara:50607080.* [| struct LL{s:L}{} | xor -B2 accu[s]:@msvc | xtp url ]
```
从一个被遗忘的时代的恶意 VBA 宏(当时就是那样做的):
```
emit ee103f8d64cd8fa884ff6a041db2f7aa403c502f54e26337c606044c2f205394 \
| xtvba
```
然后提取恶意的下载器 payload:
```
emit ee103f8d64cd8fa884ff6a041db2f7aa403c502f54e26337c606044c2f205394 \
| doctxt | repl drp:c: | carve -s b64 | rev | b64 | rev | ppjscript
```
从恶意 PDF 文档中提取 payload URL:
```
emit 066aec7b106f669e587b10b3e3c6745f11f1c116f7728002f30c072bd42d6253 \
| xt JS | csd string | csd string | url | xtp url [| urlfix ]]
```
从公式编辑器漏洞利用文档中提取 payload URL:
```
emit e850f3849ea82980cf23844ad3caadf73856b2d5b0c4179847d82ce4016e80ee \
| officecrypt | xt oleObject | xt native | rex Y:E9[] | vstack -a=x32 -w=200 | xtp
```
### AES 加密
假设 `data` 是一个使用 CBC 模式下的 256 位 AES 加密的文件。
密钥是使用 PBKDF2 密钥派生例程从秘密口令 `swordfish` 派生的,使用的盐是 `s4lty`。
IV 作为前16 个字节前缀到缓冲区。
可以使用以下管线对其进行解密:
```
emit data | aes --mode cbc --iv cut::16 pbkdf2[32,s4lty]:swordfish
```
这里,`cut:0:16` 和 `pbkdf2[32,s4lty]:swordfish` 都是使用特殊处理程序的多二进制参数 (multibin arguments)。
在这种情况下,`cut:0:16` 从输入数据中提取切片 `0:16`(即前 16 个字节)——在应用此多二进制处理程序之后,
输入数据的前 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
```
标签:AMSI绕过, Binary Refinery, CTF 工具, CyberChef 替代品, DAST, DNS 反向解析, IP 地址批量处理, Python, Shell 脚本, 二进制分析, 二进制提炼厂, 二进制数据处理, 云安全运维, 云资产清单, 反混淆, 威胁检测, 威胁狩猎, 恶意软件分析, 数字取证, 数据解码, 数据转换, 文档结构分析, 无后门, 管道操作, 网络信息收集, 自动化脚本, 解密, 逆向工具, 逆向工程