garble -literals build exec ./main$exe cmp stderr main.stderr binsubstr main$exe 'skip typed const' 'skip typed var' 'skip typed var assign' 'stringTypeField strType' 'stringType lambda func return' 'testMap2 key' 'testMap3 key' 'testMap1 value' 'testMap3 value' 'testMap1 new value' 'testMap3 new value' 'stringType func param' 'stringType return' 'skip untyped const' ! binsubstr main$exe 'garbleDecrypt' 'Lorem' 'dolor' 'first assign' 'second assign' 'First Line' 'Second Line' 'map value' 'obfuscated with shadowed builtins' '1: literal in' 'an array' '2: literal in' 'a slice' 'to obfuscate' 'also obfuscate' 'stringTypeField String' 'testMap1 key' 'Obfuscate this block' 'also obfuscate this' [short] stop # checking that the build is reproducible is slow # Also check that the binary is reproducible. cp main$exe main_old$exe rm main$exe garble -literals build bincmp main$exe main_old$exe # Check that the program works as expected without garble. go build exec ./main$exe cmp stderr main.stderr binsubstr main$exe 'Lorem' 'dolor' 'second assign' 'First Line' 'Second Line' 'map value' 'to obfuscate' 'also obfuscate' 'stringTypeField String' # Generate and write random literals into a separate file. # Some of them will be huge; assuming that we don't try to obfuscate them, the # test should generally run in under a second. If this test hangs for over ten # seconds, it means we're trying to obfuscate them. generate-literals extra_literals.go garble -literals -debugdir=debug1 build exec ./main$exe cmp stderr main.stderr # Check obfuscators. # Xor obfuscator. Detect a[i] = a[i] (^|-|+) b[i] grep '^\s+\w+\[\w+\] = \w+\[\w+\] [\^\-+] \w+$' debug1/test/main/extra_literals.go # Swap obfuscator. Detect [...]byte|uint16|uint32|uint64{...} grep '^\s+\w+ := \[\.{3}\](byte|uint16|uint32|uint64)\{[0-9\s,]+\}$' debug1/test/main/extra_literals.go # Split obfuscator. Detect decryptKey ^= i * counter grep '^\s+\w+ \^= \w+ \* \w+$' debug1/test/main/extra_literals.go # XorShuffle obfuscator. Detect data = append(data, x (^|-|+) y...). # Note that the line obfuscator adds an inline comment before the call. grep '^\s+\w+ = .*\bappend\(\w+,(\s+\w+\[\d+\][\^\-+]\w+\[\d+\],?)+\)$' debug1/test/main/extra_literals.go # TODO: re-enable once https://github.com/golang/go/issues/47631 is fixed # XorSeed obfuscator. Detect type decFunc func(byte) decFunc # grep '^\s+type \w+ func\(byte\) \w+$' debug1/test/main/extra_literals.go # Finally, sanity check that we can build all of std with -literals. # Analogous to gogarble.txt. garble -literals build std -- go.mod -- module test/main go 1.19 -- main.go -- package main import ( _ "runtime/debug" "test/main/imp" ) type structTest struct { field string anotherField string } const ( cnst string = "Lorem" multiline string = `First Line Second Line` ) const ( i = 1 boolean = true mixedBlock = "Obfuscate this block" ) const ( foo = iota bar iotaBlock = "also obfuscate this" ) // We used to conver this to a var in an attempt of obfuscating the literal. // That would break the iota, which only works inside const declarations. // We only obfuscate constant declarations with string values, anyway. const fooTyped uint64 = 1 << iota const arrayLen = 4 var array [arrayLen]byte type typeAlias [arrayLen]byte func main() { empty := "" localVar := "dolor" reassign := "first assign" reassign = "second assign" add := "total" + " string" println(cnst, boolean) println(multiline, add) println(localVar) println(reassign) println(empty) x := structTest{ field: "to obfuscate", anotherField: "also obfuscate", } lambda := func() string { return "😅 😅" }() println(lambda) println(x.field, x.anotherField) testMap := map[string]string{"map key": "map value"} testMap["map key"] = "new value" println(testMap["map key"]) println("another literal") println(mixedBlock, iotaBlock) println(i, foo, bar) typedTest() constantTest() byteTest() shadowTest() strArray := [2]string{"1: literal in", "an array"} println(strArray[0], strArray[1]) strSlice := []string{"2: literal in", "a slice"} println(strSlice[0], strSlice[1]) emptyStrSlice := []string{""} print(emptyStrSlice[0]) } type stringType string type stringTypeStruct struct { str string strType stringType } // typedTest types defined from string broke previously func typedTest() { const skipUntypedConst = "skip untyped const" stringTypeFunc(skipUntypedConst) const skipTypedConst stringType = "skip typed const" // skip var skipTypedVar stringType = "skip typed var" // skip var skipTypedVarAssign stringType skipTypedVarAssign = "skip typed var assign" // skip println(skipTypedConst, skipTypedVar, skipTypedVarAssign) y := stringTypeStruct{ str: "stringTypeField String", // obfuscate strType: "stringTypeField strType", // skip } println(y.str, y.strType) z := func(s stringType) stringType { return "stringType lambda func return" // skip }("lambda call") // skip println(z) testMap1 := map[string]stringType{"testMap1 key": "testMap1 value"} // skip testMap1["testMap1 key"] = "testMap1 new value" // skip testMap2 := map[stringType]string{"testMap2 key": "testMap2 value"} // skip key testMap2["testMap2 key"] = "testMap2 new value" // skip key testMap3 := map[stringType]stringType{"testMap3 key": "testMap3 value"} // skip testMap3["testMap3 key"] = "testMap3 new value" // skip println(stringTypeFunc("stringType func param")) // skip } // constantTest tests that string constants which need to be constant are skipped func constantTest() { const a = "foo" // skip const length = len(a) const b = "bar" // skip type T [len(b)]byte const c = "foo" // skip var _ [len(c)]byte const d = "foo" // skip var arr = [5]string{len(d): "foo"} for _, elm := range arr { if elm != "" { println(elm) } } const e = "foo" // skip var slice = []string{len(e): "foo"} for _, elm := range slice { if elm != "" { println(elm) } } const f = "foo" // skip const i = length + len(f) println(length, i) // We should still obfuscate ImportedType here. // Otherwise, the build will fail, // as the name was obfuscated in the original package. const impType = imported.ImportedType(3) } // TODO: This only tests that we don't break byte slices. // It was manually verified that they do get obfuscated, // The original bytes don't seem to show up in the binary, // meaning that we can't test for them via binsubstr. // We should figure out a way to test for the byte sequences. // For now, we manually tested these when they got added. func byteTest() { a := []byte{12, 13} for _, elm := range a { print(elm, ",") } println() var b = []byte{12, 13} for _, elm := range b { print(elm, ",") } println() var c = [2]byte{12, 13} for _, elm := range c { print(elm, ",") } println() d := func() [4]byte { return [4]byte{12, 13} }() for _, elm := range d { print(elm, ",") } println() e := []byte{0x43, 11_1, 0b01101101, 'p', 'l', 'e', 'x'} println(string(e)) // Testing for issue #520. func(s []byte) { print(string(s)) }([]byte("chungus")) println() func(s *[]byte) { print(string(*s)) }(&[]byte{99, 104, 117, 110, 103, 117, 115}) println() func(s [7]byte) { for _, elm := range s { print(elm, ",") } }([7]byte{99, 104, 117, 110, 103, 117, 115}) println() func(s *[7]byte) { for _, elm := range s { print(elm, ",") } }(&[7]byte{99, 104, 117, 110, 103, 117, 115}) println() } func stringTypeFunc(s stringType) stringType { println(s) return "stringType return" // skip } // obfuscating this broke before const ( iota0 uint8 = iota iota1 ) // Our inserted code used to break due to the shadowed builtins. // The name "fnc" is used as a func var name in garble's inserted code. func shadowTest() { { var append, bool, string, fnc int _, _, _, _ = append, bool, string, fnc println("obfuscated with shadowed builtins (vars)") } { type append int type bool int type string int type fnc int println("obfuscated with shadowed builtins (types)") } } -- imp/imported.go -- package imported type ImportedType int -- directives.go -- // If we misplace any of the directives below, // cmd/compile will complain with "misplaced compiler directive". // // We use many literals and functions, mixing different types, // so that it's more likely that bugs will be caught. package main //go:noinline func str0() { println("foo") } //go:noinline func str1() { println("foo") } //go:noinline func str2() { println("foo") } //go:noinline func str3() { println("foo") } //go:noinline func str4() { println("foo") } //go:noinline func arr0() { println(len([...]byte{0, 1, 2})) } //go:noinline func arr1() { println(len([...]byte{0, 1, 2})) } //go:noinline func slc0() { println([]byte{0, 1, 2}) } //go:noinline func slc1() { println([]byte{0, 1, 2}) } //go:noinline func str5() { println("foo") } //go:noinline func str6() { println("foo") } //go:noinline func str7() { println("foo") } //go:noinline func str8() { println("foo") } //go:noinline func str9() { println("foo") } -- main.stderr -- Lorem true First Line Second Line total string dolor second assign 😅 😅 to obfuscate also obfuscate new value another literal Obfuscate this block also obfuscate this 1 0 1 skip untyped const skip typed const skip typed var skip typed var assign stringTypeField String stringTypeField strType stringType lambda func return stringType func param stringType return foo foo 3 6 12,13, 12,13, 12,13, 12,13,0,0, Complex chungus chungus 99,104,117,110,103,117,115, 99,104,117,110,103,117,115, obfuscated with shadowed builtins (vars) obfuscated with shadowed builtins (types) 1: literal in an array 2: literal in a slice