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 bytes — VirtualSize=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:
- Fall between
sym[i].address + sym[i].size and sym[i+1].address
- 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
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
Summary
extend_text()andfind_text_gaps()are implemented on all three platforms (ELF, MachO, PE) but cannot be safely wired intopatch()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 usesadd_segment()to create a dedicated.vmpilot/__VMPILOT/.vmplttsection. This is the right approach for v1.This issue tracks the future work needed to support stealth
.textextension (D15§13.2) in v2.Problem Analysis
All three platforms do binary shifting
text_sec->set_data(bigger_buf)→ ELFIOsave()auto-relayouts entire file, pushing all subsequent sections forwardPT_LOAD file_size/memory_sizemay conflict with ELFIO's relayout; no control over cascading effectsbuf_.insert()at__textend + cascading fixup loopLC_SYMTAB,LC_DYSYMTAB,LC_FUNCTION_STARTS,LC_DATA_IN_CODEoffset updates; section VA (addrfield) not updated — dyld will crashsec->set_data(bigger_buf)grows section dataSizeOfRawData,SizeOfCode, orSizeOfImagein PE Optional Header — Windows loader will reject or AV will flagMeasured slack space in test binaries
__textends exactly where__stubsbeginsotool -lmeasurementVirtualSize=0xF01,RawSize=0x1000(FileAlignment=0x200)MachO has near-zero slack
Within
__TEXTsegment, 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_infoand 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
ELF
MachO
find_text_gaps() improvements
Current
find_text_gaps()scans for filler bytes (0x90/0xCC/0x00) but has false positive risk:Future improvement: Use SDK's
NativeSymbolTable(hasaddress+size+type=FUNCper symbol) to identify inter-function boundaries. Only trust gaps that:sym[i].address + sym[i].sizeandsym[i+1].addresspatch() wire-in strategy (future)
When extend_text is rewritten:
Current test coverage
Action items
extend_text()on all 3 platforms as slack-space-only (no binary shifting)SizeOfCode/SizeOfImageheader updatesNativeSymbolTableintofind_text_gaps()for safe inter-function gap detectionpatch()as primary strategy withadd_segment()fallbackReferences
NativeSymbolTable:sdk/include/core/NativeSymbolTable.hpp