Skip to content

objfile: fallback to modern moduledata layouts when runtimeVersion is unknown - #102

Merged
stevemk14ebr merged 1 commit into
mandiant:masterfrom
mlhaigh:fix-modern-go-support
Sep 14, 2026
Merged

stevemk14ebr merged 1 commit into
mandiant:masterfrom
mlhaigh:fix-modern-go-support

Conversation

@mlhaigh

@mlhaigh mlhaigh commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary of Changes

When a Go binary has its build information stripped or obfuscated (e.g., using tools like garble that remove runtime.buildVersion), runtimeVersion is detected as "unknown". Previously in objfile/objfile.go, GoReSym fell back to using layoutVersion directly ("1.20" for all Go 1.20+ binaries).

While Go 1.20 through Go 1.25 share identical moduledata struct offsets, Go 1.26 introduced epclntab, shifting subsequent fields (Textsectmap, Typelinks, and ITablinks) by 8 bytes on 64-bit and 4 bytes on 32-bit (e.g., Textsectmap moved from offset 328 to 336 on 64-bit).

Because runtimeVersion was defaulted strictly to "1.20", GoReSym selected the 1.20 layout, causing candidate moduledata validation in validateAndConvertModuleData to fail on stripped Go 1.26 binaries, resulting in {"error": "Failed to parse file: no valid pclntab found"}.

Solution

In objfile/objfile.go, when runtimeVersion is unknown and layoutVersion is "1.20", ModuleDataTable now tests candidate modern layouts ("1.20" followed by "1.26") during candidate validation. Testing "1.20" first ensures backwards compatibility and avoids false-positive matches on Go 1.20–1.25 binaries, while cleanly falling back to "1.26" for stripped Go 1.26 binaries. In addition, candidate ignorelist handling was cleaned up to avoid duplicate entries during candidate layout evaluation.

Testing & Verification

  1. Unit Tests: Full test suite passes cleanly:
    • go test -v -count=1 ./objfile: 20 test suites, 70 subtests (90 PASS assertions, 0 failures).
    • go test -v -count=1 ./...: 111 passed, 7 skipped, 0 failures.
  2. Obfuscated / Stripped Corpus Validation:
    • Verified across a 40-binary test corpus of Go 1.26 binaries built under standard, stripped, and Garble obfuscation modes (garble, garble -literals, garble -tiny) across Linux amd64, Windows amd64, and Windows 386.
    • Prior to this change, Garble-obfuscated Go 1.26 binaries failed (0/15 passed) with Failed to parse file: no valid pclntab found.
    • With this change, all 40 binaries parse successfully (40/40 passed, 100%) without requiring manual -v flag overrides.

@mlhaigh
mlhaigh force-pushed the fix-modern-go-support branch from 046aefa to 8492eab Compare September 11, 2026 15:02

@stevemk14ebr stevemk14ebr left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the contribution, small changes requested

Comment thread objfile/objfile.go
Comment thread objfile/objfile.go
Comment thread objfile/objfile.go Outdated
Comment thread objfile/objfile.go Outdated
Comment thread objfile/objfile.go Outdated
Comment thread objfile/objfile.go Outdated
Comment thread objfile/objfile.go Outdated
… unknown

When a Go binary has its build information stripped or obfuscated (e.g. via
Garble), runtimeVersion is detected as 'unknown'. Previously, GoReSym fell back
to using layoutVersion directly (which is '1.20' for all Go 1.20+ binaries).

While Go 1.20 through 1.25 share the same moduledata struct offsets, Go 1.26
introduced epclntab, shifting subsequent fields (textsectmap, typelinks,
itablinks) by 8 bytes on 64-bit and 4 bytes on 32-bit. As a result, candidate
validation failed on stripped/obfuscated Go 1.26 binaries when defaulting to
the 1.20 layout.

When runtimeVersion is unknown and layoutVersion is '1.20', this change tests
candidate layouts ('1.20' followed by '1.26') during moduledata validation.
This enables stripped/obfuscated Go 1.26 binaries to parse successfully
without requiring a manual -v flag override, while preserving exact behavior
for Go 1.20-1.25 binaries.
@mlhaigh
mlhaigh force-pushed the fix-modern-go-support branch from 8492eab to e7b45f5 Compare September 14, 2026 16:32
@mlhaigh

mlhaigh commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review and suggestions @stevemk14ebr!

I've updated the PR with both changes:

  • Renamed the candidate slice and loop variable to runtimeVersions / runtimeVersion so the naming stays consistent with the rest of the codebase.
  • Preserved the ignorelist = newIgnorelist logic across all validation branches to maintain proper handling of invalid/decoy candidate tables.

All unit tests (./...) pass cleanly and verified. Ready for another look!

@stevemk14ebr
stevemk14ebr merged commit 722897e into mandiant:master Sep 14, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants