bitdefender/bddisasm
GitHub: bitdefender/bddisasm
一个轻量级、高性能的 x86/x64 指令解码器,附带 shellcode 行为检测模拟器,适用于安全分析和底层系统开发。
Stars: 1068 | Forks: 122
# Bitdefender 反汇编器
Bitdefender 反汇编器 (bddisasm) 是一个轻量级的、仅支持 x86/x64 的指令解码器。它易于集成、易于使用,没有外部依赖,是线程安全的,完全不分配内存,几乎可以在任何环境中工作(我们在用户态、内核态、Hypervisor 以及 Windows 和 Linux 环境中都使用它),并且提供了大量关于解码指令的信息,例如:操作数(包括显式和隐式的)、每个操作数的访问模式、CPUID 特性标志、标志访问等。更多关于该项目的示例和信息可以在官方文档中找到:[Bitdefender 反汇编器](http://bddisasm.readthedocs.io)
## 项目
1. [bddisasm](https://github.com/bitdefender/bddisasm/tree/master/bddisasm) - 这是主要的反汇编器项目。为了使用 Bitdefender 反汇编器,你所要做的就是构建这个项目,并链接到输出的库。你需要的唯一头文件位于 `inc` 文件夹中。
2. [bdshemu](https://github.com/bitdefender/bddisasm/tree/master/bdshemu) - 该项目利用主要的 bddisasm 库,构建了一个简单、轻量、快速的指令模拟器,专门针对 shellcode。这个项目也被集成到了 disasmtool 中,因此你可以模拟原始二进制文件并查看其输出。请注意,这个简单的模拟器支持基本的 x86/x64 指令,但不支持模拟任何类型的 API 调用。此外,唯一支持的内存访问是在 shellcode 本身内部以及模拟的栈上。
3. [isagenerator_x86](https://github.com/bitdefender/bddisasm/tree/master/isagenerator) - 该项目包含指令定义和生成反汇编表所需的脚本。如果你想添加对新指令的支持,就是在这里添加。该项目会自动生成几个头文件(instructions.h、mnemonics.h、constants.h、table_\*.h),所以请确保不要手动编辑任何这些文件。你需要 Python 3 来运行生成脚本。
4. [disasmtool](https://github.com/bitdefender/bddisasm/tree/master/disasmtool) - 该项目是一个命令行反汇编工具,主要用作如何集成 bddisasm 和 bdshemu 库的示例。
5. [bindings](https://github.com/bitdefender/bddisasm/tree/master/bindings) - [python](https://github.com/bitdefender/bddisasm/tree/master/bindings/pybddisasm) 和 [Rust](https://github.com/bitdefender/bddisasm/tree/master/bindings/rsbddisasm) 的绑定。
## 核心特性
1. 轻量级 - 使用 C 语言编写,没有外部依赖,不分配内存,且在设计上是线程安全的。
2. 快速 - 在 Intel® Core™ Ultra 7 165H 上使用 BDDISASM v3.0 测量:
- 传统解码器:每条解码指令大约需要 240 个 CPU 时钟周期(每秒可解码约 1200 万条指令);
- 精简解码器:每条解码指令大约需要 150 个 CPU 时钟周期(每秒可解码约 1700 万条指令)
3. 高鲁棒性 - 已通过内部 fuzzers 和著名的 [mishegos](https://github.com/trailofbits/mishegos) 工具测试。
4. 易于使用 - 只需包含主头文件 bddisasm.h,链接到 bddisasm 库,然后调用 NdDecode API 即可!
5. 完整 – 全面支持在现代 CPU 中实现的 Intel 和 AMD x86 指令,包括丰富的每指令元数据。
## 构建和安装
### 使用 [vcpkg](https://github.com/microsoft/vcpkg)
安装 bddisasm 最简单的方法是使用 [vcpkg](https://github.com/microsoft/vcpkg):
```
vcpkg install bddisasm
```
这将安装 `bddisasm` 和 `bdshemu` 静态库。
请注意,vcpkg 上可用的版本可能并不总是最新版本。
### 使用 CMake
这是使用该库的推荐方式。
前置条件:
- CMake 3.16 或更新版本(旧版本可能有效,但未经测试)
- Ninja(可选,但推荐使用,尤其是在 Windows 上)
构建过程已在 Linux 上的 GCC 和 Clang 以及 Windows 上的 MSVC 中进行了测试。请注意,Windows 内核构建目标仅在使用 [MSBuild](#Using-MSBuild-on-Windows) 时可用。
要构建项目,请运行:
```
mkdir build
cmake -B build .
cmake --build build
```
这将构建 `bddisasm`、`bdshemu` 和 `disasmtool`。要跳过 `disasmtool` 的构建,请使用 `BDD_INCLUDE_TOOL=OFF` 配置 CMake:
```
mkdir build
cmake -B build . -DBDD_INCLUDE_TOOL=OFF
```
要安装项目,请使用:
```
cmake --build build --target install
```
这将安装 `bddisasm` 和 `bdshemu` 静态库及其公共头文件。如果构建了 `disasmtool`,它也将被安装。根据安装位置,你可能需要以 root 用户身份运行该命令。
可选地,如果找到了 python 3 解释器,可以使用以下命令重新生成指令表:
```
cmake --build build --target isagenerator
```
要禁用 `isagenerator` 目标,请使用 `BDD_INCLUDE_ISAGENERATOR=OFF` 配置 CMake。
安装完成后,CMake 项目可以使用 `find_package` 来查找该库:
```
find_package(bddisasm REQUIRED)
```
提供了两个 target:
- `bddisasm::bddisasm` - 这应该用于只需要解码器,而不需要 shellcode 模拟器的 target
- `bddisasm::bdshemu` - 这应该用于需要 shellcode 模拟器的 target(注意,它会自动引入 `bddisasm::bddisasm`)
无需手动设置 include 或链接目录,只需将 `target_link_libraries` 与所需的 target 一起使用,例如:
```
find_package(bddisasm REQUIRED)
# decoder-tool 只需要 decoder 库
target_link_libraries(decoder-tool PRIVATE bddisasm::bddisasm)
# emulator-tool 需要 bdshemu
target_link_libraries(emulator-tool PRIVATE bddisasm::bdshemu)
```
### nd_vsnprintf_s 和 nd_memset
默认情况下,如果 `vsnprintf` 和 `memset` 函数可用,`nd_vsnprintf_s` 和 `nd_memset` 函数将直接由 `bddisasm` 实现。为了表明这一点,`BDDISASM_HAS_VSNPRINTF` 和 `BDDISASM_HAS_MEMSET` 将被添加到 `bddisasm` 的公共编译定义中。可以通过使用 `BDD_USE_EXTERNAL_VSNPRINTF=ON` 和 `BDD_USE_EXTERNAL_MEMSET=ON` 配置 CMake 来禁用此功能。
#### 作为子项目使用
该项目可以作为子项目使用,可以通过将其添加为 git submodule,或者使用 [CMake 的 FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html):
使用 `FetchContent`:
```
FetchContent_Declare(
bddisasm
GIT_REPOSITORY https://github.com/bitdefender/bddisasm
GIT_TAG origin/master
)
FetchContent_MakeAvailable(bddisasm)
```
作为 git submodule:
```
# 假设 submodule 检出到 external/bddisasm
add_subdirectory(external/bddisasm)
```
在这两种情况下,都会提供 `bddisasm::bddisasm` 和 `bddisasm::bdshemu` target。
当作为子项目使用时,`disasmtool`、`isagenerator` 和 `install` 目标不可用。
### 在 Linux 上使用 Make
在仓库根目录下运行 `make` 来构建项目。这只会构建 `bddisasm` 和 `bdshemu` 静态库,不包含 `disasmtool`。
运行 `make install` 来安装项目。根据安装位置,你可能需要以 root 用户身份运行该命令。
[nd_vsnprintf_s 和 nd_memset](#nd_vsnprintf_s-and-nd_memset) 将不会由 `bddisasm` 定义,集成者必须提供这些函数。
### 在 Windows 上使用 MSBuild
为了在 Windows 上构建项目,你需要:
- [Visual Studio 2019](https://visualstudio.microsoft.com/vs/) 并带有使用 C++ 的桌面开发工作负载。
- [Windows SDK 10.0.18362.0](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk/)。
- [Python 3.7 或更新版本](https://www.python.org/downloads/release/python-373/)(可选)
当你第一次在 Visual Studio 中打开 `bddisasm.sln` 时,应该会提示你安装任何缺失的组件。
这应该足以构建 `bddisasm`、`bdshemu` 和 `disasmtool`。
对于 DebugKernel 和 ReleaseKernel 配置,需要 [WDK 1903](https://go.microsoft.com/fwlink/?linkid=2085767),以及 Windows Driver Kit Visual Studio 扩展(WDK 安装程序应该会自动处理)。
对于 `isagenerator`,需要 Python 3。
任何项目的构建都可以直接在 Visual Studio 中完成。
结果将位于仓库根目录的 bin 目录中。
[nd_vsnprintf_s 和 nd_memset](#nd_vsnprintf_s-and-nd_memset) 将不会由 `bddisasm` 定义,集成者必须提供这些函数。
## 使用预编译的二进制文件
每个版本都会发布静态库,以及 `disasmtool` CLI 工具。
你可以使用 [GitHub Artifact Attestation](https://github.blog/2024-05-02-introducing-artifact-attestations-now-in-public-beta/) 验证这些产物:
```
# 验证一个组件
$ gh attestation verify disasmtool -o bitdefender
# 或者整个 bundle
$ gh attestation verify x86-windows-release.zip -o bitdefender
```
这对于 bddisasm 2.1.4 或更老版本不可用。
请注意,这目前是一个 beta 功能(有关详细信息,请参阅 [Introducing Artifact Attestations–now in public beta](https://github.blog/2024-05-02-introducing-artifact-attestations-now-in-public-beta/))。
## 解码 x86 指令
### 解码 API
有 4 个解码函数,但在内部它们的作用都是一样的,尽管有些带有隐式参数:
- `NDSTATUS NdDecode(INSTRUX *Instrux, const uint8_t *Code, uint8_t DefCode, uint8_t DefData)` - 仅当你不关心输入缓冲区的长度时才应使用此 API;
- `NDSTATUS NdDecodeEx(INSTRUX *Instrux, const uint8_t *Code, size_t Size, uint8_t DefCode, uint8_t DefData);` - 从最大长度为 `Size` 的缓冲区解码指令;
- `NDSTATUS NdDecodeEx2(INSTRUX *Instrux, const uint8_t *Code, size_t Size, uint8_t DefCode, uint8_t DefData, uint8_t DefStack, uint8_t PreferedVendor);` - 使用首选供应商解码指令;
- `NDSTATUS NdDecodeWithContext(INSTRUX *Instrux, const uint8_t *Code, size_t Size, ND_CONTEXT *Context);` - 基础解码 API;输入参数 - `DefCode`、`DefData`、`DefStack`、`VendMode` 和 `FeatMode` 必须在调用此函数之前全部填充到 `Context` 结构中。在第一次解码调用之前,还应使用 `NdInitContext` 初始化 Context 结构。
请注意,默认情况下,解码使用默认供应商 `ND_VEND_ANY`(这意味着 bddisasm 将尽可能多地进行解码)。此外,默认的特性掩码是 `ND_FEAT_ALL`,这意味着 bddisasm 将乐观地尝试解码映射到宽 NOP 空间的指令(例如 MPX 或 CET)。如果必须更改这些参数,建议使用 `NdDecodeWithContext` API。
将解码后的指令转换为文本反汇编必须使用 `NdToText` API。bddisasm 仅支持 Intel、masm 风格的语法。
### 精简解码 API
默认(传统)解码 API 提供了一个庞大的 `INSTRUX` 结构(大约 480 字节长),其中包含关于该指令的所有可能信息,包括所有的操作数。当需要更快的解码器性能和/或更小的 `INSTRUX` 时,可以使用精简解码 API:
- `NDSTATUS NdDecodeMini(INSTRUX_MINI *Instrux, const uint8_t *Code, size_t Size, uint8_t DefCode)`
- `NDSTATUS NdDecodeWithContextMini(INSTRUX_MINI *Instrux, const uint8_t *Code, size_t Size, ND_CONTEXT *Context);`
`INSTRUX_MINI` 只有 64 字节长,并提供除操作数和元数据之外的所有核心指令信息。如果需要,可以通过以下新 API 访问操作数:
- `NDSTATUS NdGetOperandMini(const INSTRUX_MINI *Instrux, ND_UINT8 Index, ND_OPERAND *Operand);` - 解码索引为 `Index` 的指令操作数
每种类型的元数据也可以使用 API 从 `INSTRUX_MINI` 中检索。例如,为了检索栈访问类型,可以使用 `NdGetStackAccessMini` API;为了检索有效模式,可以使用 `NdGetValidModesMini`,等等。请查阅 `bdx86_api_mini.h` 获取所有可用 API 的列表。
### 示例
使用 bddisasm 非常简单。解码并打印指令的反汇编既快速又简单:
```
#include
#include "bddisasm/disasmtypes.h"
#include "bddisasm/bddisasm.h"
int nd_vsnprintf_s(
char *buffer,
size_t sizeOfBuffer,
size_t count,
const char *format,
va_list argptr
)
{
return vsnprintf(buffer, sizeOfBuffer, format, argptr);
}
void* nd_memset(void *s, int c, size_t n)
{
return memset(s, c, n);
}
int main()
{
INSTRUX ix;
uint8_t code[] = { 0x48, 0x8B, 0x48, 0x28 };
NDSTATUS status = NdDecodeEx(&ix, code, sizeof(code), ND_CODE_64, ND_DATA_64);
// Check if decoding failed.
if (!ND_SUCCESS(status))
{
printf("Decode failed with error %x!\n", status);
return -1;
}
// Checking if the instruction is a MOV.
if (ix.Instruction == ND_INS_MOV)
{
printf("The instruction is mov!\n");
}
// Checking if memory is read by the instruction.
if (!!(ix.MemoryAccess & ND_ACCESS_ANY_READ))
{
printf("The instruction reads memory!\n");
}
// Checking if the destination is the RCX register.
if (ND_IS_OP_REG(&ix.Operands[0], ND_REG_GPR, 8, NDR_RCX))
{
printf("The first op is RCX!\n");
}
// Checking if the source is memory.
if (ix.Operands[1].Type == ND_OP_MEM)
{
printf("Source is memory!\n");
if (ix.Operands[1].Info.Memory.HasSeg)
{
printf(" Segment reg: %d\n", ix.Operands[1].Info.Memory.Seg);
}
if (ix.Operands[1].Info.Memory.HasBase)
{
printf(" Base reg: %d\n", ix.Operands[1].Info.Memory.Base);
}
if (ix.Operands[1].Info.Memory.HasIndex)
{
printf(" Index reg: %d, scale: %d\n", ix.Operands[1].Info.Memory.Index, ix.Operands[1].Info.Memory.Scale);
}
if (ix.Operands[1].Info.Memory.HasDisp)
{
printf(" Displacement: 0x%llx\n", ix.Operands[1].Info.Memory.Disp);
}
}
// Create the text disassembly for this instruction.
char text[ND_MIN_BUF_SIZE];
NdToText(&ix, 0, sizeof(text), text);
printf("Instruction: %s\n", text);
return 0;
}
```
使用扩展 API 同样非常简单:
```
INSTRUX ix;
ND_CONTEXT ctx;
uint8_t code[] = { 0x48, 0x8B, 0x48, 0x28 };
// This has to be done only once.
NdInitContext(&ctx);
ctx.DefCode = ND_CODE_64;
ctx.DefData = ND_DATA_64;
ctx.DefStack = ND_STACK_64;
ctx.VendMode = ND_VEND_ANY;
ctx.FeatMode = ND_FEAT_ALL; // Use ND_FEAT_NONE, if you wish to see NOPs instead of MPX/CET/CLDEMOTE instructions.
// From here one, the ctx can be reused for any number of NdDecodeWithContext calls.
NDSTATUS status = NdDecodeWithContext(&ix, code, sizeof(code), &ctx);
...
```
使用精简解码器也一样简单:
```
INSTRUX_MINI ix;
ND_CONTEXT ctx;
ND_OPERAND op;
char text[ND_MIN_BUF_SIZE];
uint8_t code[] = { 0x48, 0x8B, 0x48, 0x28 };
// This has to be done only once. The same context can be used by both the legacy and mini API!
NdInitContext(&ctx);
ctx.DefCode = ND_CODE_64;
ctx.DefData = ND_DATA_64;
ctx.DefStack = ND_STACK_64;
ctx.VendMode = ND_VEND_ANY;
ctx.FeatMode = ND_FEAT_ALL; // Use ND_FEAT_NONE, if you wish to see NOPs instead of MPX/CET/CLDEMOTE instructions.
// From here one, the ctx can be reused for any number of NdDecodeWithContextMini calls.
NDSTATUS status = NdDecodeWithContextMini(&ix, code, sizeof(code), &ctx);
...
// Getting the first operand.
status = NdGetOperandMini(&ix, 0, &op);
...
// Formatting the instruction.
status = NdToTextMini(&ix, 0, sizeof(text), text);
...
```
## 鸣谢
整个 Bitdefender HVI 团队。
标签:Bash脚本, DNS 反向解析, macOS, x86/x64, 反汇编器, 可视化界面, 客户端加密, 底层开发, 指令模拟器, 逆向工具