Files
reverse-engineering-anything/docs/specs/native/native-initializers/spec.md
T
0909d89c96 fix(native): list ELF initializers of images with COPY relocations (#10084)
* fix(native): list ELF initializers of images with COPY relocations

inspect-native-initializers reported every init_array and fini_array slot
of a non-PIE executable as unresolved, and ordinary PIE executables as
incomplete, when the image carried a COPY relocation such as the one for
stdout.

The shared dynamic relocation reader had no write extent for COPY or TLS
descriptor relocations, so one such record made pointer coverage unknown.
It now sizes a COPY from the image's own dynamic symbol and a TLS
descriptor as two words. COPY sizes are accepted up to the file byte count
in total, so repeated records cannot multiply consumer work.

The section relocation fallback also treated a record whose destination
has no file bytes, as COPY into .bss does, as an unread table. A linked
image's record now affects only the slots it overlaps in memory.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YT2WLf5G9AnLKF3bZtNLwf

* test(native): cover a COPY relocation against symbol index 0

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YT2WLf5G9AnLKF3bZtNLwf

* fix(native): size section COPY records and keep write extents opt-in

Address two review findings on the COPY relocation change.

The shared dynamic relocation reader yields COPY and TLS descriptor
records as writes only to a consumer that asks for whole extents. The
initializer walk asks. IL2CPP and the Android JNI overlay do not, so
both see these kinds as not decoded, exactly as before this branch.
IL2CPP finds unknown bytes by looking one pointer back from a read, so
a wider write of unknown value was read as stored zeros.

The section fallback takes a COPY record's span from the symbol its
table links to instead of assuming one word. A table counts as not
fully read when that size is not established, or when a record of
unknown extent writes memory that has no file bytes.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YT2WLf5G9AnLKF3bZtNLwf

* refactor(native): size section COPY records in one helper

Move the symbol lookup, the allowance and the unread-table note out of
the record loop, so the loop only asks for the extent a record is known
to write. Behavior is unchanged.

Cover the allowance across two section records, and keep the new test
code within the lint thresholds the base already meets.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YT2WLf5G9AnLKF3bZtNLwf

---------

Co-authored-by: thunderq10 <195775442+thunderq10@users.noreply.github.com>
Co-authored-by: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 23:49:30 +08:00

100 lines
6.1 KiB
Markdown

# Native initializer and finalizer functions
## Problem and outcome
Analysts reviewing a native image need the functions it declares to run before
`main` and at exit, without executing it or opening a disassembler provider.
`inspect_native_initializers` / `inspect-native-initializers` lists the
initializer and finalizer slots of one local ELF or Mach-O image, with each
slot's location, resolved target and how that target was obtained. The
[user guide](../../../guides/native/native-initializers.md) documents the result.
## Requirements
- Accept one local ELF32, ELF64, thin Mach-O or universal Mach-O image. A
universal Mach-O requires `architecture` to select one slice; other inputs
reject it. The CLI resolves relative paths; MCP requires an absolute path.
- ELF sources, in order: `DT_PREINIT_ARRAY`, `DT_INIT`, `DT_INIT_ARRAY`, legacy
`.ctors`, `DT_FINI`, `DT_FINI_ARRAY`, legacy `.dtors`. Without the dynamic tag,
fall back to `SHT_*_ARRAY` sections or `.init_array*` / `.fini_array*` /
`.preinit_array` names. Mach-O sources are sections typed
`S_MOD_INIT_FUNC_POINTERS`, `S_INIT_FUNC_OFFSETS` and
`S_MOD_TERM_FUNC_POINTERS`.
- Resolve values through RELATIVE and RELR dynamic relocations, then section
REL / RELA records for slots no dynamic relocation covers, then the stored
word when relocation coverage is known. Mach-O uses chained-fixup rebases,
`LC_DYLD_INFO` rebases (reported separately), `MH_OBJECT` section
relocations and `__init_offsets` image-base offsets. Anything else is
`unresolved`, never a stored zero.
- Relocation coverage is per slot. A slot that a dynamic or section relocation
only partly overwrites or writes more than once is `unresolved`, and so is an
absolute relocation against an `STT_GNU_IFUNC` resolver. On 32-bit ARM the
Thumb bit is cleared for target section and file-offset lookup only. A slot with no
relocation found is `unresolved` when a dynamic table, or a section table
that could reach it, was not fully read; such a `.ctors` slot is not dropped
as a sentinel. A `COPY` relocation covers the bytes of its symbol's size and
a TLS descriptor two words. The size comes from the dynamic symbol table for
a dynamic record and from the table's linked symbols for a section record of
a linked image; an unread size leaves coverage unknown. A linked image's
section record that writes memory with no file bytes covers only the slots
it overlaps there when its extent is established, by a fixed width or a
`COPY` size, and otherwise leaves its table not fully read. A
linked image's non-`SHF_ALLOC` relocation sections were
applied at link time and are not reapplied. A Mach-O stored pointer word is
used only when no undecoded bind stream or external relocation could
replace it.
- Relocatable objects (`ET_REL`) report section-relative offsets and say so.
- `.ctors` / `.dtors` sentinels are 0 and all ones at the image's pointer width;
they are omitted and counted in a limitation.
- `DT_INIT` / `DT_FINI` have no slot pointer: their slot is the dynamic value
field, and `slot_va` / `slot_section` are null when that field is outside
every load segment.
- Names are exact untrusted file text marked `observed-untrusted`. A target is
named only by a defined symbol whose value equals it, or by the import name
of an unresolved symbolic relocation or chained bind. An unreadable name
(missing string table or offset outside it) is reported as no name and makes
the report `incomplete`.
- Bounds: 64 MiB default file budget (512 MiB maximum); 65536 slots in total;
65536 symbols across all ELF symbol tables; 65536 symbols across all
Mach-O `LC_SYMTAB` commands; 1048576 section relocation records across all
relocation sections; 1024-byte names. Reaching a bound makes the report
`incomplete` with a limitation.
- Legacy Mach-O bind lookup permits at most 4096 candidate runs per query
and twice the file byte count across image queries. Exhaustion leaves
affected pointers unresolved; stream parsing and slot batches yield.
- Limitations are bounded: at most 24 distinct image observations, repeated
per-row reasons reported once with a count, and at most 40 lines in total.
- Section lookups for slot and target addresses use an index built once per
image, so their cost does not grow with headers times slots.
- Paging uses `offset` / `limit` (default 64, maximum 4096) over a stable
ordered index. `coverage.examined_entries` counts every slot read, including
omitted sentinels; `total_entries` is set only when the inventory is complete.
- Malformed headers, multiple `PT_DYNAMIC` segments and truncated dynamic
sections fail without partial success. Cancellation is honored during slot
collection, the section relocation walk and the dynamic relocation walk,
which yields to other work at the shared reader's step boundaries.
## Acceptance
Parser tests cover ELF64 PIE (RELA, RELR, imports, `DT_INIT` / `DT_FINI`),
ELF32 REL in-place addends, `ET_REL` section relocations and sentinels,
unsupported relocation machines, slot, symbol and relocation budgets,
over-long names, tallied repeated observations, a near-maximal section table
with a full slot budget, cancellation in the relocation walks, skipped section
relocation tables, partly overwritten slots, `COPY` and TLS descriptor extents,
section `COPY` records sized by their symbol, section records that write
memory without file bytes, retained link-time
relocations, unreadable names, Mach-O chained fixups, `LC_DYLD_INFO` rebases, 32-bit images
with undecoded binds, `MH_OBJECT` section relocations, `__init_offsets`, the
shared and repeated `LC_SYMTAB` budget, and universal slices. CLI acceptance, filesystem boundary
and MCP boundary tests exercise the public workflows and Evidence output.
## Out of scope
Executing the image or proving that a listed address runs; runtime ordering
(file order is reported); disassembling initializer bodies; PE/COFF TLS
callbacks; Android packed relocations and machines other than x86-64, i386,
AArch64 and 32-bit ARM for relocation decoding; Mach-O fixup decoding for
images other than little-endian 64-bit arm64 and x86-64, and legacy
`LC_DYSYMTAB` external relocations.