From f7e59c3f6c14c120fee6545fbdd4a391954b62ff Mon Sep 17 00:00:00 2001 From: Paul Scheduikat Date: Wed, 27 Nov 2024 20:37:15 +0100 Subject: [PATCH] revised reflection handling Instead of excluding specific names from obfuscation "all" names are now obfuscated. For reflected names, a mapping to the original name is injected in internal/abi to resolve them correctly. Fixes #884, #799, #817, #881, #858, #843, #842 Closes #406 --- .gitignore | 1 + AUTHORS | 2 +- README.md | 16 ----- hash.go | 29 ++------- main.go | 84 +++++++++++++++----------- position.go | 2 +- reflect.go | 68 +++++++++++++-------- reflect_abi_patch.go | 108 ++++++++++++++++++++++++++++++++++ reverse.go | 6 +- shared.go | 2 +- testdata/script/reflect.txtar | 40 +++++++++++++ 11 files changed, 251 insertions(+), 107 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..b6227be0 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. @@ -348,8 +340,7 @@ func hashWithStruct(strct *types.Struct, field *types.Var) string { // For example, increasing the average length of 9 by 1 results in roughly a 1% // increase in binary sizes. const ( - minHashLength = 6 - maxHashLength = 12 + hashLength = 9 // At most we'll need maxHashLength base64 characters, // so 9 checksum bytes are enough for that purpose, @@ -377,14 +368,6 @@ func hashWithCustomSalt(salt []byte, name string) string { io.WriteString(hasher, name) sum := hasher.Sum(sumBuffer[:0]) - // The byte after neededSumBytes is never used as part of the name, - // but it is still deterministic and hard to predict, - // so it provides us with useful randomness between 0 and 255. - // We want the number to be between 0 and hashLenthRange-1 as well, - // so we use a remainder operation. - hashLengthRandomness := sum[neededSumBytes] % ((maxHashLength - minHashLength) + 1) - hashLength := minHashLength + hashLengthRandomness - nameBase64.Encode(b64NameBuffer[:], sum[:neededSumBytes]) b64Name := b64NameBuffer[:hashLength] @@ -412,11 +395,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..f3444766 --- /dev/null +++ b/reflect_abi_patch.go @@ -0,0 +1,108 @@ +package main + +import ( + "bytes" + "fmt" + "maps" + "os" + "slices" + "strconv" + "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) +} + +var hashLengthStr = strconv.Itoa(hashLength) + +// 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 +// } +var realNameCode = ` +//go:linkname _realName internal/abi._realName +func _realName(name string) string { + for i := 0; i < len(name); { + remLen := len(name[i:]) + if ` + hashLengthStr + ` > remLen { + break + } + + if real, ok := _nameMap[name[i:i+` + hashLengthStr + `]]; ok { + name = name[:i] + real + name[i+` + hashLengthStr + `:] + i += len(real) + } else { + 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