emproof-com/nyxstone

GitHub: emproof-com/nyxstone

Nyxstone 是一个基于 LLVM 的高效汇编/反汇编库,通过 C++、Rust 和 Python 绑定为多种 CPU 架构提供指令级编码与解码能力。

Stars: 409 | Forks: 22

# Nyxstone [![Github Cpp CI 徽章](https://static.pigsec.cn/wp-content/uploads/repos/cas/72/72695ebfb71d8b238d29dfd5d147e01231d4f78011bae271e5265621428bf21a.svg)](https://github.com/emproof-com/nyxstone/actions/workflows/cpp.yml) [![crates.io](https://img.shields.io/crates/v/nyxstone.svg)](https://crates.io/crates/nyxstone) [![PyPI](https://img.shields.io/pypi/v/nyxstone.svg)](https://pypi.org/project/nyxstone) [![cpp-docs](https://static.pigsec.cn/wp-content/uploads/repos/cas/52/52b2c1bb5c7227957ea33903dea2c2525b81ba077e19b936f9e228960fd21aee.svg)](https://emproof-com.github.io/nyxstone/) Nyxstone 是一个基于 LLVM 构建的快速汇编和反汇编库。它不需要对 LLVM 源码树打补丁,并且可以链接到大多数 Linux 发行版、Homebrew 和 `apt.llvm.org` 提供的标准 LLVM 库。其核心是一个 C++ 库,并提供了 Rust 和 Python 的绑定。Nyxstone 支持所链接的 LLVM 附带的所有架构,并允许你配置特定于架构的 CPU 和功能设置。 ![Nyxstone Python 绑定演示](/images/demo.svg) ## 目录 1. [核心功能](#core-features) 2. [使用 Nyxstone](#using-nyxstone) 1. [前置条件](#prerequisites) 2. [CLI 工具](#cli-tool) 3. [C++ 库](#c-library) 4. [Rust 绑定](#rust-bindings) 5. [Python 绑定](#python-bindings) 3. [工作原理](#how-it-works) 4. [基准测试](#benchmarks) 5. [路线图](#roadmap) 6. [许可证](#license) 7. [贡献](#contributing) 8. [维护者](#maintainers) ## 核心功能 * 为所链接 LLVM 支持的每种架构汇编和反汇编代码,包括 x86、ARM、AArch64、MIPS、RISC-V 等。 * 基于 LLVM 构建的 C++ 库,并提供 Rust 和 Python 绑定。 * 原生支持 Linux 和 macOS 平台。 * 支持汇编器中的标签,包括用户提供的标签到地址的映射。 * 输出原始字节、文本反汇编,或者同时包含地址、原始字节和汇编文本的详细指令对象。 * 反汇编可以限制为用户指定的指令数量。 * 可配置的各架构目标设置(CPU、ISA 扩展、硬件功能)。 * 汇编常见的数据指令(`.byte`、`.word`、`.org`、`.nops`、`.align`、`.fill`、`.uleb128`、…)以及 ARM/AArch64 的 `ldr rX, =const` 字面量池。 * 对于无法表示的输入(例如,切换到 `.text` 以外的 section),会报告明确的错误,而不是静默丢弃受影响的字节。 有关支持的架构列表,请运行 `clang -print-targets`。有关各架构的特性,请运行 `llc -march=ARCH -mattr=help`。 ## 使用 Nyxstone 本部分提供了如何开始使用 Nyxstone 的说明,涵盖必要的前置条件、如何使用 CLI 工具,以及通过 C++、Rust 和 Python 使用该库的分步指南。 ### 前置条件 在构建 Nyxstone 之前,请确保你的系统上安装了 clang 和 LLVM。**Nyxstone 支持 LLVM 的 15-20 主版本。** 这些主版本内的任何次要/修补版本都可以使用;构建过程会选择它能找到的最新版 LLVM,除非你指定了特定版本。 构建过程按以下顺序解析 LLVM: 1. `$NYXSTONE_LLVM_PREFIX`(如果已设置)。构建过程会专门搜索该前缀(忽略系统路径),因此当安装了多个版本时,这是固定使用特定版本的方法。 2. 按照从新到旧的顺序探测已知的各主版本安装布局:`/usr/lib/llvm-`(Debian/Ubuntu)、`/opt/homebrew/opt/llvm@`(Apple Silicon 上的 Homebrew)、`/usr/local/opt/llvm@`(x86 macOS 上的 Homebrew)、`/opt/brew/opt/llvm@`(Linux 上自定义前缀的 Homebrew)。 3. CMake 默认的 `find_package(LLVM)` 搜索。 如果解析到的版本不在 15-20 之间,配置步骤将失败并显示明确的错误。 #### 安装 * **Debian / Ubuntu** sudo apt install llvm-${version} llvm-${version}-dev Debian trixie 提供 17-19 版本,Ubuntu 在默认仓库中提供 15-17 版本。对于你的发行版仓库中没有的版本,请按照 [apt.llvm.org](https://apt.llvm.org/) 上的说明进行操作。脚本 `apt.llvm.org/llvm.sh ` 是最简单的方法。 * **Arch** sudo pacman -S llvm llvm-libs * **Homebrew (macOS / Linux)** brew install llvm@20 export NYXSTONE_LLVM_PREFIX="$(brew --prefix llvm@20)" * **从源码构建** 在 Windows 上,请从 Visual Studio 2022 x64 命令提示符中运行这些命令,并将 `~/lib/my-llvm-20` 替换为你选择的路径。 git clone -b release/20.x --single-branch https://github.com/llvm/llvm-project.git cd llvm-project cmake -S llvm -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DLLVM_PARALLEL_LINK_JOBS=1 cmake --build build cmake --install build --prefix ~/lib/my-llvm-20 export NYXSTONE_LLVM_PREFIX=~/lib/my-llvm-20 你可能还需要 LLVM 编译时所依赖的任何系统库。使用 `llvm-config --system-libs` 进行检查;在 Debian/Ubuntu 上,这通常是 `zlib1g-dev` 和 `libzstd-dev`。 ### CLI 工具 Nyxstone 提供了一个用于一次性汇编和反汇编的 [CLI 工具](examples/nyxstone-cli.cpp)。克隆仓库并使用 CMake 构建它: ``` git clone https://github.com/emproof-com/nyxstone cd nyxstone cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build -j ``` 生成的 `nyxstone` 二进制文件位于 `build/` 目录下。其帮助菜单如下: ``` $ ./nyxstone -h Usage: nyxstone [-t=] [-p=] [-d] Examples: # Assemble an instruction with the default architecture ('x86_64'). nyxstone 'push eax' # Disassemble the bytes 'ffc300d1' as AArch64 code. nyxstone -t aarch64 -d ffc300d1 Options: -t, --triple= LLVM target triple or alias, e.g. 'aarch64' -c, --cpu= LLVM CPU specifier, e.g. 'cortex-a53' -f, --features= LLVM architecture/CPU feature list, e.g. '+mte,-neon' -p, --address= Initial address to assemble/disassemble relative to -l, --labels= Label-to-address mappings (used when assembling only) -d, --disassemble Treat as bytes to disassemble instead of assembly -h, --help Show this help and usage message Notes: The '--triple' parameter also supports aliases for common target triples: 'x86_32' -> 'i686-linux-gnu' 'x86_64' -> 'x86_64-linux-gnu' 'armv6m' -> 'armv6m-none-eabi' 'armv7m' -> 'armv7m-none-eabi' 'armv8m' -> 'armv8m.main-none-eabi' 'aarch64' -> 'aarch64-linux-gnueabihf' The CPUs for a target can be found with 'llc -mtriple= -mcpu=help'. The features for a target can be found with 'llc -mtriple= -mattr=help'. ``` 汇编单条 x86_64 指令: ``` $ ./nyxstone -t x86_64 "mov rax, rbx" 0x00000000: mov rax, rbx ; 48 89 d8 ``` 汇编一段带有内部标签的序列,锚定在 `0xdeadbeef`: ``` $ ./nyxstone -t x86_64 -p 0xdeadbeef "cmp rax, rbx; jz .exit; inc rax; .exit: ret" 0xdeadbeef: cmp rax, rbx ; 48 39 d8 0xdeadbef2: je .exit ; 74 03 0xdeadbef4: inc rax ; 48 ff c0 0xdeadbef7: ret ; c3 ``` 为不同的 ISA 进行反汇编,例如 ARM Thumb: ``` $ ./nyxstone -t thumbv8 -d "13 37" 0x00000000: adds r7, #19 ; 13 37 ``` 使用 `--labels` 将外部标签固定到特定地址: ``` $ ./nyxstone -p "0x1000" -l ".label=0x1238" "jmp .label" 0x00001000: jmp .label ; e9 33 02 00 00 ``` ### C++ 库 将你的代码链接到 `nyxstone::nyxstone` CMake 目标。Nyxstone 会传递其 LLVM 依赖项,因此使用者无需自行调用 `find_package(LLVM)`。 假设 Nyxstone 位于 `nyxstone/` 子目录中: ``` add_subdirectory(nyxstone) add_executable(my_executable main.cpp) target_link_libraries(my_executable PRIVATE nyxstone::nyxstone) ``` 一个最小化的 C++ 使用示例: ``` #include #include #include "nyxstone.h" using namespace nyxstone; int main() { auto nyxstone = NyxstoneBuilder("x86_64").build().value(); // Assemble to bytes. auto bytes = nyxstone->assemble(/*assembly=*/"mov rax, rbx", /*address=*/0x1000, /*labels=*/{}).value(); const std::vector expected{0x48, 0x89, 0xd8}; assert(bytes == expected); } ``` 有关包含 `assemble_to_instructions`、标签定义和反汇编的完整演练,请参阅 [examples/example.cpp](examples/example.cpp)。 ### Rust 绑定 将 `nyxstone` 添加到你的 `Cargo.toml` 中,并按如下方式使用: ``` use anyhow::Result; use nyxstone::{Nyxstone, NyxstoneConfig}; use std::collections::HashMap; fn main() -> Result<()> { let nyxstone = Nyxstone::new("x86_64", NyxstoneConfig::default())?; let bytes = nyxstone.assemble_with( "mov rax, rbx; cmp rax, rdx; jne .label", 0x1000, &HashMap::from([(".label", 0x1200)]), )?; println!("Bytes: {:x?}", bytes); Ok(()) } ``` 有关构建选项(静态链接与动态链接 LLVM、FFI 怪癖),请参阅 [Rust 绑定 README](bindings/rust/README.md)。 ### Python 绑定 通过 pip 安装: ``` pip install nyxstone ``` 然后在 Python 中: ``` $ python -q >>> from nyxstone import Nyxstone >>> nyxstone = Nyxstone("x86_64") >>> nyxstone.assemble("jne .loop", 0x1100, {".loop": 0x1000}) ``` 有关从源码构建的说明,请参阅 [Python 绑定 README](bindings/python/README.md)。 ## 工作原理 Nyxstone 通过 LLVM 的公共 C++ API 驱动其 MC 层。汇编器通过 LLVM 常规的 `MCELFStreamer` 对象生成流水线处理输入,因此由 LLVM 自身执行布局、松弛和重定位解析。这就是确保特定于目标的重定位在每个后端都正确的原因——例如,`la` 伪指令背后的 RISC-V 成对的 `%pcrel_hi`/`%pcrel_lo`。随后,Nyxstone 提取 `.text` 字节并应用几个有针对性的后处理步骤。 汇编路径的结构如下: * **仅限 `.text` 的设置。** Nyxstone 安装了一个最小化的 `TextOnlyObjectFileInfo`,它只向 `MCContext` 注册 `.text`,跳过了 `MCObjectFileInfo::initMCObjectFileInfo` 默认执行的大约 40 个其他 section 的创建。这是相对于完整对象文件设置而言,单次性能提升最大的一项。 * **流经 LLVM。** 一个轻量级的 `MCELFStreamer` 子类 [`ELFStreamerWrapper`](src/ELFStreamerWrapper.h),为指令详情 API 记录每条指令的汇编文本,并拒绝切换到 `.text` 以外的任何 section,因此不受支持的指令会引发明确的错误,而不是静默丢弃随后的字节。常见的数据指令(`.byte`/`.word`/`.org`/`.nops`/`.align`/`.fill`/`.uleb128`/…)和 `ldr rX, =const` 字面量池能够正常工作,是因为它们会流经常规流水线。 * **解析对地址敏感的修正。** LLVM 在基地址 0 处布置 section,因此在布局之后,Nyxstone 会重新应用 LLVM 推迟到链接时且依赖于运行时基地址的那一项重定位——即 AArch64 的 `adrp`(目标页减去 pc 页)——将其应用到用户提供的地址上,然后通过 `MCAssembler::writeSectionData` 提取 `.text`。 * **处理 ARM Thumb 对齐。** 当起始地址是 2 字节对齐但非 4 字节对齐时,LLVM 的基地址 0 布局会具有错误的对齐奇偶性,并可能导致 Thumb PC 相对加载的松弛错误或被拒绝。Nyxstone 会在内部预置一个 2 字节的 `bkpt` 以恢复奇偶性,并在输出中将其剥离。 * **验证。** Nyxstone 对 ARM Thumb(`adr`、`ldr` literal、`b/bl/bcc`、…)和 AArch64(`adr`)的修正类型运行额外的范围和对齐检查,这些修正类型在超出范围时会被 LLVM 后端静默错误编码。 反汇编路径要简单得多:每个 `Nyxstone` 实例都会构造一次 `MCDisassembler` 及其 `MCContext` 并在后续调用中复用,因为反汇编永远不会修改上下文。 * **缓存。** 与版本无关的目标信息对象(`MCRegisterInfo`、`MCInstrInfo`、`MCSubtargetInfo`、`MCAsmInfo`)、指令打印器以及 `MCAsmBackend` 在每个 `Nyxstone` 实例中只构建一次并被复用。汇编器的 `MCContext` 以及每次调用的 streamer/parser 会在每次调用时重建,因为 LLVM 将上下文与输入源缓冲区绑定在一起。 * **版本耦合。** Nyxstone 使用了 LLVM 不保证在主版本间保持稳定的 MC 头文件;受支持的范围(15-20)由 [src/nyxstone.cpp](src/nyxstone.cpp) 和 [src/ELFStreamerWrapper.h](src/ELFStreamerWrapper.h) 中的 `#if LLVM_VERSION_MAJOR` 防护代码覆盖——最显著的是在 LLVM 19 中移除了 `MCAsmLayout`。[src/Target/](src/Target/) 下内置的 LLVM 内部头文件(`AArch64FixupKinds.h`、`AArch64MCExpr.h`、`ARMFixupKinds.h`)也以类似的方式进行追踪,因为 LLVM 并没有安装它们。 ## 基准测试 以下数据是使用内置基准测试二进制文件,在 13 代 Intel Core i7-1370P(Linux,LLVM 19,release 构建,每次测量 2 秒)上收集的。可通过以下方式复现: ``` # C++ cmake --build build --target benchmark ./build/benchmark 2 # Rust cargo run --release --example benchmark -- 2 ``` 每次调用都会为指定的架构汇编/反汇编包含 1 条或 10 条指令的包。10 条指令的包摊销了每次调用的固定开销,这就是为什么 `ops/s` 会下降,但 `insns/s`(括号内)会上升的原因。 | 架构 | 指令包 | C++ 汇编 (ops/s) | C++ 反汇编 (ops/s) | | ------------ | ------------ | -------------------- | ----------------------- | | x86_64 | 1 instr | 68 k | 6.34 M | | x86_64 | 10 instr | 42 k (425 k insns/s) | 671 k (6.71 M insns/s) | | x86_32 | 1 instr | 69 k | 6.29 M | | x86_32 | 10 instr | 45 k (448 k insns/s) | 685 k (6.85 M insns/s) | | aarch64 | 1 instr | 24 k | 4.54 M | | a64 | 10 instr | 6.9 k (69 k insns/s) | 376 k (3.76 M insns/s) | | armv8m | 1 instr | 60 k | 5.64 M | | armv8m | 10 instr | 29 k (285 k insns/s) | 392 k (3.92 M insns/s) | Rust 绑定增加了 cxx-bridge 的调用开销。对于汇编来说,这可以忽略不计(与 C++ 数据相比在 ~3% 以内);对于速度更快的反汇编路径,单条指令调用大约会有 30% 的损耗,但随着调用完成的工作量增加,这种损耗会缩减至接近于零。 ## 路线图 近期工作: * [x] 通过 LLVM 的 `MCELFStreamer` 流水线驱动汇编,以便 LLVM 解析每个目标的重定位,包括早期手动实现的修正过程弄错的 RISC-V `%pcrel_hi`/`%pcrel_lo`(`la` 伪指令)。 * [x] 通过使用仅限 `.text` 的 `MCObjectFileInfo` 并在多次调用间缓存目标信息对象、指令打印器和 `MCAsmBackend`,保持汇编的快速。 * [x] 支持常见的数据指令(`.byte`/`.word`/`.org`/`.nops`/`.align`/`.fill`/`.uleb128`/…)和 `ldr =const` 字面量池。 * [x] 在输入 Nyxstone 无法表示的内容时(例如从 `.text` 切换出去)引发明确的错误,而不是静默丢弃字节。 * [x] 解析 LLVM 推迟到链接时的重定位(AArch64 `adrp`),并针对会被 LLVM 错误编码的 ARM Thumb / AArch64 修正类型运行范围/对齐验证器。 * [x] 支持 LLVM 15-20,并自动选择安装的最新版本。 * [x] 修复 ARM Thumb IT 块状态泄漏:LLVM Thumb 反汇编器在 `getInstruction()` 调用之间会携带可变的 ITSTATE。通过为每次调用创建新的反汇编器来修复此问题;由于较重的 `MCContext` 保持缓存状态,对性能的影响微乎其微。 仍在进行中: * [ ] 将支持扩展到 LLVM 21+(每个新的主版本往往会改变 Nyxstone 所依赖的不稳定 MC 头文件;LLVM 19 已经需要处理移除 `MCAsmLayout` 的情况)。 ## 许可证 Nyxstone 基于 [MIT 许可证](LICENSE) 提供。 ## 维护者 Nyxstone 由 [emproof](https://emproof.com) 维护。如果有任何问题或需要人工协助,请联系: * [Philipp Koppe](https://github.com/pkoppe) * [Darius Hartlief](https://github.com/stuxnot) 有关所有贡献过代码的人员的完整列表,请查看[贡献者图表](https://github.com/emproof-com/nyxstone/graphs/contributors)。 ## 鸣谢 为了确保我们在 Rust 中以正确的版本控制链接 LLVM,我们改编了 [llvm-sys](https://gitlab.com/taricorp/llvm-sys.rs/) 中的 build.rs。
标签:Bash脚本, C++, LLVM, Python, Rust, 可视化界面, 底层开发, 数据擦除, 无后门, 汇编/反汇编, 网络流量审计, 逆向工具, 通知系统