# Technical Documentation: burrowers/garble

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/burrowers/garble)

### Overview (`README.md`)

# garble

	go install mvdan.cc/garble@latest # or @master

Obfuscate Go code by wrapping the Go toolchain. Requires Go 1.27 or later.

	garble build [build flags] [packages]

The tool also supports `garble test` to run tests with obfuscated code,
`garble run` to obfuscate and execute simple programs,
`garble reverse` to de-obfuscate text such as stack traces,
and `garble bug` to file a pre-filled bug report.
Run `garble -h` to see all available commands and flags.

### Purpose

Produce a binary that works as well as a regular build, but that has as little
information about the original source code as possible.

The tool is designed to be:

* Coupled with `cmd/go`, to support modules and build caching
* Deterministic and reproducible, given the same initial source code
* Reversible given the original source, to de-obfuscate panic stack traces

### Mechanism

The tool wraps calls to the Go compiler and linker to transform the Go build, in
order to:

* Replace identifiers and package paths with short base64 hashes
* Replace position information with short base64 hashed filenames
* Remove all [build](https://go.dev/pkg/runtime/#Version), [module](https://go.dev/pkg/runtime/debug/#ReadBuildInfo), and debug information
* [Obfuscate literals](#literal-obfuscation) and [embedded files](#embedded-file-obfuscation), if the `-literals` flag is given
* Remove [extra information](#tiny-mode), if the `-tiny` flag is given

The tool obfuscates all supported packages being built, including the standard runtime.
The unsupported `runtime/cgo` and `crypto/internal/fips140` packages are excluded.
Runtime obfuscation follows Go's supported `GOOS`/`GOARCH` targets; Garble does not
maintain a narrower architecture allowlist.

Note that commands like `garble build` will use the `go` version found in your
`$PATH`. To use different versions of Go, you can [use `GOTOOLCHAIN`](https://go.dev/doc/toolchain).

### Use cases

A common question is why a code obfuscator is needed for Go, a compiled language.
Go binaries include a surprising amount of information about the original source;
even with debug information and symbol tables stripped, many names and positions
remain in place for the sake of traces, reflection, and debugging.

Some use cases for Go require sharing a Go binary with the end user.
If the source code for the binary is private or requires a purchase,
its obfuscation can help discourage reverse engineering.

A similar use case is a Go library whose source is private or purchased.
Since Go libraries cannot be imported in binary form, and Go plugins
[have their shortcomings](https://github.com/golang/go/issues/19282),
sharing obfuscated source code becomes an option.
See [#369](https://github.com/burrowers/garble/issues/369).

Obfuscation can also help with aspects entirely unrelated to licensing.
For example, the `-tiny` flag can make binaries 15% smaller,
similar to the [common practice in Android](https://developer.android.com/build/shrink-code#obfuscate) to reduce app sizes.
Obfuscation has also helped some open source developers work around
anti-virus scans incorrectly treating Go binaries as malware.

### Literal obfuscation

Using the `-literals` flag causes literal expressions such as strings to be
replaced with more complex expressions, resolving to the same value at run-time.
String literals injected via `-ldflags=-X` are also replaced by this flag.
This feature is opt-in, as it can cause slow-downs depending on the input code.

Injected strings use the same size limits as source literals. Variables in the
runtime and its dependencies are not covered. With `-literals`, `-ldflags` must
be unpatterned; build commands separately if they need different injected values.

Literals used in constant expressions cannot be obfuscated, since they are
resolved at compile time. This includes any expressions part of a `const`
declaration, for example.

Note that this process can be reversed given enough effort;
see [#984](https://github.com/burrowers/garble/issues/984).

### Embedded file obfuscation

The `-literals` flag also obfuscates files included with `//go:embed`.
It supports `string`, `[]byte`, and `embed.FS`, including named string and
byte-slice types. Garble leaves the original files untouched.

Contents are decoded at run time:

* Embedded string and byte-slice variables are decoded during package initialization.
* Files in an `embed.FS` are decoded each time they are opened. Paths, directory
  entries, file sizes, seeking, and `ReadAt` behavior are unchanged.

Embedded variables use the same obfuscation algorithms as source literals.
Values larger than 2 KiB and `embed.FS` contents share a simpler decoder that
scales to large files. Decoding adds runtime work; opening files in an `embed.FS`
also allocates temporary buffers.

Embedded filenames remain visible. This is obfuscation, not encryption:
the executable contains the decoding key, so its contents can still be recovered.

### Tiny mode

With the `-tiny` flag, even more information is stripped from the Go binary.
Position information is removed entirely, rather than being obfuscated.
Runtime code which prints panics, fatal errors, and trace/debug info is removed.
Many symbol names are also omitted from binary sections at link time.
All in all, this can make binaries about 15% smaller.

With this flag, no panics or fatal runtime errors will ever be printed, but they
can still be handled internally with `recover` as normal.

Note that this flag can make debugging crashes harder, as a panic will simply
exit the entire program without printing a stack trace, and source code
positions and many names are removed.
Similarly, `garble reverse` is generally not useful in this mode.

### Control flow obfuscation

See: [CONTROLFLOW.md](docs/CONTROLFLOW.md)

### Speed

`garble build` should take about twice as long as `go build`, as it needs to
complete two builds. The original build, to be able to load and type-check the
input code, and then the obfuscated build.

Garble obfuscates one package at a time, mirroring how Go compiles one package
at a time. This allows Garble to fully support Go's build cache; incremental
`garble build` calls should only re-build and re-obfuscate modified code.

Note that the first call to `garble build` may be comparatively slow,
as it has to obfuscate each package for the first time. This is akin to clearing
`GOCACHE` with `go clean -cache` and running a `go build` from scratch.

Garble also makes use of its own cache to reuse work, akin to Go's `GOCACHE`.
It defaults to a directory under your user's cache directory,
such as `~/.cache/garble`, and can be placed elsewhere by setting `GARBLE_CACHE`.

### Determinism and seeds

Just like Go, garble builds are deterministic and reproducible in nature.
This has significant benefits, such as caching builds and being able to use
`garble reverse` to de-obfuscate stack traces.

By default, garble will obfuscate each package in a unique way,
which will change if its build input changes: the version of garble, the version
of Go, the package's source code, or any build parameter such as GOOS or -tags.
This is a reasonable default since guessing those inputs is very hard.

You can use the `-seed` flag to provide your own obfuscation randomness seed.
Reusing the same seed can help produce the same code obfuscation,
which can help when debugging or reproducing problems.
Regularly rotating the seed can also help against reverse-engineering in the long run,
as otherwise one can look at changes in how Go's standard library is obfuscated
to guess when the Go or garble versions were changed across a series of builds.

To always use a different seed for each build, use `-seed=random`.
Note that extra care should be taken when using custom seeds:
if a `-seed` value used in a build is lost, `garble reverse` will not work.

### Caveats

Most of these can improve with time and effort. The purpose of this section is
to document the current shortcomings of this tool.

* Exported methods are never obfuscated at the moment, since they could
  be required by interfaces. This area is a work in progress; see
  [#3](https://github.com/burrowers/garble/issues/3).

* There is no supported way to exclude obfuscating a selection of files or packages.
  More often than not, a user would want to do this to work around a bug; please file the bug instead.

* Go programs [are initialized](https://go.dev/ref/spec#Program_initialization) one package at a time,
  where imported packages are always initialized before their importers,
  and otherwise they are initialized in the lexical order of their import paths.
  Since garble obfuscates import paths, this lexical order may change arbitrarily.

* Go plugins are not currently supported; see [#87](https://github.com/burrowers/garble/issues/87).

* Garble requires `git` to patch the linker. That can be avoided once go-gitdiff
  supports [non-strict patches](https://github.com/bluekeyes/go-gitdiff/issues/30).

* APIs like [`runtime.GOROOT`](https://pkg.go.dev/runtime#GOROOT)
  and [`runtime/debug.ReadBuildInfo`](https://pkg.go.dev/runtime/debug#ReadBuildInfo)
  will not work in obfuscated binaries. This can affect
  [loading timezones](https://github.com/golang/go/issues/51473#issuecomment-2490564684), for example.

### Contributing

We welcome new contributors. If you would like to contribute, see
[CONTRIBUTING.md](CONTRIBUTING.md) as a starting point.

Please file an issue before sending a PR, whether it is a bug report or a
feature request, so that we can agree on the problem and its solution first.


[](https://namespace.so/github-actions/?utm_source=oss&utm_campaign=garble)

---

### CONTROLFLOW (`docs/CONTROLFLOW.md`)

# Control Flow Obfuscation

> **This feature is experimental**. To enable it, set the environment variable `GARBLE_EXPERIMENTAL_CONTROLFLOW=1`

### Mechanism


Control flow obfuscation works in several stages:

1) Collect functions with `//garble:controlflow` comment
2) Converts [go/ast](https://pkg.go.dev/go/ast) representation to [go/ssa](https://pkg.go.dev/golang.org/x/tools/go/ssa)
3) Applies [block splitting](#block-splitting)
4) Generates [junk jumps](#junk-jumps)
5) Applies [control flow flattening](#control-flow-flattening)
6) Applies (if enabled) [control flow hardening](#control-flow-hardening)
7) Generates [trash blocks](#trash-blocks)
8) Converts go/ssa back into go/ast

### Example usage

```go
// Obfuscate with defaults parameters
//garble:controlflow
func main() {
	println("Hello world!")
}


// Obfuscate with maximum parameters
//garble:controlflow block_splits=max junk_jumps=max flatten_passes=max
func main() {
    println("Hello world!")
}
```

### Parameter explanation

> Unlike other garble features (which just work), we recommend that you understand how parameters affect control flow obfuscation and which caveats exist.

> Code snippets below without name obfuscation, for better readability.

#### Block splitting

Parameter: `block_splits` (default: `0`)

> Warning: this param affects resulting binary only when used in combination with [flattening](#control-flow-flattening)


Block splitting splits the largest SSA block into 2 parts of random size, this is done `block_splits` times at most. If there is no more suitable block to split (number of ssa instructions in block is less than 3), splitting stops.

This param is very useful if your code has few branches (`if`, `switch` etc.).

Input:
```go
package main

// Note that the block_splits value is "max", so code blocks are split into the smallest possible blocks.
//garble:controlflow flatten_passes=0 junk_jumps=0 block_splits=max
func main() {
	println("1")
	println("2")
	println("3")
	println("4")
	println("5")
}
```

Result:

```go
func main() {
	{
		println("1")
		goto _s2a_l3
	}
_s2a_l1:
	{
		println("3")
		goto _s2a_l4
	}
_s2a_l2:
	{
		println("5")
		return
	}
_s2a_l3:
	{
		println("2")
		goto _s2a_l1
	}
_s2a_l4:
	{
		println("4")
		goto _s2a_l2
	}
}
 
```


#### Junk jumps

Parameter: `junk_jumps` (default: `0`, maximum: `1024`)

> Warning: this param affects resulting binary only when used in combination with [flattening](#control-flow-flattening)

Junk jumps adds jumps to random blocks `junk_jumps` times, these inserted jumps can also form a chain. This param is useful for linearly increasing the functions complexity.

Input:
```go
//garble:controlflow flatten_passes=0 junk_jumps=5 block_splits=0
func main() {
	if true {
		println("hello world")
	}
}
```

Result: 

```go
func main() {
	{
		if true {
			goto _s2a_l4
		} else {
			goto _s2a_l2
		}
	}
_s2a_l1:
	{
		println("hello world")
		goto _s2a_l7
	}
_s2a_l2:
	{
		return
	}
_s2a_l3:
	{
		goto _s2a_l2
	}
_s2a_l4:
	{
		goto _s2a_l6
	}
_s2a_l5:
	{
		goto _s2a_l3
	}
_s2a_l6:
	{
		goto _s2a_l1
	}
_s2a_l7:
	{
		goto _s2a_l5
	}
}
```

#### Control flow flattening

Parameter: `flatten_passes` (default: `1`, maximum: `4`)


This parameter completely [flattens the control flow](https://github.com/obfuscator-llvm/obfuscator/wiki/Control-Flow-Flattening) `flatten_passes` times, which makes analysing the logic of the function very difficult
This is the most important parameter without which the other parameters have no effect on the resulting binary.

> Warning: Unlike junk jumps, this parameter increases control flow complexity non-linearly. In most cases we do not recommend specifying a value greater than 3. Check [Benchmark](#complexity-benchmark)


Input:
```go
//garble:controlflow flatten_passes=1 junk_jumps=0 block_splits=0
func main() {
	if true {
		println("hello world")
	} else {
		println("not hello world")
	}
}
```

Result:

```go
func main() {
	var _s2a_0 int
_s2a_l0:
	{
		goto _s2a_l2
	}
_s2a_l1:
	{
		println("not hello world")
		goto _s2a_l6
	}
_s2a_l2:
	{
		_s2a_1 := _s2a_0 == (int)(1)
		if _s2a_1 {
			goto _s2a_l9
		} else {
			goto _s2a_l8
		}
	}
_s2a_l3:
	{
		_s2a_2 := _s2a_0 == (int)(4)
		if _s2a_2 {
			goto _s2a_l4
		} else {
			goto _s2a_l5
		}
	}
_s2a_l4:
	{
		return
	}
_s2a_l5:
	{
		if true {
			goto _s2a_l7
		} else {
			goto _s2a_l11
		}
	}
_s2a_l6:
	{
		_s2a_0 = (int)(4)
		goto _s2a_l0
	}
_s2a_l7:
	{
		_s2a_0 = (int)(1)
		goto _s2a_l0
	}
_s2a_l8:
	{
		_s2a_3 := _s2a_0 == (int)(2)
		if _s2a_3 {
			goto _s2a_l1
		} else {
			goto _s2a_l12
		}
	}
_s2a_l9:
	{
		println("hello world")
		goto _s2a_l10
	}
_s2a_l10:
	{
		_s2a_0 = (int)(3)
		goto _s2a_l0
	}
_s2a_l11:
	{
		_s2a_0 = (int)(2)
		goto _s2a_l0
	}
_s2a_l12:
	{
		_s2a_4 := _s2a_0 == (int)(3)
		if _s2a_4 {
			goto _s2a_l4
		} else {
			goto _s2a_l3
		}
	}
}
```

#### Control flow hardening

Parameter: `flatten_hardening` (default: empty, supported: `xor,delegate_table`)

Dispatcher is the main and most vulnerable part of control flow flattening. By static analysis of the dispatcher, it is possible to reconstruct the original control flow ([example](https://research.openanalysis.net/angr/symbolic%20execution/deobfuscation/research/2022/03/26/angr_notes.html)). Hardening can be used to make this analysis more difficult by adding an extra layer of obfuscation and moving some of the computation to runtime

Input:
```go
//garble:controlflow flatten_passes=1 junk_jumps=0 block_splits=0 flatten_hardening=xor,delegate_table
func main() {
    delegateTableExample := func() {
        if true {
            println("hello world")
        } else {
            println("not hello world")
        }
    }
    xorHardeningExample := func() {
        if true {
            println("hello world")
        } else {
            println("not hello world")
        }
    }
    xorHardeningExample()
    delegateTableExample()
}
```

Result:

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### Trash blocks

Parameter: `trash_blocks` (default: `0`, maximum: `1024`)

> Warning: this param affects resulting binary only when used in combination with [flattening](#control-flow-flattening)

Trash blocks generator generates blocks that will never be called. Trash blocks contain random function calls and random variable assignments. The purpose of this is to create a large number of references to different methods and local variables and in combination with other controlflow obfuscation parameters it helps to effectively hide the real code.

The generator does not add new dependencies to the project, it uses only existing direct or indirect dependencies. In the following example, the `fmt` package implicitly imports the `io` and `os` packages
Input:

```go
package main

import "fmt"

//garble:controlflow block_splits=0 junk_jumps=0 flatten_passes=0 trash_blocks=1
func main() {
	if true {
		fmt.Println("hello world")
	}
}
```

Result:

```go
package main

import (
	"fmt"
	"io"
	"os"
)

func main() {
	var (
		_s2a_4 int
		_s2a_5 bool
	)
	{
		if true {
			goto _s2a_l1
		} else {
			goto _s2a_l2
		}
	}
_s2a_l1:
	{
		_s2a_0 := new([1]interface {
		})
		_s2a_1 := &_s2a_0[(int)(0)]
		_s2a_2 := (interface {
		})("hello world")
		*_s2a_1 = _s2a_2
		_s2a_3 := _s2a_0[:]
		_, _ = fmt.Println(_s2a_3...)
		_s2a_4 = (int)(1375793722)
		goto _s2a_l3
	}
_s2a_l2:
	{
		return
	}
_s2a_l3:
	{
		_s2a_5 = _s2a_4 == (int)(1414729372)
		if _s2a_5 {
			goto _s2a_l4
		} else {
			goto _s2a_l2
		}
	}
_s2a_l4:
	{
		_s2a_5, _s2a_4 = true, _s2a_4
		_garble1v7i062eba5j0, _ := fmt.Printf((string)(_s2a_4), _s2a_4, _s2a_5)
		_garble27oahvink0hig, _ := fmt.Println(_garble1v7i062eba5j0)
		_ = fmt.Errorf((string)(_garble27oahvink0hig), os.Stdout, 714272279, _garble27oahvink0hig, io.EOF)
		_garble27oahvink0hig, _s2a_4, _garble1v7i062eba5j0, _s2a_5 = _s2a_4, (int)(77), _s2a_4, _s2a_5
		_, _garble6oknljnlggutn := fmt.Printf((string)(_garble1v7i062eba5j0))
		_, _garble8ne429queuq8n := fmt.Scanf((string)(751648516), os.Stdin, 0.17251639929485216, "UAN4E===")
		_garble1qt8quedh1fo9, _ := fmt.Scanf((string)(_s2a_4), _garble8ne429queuq8n, _s2a_5, _garble6oknljnlggutn)
		_, _ = fmt.Scanf((string)(_garble1qt8quedh1fo9))
		goto _s2a_l4
	}
}
```

### Caveats

* Obfuscation breaks the lazy iteration over maps. See: [ssa2ast/polyfill.go](../internal/ssa2ast/polyfill.go)

### Complexity benchmark

We approximate complexity by counting the number of blocks.

Analysed function:
```go
func sort(arr []int) []int {
	for i := 0; i < len(arr)-1; i++ {
		for j := 0; j < len(arr)-i-1; j++ {
			if arr[j] > arr[j+1] {
				arr[j], arr[j+1] = arr[j+1], arr[j]
			}
		}
	}
	return arr
}
```

Before obfuscation this function has `8` blocks.

| flatten_passes | block_splits | junk_jumps | block_count |
|----------------|--------------|------------|-------------|
| 1              | 0            | 0          | 32          |
| 1              | 10           | 0          | 62          |
| 1              | 100          | 0          | 95          |
| 1              | 1024         | 0          | 95          |
| 1              | 0            | 10         | 62          |
| 1              | 10           | 10         | 92          |
| 1              | 100          | 10         | 125         |
| 1              | 1024         | 10         | 125         |
| 1              | 0            | 100        | 332         |
| 1              | 10           | 100        | 362         |
| 1              | 100          | 100        | 395         |
| 1              | 1024         | 100        | 395         |
| 2              | 0            | 0          | 123         |
| 2              | 10           | 0          | 233         |
| 2              | 100          | 0          | 354         |
| 2              | 1024         | 0          | 354         |
| 2              | 0            | 10         | 233         |
| 2              | 10           | 10         | 343         |
| 2              | 100          | 10         | 464         |
| 2              | 1024         | 10         | 464         |
| 2              | 0            | 100        | 1223        |
| 2              | 10           | 100        | 1333        |
| 2              | 100          | 100        | 1454        |
| 2              | 1024         | 100        | 1454        |
| 3              | 0            | 0          | 486         |
| 3              | 10           | 0          | 916         |
| 3              | 100          | 0          | 1389        |
| 3              | 1024         | 0          | 1389        |
| 3              | 0            | 10         | 916         |
| 3              | 10           | 10         | 1346        |
| 3              | 100          | 10         | 1819        |
| 3              | 1024         | 10         | 1819        |
| 3              | 0            | 100        | 4786        |
| 3              | 10           | 100        | 5216        |
| 3              | 100          | 100        | 5689        |
| 3              | 1024         | 100        | 5689        |
| 4              | 0            | 0          | 1937        |
| 4              | 10           | 0          | 3647        |
| 4              | 100          | 0          | 5528        |
| 4              | 1024         | 0          | 5528        |
| 4              | 0            | 10         | 3647        |
| 4              | 10           | 10         | 5357        |
| 4              | 100          | 10         | 7238        |
| 4              | 1024         | 10         | 7238        |
| 4              | 0            | 100        | 19037       |
| 4              | 10           | 100        | 20747       |
| 4              | 100          | 100        | 22628       |
| 4              | 1024         | 100        | 22628       |

---

--- METRICS ---
- Files Extracted: 2
- Estimated Token Budget: ~5384 tokens
- Canonical Reference: https://codewiki.google/github.com/burrowers/garble
