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.go
The 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 ripgrep
1 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.go
And 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..inittask
We 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 stripped
The 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 section
Trimming 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 -trimpath
Using 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.go
Replacing 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 stripped
Compile command example
Putting all of the above together, we arrive at the following build command:
1go build -ldflags="-w -s -buildid=" -trimpath
This 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_full
The 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.