Skip to content

Loader: extend_text() does binary shifting — must rewrite as slack-space-only before wiring to patch() #9

@scc-tw

Description

@scc-tw

Summary

extend_text() and find_text_gaps() are implemented on all three platforms (ELF, MachO, PE) but cannot be safely wired into patch() in their current form. All three implementations perform binary shifting (growing .text and pushing subsequent data forward), which corrupts binary metadata.

Current stage: patch() correctly uses add_segment() to create a dedicated .vmpilot / __VMPILOT / .vmpltt section. This is the right approach for v1.

This issue tracks the future work needed to support stealth .text extension (D15§13.2) in v2.

Problem Analysis

All three platforms do binary shifting

Platform Mechanism What breaks
ELF text_sec->set_data(bigger_buf) → ELFIO save() auto-relayouts entire file, pushing all subsequent sections forward Our manually-set PT_LOAD file_size/memory_size may conflict with ELFIO's relayout; no control over cascading effects
MachO buf_.insert() at __text end + cascading fixup loop Misses LC_SYMTAB, LC_DYSYMTAB, LC_FUNCTION_STARTS, LC_DATA_IN_CODE offset updates; section VA (addr field) not updated — dyld will crash
PE sec->set_data(bigger_buf) grows section data Does NOT update SizeOfRawData, SizeOfCode, or SizeOfImage in PE Optional Header — Windows loader will reject or AV will flag

Measured slack space in test binaries

Binary Gap after .text Source
MachO ARM64 (basic_binary.Darwin.arm64) 0 bytes__text ends exactly where __stubs begins otool -l measurement
PE x86-64 (basic_binary.Windows.x86_64.exe) 255 bytesVirtualSize=0xF01, RawSize=0x1000 (FileAlignment=0x200) PE header parsing
ELF x86-64 Typically 0–few dozen bytes of page alignment padding Linker-dependent

MachO has near-zero slack

Within __TEXT segment, Apple's linker packs sections (__text, __stubs, __stub_helper, __cstring, __unwind_info) back-to-back. The only slack is at the very end of the segment to the page boundary — but that's shared with __unwind_info and touching it breaks compact unwind.

Correct Future Design: Slack Space Utilization (no shifting)

Per previous design discussion, extend_text() should be strictly redefined as "use existing padding without moving anything":

PE

slack = SizeOfRawData - VirtualSize
if (payload_size > slack) return false  // fallback to add_segment
// Write into existing padding zone
// Update: VirtualSize += payload_size, SizeOfCode += payload_size
// Do NOT touch SizeOfRawData or any other section

ELF

slack = next_section.sh_offset - (.text.sh_offset + .text.sh_size)
if (payload_size > slack) return false
// Direct byte write into padding zone (NOT set_data with larger buffer)
// Update: .text sh_size, PT_LOAD memsz/filesz

MachO

slack = segment.fileoff + segment.filesize - last_section_file_end
if (payload_size > slack) return false
// Write into segment tail padding
// Update last section size or add zero-overhead sub-section

find_text_gaps() improvements

Current find_text_gaps() scans for filler bytes (0x90/0xCC/0x00) but has false positive risk:

  • 0x00 runs may be embedded data (jump tables, literals), not gaps
  • No cross-reference check (gap might be a branch/relocation target)

Future improvement: Use SDK's NativeSymbolTable (has address + size + type=FUNC per symbol) to identify inter-function boundaries. Only trust gaps that:

  1. Fall between sym[i].address + sym[i].size and sym[i+1].address
  2. Are filled with 0xCC or 0x90 (not 0x00)

patch() wire-in strategy (future)

When extend_text is rewritten:

1. Try extend_text(payload, slack_only=true)
2. If fails: try find_text_gaps() for scatter placement
3. If fails: fallback to add_segment() (current behavior, always works)

Current test coverage

Platform extend_text tests find_text_gaps tests MachO tests
ELF 6 tests 3 tests
PE 1 test 2 tests
MachO 0 tests included in E2E 0 extend_text tests

Action items

  • Rewrite extend_text() on all 3 platforms as slack-space-only (no binary shifting)
  • Add boundary checks: if payload > slack → return error (not silently corrupt)
  • Fix ELF padding filler from 0x00 to 0xCC (INT3)
  • Fix MachO: remove cascading fixup logic entirely
  • Fix PE: add SizeOfCode / SizeOfImage header updates
  • Add MachO extend_text tests
  • Integrate NativeSymbolTable into find_text_gaps() for safe inter-function gap detection
  • Wire into patch() as primary strategy with add_segment() fallback
  • Consider whether current (broken) extend_text code should be removed or gated behind a flag to prevent accidental use

References

  • D15§13.2: "A dedicated section is a neon sign for static analysis tools. Code cave injection: stubs placed in alignment padding, function gaps, and dead code regions within .text"
  • D13§D1: Bytecode injection location options
  • SDK NativeSymbolTable: sdk/include/core/NativeSymbolTable.hpp

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions