10 KiB
10 KiB
name, description, capabilities
| name | description | capabilities | |
|---|---|---|---|
| re-rust | Rust 二进制逆向:符号、monomorphization、所有权模式。触发词:Rust、rust、Rust二进制、cargo |
|
Rust 二进制逆向(符号解译 / monomorphization / 所有权)
何时使用 / 何时不用
- 用:确认是 Rust 编译的二进制(符号带
_ZN/_RNv前缀)后,demangle 还原符号;识别泛型实例化(monomorphization)产生的重复代码;通过所有权/析构(drop)模式理解对象生命周期;分析 async/await 状态机与 tokio/serde 等库模式 - 用:恶意 Rust 样本(re-malware 静态还原——Rust 重写的木马/勒索增多)
- 不用:非 Rust 原生程序(C/C++/Go 直接 re-binary-core;Go 走 re-go)
- 不用:确认带壳先走 re-anti-analysis(Rust 二进制普遍无壳,但体积大)
- 注意:动态步骤默认沙箱(re-analyze/platform-tips 最高原则);静态优先(大型样本原则)
工具准备
参考 re-analyze/platform-tips——反编译/符号为静态步骤,免沙箱;Rust 二进制通常 1-10MB+,静态定位先行。
Rust 工具链(rustc/cargo——装符号工具用,可选)
- 推荐 rustup(官方安装器,全平台最新):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - Debian/Ubuntu:
apt install rustc cargo(官方包,版本可能滞后);Fedora:dnf install rust cargo;Arch:pacman -S rust - macOS:
brew install rust;Windows:choco install rust或 rustup - 验证:
rustc --version && cargo --version
rustfilt(Rust 符号 demangle,v0 新式 _RNv 必用)
- Debian/Ubuntu:
apt install rustfilt(官方包) - 其他发行版无官方包 → 官方安装:
cargo install rustfilt - 验证:
echo '_ZN3foo3barE' | rustfilt输出foo::bar
c++filt(legacy _ZN 可解;v0 不行)
- Linux: binutils 自带(
apt install binutils/dnf install binutils/pacman -S binutils) - macOS:
brew install binutils(命令为g++filt)或brew install llvm(llvm-cxxfilt) - Windows: WSL 内 Linux 版或 MinGW binutils
- 验证:
c++filt _ZN3foo3barE输出foo::bar
Ghidra / IDA(反编译底座,装法见对应技能)
- Ghidra 11+ 内置 Rust demangler(分析选项 "Demangler Rust",见 re-ghidra)
- IDA 无内置 → 社区 Rust demangler 插件(如 idarustdemangler),或先 rustfilt 预处理符号表
- 验证: 反编译器函数名呈可读形式(而非
_ZN.../_RNv...乱码)
nm / readelf(符号表与节,binutils)
- 装法同 c++filt(binutils 自带)
- 验证:
nm --version
rustc --emit(编译产物理解,可选)
cargo rustc -- --emit=asm/--emit=llvm-ir/--emit=mir:用匹配版本对样本重编译,得到带符号的汇编/IR/MIR,对照理解编译器展开(monomorphization 泛型实例、async 状态机、drop glue 插入点)--emit=link为默认产物;--emit=metadata不含代码但含类型/依赖信息- 验证:
rustc --emit=asm hello.rs产出.s文件
bloaty(体积分析,坑 2 用;安装见 re-go 工具准备)
- 用途:
bloaty -d symbols sample按符号统计体积占比(monomorphization 展开体量可观) - 验证:
bloaty --version
操作步骤
按顺序执行,每步记录证据(路径 + sha256,见 re-triage)。
-
识别 Rust:
file sample nm sample | head -20 # 出现 _ZN...(legacy)或 _RNv...(v0)即 Rust strings sample | grep -iE 'rustc|rust_begin_unwind|panicking|__rust_alloc'- 特征符号:
_ZN4core9panicking...E(panic 基建)、_ZN3std2rt...E(std 运行时)、__rust_alloc/__rust_dealloc(替代 malloc/free 的分配器) - std 程序入口是
main;no_std(嵌入式)用_start .comment节指纹:readelf -p .comment sample直接给出rustc version <版本> (<commit>)与链接器版本(实测 1.95 产物含rustc version 1.95.0 (59807616e 2026-04-14)与Linker: LLD 22.1.2)——版本指纹先看这里,比猜 panic 风格快- panic 文案泄漏源码路径:
strings sample | grep 'panicked at'后的路径形如/rustc/<commit>/library/std/src/...,<commit>即精确工具链版本;业务代码路径同样在文案里
- 特征符号:
-
符号解译(demangle):
nm -C sample | grep main # 内置 demangle(仅 legacy _ZN 可解) nm sample | rustfilt > symbols.txt # 全量 demangle(v0/legacy 通吃) grep -iE 'main|tokio|serde' symbols.txt- v0 新式(
_RNv)只有 rustfilt/Ghidra 11+ 能解,c++filt 原样吐出(坑 5) - Ghidra 11+ 导入后自动 demangle:函数树直接可读;符号保留确认: 无输出 → 坑 1
- v0 默认化后产物仍是混合命名:实测 1.95 产物 v0 符号占绝大多数(455 个 vs legacy 30 个),但
main与部分 std 内部符号(含.llvm.后缀实例)仍是 legacy_ZN——识别与批量 demangle 时两种前缀都要覆盖
- v0 新式(
-
monomorphization 识别:
grep -oE '::[a-z_]+<[^>]*>' symbols.txt | sort | uniq -c | sort -rn | head- 同一泛型名多种类型参数 = 多份实例代码(例:
Vec<u8>与Vec<i32>各一份完整展开) - 分析策略:选最常见类型参数的代表实例,同组实例之间只比对类型相关分支;重复代码跳过(坑 2)
- 同一泛型名多种类型参数 = 多份实例代码(例:
-
所有权/借用模式:
- drop:
core::ptr::drop_in_place::<T>调用 = 对象析构点(vtable drop glue),沿它定位结构体生命周期与字段 - 分配:
__rust_alloc/__rust_dealloc对应 malloc/free;alloc::boxed::Box(独占堆)、alloc::sync::Arc(引用计数增减)、alloc::vec::Vec/alloc::string::String(容量变化触发 realloc) core::mem::replace/take/swap调用是借用检查器催生的常见操作- 反编译里大量 memcpy + size 参数 → 结构拷贝/Clone(derive(Clone))
- drop:
-
常见库模式:
- tokio/async:
tokio::runtime::Runtime::block_on、tokio::task/executor 符号;async fn 展开为状态机(坑 4) - serde:
serde::de::/serde::ser::与deserialize符号——配置/协议解析入口,沿它找数据格式 - 网络:
std::net::TcpStream、tokio::net::;日志/配置:log::/tracing::、serde_json - strings 中的错误文案/URL/配置串辅助定位目标逻辑
- tokio/async:
跨域联合
- re-binary-core:本技能由 core 网关引用——初勘 re-triage、反编译底座 re-ghidra/re-ida、库指纹 re-imports
- re-ghidra:11+ 内置 Rust demangler 优先;re-ida:Rust demangler 插件或 rustfilt 预处理
- re-emulation:no_std/嵌入式 Rust 固件无运行环境时可模拟执行辅助
- 恶意场景:Rust 恶意样本(勒索/木马重写版多)静态按本技能 → 行为分析转 re-malware
- re-analyze/platform-tips 相关分支:静态优先(大型样本)、动态默认沙箱、跨平台样本的 Wine/QEMU 分支
常见坑与陷阱
- 符号被 strip,恢复极难:现象——
nm无输出、函数全sub_*;原因——release profile 设strip = true或发布流程 strip(Rust 没有类似 Go pclntab 的恢复结构);对策——先确认nm是否真的空(多数发布版默认保留符号,操作步骤 2 排查);strip 后用 strings 找 panic 文案/错误信息(含文件路径与类型线索),.eh_frame/.gcc_except_table的 FDE 可还原函数边界(Ghidra 分析选项开启 unwind 解析) - monomorphization 大量重复代码:现象——同一逻辑函数出现几十上百份,仅类型不同,函数列表爆炸;原因——泛型按每个类型参数实例化,每实例一份完整代码(体积也暴涨);对策——demangle 后按泛型名分组统计,选一个代表性实例分析,同组之间 diff 只比对类型相关分支;体积/重复问题用 bloaty(
bloaty -d symbols)确认;分析时忽略其余实例 - panic 路径与正常路径交织:现象——反编译里每个下标/
unwrap/断言都带分支与 panic 调用,可读性被淹没;原因——Rust 在越界索引、unwrap()、assert!处插入panic_bounds_check/core::panicking::panic检查(release 也保留索引检查),加上unreachable!死路径;对策——识别并跳过core::panicking::/std::panicking::/panic_impl相关调用与异常分支;panic 文案在 strings 里可见,可反向辅助定位调用点 - async/await 状态机展开:现象——一个 async fn 变成结构体 + 巨大
match状态循环,函数名带{{closure}}/{{opaque}},找不到线性流程;原因——async fn 编译为 Future 状态机,每个 await 点是一个状态与保存点,poll 内按状态转移;对策——按状态号(0..N)追踪转移逻辑,重点读 poll 方法;识别core::future/std::future::poll_with_tls_context调用;tokio 程序找tokio::runtime符号与block_on定位入口 - v0 新式命名 c++filt 解不了:现象——
c++filt/nm -C对_RNv...原样输出,符号仍是乱码;原因——v0 mangling 自 1.37 起可选(-C symbol-mangling-version=v0),2025 年底起成为默认(此前 2019-2025 年间绝大多数二进制仍是 legacy_ZN,nm -C可解);对策——统一用 rustfilt(Debian/Ubuntu 官方包apt install rustfilt,其余cargo install rustfilt)批处理nm sample | rustfilt;Ghidra 11+ 内置 demangler 直接自动解 - panic 策略决定产物形态(unwind vs abort):现象——部分产物找不到
rust_begin_unwind符号、.eh_frame很小或缺失,反编译里core::panicking调用点直接是终止路径,行为分析时程序整体退出;原因——-C panic=abort(常见于嵌入式/体积敏感/安全硬化构建)去掉 unwind 表与恢复路径,默认panic=unwind才保留;对策——用readelf -S看.eh_frame存在性 +nm找rust_begin_unwind/rust_panic判定策略;abort 产物里不必找 panic 后的恢复逻辑,panic 即终止;panic 文案(panicked at)两种策略都在 - no_std 产物无 std 运行时符号:现象——找不到
_ZN3std2rt...E、__rust_alloc也时有时无;原因——no_std(嵌入式/内核)不链接 std,仅 core + 自定义分配器;对策——识别改看 core 特征(_ZN4core...)+.comment版本指纹;入口是_start而非main,分配器符号(__rust_alloc或自定义malloc)按需确认;这类固件常配 re-emulation 做无环境模拟