--- name: re-riscv description: > RISC-V 架构逆向:RV32/RV64、压缩指令(RVC)、gp 相对寻址、ABI 与 ecall 系统调用约定、工具链指纹。 触发词:RISC-V、riscv、RV32、RV64、RVC、压缩指令、ecall、ESP32-C3、GD32V。 capabilities: [arch-analysis] --- # RISC-V 架构逆向(RV32/RV64、RVC、gp 相对寻址) ## 何时使用 / 何时不用 - 用:RV32/RV64 架构二进制——Linux 用户态程序、裸机固件、RISC-V MCU 固件(ESP32-C3 类 RV32IMC) - 用:压缩指令(RVC)混编代码流、gp 相对寻址恢复、RISC-V ABI 识别、ecall 系统调用/环境调用边界 - 用:工具链与库函数指纹(glibc/musl/newlib 系、指令扩展特征) - 不用:ARM / x86 / MIPS 等其他架构(走 [[re-binary-core]] 通用底座,本技能只补 RISC-V 特有语义) - 不用:工具链极端定制且无头绪时,先走 [[re-fw-emulate]] 仿真兜底(能跑就先看行为,再回静态) - 不用:只需解包固件看内容([[re-fw-extract]])/ 分析文件系统与配置([[re-fw-rootfs]])/ 整体启动固件([[re-fw-emulate]]) ## 工具准备 所有工具先验证再使用。静态分析可免沙箱;qemu 动态执行默认沙箱 + 网络隔离([[re-analyze/platform-tips]] 最高原则)。 ### 反编译器(Ghidra / IDA 任选其一) - Ghidra(内置 RISC-V 处理器模块,RV32/RV64 + I/M/A/F/D/C 扩展,自动处理 RVC 压缩指令): - Linux: 官方 release 包(需 JDK);部分发行版仓库有 `apt install ghidra` / `pacman -S ghidra` - macOS: `brew install --cask ghidra`;Windows: 官方 zip - 验证: `analyzeHeadless -help`(headless 模式)或 GUI 导入 RISC-V ELF - IDA:商业版含 RISC-V 模块;Freeware 版架构支持范围以官方页面为准 - 导入时确认 RV32/RV64 与端序——标准 RISC-V 小端、大端变体罕见,选错全部错位 ### readelf / file —— 架构与字节序确认(binutils) - Linux: binutils 自带(`apt install binutils` 等);macOS: `brew install binutils` 或 LLVM 系 readelf;Windows: WSL 内 - 验证: `readelf --version` ### binutils 交叉工具链 —— RISC-V 反汇编/符号表(objdump) - Debian/Ubuntu: `apt install binutils-riscv64-linux-gnu`(提供 `riscv64-linux-gnu-objdump`;编译链加 `gcc-riscv64-linux-gnu`,gcc 依赖带入 binutils 包) - Fedora: `dnf install binutils-riscv64-linux-gnu gcc-riscv64-linux-gnu`(cross-binutils / cross-gcc 源包,提供 `riscv64-linux-gnu-objdump`;官方仓库无 riscv64-unknown-elf 裸机工具链,裸机反汇编用 riscv64-linux-gnu-objdump,RVC 支持一致) - Arch: `pacman -S riscv64-elf-binutils riscv64-elf-gcc`(裸机,提供 `riscv64-unknown-elf-objdump`)或 `pacman -S riscv64-linux-gnu-binutils riscv64-linux-gnu-gcc`(用户态) - macOS: `brew install riscv64-elf-binutils riscv64-elf-gcc`(homebrew-core,提供 `riscv64-unknown-elf-objdump`);Windows/WSL: WSL 内 Linux 版 - 验证: 按所装包一一对应——`binutils-riscv64-linux-gnu` → `riscv64-linux-gnu-objdump --version`(Debian/Ubuntu/Fedora);`binutils-riscv64-unknown-elf` → `riscv64-unknown-elf-objdump --version`(Arch/brew,Debian/Ubuntu 需裸机 triplet 时单列该包);objdump 对 RVC 混编流自动识别(2/4 字节) ### qemu-riscv64 / qemu-riscv32 —— RISC-V 用户态仿真(动态验证) - Linux: `apt install qemu-user` / `dnf install qemu-user` / `pacman -S qemu-user` - macOS: `brew install qemu`(含用户态);Windows/WSL: WSL 内 Linux 版 - 64 位为 `qemu-riscv64`、32 位为 `qemu-riscv32`;`-L ` 指定动态库/链接器来源,`-strace` 跟踪系统调用 - 验证: `qemu-riscv64 --version`、`qemu-riscv32 --version` - 裸机 RISC-V(无 OS)qemu-user 不适用——用 `qemu-system-riscv64/32`(Debian trixie 起 / Ubuntu 25.10 起独立包 `apt install qemu-system-riscv`,Ubuntu 24.04 及更早、Debian bookworm 在 `qemu-system-misc` 内;Fedora / Arch `dnf install qemu-system-riscv` / `pacman -S qemu-system-riscv`)`-machine virt` 或板级模型(`-s -S` 接 gdb),或 [[re-emulation]] 用 Unicorn 逐指令 ### gdb-multiarch —— 交叉调试 - Linux: Debian/Ubuntu `apt install gdb-multiarch`(riscv32/riscv64 目标内置);Fedora `dnf install gdb`(官方 gdb 支持多目标);Arch `pacman -S gdb`(内置 multiarch) - macOS: `brew install gdb`(需 Developer Tools 授权,见 [[re-analyze/platform-tips]] macOS 分支)或 WSL 内 Linux 版;Windows/WSL: WSL 内 Linux 版 - 验证: Debian/Ubuntu `gdb-multiarch --version`;Fedora/Arch `gdb --version`;载入固件后 `set architecture riscv:rv64`(或 riscv:rv32)再 `info registers` 确认 ## 操作步骤 按顺序执行,每步结果存档;动态执行默认沙箱。 1. **架构确认(RV32/RV64 + 字节序)**: ```sh file target.bin # "RISC-V" + 32-bit/64-bit + little-endian 提示 file target.elf readelf -h target.elf # Machine=RISC-V (243);Data=little-endian;Flags 含 RVC/浮点 ABI 位 readelf -A target.elf | head # Tag_RISCV_arch 属性,如 rv64imafdc ``` - e_flags(readelf -h 的 Flags 字段):`EF_RISCV_RVC`(0x1)=含压缩指令;浮点 ABI 位掩码 0x6(0=软浮点、2=单精度 F、4=双精度 D、6=四精度 Q);`EF_RISCV_RVE`(0x8)=RV32E 精简寄存器 - 标准 RISC-V 为小端;大端变体罕见,以 file/readelf 实际输出为准 - 裸机 .bin(无 ELF 头)由步骤 2 的复位向量上下文与指令形态判定(RV32 定宽 4 字节 vs RVC 混编 2/4 字节) 2. **入口定位(reset / 启动代码)**: - **Linux 用户态**:入口即 ELF e_entry(`readelf -h` 的 Entry point)= `_start`;动态链接程序先经 ld.so(PT_INTERP),断点/分析从 _start 起 - **裸机**:RISC-V 无固定向量表(对比 ARM Cortex-M),入口 = SoC datasheet 的 reset 向量;QEMU virt 机器 reset 向量在 MROM `0x1000`(reset stub 再跳 start_addr,默认 DRAM `0x80000000`,pflash 固件场景为 `0x20000000`)——**别把 DRAM 基址当 reset 入口**;部分 MCU flash 基址形如 0x08000000;异常/中断向量基址在 mtvec(M 态)/ stvec(S 态)CSR,trap 处理代码按 CSR 值定位 - 启动流程一般为:设栈(`la sp, ...`)→ 拷贝 .data / 清零 .bss → 跳 main(裸机)或 __libc_start_main(Linux) - 定位入口后在反编译器中标注,沿调用链展开主逻辑 3. **RVC 压缩指令识别(2/4 字节混合,勿固定宽度切割)**: - RVC 让代码 2 字节对齐(半字),地址全为偶数;32 位指令可落在 2 mod 4 的奇半字位置——不能按固定 4 字节步长切割 - 起点错 → 整条指令流错位成乱码;正确做法:用支持 RVC 的反汇编器(Ghidra / riscv64-*-objdump 自动识别),从入口/跳转目标对齐,出现合法压缩指令(c.addi/c.li/c.j/c.lw 等)即对齐正确 - 识别信号:e_flags RVC 位(0x1)、objdump 输出含 2 字节指令、-Os 编译的裸机固件 - 同一函数内 16/32 位指令交错是常态,无固定步长 4. **gp 相对寻址恢复(lui+addi 对、.sdata/.sbss)**: - gp(x3)= `__global_pointer$`,一般落在 .sdata 段内,12 位有符号偏移(±2048)覆盖 .sdata/.sbss 小数据区 - 访问形态:启动时 `lui gp, %hi(...)` + `addi gp, gp, %lo(...)` 一次设置 → 后续 `lw/ld aN, off(gp)` 小偏移数据访问(-msmall-data-limit 默认 8 字节以内的全局变量走 gp) - 符号剥离后:从 _start/crt0 序言的 lui+addi 序列计算 gp 实际值 → 反编译器中标注 → 数据引用恢复;有 ELF 时 `readelf -s` 查 `__global_pointer$` 符号值 - gp 缺失时这些访问解析为「未知全局」、数据流散乱——先定 gp 再分析(见坑 2) 5. **ABI 识别(整数/浮点 ABI、a0-a7 约定)**: - 整数约定:参数 a0-a7(x10-x17,第 9 个起栈传)、返回值 a0、被调用者保存 s0-s11(x8-x9、x18-x27);ra=x1、sp=x2、gp=x3、tp=x4、fp=s0(x8);调用边界 sp 16 字节对齐 - ABI 名:RV32 为 ilp32/ilp32f/ilp32d,RV64 为 lp64/lp64f/lp64d——f/d 后缀表示浮点参数走浮点寄存器(F/D 扩展) - 浮点约定:参数 fa0-fa7(f10-f17)、返回值 fa0、被调用者保存 fs0-fs11(f8-f9、f18-f27);硬浮点 ABI 浮点参数进 fa0-fa7,软浮点进 a0-a7 - 判定:以 **ELF `e_flags & EF_RISCV_FLOAT_ABI`(掩码 0x6)** 为准——它是**整个 object** 的 Float ABI 字段。**不能靠浮点指令出现与否反推 soft/hard-float**:`-march=rv64ifd -mabi=lp64` 是合法组合,产物照用 fld/fmul 等硬件浮点指令,但函数参数仍走整数寄存器(实测:lp64 产物用 `fmv.d.x fa5, a1` 把 a0/a1 搬进浮点寄存器,lp64d 产物直接用 `fsd fa0` 收浮点寄存器)。浮点指令密度只能佐证 **ISA 含 F/D/Q 扩展**,不能佐证参数 ABI - **不同 Float ABI 的 object 不能混合链接**:linker 直接报 `cannot link object files with different floating-point ABI`,标准流程下不存在"按单元确认"的软硬混编产物。逆向中若观察到局部调用约定不同,先考虑手写汇编、非标准 ABI、`STO_RISCV_VARIANT_CC`、FFI/thunk、或被错误识别的函数边界 6. **系统调用边界(ecall + a7 号)**: - **Linux 用户态**:`ecall` 触发系统调用,号在 a7(x17)、参数 a0-a5、返回 a0;RISC-V 用 asm-generic 编号(read=63、write=64、openat=56、exit=93、mmap=222)——与 x86/ARM 编号不同,别拿其他架构的号套 - 定位:objdump 搜 `ecall`(0x73),往回找 `li a7, imm` 立即数;Ghidra 反编译中识别系统调用点 - **裸机(无 OS)**:ecall 是自定义语义——M 态固件/SBI 调用(a7=扩展号、a6=功能号)或厂商 monitor,不能按 Linux 号解读;按运行环境查 SBI/厂商 SDK 文档对照 - 行为验证:`qemu-riscv64 -strace` 直接打印系统调用序列(联动 [[re-tracing]]) 7. **生态指纹(工具链/库函数特征)**: - 用户态:glibc(动态、PT_INTERP、__libc_start_main)/ musl(静态常见、入口小)/ 目标板发行版特征(busybox 等) - 裸机:newlib/picolibc(crt0:_start → __libc_init_array → main);厂商 SDK 启动链(ROM → bootloader → app)与分区表结构 - 指令扩展:向量指令(RVV,v0-v31 寄存器组)提示较新工具链;压缩指令密度高多为 -Os 编译 - 特征串定位库函数版本(思路同 [[re-imports]]),交叉库指纹辅助还原 ## 跨域联合 - [[re-binary-core]]:RISC-V ELF 通用初勘/反编译/调试底座([[re-ghidra]]、[[re-ida]]、[[re-gdb]]、[[re-radare2]] 照常使用) - [[re-firmware]]:固件类样本网关路径(re-firmware → re-fw-extract → re-fw-rootfs → 本技能) - [[re-fw-extract]] / [[re-fw-rootfs]]:固件解包与文件系统/配置分析前置 - [[re-fw-emulate]]:整体启动固件或用户态运行 RISC-V 程序(qemu 系列) - [[re-emulation]]:无 qemu 场景用 Unicorn/Qiling 模拟执行(裸机逐指令验证) - [[re-rtos]]:MCU 固件内 RTOS 任务表/TCB 定位后按任务拆解反编译 - [[re-hardware-io]]:JTAG/SWD 真机调试与 flash 读取(OpenOCD + gdb) - [[re-cpp-abi]]:RISC-V 上 C++ 产物 RTTI/异常/虚表恢复 - [[re-variant]]:固件多版本对比与补丁 diff - [[re-tracing]]:qemu -strace / strace 系统调用跟踪验证 ecall 边界 - [[re-sandbox]]:一切动态执行强制前置([[re-analyze/platform-tips]] 默认沙箱原则) - 配套:[[re-patching]](RVC 感知的字节补丁)、[[re-triage]](初勘/架构识别前置) ## 常见坑与陷阱 - **压缩指令流按 4 字节切割全错**:现象——按固定 4 字节步长反汇编,指令全乱、引用全错位;原因——RVC 混编流 2/4 字节混合、2 字节对齐,固定宽度切割会从指令中间起拆;对策——用支持 RVC 的反汇编器(Ghidra/objdump 自动识别),从入口/跳转目标对齐起点,出现合法压缩指令(c.addi/c.j/c.lw)即对齐正确;补丁时指令长度可能 2↔4 字节变化 - **gp 全局指针缺失致数据访问散乱**:现象——大量 `lw/ld off(gp)` 数据访问解不出地址,全局变量互不关联、逻辑碎片化;原因——符号剥离后 `__global_pointer$` 丢失,或反编译器未推导 gp;对策——从 _start/crt0 的 `lui gp`+`addi gp` 序列计算实际 gp 值并在反编译器中标注;有 ELF 时 `readelf -s` 查 `__global_pointer$` - **la/li 伪指令展开形态多变**:现象——同一逻辑不同函数里指令序列不同(单条 addi / lui+addi / auipc+addi),被误判为不同逻辑;原因——汇编器按立即数大小与重定位类型选展开形态,链接器 relax 后还可能缩短成单条;对策——以机器码语义为准(目标地址一致即同一引用),跨工具对照,关键逻辑回指令级核实 - **ecall 系统调用号绑定运行环境、无 OS 时为自定义语义**:现象——裸机固件里 ecall 被当 Linux 系统调用解读,语义全错;原因——ecall 只是「环境调用」指令,号语义由运行环境定义(Linux:a7=系统调用号;裸机:SBI 扩展/厂商 monitor);对策——先确认运行环境(有无 OS、动态库、_start 形态),无 OS 时查 SBI 扩展号或厂商 SDK 文档对照 a7/a6 - **链接器 relax(R_RISCV_RELAX)影响反汇编边界**:现象——反汇编 .o 中间产物与最终二进制指令流对不上,或按未 relax 的布局找地址错位;原因——链接器 relax 把 auipc+addi 收缩为 addi、jal 缩短为 c.j 等,R_RISCV_RELAX 标记的序列尺寸在链接后变小;对策——分析最终链接产物(不是 .o),地址引用以最终二进制为准;改动字节前先确认是否受 relax 影响(重新链接或比对反汇编)