-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Go-Embed
Starting from Go 1.16, the language introduced the go:embed directive, providing native support for embedding static files.
Embedded files can be accessed via an embed.FS pseudo-filesystem. They are read-only and packaged directly within the compiled binary.
Understanding How Go-Embed Works
The embed.FS file container structure:
type FS struct {
files *[]file
}
type file struct {
name string // file name
data string // file content
hash [16]byte // truncated SHA256 hash
}- The demo directory structure:
tests
├── embedemo.go
└── misc
├── bdir
│ └── sample.txt
├── sample.txt
└── sample2.txt
embedemo.go:
package main
import (
"embed"
"fmt"
"log"
)
//go:embed misc
var embedFiles embed.FS
func main() {
content, err := embedFiles.ReadFile("misc/sample.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(content))
}Use debugging to inspect what contents are stored in the files after compilation:
//go:embed <filename> is a compiler directive. During go build, it triggers the WriteEmbed function to process the directive:
L138-L140:slicedatais the pointer toFS.files, writing the length offilestwice;L150: writes the filename (pointer);L152-156: filenames ending with/are directories, skipped, withdataandhashset to 0;L158-164: writesdata(pointer) andhash(16 bytes);
Analyzing with Decompiler Tools
In the main function, calling embed.FS.ReadFile
x1refers to the string"misc/sample.txt"x0is the pointer to theFS.
Locate .rodata and display it using a hex viewer:
- The three bytes marked in red represent the
filespointer (little-endian), the length, and the length again. - The first blue box, spanning six bytes, represents the directory structure: filename pointer, filename length, data pointer, data length, and a 2-byte hash. Since this is a directory,
dataandhashare empty. - The second blue box highlights the structure of a file that contains actual content.
The files studied above are Mach-O for the ARM64 architecture. Later, I also compiled ELF and PE binaries, and their storage structures are the same. You can use the debug/* packages to parse files for each architecture, convert virtual addresses to file offsets, and thus extract the embedded files.
Building an Automated Tool
It has been open-sourced and can extract embedded files from PE, ELF, and Mach-O binaries: BreakOnCrash/go-embed-extractor.
The downside is that you have to manually locate the FS pointer 😅
@leonjza shared a method using the radare tool to find the FS structure pointer, which can then be combined with go-embed-extractor to extract embedded files.