diff --git a/main.go b/main.go index 94baf3b..53714ce 100644 --- a/main.go +++ b/main.go @@ -371,10 +371,6 @@ func transformCompile(args []string) ([]string, error) { mathrand.Seed(int64(binary.BigEndian.Uint64([]byte(buildInfo.buildID)))) } - if envGarbleLiterals { - files = obfuscateLiterals(files) - } - info := &types.Info{ Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), @@ -384,6 +380,15 @@ func transformCompile(args []string) ([]string, error) { return nil, fmt.Errorf("typecheck error: %v", err) } + if envGarbleLiterals { + files = obfuscateLiterals(files, info) + // ast changed so we need to typecheck again + pkg, err = origTypesConfig.Check(pkgPath, fset, files, info) + if err != nil { + return nil, fmt.Errorf("typecheck error: %v", err) + } + } + tempDir, err := ioutil.TempDir("", "garble-build") if err != nil { return nil, err diff --git a/strings.go b/strings.go index 33a7c32..5e44627 100644 --- a/strings.go +++ b/strings.go @@ -5,30 +5,107 @@ import ( "fmt" "go/ast" "go/token" + "go/types" "strconv" "strings" "golang.org/x/tools/go/ast/astutil" ) -func obfuscateLiterals(files []*ast.File) []*ast.File { - pre := func(cursor *astutil.Cursor) bool { - decl, ok := cursor.Node().(*ast.GenDecl) - if !ok || decl.Tok != token.CONST { - return true +func isTypeDefStr(typ types.Type) bool { + strType := types.Typ[types.String] + + if named, ok := typ.(*types.Named); ok { + return types.Identical(named.Underlying(), strType) + } + + return false +} + +func containsTypeDefStr(expr ast.Expr, info *types.Info) bool { + typ := info.TypeOf(expr) + //log.Println(expr, typ, reflect.TypeOf(expr), reflect.TypeOf(typ)) + + if sig, ok := typ.(*types.Signature); ok { + for i := 0; i < sig.Params().Len(); i++ { + if isTypeDefStr(sig.Params().At(i).Type()) { + return true + } } + } - for _, spec := range decl.Specs { - for _, val := range spec.(*ast.ValueSpec).Values { - if v, ok := val.(*ast.BasicLit); !ok || v.Kind != token.STRING { - return false // skip the block if it contains non basic literals + if mapT, ok := typ.(*types.Map); ok { + return isTypeDefStr(mapT.Elem()) || isTypeDefStr(mapT.Key()) + } + + if named, ok := typ.(*types.Named); ok { + return isTypeDefStr(named) + } + + return false +} + +func obfuscateLiterals(files []*ast.File, info *types.Info) []*ast.File { + pre := func(cursor *astutil.Cursor) bool { + switch x := cursor.Node().(type) { + case *ast.ValueSpec: + return !containsTypeDefStr(x.Type, info) + + case *ast.AssignStmt: + for _, expr := range x.Lhs { + if index, ok := expr.(*ast.IndexExpr); ok { + return !containsTypeDefStr(index.X, info) + } + + if ident, ok := expr.(*ast.Ident); ok { + return !containsTypeDefStr(ident, info) } } - } + case *ast.CallExpr: + return !containsTypeDefStr(x.Fun, info) + + case *ast.CompositeLit: + if t, ok := x.Type.(*ast.MapType); ok { + return !(containsTypeDefStr(t.Key, info) || containsTypeDefStr(t.Value, info)) + } + + case *ast.FuncDecl: + if x.Type.Results == nil { + return true + } + for _, result := range x.Type.Results.List { + for _, name := range result.Names { + return !containsTypeDefStr(name, info) + } + } + + case *ast.KeyValueExpr: + if ident, ok := x.Key.(*ast.Ident); ok { + return !containsTypeDefStr(ident, info) + } + case *ast.GenDecl: + if x.Tok != token.CONST { + return true + } + for _, spec := range x.Specs { + spec, ok := spec.(*ast.ValueSpec) + if !ok { + return false + } + + for _, val := range spec.Values { + if v, ok := val.(*ast.BasicLit); !ok || v.Kind != token.STRING { + return false // skip the block if it contains non basic literals + } + } - // constants are not possible if we want to obfuscate literals, therefore - // move all constant blocks which only contain strings to variables - decl.Tok = token.VAR + } + + x.Tok = token.VAR + // constants are not possible if we want to obfuscate literals, therefore + // move all constant blocks which only contain strings to variables + + } return true } diff --git a/testdata/scripts/strings.txt b/testdata/scripts/strings.txt index 25d62de..db2f051 100644 --- a/testdata/scripts/strings.txt +++ b/testdata/scripts/strings.txt @@ -2,10 +2,9 @@ garble -literals build exec ./main$exe cmp stderr main.stderr -! binsubstr main$exe 'Lorem' 'ipsum' 'dolor' 'first assign' 'second assign' 'First Line' 'Second Line' 'map value' 'to obfuscate' 'also obfuscate' - -binsubstr main$exe 'Skip this block,' 'also skip this' +! binsubstr main$exe 'Lorem' 'ipsum' 'dolor' 'first assign' 'second assign' 'First Line' 'Second Line' 'map value' 'to obfuscate' 'also obfuscate' 'stringTypeField String' 'stringTypeStruct' +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' 'testMap2 value' 'testMap3 value' 'testMap1 new value' 'testMap2 new value' 'testMap3 new value' 'stringType func param' 'stringType return' [short] stop # checking that the build is reproducible is slow # Also check that the binary is reproducible. @@ -19,14 +18,17 @@ module test/main -- main.go -- package main +// The lack of imports is an edge case, since we have to add imports like +// crypto/aes. + type strucTest struct { field string anotherfield string } const ( - cnst = "Lorem" - multiline = `First Line + cnst string = "Lorem" + multiline = `First Line Second Line` ) @@ -58,7 +60,6 @@ func main() { println(cnst) println(multiline) - println(variable) println(localVar) println(reassign) println(empty) @@ -68,38 +69,76 @@ func main() { anotherfield: "also obfuscate", } - println(x.field) - println(x.anotherfield) + 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(skip1, skip2) - println(i, foo, bar) + typedTest() } --- decl_without_imports.go -- -package main +type stringType string -// The lack of imports is an edge case, since we have to add imports like -// crypto/aes. +type stringTypeStruct struct { + str string + strType stringType +} + +// typedTest types defined from string broke previously +func typedTest() { + const skipTypedConst stringType = "skip typed const" // skip + var skipTypedVar stringType = "skip typed var" // skip -var variable = "ipsum" + 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 + testMap2["testMap2 key"] = "testMap2 new value" // skip + + testMap3 := map[stringType]stringType{"testMap3 key": "testMap3 value"} // skip + testMap3["testMap3 key"] = "testMap3 new value" // skip + + println(stringTypeFunc("stringType func param")) // skip +} + +func stringTypeFunc(s stringType) stringType { + println(s) + return "stringType return" // skip +} -- main.stderr -- Lorem First Line Second Line -ipsum dolor second assign -to obfuscate -also obfuscate -map value +to obfuscate also obfuscate +new value another literal Skip this block, also skip this 1 0 1 +skip typed const skip typed var skip typed var assign +stringTypeField String stringTypeField strType +stringType lambda func return +stringType func param +stringType return \ No newline at end of file