The go compiler includes a lot of information about the system the binary was compiled on in the resulting executable. In some cases, this is not desirable; therefore, I will introduce several types of information embedded by the compiler and how to remove or replace them.
To visualize the following chapters, I created a simple go project via the go mod init metadata_example command with a singular module named logger:
1$ exa --tree
2.
3├── go.mod
4├── logger
5│ └── logger.go
6└── main.goThe main.go file contains the following call to the exported Print() function
of the logger module:
1package main
2
3import "metadata_example/logger"
4
5func main() {
6 logger.Print("Hello World")
7}The logger module contains as previously mentioned the Print() function:
1package logger
2
3import "fmt"
4
5func Print(s string) {
6 fmt.Printf("Printing: %s", s)
7}Compiling the given source files via go build creates a binary called
metadata_example which can now be examined with the gnu coreutils: strings,
file and the go tool objdump.
Simply running strings on the metadata_example binary and filtering the
output via ripgrep1 allows us to view paths to included modules and
the name of the compiling user:
1$ strings metadata_example | rg "teo"
2/home/teo/programming/test/main.go
3/home/teo/programming/test/logger/logger.goAnd the function we defined in the logger module:
1$ strings metadata_example | rg "logger"
2metadata_example/logger.Print
3/home/teo/programming/test/logger/logger.go
4metadata_example/logger..inittaskWe can see the unique buildid the go compiler generated2 for this build using file:
1$ file metadata_example
2metadata_example: ELF 64-bit LSB executable, x86-64, version 1 (SYSV),
3statically linked, Go
4BuildID=EeK2FDOWyG6IGg5mazlg/ENHyunFNhP8fxN7Y3QPV/12qIzndxNxBtjs7mhfY7/RRZcvOGD57KThfaaOCUV,
5with debug_info, not strippedThe binary contains debug information and can therefore be objdumped fairly easy:
1$ go tool objdump metadata_example | tail -n 26
2TEXT main.main(SB) /home/teo/programming/test/main.go
3 main.go:5 0x482820 493b6610 CMPQ 0x10(R14), SP
4 ....Removing Metadata
The following three types of information are fairly easy to remove: Each one will have an explanation and a removal example.
Removing Debug information
Technically the go tool chain includes the following information via the linker not the compiler. 3
omitting DWARF symbol table
The DWARF symbol table 4 is used for debugging. It can be
stripped by passing the -w flag to the go linker.
1go build -ldflags="-w"omitting symbol table and debug information
The go symbol table and the debug information embedded in the binary enable and
enhance the usage of the gosym-package 5 and the objdump go tool
6. Omitting both the symbol table and debug information can be achieved
by invoking the go linker with the -s flag.
1go build -ldflags="-s"Running the go objdump tool on the resulting binary returns an error:
1$ go tool objdump metadata_example
2objdump: disassemble metadata_example: no symbol sectionTrimming module paths
As showcased before, the compiled binary contains all the files included in the
resulting binary. The go compiler exposes the -trimpath to strip common
prefixes from these files, which almost always contain compromising information
such as the name of the compiling user.
The compiler does the above when invoked with the flag, as follows:
1go build -trimpathUsing the flag when compiling results in the binary not including the absolute paths but project root relative paths:
1$ go build
2$ go tool objdump metadata_example | rg "main.main"
3TEXT main.main(SB) /home/teo/programming/test/main.go
4 main.go:5 0x482898 eb86 JMP main.main(SB)
5$ go build -trimpath
6$ go tool objdump metadata_example | rg "main.main"
7TEXT main.main(SB) metadata_example/main.go
8 main.go:5 0x482898 eb86 JMP main.main(SB)Comparing the strings output from the start of this post, the binary now
no longer contains the given example:
1$ strings metadata_example | rg "teo"
2$ strings metadata_example | rg "logger"
3metadata_example/logger.Print
4metadata_example/logger/logger.goReplacing the buildid
The buildid can not be removed but it can be replaced with the -buildid
linker flag:
1go build -ldflags="-buildid="Which results in the following file output:
1$ go build -ldflags="-buildid="
2$ file metadata_example
3metadata_example: ELF 64-bit LSB executable, x86-64, version 1 (SYSV),
4statically linked, with debug_info, not strippedCompile command example
Putting all of the above together, we arrive at the following build command:
1go build -ldflags="-w -s -buildid=" -trimpathThis build does not include the DWARF symbol table, the go symbol table, debug
information, the unique buildid or compromising file paths.
Difference in file size7:
1 33 go.mod
2 - logger
3 93 main.go
41.2M metadata_example
51.9M metadata_example_fullThe metadata_example_full was build with go build and the
metadata_example was build with all flags included in this post.
Further options
Stripping binaries
Its always possible to strip binaries using the gnu coreutils strip tool,
this is however strongly advised against when stripping go binaries due to
random panics.8
Obfuscation
There are several go modules out there for replacing strings with computations that evaluate to strings at runtime or replacing paths to modules with hashes. 9 10
These can be useful for creating more obscure binaries, which are hard to reverse engineer.