Skip to content

Commit

Permalink
obfuscate all variable names, even local ones (burrowers#420)
Browse files Browse the repository at this point in the history
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 burrowers#369.

Fixes burrowers#417.
  • Loading branch information
mvdan committed Nov 16, 2021
1 parent caa9831 commit ea2e0bd
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 15 deletions.
34 changes: 20 additions & 14 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
25 changes: 24 additions & 1 deletion testdata/scripts/literals.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -136,6 +136,7 @@ func main() {
typedTest()
constantTest()
byteTest()
shadowTest()

strArray := [2]string{"1: literal in", "an array"}
println(strArray[0], strArray[1])
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

0 comments on commit ea2e0bd

Please sign in to comment.