From 70d72678e6f2395bcff49690ac3e933a125d89a0 Mon Sep 17 00:00:00 2001 From: Paul Scheduikat Date: Wed, 27 Nov 2024 22:34:45 +0100 Subject: [PATCH] obfuscate all names used in reflection Go code can retrieve and use field and method names via the `reflect` package. For that reason, historically we did not obfuscate names of fields and methods underneath types that we detected as used for reflection, via e.g. `reflect.TypeOf`. However, that caused a number of issues. Since we obfuscate and build one package at a time, we could only detect when types were used for reflection in their own package or in upstream packages. Use of reflection in downstream packages would be detected too late, causing one package to obfuscate the names and the other not to, leading to a build failure. A different approach is implemented here. All names are obfuscated now, but we collect those types used for reflection, and at the end of a build in `package main`, we inject a function into the runtime's `internal/abi` package to reverse the obfuscation for those names which can be used for reflection. This does mean that the obfuscation for these names is very weak, as the binary contains a one-to-one mapping to their original names, but they cannot be obfuscated without breaking too many Go packages out in the wild. There is also some amount of overhead in `internal/abi` due to this, but we aim to make the overhead insignificant. Fixes #884, #799, #817, #881, #858, #843, #842 Closes #406 --- .gitignore | 1 + AUTHORS | 2 +- README.md | 16 ----- hash.go | 18 ++---- main.go | 84 +++++++++++++++----------- position.go | 2 +- reflect.go | 68 +++++++++++++-------- reflect_abi_patch.go | 110 ++++++++++++++++++++++++++++++++++ reverse.go | 6 +- shared.go | 2 +- testdata/script/reflect.txtar | 40 +++++++++++++ 11 files changed, 252 insertions(+), 97 deletions(-) create mode 100644 reflect_abi_patch.go diff --git a/.gitignore b/.gitignore index dc5a9738..5f6d9263 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /garble /test /bincmp_output/ +debug diff --git a/AUTHORS b/AUTHORS index f7bd6582..2a402795 100644 --- a/AUTHORS +++ b/AUTHORS @@ -11,8 +11,8 @@ Daniel Martí Emmanuel Chee-zaram Okeke NHAS Nicholas Jones +Paul Scheduikat Zachary Wasserman -lu4p pagran shellhazard xuannv diff --git a/README.md b/README.md index 4cfc0bb9..51fc019b 100644 --- a/README.md +++ b/README.md @@ -157,23 +157,7 @@ to document the current shortcomings of this tool. be required by interfaces. This area is a work in progress; see [#3](https://github.com/burrowers/garble/issues/3). -* Garble automatically detects which Go types are used with reflection - to avoid obfuscating them, as that might break your program. - Note that Garble obfuscates [one package at a time](#speed), - so if your reflection code inspects a type from an imported package, - you may need to add a "hint" in the imported package to exclude obfuscating it: - ```go - type Message struct { - Command string - Args string - } - - // Never obfuscate the Message type. - var _ = reflect.TypeOf(Message{}) - ``` - * Aside from `GOGARBLE` to select patterns of packages to obfuscate, - and the hint above with `reflect.TypeOf` to exclude obfuscating particular types, there is no supported way to exclude obfuscating a selection of files or packages. More often than not, a user would want to do this to work around a bug; please file the bug instead. diff --git a/hash.go b/hash.go index bcf442db..0b0357e0 100644 --- a/hash.go +++ b/hash.go @@ -223,15 +223,7 @@ func entryOffKey() uint32 { return runtimeHashWithCustomSalt([]byte("entryOffKey")) } -func hashWithPackage(tf *transformer, pkg *listedPackage, name string) string { - - // In some places it is not appropriate to access the transformer - if tf != nil { - // If the package is marked as "in-use" by reflection, the private structures are not obfuscated, so dont return them as a hash. Fixes #882 - if _, ok := tf.curPkgCache.ReflectObjects[pkg.ImportPath+"."+name]; ok { - return name - } - } +func hashWithPackage(pkg *listedPackage, name string) string { // If the user provided us with an obfuscation seed, // we use that with the package import path directly.. // Otherwise, we use GarbleActionID as a fallback salt. @@ -412,11 +404,9 @@ func hashWithCustomSalt(salt []byte, name string) string { // Turn "afoo" into "Afoo". b64Name[0] = toUpper(b64Name[0]) } - } else { - if isUpper(b64Name[0]) { - // Turn "Afoo" into "afoo". - b64Name[0] = toLower(b64Name[0]) - } + } else if isUpper(b64Name[0]) { + // Turn "Afoo" into "afoo". + b64Name[0] = toLower(b64Name[0]) } } return string(b64Name) diff --git a/main.go b/main.go index 42e53af6..abfb3f2b 100644 --- a/main.go +++ b/main.go @@ -696,7 +696,7 @@ func (tf *transformer) transformAsm(args []string) ([]string, error) { newPaths := make([]string, 0, len(paths)) if !slices.Contains(args, "-gensymabis") { for _, path := range paths { - name := hashWithPackage(tf, tf.curPkg, filepath.Base(path)) + ".s" + name := hashWithPackage(tf.curPkg, filepath.Base(path)) + ".s" pkgDir := filepath.Join(sharedTempDir, tf.curPkg.obfuscatedImportPath()) newPath := filepath.Join(pkgDir, name) newPaths = append(newPaths, newPath) @@ -786,7 +786,7 @@ func (tf *transformer) transformAsm(args []string) ([]string, error) { // directory, as assembly files do not support `/*line` directives. // TODO(mvdan): per cmd/asm/internal/lex, they do support `#line`. basename := filepath.Base(path) - newName := hashWithPackage(tf, tf.curPkg, basename) + ".s" + newName := hashWithPackage(tf.curPkg, basename) + ".s" if path, err := tf.writeSourceFile(basename, newName, buf.Bytes()); err != nil { return nil, err } else { @@ -902,7 +902,7 @@ func (tf *transformer) replaceAsmNames(buf *bytes.Buffer, remaining []byte) { remaining = remaining[nameEnd:] if lpkg.ToObfuscate && !compilerIntrinsics[lpkg.ImportPath][name] { - newName := hashWithPackage(tf, lpkg, name) + newName := hashWithPackage(lpkg, name) if flagDebug { // TODO(mvdan): remove once https://go.dev/issue/53465 if fixed log.Printf("asm name %q hashed with %x to %q", name, tf.curPkg.GarbleActionID, newName) } @@ -949,16 +949,39 @@ func (tf *transformer) writeSourceFile(basename, obfuscated string, content []by // parseFiles parses a list of Go files. // It supports relative file paths, such as those found in listedPackage.CompiledGoFiles, // as long as dir is set to listedPackage.Dir. -func parseFiles(dir string, paths []string) ([]*ast.File, error) { - var files []*ast.File +func parseFiles(lpkg *listedPackage, dir string, paths []string) (files []*ast.File, err error) { + mainPackage := lpkg.Name == "main" && lpkg.ForTest == "" + for _, path := range paths { if !filepath.IsAbs(path) { path = filepath.Join(dir, path) } - file, err := parser.ParseFile(fset, path, nil, parser.SkipObjectResolution|parser.ParseComments) + + var src any + + if lpkg.ImportPath == "internal/abi" && filepath.Base(path) == "type.go" { + src, err = abiNamePatch(path) + if err != nil { + return nil, err + } + } else if mainPackage && reflectPatchFile == "" { + src, err = reflectMainPrePatch(path) + if err != nil { + return nil, err + } + + reflectPatchFile = path + } + + file, err := parser.ParseFile(fset, path, src, parser.SkipObjectResolution|parser.ParseComments) if err != nil { return nil, err } + + if mainPackage && src != nil { + astutil.AddNamedImport(fset, file, "_", "unsafe") + } + files = append(files, file) } return files, nil @@ -972,7 +995,7 @@ func (tf *transformer) transformCompile(args []string) ([]string, error) { flags = append(flags, "-dwarf=false") // The Go file paths given to the compiler are always absolute paths. - files, err := parseFiles("", paths) + files, err := parseFiles(tf.curPkg, "", paths) if err != nil { return nil, err } @@ -1085,6 +1108,10 @@ func (tf *transformer) transformCompile(args []string) ([]string, error) { return nil, err } + if tf.curPkg.Name == "main" && strings.HasSuffix(reflectPatchFile, basename) { + src = reflectMainPostPatch(src, tf.curPkg, tf.curPkgCache) + } + // We hide Go source filenames via "//line" directives, // so there is no need to use obfuscated filenames here. if path, err := tf.writeSourceFile(basename, basename, src); err != nil { @@ -1140,7 +1167,7 @@ func (tf *transformer) transformDirectives(comments []*ast.CommentGroup) { func (tf *transformer) transformLinkname(localName, newName string) (string, string) { // obfuscate the local name, if the current package is obfuscated if tf.curPkg.ToObfuscate && !compilerIntrinsics[tf.curPkg.ImportPath][localName] { - localName = hashWithPackage(tf, tf.curPkg, localName) + localName = hashWithPackage(tf.curPkg, localName) } if newName == "" { return localName, "" @@ -1208,29 +1235,26 @@ func (tf *transformer) transformLinkname(localName, newName string) (string, str var newForeignName string if receiver, name, ok := strings.Cut(foreignName, "."); ok { - if lpkg.ImportPath == "reflect" && (receiver == "(*rtype)" || receiver == "Value") { - // These receivers are not obfuscated. - // See the TODO below. - } else if strings.HasPrefix(receiver, "(*") { + if strings.HasPrefix(receiver, "(*") { // pkg/path.(*Receiver).method receiver = strings.TrimPrefix(receiver, "(*") receiver = strings.TrimSuffix(receiver, ")") - receiver = "(*" + hashWithPackage(tf, lpkg, receiver) + ")" + receiver = "(*" + hashWithPackage(lpkg, receiver) + ")" } else { // pkg/path.Receiver.method - receiver = hashWithPackage(tf, lpkg, receiver) + receiver = hashWithPackage(lpkg, receiver) } // Exported methods are never obfuscated. // // TODO(mvdan): We're duplicating the logic behind these decisions. // Reuse the logic with transformCompile. if !token.IsExported(name) { - name = hashWithPackage(tf, lpkg, name) + name = hashWithPackage(lpkg, name) } newForeignName = receiver + "." + name } else { // pkg/path.function - newForeignName = hashWithPackage(tf, lpkg, foreignName) + newForeignName = hashWithPackage(lpkg, foreignName) } newPkgPath := lpkg.ImportPath @@ -1308,7 +1332,7 @@ func (tf *transformer) processImportCfg(flags []string, requiredPkgs []string) ( // For beforePath="vendor/foo", afterPath and // lpkg.ImportPath can be just "foo". // Don't use obfuscatedImportPath here. - beforePath = hashWithPackage(tf, lpkg, beforePath) + beforePath = hashWithPackage(lpkg, beforePath) afterPath = lpkg.obfuscatedImportPath() } @@ -1405,14 +1429,9 @@ type pkgCache struct { // unless we were smart enough to detect which arguments get used as %#v or %T. ReflectAPIs map[funcFullName]map[int]bool - // ReflectObjects is filled with the fully qualified names from each - // package that we cannot obfuscate due to reflection. - // The included objects are named types and their fields, - // since it is those names being obfuscated that could break the use of reflect. - // - // This record is necessary for knowing what names from imported packages - // weren't obfuscated, so we can obfuscate their local uses accordingly. - ReflectObjects map[objectString]struct{} + // ReflectObjectNames maps obfuscated names which are reflected to their "real" + // non-obfuscated names. + ReflectObjectNames map[objectString]string // EmbeddedAliasFields records which embedded fields use a type alias. // They are the only instance where a type alias matters for obfuscation, @@ -1425,7 +1444,7 @@ type pkgCache struct { func (c *pkgCache) CopyFrom(c2 pkgCache) { maps.Copy(c.ReflectAPIs, c2.ReflectAPIs) - maps.Copy(c.ReflectObjects, c2.ReflectObjects) + maps.Copy(c.ReflectObjectNames, c2.ReflectObjectNames) maps.Copy(c.EmbeddedAliasFields, c2.EmbeddedAliasFields) } @@ -1497,7 +1516,7 @@ func computePkgCache(fsCache *cache.Cache, lpkg *listedPackage, pkg *types.Packa "reflect.TypeOf": {0: true}, "reflect.ValueOf": {0: true}, }, - ReflectObjects: map[objectString]struct{}{}, + ReflectObjectNames: map[objectString]string{}, EmbeddedAliasFields: map[objectString]typeName{}, } for _, imp := range lpkg.Imports { @@ -1530,7 +1549,7 @@ func computePkgCache(fsCache *cache.Cache, lpkg *listedPackage, pkg *types.Packa // Missing or corrupted entry in the cache for a dependency. // Could happen if GARBLE_CACHE was emptied but GOCACHE was not. // Compute it, which can recurse if many entries are missing. - files, err := parseFiles(lpkg.Dir, lpkg.CompiledGoFiles) + files, err := parseFiles(lpkg, lpkg.Dir, lpkg.CompiledGoFiles) if err != nil { return err } @@ -1997,11 +2016,6 @@ func (tf *transformer) transformGoFile(file *ast.File) *ast.File { } } - // The package that declared this object did not obfuscate it. - if usedForReflect(tf.curPkgCache, obj) { - return true - } - lpkg, err := listPackage(tf.curPkg, path) if err != nil { panic(err) // shouldn't happen @@ -2071,7 +2085,7 @@ func (tf *transformer) transformGoFile(file *ast.File) *ast.File { return true // we only want to rename the above } - node.Name = hashWithPackage(tf, lpkg, name) + node.Name = hashWithPackage(lpkg, name) // TODO: probably move the debugf lines inside the hash funcs if flagDebug { // TODO(mvdan): remove once https://go.dev/issue/53465 if fixed log.Printf("%s %q hashed with %x… to %q", debugName, name, hashToUse[:4], node.Name) @@ -2192,7 +2206,7 @@ func (tf *transformer) transformLink(args []string) ([]string, error) { if path != "main" { newPath = lpkg.obfuscatedImportPath() } - newName := hashWithPackage(tf, lpkg, name) + newName := hashWithPackage(lpkg, name) flags = append(flags, fmt.Sprintf("-X=%s.%s=%s", newPath, newName, stringValue)) }) diff --git a/position.go b/position.go index a502bb04..beadd012 100644 --- a/position.go +++ b/position.go @@ -127,7 +127,7 @@ func printFile(lpkg *listedPackage, file *ast.File) ([]byte, error) { newName := "" if !flagTiny { origPos := fmt.Sprintf("%s:%d", filename, origOffset) - newName = hashWithPackage(nil, lpkg, origPos) + ".go" + newName = hashWithPackage(lpkg, origPos) + ".go" // log.Printf("%q hashed with %x to %q", origPos, curPkg.GarbleActionID, newName) } diff --git a/reflect.go b/reflect.go index b88a766f..e95198b0 100644 --- a/reflect.go +++ b/reflect.go @@ -26,7 +26,7 @@ func (ri *reflectInspector) recordReflection(ssaPkg *ssa.Package) { return } - prevDone := len(ri.result.ReflectAPIs) + len(ri.result.ReflectObjects) + prevDone := len(ri.result.ReflectAPIs) + len(ri.result.ReflectObjectNames) // find all unchecked APIs to add them to checkedAPIs after the pass notCheckedAPIs := make(map[string]bool) @@ -43,7 +43,7 @@ func (ri *reflectInspector) recordReflection(ssaPkg *ssa.Package) { maps.Copy(ri.checkedAPIs, notCheckedAPIs) // if a new reflectAPI is found we need to Re-evaluate all functions which might be using that API - newDone := len(ri.result.ReflectAPIs) + len(ri.result.ReflectObjects) + newDone := len(ri.result.ReflectAPIs) + len(ri.result.ReflectObjectNames) if newDone > prevDone { ri.recordReflection(ssaPkg) // TODO: avoid recursing } @@ -58,8 +58,8 @@ func (ri *reflectInspector) ignoreReflectedTypes(ssaPkg *ssa.Package) { // At least it's enough to leave the rtype and Value types intact. if ri.pkg.Path() == "reflect" { scope := ri.pkg.Scope() - ri.recursivelyRecordUsedForReflect(scope.Lookup("rtype").Type()) - ri.recursivelyRecordUsedForReflect(scope.Lookup("Value").Type()) + ri.recursivelyRecordUsedForReflect(scope.Lookup("rtype").Type(), nil) + ri.recursivelyRecordUsedForReflect(scope.Lookup("Value").Type(), nil) } for _, memb := range ssaPkg.Members { @@ -135,7 +135,7 @@ func (ri *reflectInspector) checkMethodSignature(reflectParams map[int]bool, sig if ignore { reflectParams[i] = true - ri.recursivelyRecordUsedForReflect(param.Type()) + ri.recursivelyRecordUsedForReflect(param.Type(), nil) } } } @@ -196,7 +196,7 @@ func (ri *reflectInspector) checkFunction(fun *ssa.Function) { case *ssa.ChangeType: obj := typeToObj(inst.X.Type()) if usedForReflect(ri.result, obj) { - ri.recursivelyRecordUsedForReflect(inst.Type()) + ri.recursivelyRecordUsedForReflect(inst.Type(), nil) ri.propagatedInstr[inst] = true } case *ssa.Call: @@ -284,7 +284,7 @@ func (ri *reflectInspector) recordArgReflected(val ssa.Value, visited map[ssa.Va case *ssa.Alloc: /* fmt.Printf("recording val %v \n", *val.Referrers()) */ - ri.recursivelyRecordUsedForReflect(val.Type()) + ri.recursivelyRecordUsedForReflect(val.Type(), nil) for _, ref := range *val.Referrers() { if idx, ok := ref.(ssa.Value); ok { @@ -299,11 +299,11 @@ func (ri *reflectInspector) recordArgReflected(val ssa.Value, visited map[ssa.Va return relatedParam(val, visited) case *ssa.ChangeType: - ri.recursivelyRecordUsedForReflect(val.X.Type()) + ri.recursivelyRecordUsedForReflect(val.X.Type(), nil) case *ssa.MakeSlice, *ssa.MakeMap, *ssa.MakeChan, *ssa.Const: - ri.recursivelyRecordUsedForReflect(val.Type()) + ri.recursivelyRecordUsedForReflect(val.Type(), nil) case *ssa.Global: - ri.recursivelyRecordUsedForReflect(val.Type()) + ri.recursivelyRecordUsedForReflect(val.Type(), nil) // TODO: this might need similar logic to *ssa.Alloc, however // reassigning a function param to a global variable and then reflecting @@ -312,7 +312,7 @@ func (ri *reflectInspector) recordArgReflected(val ssa.Value, visited map[ssa.Va // this only finds the parameters who want to be found, // otherwise relatedParam is used for more in depth analysis - ri.recursivelyRecordUsedForReflect(val.Type()) + ri.recursivelyRecordUsedForReflect(val.Type(), nil) return val } @@ -388,7 +388,7 @@ func relatedParam(val ssa.Value, visited map[ssa.Value]bool) *ssa.Parameter { // Only the names declared in the current package are recorded. This is to ensure // that reflection detection only happens within the package declaring a type. // Detecting it in downstream packages could result in inconsistencies. -func (ri *reflectInspector) recursivelyRecordUsedForReflect(t types.Type) { +func (ri *reflectInspector) recursivelyRecordUsedForReflect(t types.Type, parent *types.Struct) { switch t := t.(type) { case *types.Named: obj := t.Obj() @@ -398,10 +398,10 @@ func (ri *reflectInspector) recursivelyRecordUsedForReflect(t types.Type) { if usedForReflect(ri.result, obj) { return // prevent endless recursion } - ri.recordUsedForReflect(obj) + ri.recordUsedForReflect(obj, parent) // Record the underlying type, too. - ri.recursivelyRecordUsedForReflect(t.Underlying()) + ri.recursivelyRecordUsedForReflect(t.Underlying(), nil) case *types.Struct: for i := range t.NumFields() { @@ -415,19 +415,18 @@ func (ri *reflectInspector) recursivelyRecordUsedForReflect(t types.Type) { } // Record the field itself, too. - ri.recordUsedForReflect(field) + ri.recordUsedForReflect(field, t) - ri.recursivelyRecordUsedForReflect(field.Type()) + ri.recursivelyRecordUsedForReflect(field.Type(), nil) } case interface{ Elem() types.Type }: // Get past pointers, slices, etc. - ri.recursivelyRecordUsedForReflect(t.Elem()) + ri.recursivelyRecordUsedForReflect(t.Elem(), nil) } } -// TODO: consider caching recordedObjectString via a map, -// if that shows an improvement in our benchmark +// TODO: remove once alias tracking is properly implemented func recordedObjectString(obj types.Object) objectString { if obj == nil { return "" @@ -465,27 +464,44 @@ func recordedObjectString(obj types.Object) objectString { return pkg.Path() + "." + obj.Name() } +// reflectedObjectString returns the obfucated name of a types.Object, +// parent is needed to correctly get the obfucated name of struct fields +func reflectedObjectString(obj types.Object, parent *types.Struct) string { + if obj == nil { + return "" + } + pkg := obj.Pkg() + if pkg == nil { + return "" + } + + if v, ok := obj.(*types.Var); ok && parent != nil { + return hashWithStruct(parent, v) + } + + lpkg := sharedCache.ListedPackages[obj.Pkg().Path()] + return hashWithPackage(lpkg, obj.Name()) +} + // recordUsedForReflect records the objects whose names we cannot obfuscate due to reflection. // We currently record named types and fields. -func (ri *reflectInspector) recordUsedForReflect(obj types.Object) { +func (ri *reflectInspector) recordUsedForReflect(obj types.Object, parent *types.Struct) { if obj.Pkg().Path() != ri.pkg.Path() { panic("called recordUsedForReflect with a foreign object") } - objStr := recordedObjectString(obj) + objStr := reflectedObjectString(obj, parent) if objStr == "" { - // If the object can't be described via a qualified string, - // do we need to record it at all? return } - ri.result.ReflectObjects[objStr] = struct{}{} + ri.result.ReflectObjectNames[objStr] = obj.Name() } func usedForReflect(cache pkgCache, obj types.Object) bool { - objStr := recordedObjectString(obj) + objStr := reflectedObjectString(obj, nil) if objStr == "" { return false } - _, ok := cache.ReflectObjects[objStr] + _, ok := cache.ReflectObjectNames[objStr] return ok } diff --git a/reflect_abi_patch.go b/reflect_abi_patch.go new file mode 100644 index 00000000..e934cb37 --- /dev/null +++ b/reflect_abi_patch.go @@ -0,0 +1,110 @@ +package main + +import ( + "bytes" + "fmt" + "maps" + "os" + "slices" + "strings" +) + +func abiNamePatch(path string) (string, error) { + data, err := os.ReadFile(path) + if err != nil { + return "", err + } + + find := `return unsafe.String(n.DataChecked(1+i, "non-empty string"), l)` + replace := `return _realName(unsafe.String(n.DataChecked(1+i, "non-empty string"), l))` + + str := strings.Replace(string(data), find, replace, 1) + + realname := ` +//go:linkname _realName +func _realName(name string) string +` + + return str + realname, nil +} + +var reflectPatchFile = "" + +// reflectMainPrePatch adds the initial empty name mapping and _realName implementation +// to a file in the main package. The name mapping will be populated later after +// analyzing the main package, since we need to know all obfuscated names that need mapping. +// We split this into pre/post steps so that all variable names in the generated code +// can be properly obfuscated - if we added the filled map directly, the obfuscated names +// would appear as plain strings in the binary. +func reflectMainPrePatch(path string) ([]byte, error) { + if reflectPatchFile != "" { + // already patched another file in main + return nil, nil + } + + content, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + nameMap := "\nvar _nameMap = map[string]string{}" + + return append(content, []byte(realNameCode+nameMap)...), nil +} + +// reflectMainPostPatch populates the name mapping with the final obfuscated->real name +// mappings after all packages have been analyzed. +func reflectMainPostPatch(file []byte, lpkg *listedPackage, pkg pkgCache) []byte { + obfMapName := hashWithPackage(lpkg, "_nameMap") + nameMap := fmt.Sprintf("%s = map[string]string{", obfMapName) + + var b strings.Builder + keys := slices.Sorted(maps.Keys(pkg.ReflectObjectNames)) + for _, obf := range keys { + b.WriteString(fmt.Sprintf(`"%s": "%s",`, obf, pkg.ReflectObjectNames[obf])) + } + + return bytes.Replace(file, []byte(nameMap), []byte(nameMap+b.String()), 1) +} + +// The "name" internal/abi passes to this function doesn't have to be a simple "someName" +// it can also be for function names: +// "*pkgName.FuncName" (obfuscated) +// or for structs the entire struct definition: +// "*struct { AQ45rr68K string; ipq5aQSIqN string; hNfiW5O5LVq struct { gPTbGR00hu string } }" +// +// Therefore all obfuscated names which occur within name need to be replaced with their "real" equivalents. +// +// The code below does a more efficient version of: +// +// func _realName(name string) string { +// for obfName, real := range _nameMap { +// name = strings.ReplaceAll(name, obfName, real) +// } +// +// return name +// } +const realNameCode = ` +//go:linkname _realName internal/abi._realName +func _realName(name string) string { + for i := 0; i < len(name); { + remLen := len(name[i:]) + found := false + for obfName, real := range _nameMap { + keyLen := len(obfName) + if keyLen > remLen { + continue + } + if name[i:i+keyLen] == obfName { + name = name[:i] + real + name[i+keyLen:] + found = true + i += len(real) + break + } + } + if !found { + i++ + } + } + return name +}` diff --git a/reverse.go b/reverse.go index ed9fef4d..eb69a8ac 100644 --- a/reverse.go +++ b/reverse.go @@ -69,13 +69,13 @@ One can reverse a captured panic stack trace as follows: continue } addHashedWithPackage := func(str string) { - replaces = append(replaces, hashWithPackage(nil, lpkg, str), str) + replaces = append(replaces, hashWithPackage(lpkg, str), str) } // Package paths are obfuscated, too. addHashedWithPackage(lpkg.ImportPath) - files, err := parseFiles(lpkg.Dir, lpkg.CompiledGoFiles) + files, err := parseFiles(lpkg, lpkg.Dir, lpkg.CompiledGoFiles) if err != nil { return err } @@ -113,7 +113,7 @@ One can reverse a captured panic stack trace as follows: // Reverse position information of call sites. pos := fset.Position(node.Pos()) origPos := fmt.Sprintf("%s:%d", goFile, pos.Offset) - newFilename := hashWithPackage(nil, lpkg, origPos) + ".go" + newFilename := hashWithPackage(lpkg, origPos) + ".go" // Do "obfuscated.go:1", corresponding to the call site's line. // Most common in stack traces. diff --git a/shared.go b/shared.go index 69a878d4..5a76b765 100644 --- a/shared.go +++ b/shared.go @@ -227,7 +227,7 @@ func (p *listedPackage) obfuscatedImportPath() string { if !p.ToObfuscate { return p.ImportPath } - newPath := hashWithPackage(nil, p, p.ImportPath) + newPath := hashWithPackage(p, p.ImportPath) log.Printf("import path %q hashed with %x to %q", p.ImportPath, p.GarbleActionID, newPath) return newPath } diff --git a/testdata/script/reflect.txtar b/testdata/script/reflect.txtar index 2c7e0f84..eaeab593 100644 --- a/testdata/script/reflect.txtar +++ b/testdata/script/reflect.txtar @@ -172,6 +172,8 @@ func main() { mu sync.Mutex extensionMap map[int32]EncodingT }) + + fmt.Println(reflect.TypeOf(Connection{})) } type EmbeddingIndirect struct { @@ -463,6 +465,43 @@ func gobChan() { }, len([]string{})) } +type Connection struct { + MaxLen struct { + Varchar int + } +} + +// NewConnection create a new connection from databaseURL string +func NewConnection() *Connection { + return &Connection{ + MaxLen: struct { + Varchar int + }{ + Varchar: 0x7FFF, + }, + } +} + +func closure() { + type gobAlias struct { + Security func() struct { + Pad bool + } + } + + alias := gobAlias{} + + gob.NewEncoder(os.Stdout).Encode(alias) + + outer := func() func() struct{ Pad bool } { + return func() struct{ Pad bool } { + return struct{ Pad bool }{Pad: true} + } + } + + alias.Security = outer() +} + -- importedpkg/imported.go -- package importedpkg @@ -603,3 +642,4 @@ struct { UnnamedStructField string } TypeOfParent's own name: TypeOfParent TypeOfParent named: TypeOfNamedField NamedReflectionField TypeOfParent embedded: TypeOfEmbeddedField EmbeddedReflectionField +main.Connection