When to use / when not to use
- Use: Nim artifacts (
NimMain/ GC symbols, NimString structural features), need to recover string logic, exception paths, GC/ownership relationships - Use: in mixed Nim/C artifacts, distinguish Nim-side code (after grouping by symbol origin, Nim-side goes through this skill path)
- Do not use: pure C/C++ artifacts (go through the general [[re-cpp-abi]] / [[re-binary-core]] path)
- Do not use: only need function logic (use decompilation skill directly)
Tool preparation
readelf / llvm-nm (symbol and section analysis)
- Installation and verification see [[re-cpp-abi]] tool preparation
- Nim artifacts are mainly ELF (Linux default C backend); on macOS use llvm-nm
Ghidra / IDA (decompilation base)
- Installation and verification see [[re-ghidra]] / [[re-ida]]; Nim symbols (NimMain/NimStringV2 etc.) are directly readable after import
nim compiler (optional, for comparison compilation)
- Linux:
apt install nim/dnf install nim/pacman -S nim;macOS:brew install nim; Windows: choosenim/official installer - Verify:
nim -v; purpose: compile a comparison artifact with the same version to verify string layout/GC symbol form (version differences see [[layout]])
xxd + Python struct (byte-level verification)
- System built-in (
xxd); Python 3 built-instruct - Purpose: when symbol/layout output is abnormal, parse NimString and object structures directly by offset (examples see [[examples]])
Operation steps
Execute in order, archive each step's output (path + sha256, see [[re-triage]]).
-
Artifact identification:
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 features:
NimMain(entry chain), GC symbols (refc'snimGC_*/ orc'snimIncRefCyclicetc.),NimStringV2/NimStringDescstruct types - Note: in release builds GC symbols are often inlined/eliminated (see pitfall 1), fall back on
NimMainand string functions - Entry chain: C
main→NimMain→NimMainInner→NimMainModule(module initialization) → business main - Module initialization order follows dependency topology (import relationships): initialization segments within
NimMainModulerun dependent modules first — when breaking/drilling down in the initialization segment, compare against the import chain by call order
- Nim features:
-
String and sequence structures (determine GC mode first):
- orc/arc(2.x default orc):
NimStringV2 = {len: int, p: ptr NimStrPayload};NimStrPayload = {cap: int, data: 内联字符数组}— strings are "len + heap payload pointer", cap high bits carry literal marker bits (see [[layout]]) - refc(old default):
NimStringDesc = {len, reserved, data[]}— characters are inlined in the struct - Locate:
rawNewString(this importc name is the same in both 1.x and 2.x) allocation call sites → struct layout → string operation functions (eqStringsetc.) - Analysis: string comparison points are key logic (validation/protocol/command dispatch) —
eqStringscall sites are string equality checks - Sequences (seq) are isomorphic to string: v2 layout is also
len + payload 指针(payload carries cap), refc is alsolen/reserved + 内联— handle by the same discrimination table - Memory view (v2 layout, 64-bit):
The string body only has a 16-byte handle; the content is in a separate heap block—when analyzing the object structure, jump by the handle; don't look for character data inside the object.栈/对象内: len(int64) | p(ptr) ───────────────┐ 堆上 payload: cap(int64, bit62=字面量标记) | data[cap+1] ←┘
- orc/arc(2.x default orc):
-
Locating strings in memory (dynamic/dump scenarios):
# 在内存转储中按 v2 布局找字符串:cap 是 8 字节对齐的容量值,data 后跟可打印 ASCII # 候选: 8 字节对齐的 cap + len 匹配的 data → 反推 NimStringV2 起点The string content is in the payload on the heap; on the stack there is only len+pointer; first locate the payload by the
capprefix signature, then trace back to find the NimStringV2 that references it ([[examples]] has a complete example). -
Exceptions and raises paths:
- Nim 2.2.x (Linux x86-64 default) exceptions use a goto-style exception table,no setjmp symbol; the raise path goes through
raiseExceptionEx/raiseExceptionAux - The old default (
--exceptions:setjmp, Nim 2.0 and earlier) uses setjmp/longjmp:nimSetjmpsymbol visible - Locate:
raiseExceptionExcall site → exception object allocation and message → catch branch (exception-table driven) - Analysis: exception paths reveal input validation and failure handling, exposing boundary conditions earlier than normal paths
- Nim 2.2.x (Linux x86-64 default) exceptions use a goto-style exception table,no setjmp symbol; the raise path goes through
-
GC and reference counting (branching by GC mode):
- refc: reference counting + cycle collection—
nimGC_*/nimGCunref/nimIncRef/nimDecRefcall site - orc/arc: ARC semantics—
nimIncRefCyclic/nimDecRefIsLastCyclicDynetc.; few explicit inc/dec, release is inserted by the compiler - Analysis: GC call sites help identify object lifetime and ownership (together with the string structure); under release, after inlining, infer from allocation/free boundaries instead
- refc: reference counting + cycle collection—
-
C mixed-compilation boundary:
{.compile:}/{.importc:}When mixed, Nim and C symbols coexist: Nim-side symbols carry a module prefix (hello__u8form) and the runtime (NimMain/NimString); C-side symbols have no- The boundary is the logical entry point: Nim business logic is inside the NimMain call chain, and C library calls go through the import table
-
Fallback for stripped artifacts:
strings -n 6 sample | grep -E '@m.*\.nim\.c' | head # C 生成缓存文件名(含模块名,release 也嵌入) strings -n 6 sample | grep -iE 'fatal\.nim|Exception' | headNimMainetc., after being stripped, identify by signature strings (@m<模块>.nim.c, std runtime source file names) and runtime behavior ([[re-triage]] initial-triage fallback); business source file names are not embedded in the binary, so don't count on them
Cross-domain joint
- [[re-binary-core]] gateway: this skill belongs here (selection tree "Nim artifacts" branch)
- [[re-analyze/analysis-contract]]: symbol table passed according to the data contract
- [[re-cpp-abi]]: reference for the C mixed side and RTTI-free discrimination
- [[re-triage]]: initial-triage fallback (stripped artifacts identified by signature strings)
Common pitfalls and traps
- GC version differences (refc/orc): symptom—reference counting calls cannot be found; cause—orc has no explicit inc/dec (ARC semantics) and under release the GC symbols are inlined; countermeasure—first confirm by Nim version and GC mode before analyzing; debug/refc builds have more complete symbols
- NimString layout changes with GC/version: symptom—using the
len/reserved/datahandwritten parser reads 2.x artifacts all wrong; cause—2.x orc default isNimStringV2{len, p}(payload has cap),len/reserved/datainline is refc's NimStringDesc (1.x default); countermeasure—first confirm the artifact's GC mode (see the [[layout]] discrimination table) then choose the layout: for debug builds look at GC symbols (refc hasnimGCunref, orc hasnimIncRefCyclic), for release builds look at the allocation function naming and whether the cap field appears - Exported symbols get stripped: Symptom—no NimMain/NimString symbols; Cause—strip processing; Countermeasure—identify by characteristic strings/runtime behavior (step 6 fallback)
- C mixed compilation: Symptom—mixed Nim/C symbols; Cause—
{.compile:}mixed; Countermeasure—group by symbol origin, the Nim side goes through this skill path - Misjudgment at string comparison points: Symptom—key checks treated as ordinary comparisons; Cause—eqStrings wrapper; Countermeasure—trace Nim string function call sites to locate the comparison logic
- Misjudgment of exception implementation generation: Symptom—looking for nimSetjmp the old way finds nothing; Cause—2.2+ Linux amd64 defaults to goto-style exceptions (no setjmp); Countermeasure—when nimSetjmp is absent, locate catch along
raiseExceptionExand the exception table, don't treat it as "no exception handling" - release inlining causes sparse symbols: Symptom—nimGC_*/eqStrings visible in debug disappear in release; Cause—-d:release inlining; Countermeasure—analyze release artifacts by behavioral characteristics (allocation/deallocation boundaries, string function call patterns); the symbol table is only a clue, not the basis
- String content is on the heap, only a handle is on the stack: Symptom—searching for a string by content on the stack finds nothing; Cause—in the v2 layout the character data is in a separate payload heap block, and on the stack there are only
len + ptwo fields; Countermeasure—in dynamic/dump scenarios, first locate the payload block by the cap prefix characteristic, then trace back to the handle referencing it ([[examples]] memory location example) - 32-bit artifact structure sizes are halved: Symptom—applying the 64-bit layout table to a 32-bit artifact makes all offsets wrong; Cause—NI is 4 bytes on 32-bit platforms (NimStringV2 is 8 bytes instead of 16, NimStrPayload cap is 4 bytes); Countermeasure—confirm the artifact's bitness before parsing, and choose structure sizes based on 4/8-byte NI
- Non-PIE old build entry is a fixed address: Symptom—
readelf -h's e_entry is an absolute address (e.g. 0x401xxx), which matches directly in memory; Cause—non-PIE build (old default); Countermeasure—use symbols for entry chain location (the NimMain triplet) regardless of PIE, but distinguish by e_type when converting addresses