--- name: re-nim type: atomic description: > Nim 编译产物逆向:运行时识别、NimString 结构、异常与 GC 路径。 触发词:Nim逆向、nim、NimString、NimMain、nim 产物。 capabilities: [lang-runtime-analysis] --- # Nim 逆向 ## 何时使用 / 何时不用 - 用:Nim 产物(`NimMain` / GC 符号、NimString 结构特征),需要还原字符串逻辑、异常路径、GC/所有权关系 - 用:Nim/C 混合产物中区分 Nim 侧代码(按符号来源分组后 Nim 侧进本技能路径) - 不用:纯 C/C++ 产物(走 [[re-cpp-abi]] / [[re-binary-core]] 通用路径) - 不用:只需函数逻辑(直接反编译技能) ## 工具准备 ### readelf / llvm-nm(符号与节分析) - 安装与验证见 [[re-cpp-abi]] 工具准备 - Nim 产物以 ELF 为主(Linux 默认 C 后端);macOS 用 llvm-nm ### Ghidra / IDA(反编译底座) - 安装与验证见 [[re-ghidra]] / [[re-ida]];Nim 符号(NimMain/NimStringV2 等)导入后直接可读 ### nim 编译器(可选,对照编译) - Linux: `apt install nim` / `dnf install nim` / `pacman -S nim`;macOS: `brew install nim`;Windows: choosenim/官方安装器 - 验证: `nim -v`;用途: 同版本编译对照产物,验证字符串布局/GC 符号形态(版本差异见 [[layout]]) ### xxd + Python struct(字节级核对) - 系统自带(`xxd`);Python 3 自带 `struct` - 用途: 符号/布局输出异常时按偏移直接解析 NimString 与对象结构(示例见 [[examples]]) ## 操作步骤 按顺序执行,每步产物存档(路径 + sha256,见 [[re-triage]])。 1. **产物识别**: ```sh readelf -s sample | grep -iE 'NimMain|nimGC|NimString' | head # ELF;Mach-O 用 llvm-nm readelf -s sample | grep -iE 'nimIncRef|nimDecRef|rawNewString|eqStrings' | head ``` - Nim 特征:`NimMain`(入口链)、GC 符号(refc 的 `nimGC_*` / orc 的 `nimIncRefCyclic` 等)、`NimStringV2`/`NimStringDesc` 结构类型 - 注意:release 构建下 GC 符号常被内联/消除(见坑 1),靠 `NimMain` 与字符串函数兜底 - 入口链:C `main` → `NimMain` → `NimMainInner` → `NimMainModule`(模块初始化)→ 业务 main - 模块初始化顺序按依赖拓扑(import 关系):`NimMainModule` 内的初始化段先跑被依赖模块——在初始化段断点/下钻时按调用序对照 import 链 2. **字符串与序列结构(先判 GC 模式)**: - **orc/arc**(2.x 默认 orc):`NimStringV2 = {len: int, p: ptr NimStrPayload}`;`NimStrPayload = {cap: int, data: 内联字符数组}`——字符串是"len + 堆上 payload 指针",cap 高位带字面量标记位(见 [[layout]]) - **refc**(旧默认):`NimStringDesc = {len, reserved, data[]}`——字符内联在结构体里 - 定位:`rawNewString`(1.x 与 2.x 均为此 importc 名)分配调用点 → 结构布局 → 字符串操作函数(`eqStrings` 等) - 分析:字符串比较点是关键逻辑(校验/协议/命令分发)——`eqStrings` 调用点即字符串相等判断 - 序列(seq)与 string 同构:v2 布局同为 `len + payload 指针`(payload 带 cap),refc 同为 `len/reserved + 内联`——按同一判别表处理 - 内存视图(v2 布局,64 位): ``` 栈/对象内: len(int64) | p(ptr) ───────────────┐ 堆上 payload: cap(int64, bit62=字面量标记) | data[cap+1] ←┘ ``` 字符串本体只有 16 字节句柄,内容在独立堆块——分析对象结构时按句柄跳转,别在对象里找字符数据 3. **内存中定位字符串(动态/转储场景)**: ```python # 在内存转储中按 v2 布局找字符串:cap 是 8 字节对齐的容量值,data 后跟可打印 ASCII # 候选: 8 字节对齐的 cap + len 匹配的 data → 反推 NimStringV2 起点 ``` 字符串内容在堆上 payload 里,栈上只有 len+指针;先按 `cap` 前缀特征定位 payload,再往回找引用它的 NimStringV2([[examples]] 有完整示例)。 4. **异常与 raises 路径**: - Nim 2.2.x(Linux x86-64 默认)异常走 goto 式异常表,**无 setjmp 符号**;raise 路径经 `raiseExceptionEx`/`raiseExceptionAux` - 老默认(`--exceptions:setjmp`,Nim 2.0 及以前)用 setjmp/longjmp:`nimSetjmp` 符号可见 - 定位:`raiseExceptionEx` 调用点 → 异常对象分配与消息 → catch 分支(异常表驱动) - 分析:异常路径揭示输入校验与失败处理,比正常路径更早暴露边界条件 5. **GC 与引用计数(按 GC 模式分叉)**: - refc:引用计数 + 周期收集——`nimGC_*`/`nimGCunref`/`nimIncRef`/`nimDecRef` 调用点 - orc/arc:ARC 语义——`nimIncRefCyclic`/`nimDecRefIsLastCyclicDyn` 等;显式 inc/dec 少,释放由编译期插入 - 分析:GC 调用点帮助识别对象生命周期与所有权(配合字符串结构);release 下内联后改用分配/释放边界推断 6. **C 混合编译边界**: - `{.compile:}` / `{.importc:}` 混合时 Nim 与 C 符号并存:Nim 侧符号带模块前缀(`hello__u8` 形态)与运行时(NimMain/NimString),C 侧符号无 - 边界处是逻辑入口:Nim 业务逻辑在 NimMain 调用链内侧,C 库调用经导入表 7. **stripped 产物兜底**: ```sh strings -n 6 sample | grep -E '@m.*\.nim\.c' | head # C 生成缓存文件名(含模块名,release 也嵌入) strings -n 6 sample | grep -iE 'fatal\.nim|Exception' | head ``` - `NimMain` 等被 strip 后按特征串(`@m<模块>.nim.c`、std 运行时源文件名)与运行时行为识别([[re-triage]] 初勘兜底);业务源码名不嵌入二进制,别指望它 ## 跨域联合 - [[re-binary-core]] 网关:本技能归属(选择树「Nim 产物」分支) - [[re-analyze/analysis-contract]]:符号表按数据契约传递 - [[re-cpp-abi]]:C 混合侧与无 RTTI 判别参考 - [[re-triage]]:初勘兜底(strip 产物按特征串识别) ## 常见坑与陷阱 - **GC 版本差异(refc/orc)**:现象——找不到引用计数调用;原因——orc 无显式 inc/dec(ARC 语义)且 release 下 GC 符号内联;对策——先按 Nim 版本与 GC 模式确认再分析,debug/refc 构建符号更全 - **NimString 布局随 GC/版本变化**:现象——按 `len/reserved/data` 手写解析器读 2.x 产物全错;原因——2.x orc 默认是 `NimStringV2{len, p}`(payload 带 cap),`len/reserved/data` 内联是 refc 的 NimStringDesc(1.x 默认);对策——先确认产物 GC 模式(见 [[layout]] 判别表)再选布局:debug 构建看 GC 符号(refc 有 `nimGCunref`,orc 有 `nimIncRefCyclic`),release 构建看分配函数命名与 cap 字段是否出现 - **导出符号被 strip**:现象——无 NimMain/NimString 符号;原因——strip 处理;对策——按特征字符串/运行时行为识别(步骤 6 兜底) - **C 混合编译**:现象——Nim/C 符号混杂;原因——`{.compile:}` 混合;对策——按符号来源分组,Nim 侧进本技能路径 - **字符串比较点误判**:现象——关键校验被当普通比较;原因——eqStrings 包装;对策——追踪 Nim 字符串函数调用点定位比较逻辑 - **异常实现代际误判**:现象——按老思路找 nimSetjmp 找不到;原因——2.2+ Linux amd64 默认 goto 式异常(无 setjmp);对策——无 nimSetjmp 时沿 `raiseExceptionEx` 与异常表定位 catch,别当"无异常处理" - **release 内联导致符号稀疏**:现象——debug 能看到的 nimGC_*/eqStrings 在 release 里消失;原因——-d:release 内联;对策——release 产物按行为特征(分配/释放边界、字符串函数调用模式)分析,符号表只是线索不是依据 - **字符串内容在堆上,栈上只有句柄**:现象——在栈上按内容搜索字符串找不到;原因——v2 布局的字符数据在独立 payload 堆块里,栈上只有 `len + p` 两个字段;对策——动态/转储场景先按 cap 前缀特征定位 payload 块,再回找引用它的句柄([[examples]] 内存定位示例) - **32 位产物结构尺寸减半**:现象——64 位布局表套 32 位产物偏移全错;原因——NI 在 32 位平台是 4 字节(NimStringV2 为 8 字节而非 16,NimStrPayload cap 为 4 字节);对策——解析前先确认产物位数,按 4/8 字节 NI 选结构尺寸 - **非 PIE 老构建入口即固定地址**:现象——`readelf -h` 的 e_entry 是绝对地址(如 0x401xxx),在内存里直接对得上;原因——非 PIE 构建(老默认);对策——入口链定位用符号(NimMain 三连)不依赖 PIE 与否,但换算地址时按 e_type 区分