* Add -strings flag and ExtractMetadata field - Add Strings []string to ExtractMetadata struct - Add -strings command-line flag for string extraction - Update main_impl and main_impl_tmpfile signatures to accept printStrings parameter - Add placeholder string extraction logic with TODO marker - Update printForHuman to display extracted strings section - Verified flag appears in help and outputs correctly in both JSON and human format Part of #45 * Implement Go string extraction algorithm - Create objfile/strings.go with core extraction logic - Implement FLOSS-based string internment table detection - Add string candidate scanning (pointer + length pairs) - Implement findLongestMonotonicRun() for pattern detection - Add UTF-8 validation and printability filtering - Minimum string length: 4 characters, 80% printable - Add helper methods to elfFile: getSections(), is64Bit(), isLittleEndian() - Update main.go to call file.ExtractStrings() instead of placeholder - Tested with testproject/testproject: extracts 512 strings successfully - Extracts real Go strings: type names, runtime symbols, error messages Based on FLOSS floss/language/go/extract.py algorithm Part of #45 * Update README with -strings flag documentation - Add -strings flag to available flags list - Add Strings field to example JSON output - Document purpose: extract embedded Go strings from binary Part of #45 * Add tests validated against FLOSS output Per maintainer request, added comprehensive test suite: - strings_floss_test.go: Validates GoReSym against FLOSS reference output * 99.2% match rate (648/653 strings match FLOSS) * Uses FLOSS output from testproject.exe as ground truth * Reference saved in testdata/floss_reference.txt - strings_test.go: Additional unit tests for: * ELF and PE binary string extraction * Monotonic run detection algorithm * String filtering (printability, minimum length) - pe.go: Added helper methods (getSections, is64Bit, isLittleEndian) to enable string extraction from PE binaries All tests pass. * Address PR #77 review comments - Convert getSections() to iterateSections() using callback pattern to avoid memory pressure - Add Strings field to GoReSym.proto for external parsers - Implement iterateSections() for Mach-O format (previously missing) Changes requested by @stevemk14ebr in review: 1. Memory optimization: Replace array-based section loading with generator pattern 2. Proto definition: Add 'repeated string strings = 13' field 3. Mach-O support: Add missing iterateSections() implementation * Fix test failures and expand string extraction testing per PR #77 feedback - Fixed 4 test compilation errors by adding missing printStrings parameter to main_impl() calls - Added comprehensive TestStringExtraction function with 7 test cases covering Linux/macOS/Windows binaries - Implemented isPrintable() helper for ASCII validation (range 32-126) * Align string extraction with FLOSS algorithm per PR #77 review Rewrite string extraction to match FLOSS (extract.py) logic: - Sort candidates by address (not length) to fix monotonic run detection - Add image VA range and max section size filtering for candidates - Use candidate (pointer, length) pairs for direct extraction from blob - Replace 80% printable threshold with 100% fully printable check - Fix PE section addresses to include ImageBase for correct VA comparison Results: 648 strings extracted from PE test binary, 100% match with FLOSS. * Run string extraction on test build files * Extend main test to cover all new versions * Fix string extraction for old Windows binaries and add length sanity check 1. Add .text to data sections for old Go Windows binaries (1.7-1.10) that store strings in the code section instead of .rdata 2. Add maxReasonableStringLength (64KB) to filter out garbage candidates with huge lengths that cause incorrect blob boundary detection 3. Update TestIsDataSection to reflect .text being a valid data section Fixes string extraction failures on: - Old Windows PE binaries (Go 1.7-1.10): 0 -> 256 strings - Modern Windows: 574 strings - macOS: 284 strings All tests pass including TestWeirdBins on 15 real binaries. * Fix test failures for Go 1.5/1.6 and Go 1.22 macOS; fix build script typo 1. main_test.go: Skip Strings check for Go 1.5/1.6 Pre-SSA C-based linker does not produce length-sorted string blobs, so findLongestMonotonicRun never reaches the minimum threshold of 10. Pattern mirrors the existing interface-parsing guard. 2. objfile/strings.go: Handle prevNull == -1 in findStringBlobRange Apple's linker on newer Go/macOS (1.22+) packs sections without leading padding, so no null bytes exist before the first string candidate. bytes.LastIndex returns -1 in that case; treat it as offset 0 instead of bailing out with nil. 3. objfile/macho.go: Add missing is64Bit/isLittleEndian for machoFile elfFile and peFile already implement these interface methods; machoFile was the only rawFile implementation without them. Detect CPU type (CpuAmd64/Arm64 = 64-bit) and byte order from the macho.File struct. 4. build_test_files.sh: Fix $ver typo -> $GO_VER on mkdir line The directory was never created before Docker ran, causing Go 1.5 builds to produce no output. Now all 12 binaries are built for every version including 1.5 and 1.6. --------- Co-authored-by: Stephen Eckels <stevemk14ebr@gmail.com>
7.8 KiB
GoReSym
GoReSym is a Go symbol parser that extracts program metadata (such as CPU architecture, OS, endianness, compiler version, etc), function metadata (start & end addresses, names, sources), filename and line number metadata, and embedded structures and types. This cross platform program is based directly on the open source Go compiler and runtime code.
The upstream Go runtime code is extended to handle:
- stripped binaries
- malformed unpacked binaries, such as from UPX
- binaries that split single data ranges across multiple sections
- the location of the
moduledatastructure
Usage
Refer to https://www.mandiant.com/resources/blog/golang-internals-symbol-recovery for reverse engineering details and example usage.
You can download pre-built linux, macos, and windows GoReSym binaries from the Releases tab.
To build from source with a recent Go compiler, invoke the Go compiler:
go build
Once built invoke GoReSym like this:
GoReSym.exe -t -d -p /path/to/input.exe
In this example, we ask GoReSym to recover type names (-t), user package names, standard Go package names (-d), and input file paths (-p) embedded within the file /path/to/input.exe. The output looks like this:
{
"Version": "1.14.15",
"BuildId": "Zb9QmokKTiOUgHKmaIwz/wd2rtE3W9PN-um1Ocdzh/qTdqcTY_jVajHy_-TtYv/Z_kJu9M77OjfijEiHMcF",
"Arch": "amd64",
"TabMeta": {
"VA": 5174784,
"Version": "1.2",
"Endianess": "LittleEndian",
"CpuQuantum": 1,
"CpuQuantumStr": "x86/x64",
"PointerSize": 8
},
"ModuleMeta": {
"VA": 5678816,
"Types": 4845568,
"ETypes": 5171904,
"Typelinks": {
"Data": 5171904,
"Len": 695,
"Capacity": 695
},
"ITablinks": {
"Data": 5174688,
"Len": 11,
"Capacity": 11
},
"LegacyTypes": {
"Data": 0,
"Len": 0,
"Capacity": 0
}
},
"Types": [ ... ],
"Files": [ ... ],
"Strings": [ ... ],
"UserFunctions": [ ... ],
"StdFunctions": [ ... ]
}
Here are all the available flags:
-d("default", optional) flag will print standard Go packages in addition to user packages.-p("paths", optional) flag will print any file paths embedded in thepclntab.-t("types", optional) flag will print Go type names.-strings(optional) flag will extract embedded Go strings from the binary by analyzing the string internment table.-m <virtual address>("manual", optional) flag will dump theRTYPEstructure recursively at the given virtual address-v <version string>("version", optional) flag will override automated version detection and use the provided version. This is needed for some stripped binaries. Type parsing will fail if the version is not accurate.-human(optional) flag will print a flat text listing instead of JSON. Especially useful when printing structure and interface types.-about(optional) flag with print out license information
To import this information into IDA Pro you can run the script found in https://github.com/mandiant/GoReSym/blob/master/IDAPython/goresym_rename.py. It will read a json file produced by GoReSym and set symbols/labels in IDA.
Version Support
As the Go compiler and runtime have changed, so have the embedded metadata structures. GoReSym supports the following combinations of Go releases & metadata:
- all combinations of ARM64 𝒙 Intel x86/x64 𝒙 MACH-O/ELF/PE 𝒙 big/little endian
pclntabparsing: >= Go 1.2moduledatalocation: >= Go 1.2moduledatatype parsing: >= Go 1.5
The moduledata table used to extract types doesn't exist prior to Go 1.5, so this library will never support extracting types from very old Go versions.
This library current handles the pclntab layouts pre 1.2, 1.2, 1.16, 1.18, and 1.20. Note that the pclntab version is always <= the Go runtime version (ex: Go runtime 1.19 uses the 1.18 pclntab layout), we aim to always support the latest runtime versions.
Contributions
Much of the source code from GoReSym is copied from the upstream Go compiler source directory /internal. To make this work, we've had to massage the source a bit. If you want to contribute to GoReSym, read on so we can explain this import process.
Due to the way Go packages work, we needed to remove the /internal path from the source file tree. This resulted in a lot of copying of internal Go files, where the directory tree is mostly intact but with small changes to many files' imports: references to /internal paths were replaced with github.com/mandiant/GoReSym/.
We also modified many internal structures to export fields and methods. These are not exported by Go upstream because users should not rely upon them. However, the purpose of this tool is to extract internal information, so we're taking on the task of maintaining these structures. It's not a great situation, but it's not easily avoidable. If you update this repository, you must take care to keep these modifications intact. It's probably better to manually merge in commits from upstream rather than copying upstream files wholesale.
I am open to suggestions on how to better structure this project to avoid these issues while still compiling with the typical go build. There is a previous discussion involving Go maintainers here.
Ignoring some trivial changes, most new logic exists in /objfile. For example, the file objfile/internals defines the reversed internal Go structures that GoReSym parses.
References
pclntabSpecification: golang.org/s/go12symtabpclntabMagics: pclntab.go#L169objfileBug(s):buildIDLegacy bug: golang/go#50809
Changes
- Added parsing support for Go 1.24 binaries while retaining compatibility with older 1.2 layouts.
- GoReSym will now also attempt to find the pclntab based on a signature of the
runtime_modulesinitinitialization method and attempt to repair the pclntab magic (in cases where the pclntab magic has been modified). - Extended
pcln()functions inobjfile/<fileformat>to support byte scanning thepclntabmagic - Added routines such as
DataAfterSectionto support signature scan in file format parsers in/debug/<fileformat> - Added check to
debug/gosym/symtab.go'swalksymtabto bail early when the optionalsymtabsection is empty - Exported many members and internal structs (changes are too many to enumerate)
- Removed
goobjliner support inobjfile/objfile.go'sPCLineTable() - Added extra sanity checks around
loadPeTable(and other format variants) to avoid panic when symbols are present but maliciously modified to be invalid (ref: golang/go#47981) - Modified the signatures of some internal functions to provide lower level access to information such as section addresses and offsets
- Implemented
read_memoryroutines for supported file formats to read file data by virtual address - Introduced
moduledatascan routines to help locate moduledata in support of scanning for types and interfaces (via typelinks) - Added size guards to
readStringTablefor invalid symbol tables. Parsing failures are ignored as well.
License
MIT
