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.
pull/421/head
Daniel Martí 3 years ago committed by GitHub
parent caa9831a63
commit ea2e0bdf71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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 {

@ -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

Loading…
Cancel
Save