From ea2e0bdf714409b69bcb07ba593f86228963bd69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Tue, 16 Nov 2021 16:58:36 +0000 Subject: [PATCH] obfuscate all variable names, even local ones (#420) In the added test case, "garble -literals build" would fail: --- FAIL: TestScripts/literals (8.29s) testscript.go:397: > env GOPRIVATE=test/main > garble -literals build [stderr] # test/main Usz1FmFm.go:1: cannot call non-function string (type int), declared at Usz1FmFm.go:1 Usz1FmFm.go:1: string is not a type Usz1FmFm.go:1: cannot call non-function append (type int), declared at Usz1FmFm.go:1 That is, for input code such as: var append int println("foo") _ = append We'd end up with obfuscated code like: var append int println(func() string { // obfuscation... x = append(x, ...) // obfuscation... return string(x) }) _ = append Which would then break, as the code is shadowing the "append" builtin. To work around this, always obfuscate variable names, so we end up with: var mwu1xuNz int println(func() string { // obfuscation... x = append(x, ...) // obfuscation... return string(x) }) _ = mwu1xuNz This change shouldn't make the quality of our obfuscation stronger, as local variable names do not currently end up in Go binaries. However, this does make garble more consistent in treating identifiers, and it completely avoids any issues related to shadowing builtins. Moreover, this also paves the way for publishing obfuscated source code, such as #369. Fixes #417. --- main.go | 34 ++++++++++++++++++++-------------- testdata/scripts/literals.txt | 25 ++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/main.go b/main.go index d9405d5..2903745 100644 --- a/main.go +++ b/main.go @@ -1283,11 +1283,28 @@ func (tf *transformer) transformGo(file *ast.File) *ast.File { } obj := tf.info.ObjectOf(node) if obj == nil { - return true + _, isImplicit := tf.info.Defs[node] + _, parentIsFile := cursor.Parent().(*ast.File) + if isImplicit && !parentIsFile { + // In a type switch like "switch foo := bar.(type) {", + // "foo" is being declared as a symbolic variable, + // as it is only actually declared in each "case SomeType:". + // + // As such, the symbolic "foo" in the syntax tree has no object, + // but it is still recorded under Defs with a nil value. + // We still want to obfuscate that syntax tree identifier, + // so if we detect the case, create a dummy types.Var for it. + // + // Note that "package mypkg" also denotes a nil object in Defs, + // and we don't want to treat that "mypkg" as a variable, + // so avoid that case by checking the type of cursor.Parent. + obj = types.NewVar(node.Pos(), tf.pkg, node.Name, nil) + } else { + return true + } } pkg := obj.Pkg() if vr, ok := obj.(*types.Var); ok && vr.Embedded() { - // The docs for ObjectOf say: // // If id is an embedded struct field, ObjectOf returns the @@ -1364,16 +1381,10 @@ func (tf *transformer) transformGo(file *ast.File) *ast.File { hashToUse := lpkg.GarbleActionID // log.Printf("%s: %#v %T", fset.Position(node.Pos()), node, obj) - parentScope := obj.Parent() switch obj := obj.(type) { case *types.Var: - if parentScope != nil && parentScope != pkg.Scope() { - // Identifiers of non-global variables never show up in the binary. - return true - } - if !obj.IsField() { - // Identifiers of global variables are always obfuscated. + // Identifiers denoting variables are always obfuscated. break } // From this point on, we deal with struct fields. @@ -1423,11 +1434,6 @@ func (tf *transformer) transformGo(file *ast.File) *ast.File { } } case *types.TypeName: - if parentScope != pkg.Scope() { - // Identifiers of non-global types never show up in the binary. - return true - } - // If the type was not obfuscated in the package were it was defined, // do not obfuscate it here. if path != curPkg.ImportPath { diff --git a/testdata/scripts/literals.txt b/testdata/scripts/literals.txt index 9fb8b63..178c3b3 100644 --- a/testdata/scripts/literals.txt +++ b/testdata/scripts/literals.txt @@ -5,7 +5,7 @@ exec ./main$exe cmp stderr main.stderr binsubstr main$exe 'Skip this block' 'also skip this' 'skip typed const' 'skip typed var' 'skip typed var assign' 'stringTypeField strType' 'stringType lambda func return' 'testMap1 key' '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' '1: literal in' 'an array' '2: literal in' 'a slice' 'to obfuscate' 'also obfuscate' 'stringTypeField String' +! 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' [short] stop # checking that the build is reproducible is slow @@ -136,6 +136,7 @@ func main() { typedTest() constantTest() byteTest() + shadowTest() strArray := [2]string{"1: literal in", "an array"} println(strArray[0], strArray[1]) @@ -266,11 +267,31 @@ func stringTypeFunc(s stringType) stringType { 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)") + } +} -- imported/imported.go -- package imported @@ -352,5 +373,7 @@ foo 12,13, 12,13,0,0, Complex +obfuscated with shadowed builtins (vars) +obfuscated with shadowed builtins (types) 1: literal in an array 2: literal in a slice