diff --git a/main.go b/main.go index cc40255..7fbbf08 100644 --- a/main.go +++ b/main.go @@ -1077,33 +1077,73 @@ func (tf *transformer) transformLinkname(localName, newName string) (string, str return localName, newName } - // If the package path has multiple dots, split on the last one. - lastDotIdx := strings.LastIndex(newName, ".") - pkgPath, foreignName := newName[:lastDotIdx], newName[lastDotIdx+1:] - - lpkg, err := listPackage(pkgPath) - if err != nil { - if errors.Is(err, ErrNotFound) { + pkgSplit := 0 + var lpkg *listedPackage + var foreignName string + for { + i := strings.Index(newName[pkgSplit:], ".") + if i < 0 { + // We couldn't find a prefix that matched a known package. // Probably a made up name like above, but with a dot. return localName, newName } + pkgSplit += i + pkgPath := newName[:pkgSplit] + pkgSplit++ // skip over the dot + + var err error + lpkg, err = listPackage(pkgPath) + if err == nil { + foreignName = newName[pkgSplit:] + break + } + if errors.Is(err, ErrNotFound) { + // No match; find the next dot. + continue + } if errors.Is(err, ErrNotDependency) { fmt.Fprintf(os.Stderr, - "//go:linkname refers to %s - add `import _ %q` so garble can find the package", + "//go:linkname refers to %s - add `import _ %q` for garble to find the package", newName, pkgPath) return localName, newName } panic(err) // shouldn't happen } - if lpkg.ToObfuscate && !compilerIntrinsicsFuncs[lpkg.ImportPath+"."+foreignName] { - // The name exists and was obfuscated; obfuscate the new name. - newForeignName := hashWithPackage(lpkg, foreignName) - newPkgPath := pkgPath - if pkgPath != "main" { - newPkgPath = lpkg.obfuscatedImportPath() + + if !lpkg.ToObfuscate || compilerIntrinsicsFuncs[lpkg.ImportPath+"."+foreignName] { + // We're not obfuscating that package or name. + return localName, newName + } + + var newForeignName string + if receiver, name, ok := strings.Cut(foreignName, "."); ok { + if strings.HasPrefix(receiver, "(*") { + // pkg/path.(*Receiver).method + receiver = strings.TrimPrefix(receiver, "(*") + receiver = strings.TrimSuffix(receiver, ")") + receiver = "(*" + hashWithPackage(lpkg, receiver) + ")" + } else { + // pkg/path.Receiver.method + receiver = hashWithPackage(lpkg, receiver) } - newName = newPkgPath + "." + newForeignName + // Exported methods are never obfuscated. + // + // TODO: we're duplicating the logic behind these decisions. + // How can we more easily reuse the same logic? + if !token.IsExported(name) { + name = hashWithPackage(lpkg, name) + } + newForeignName = receiver + "." + name + } else { + // pkg/path.function + newForeignName = hashWithPackage(lpkg, foreignName) + } + + newPkgPath := lpkg.ImportPath + if newPkgPath != "main" { + newPkgPath = lpkg.obfuscatedImportPath() } + newName = newPkgPath + "." + newForeignName return localName, newName } @@ -1435,7 +1475,7 @@ func (tf *transformer) prefillObjectMaps(files []*ast.File) error { path, name := fullName[:i], fullName[i+1:] // -X represents the main package as "main", not its import path. - if path != curPkg.ImportPath && !(path == "main" && curPkg.Name == "main") { + if path != curPkg.ImportPath && (path != "main" || curPkg.Name != "main") { return // not the current package } @@ -2016,8 +2056,10 @@ func (tf *transformer) recursivelyRecordAsNotObfuscated(t types.Type) { switch t := t.(type) { case *types.Named: obj := t.Obj() - if obj.Pkg() == nil || obj.Pkg() != tf.pkg { + if pkg := obj.Pkg(); pkg == nil || pkg != tf.pkg { return // not from the specified package + } else if pkg.Path() == "reflect" { + return // reflect's own types can always be obfuscated } if recordedAsNotObfuscated(obj) { return // prevent endless recursion diff --git a/testdata/script/linkname.txtar b/testdata/script/linkname.txtar index 6bb9540..94e4c07 100644 --- a/testdata/script/linkname.txtar +++ b/testdata/script/linkname.txtar @@ -2,7 +2,9 @@ garble build exec ./main cmp stderr main.stderr +# TODO: why is 'obfuscatedMethod' present? ! binsubstr main$exe 'obfuscatedFunc' 'ObfuscatedFunc' +binsubstr main$exe 'UnobfuscatedMethod' [short] stop # no need to verify this with -short @@ -33,8 +35,9 @@ package main import ( _ "os/exec" + "reflect" _ "strings" - _ "unsafe" + "unsafe" _ "big.chungus/meme" "test/main/imported" @@ -52,6 +55,26 @@ func interfaceEqual(a, b any) bool //go:linkname obfuscatedFunc test/main/imported.ObfuscatedFuncImpl func obfuscatedFunc() string +// A linkname to an external obfuscated method. +//go:linkname obfuscatedMethod test/main/imported.Receiver.obfuscatedMethod +func obfuscatedMethod(imported.Receiver) string + +// A linkname to an external unobfuscated method. +//go:linkname unobfuscatedMethod test/main/imported.Receiver.UnobfuscatedMethod +func unobfuscatedMethod(imported.Receiver) string + +// A linkname to an external obfuscated pointer method, with an extra parameter. +//go:linkname obfuscatedPointerMethod test/main/imported.(*Receiver).obfuscatedPointerMethod +func obfuscatedPointerMethod(*imported.Receiver, string) string + +// Similar to the above, but for std, plus having to define a type. +// Some libraries do abuse reflect in this way, unfortunately. +type rtype struct{} +//go:linkname rtype_ptrTo reflect.(*rtype).ptrTo +func rtype_ptrTo(*rtype) *rtype +//go:linkname rtype_NumMethod reflect.(*rtype).NumMethod +func rtype_NumMethod(*rtype) int + // A linkname to an entirely made up name, implemented elsewhere. //go:linkname renamedFunc madeup.newName func renamedFunc() string @@ -64,7 +87,23 @@ func tagline() string func main() { println(byteIndex("01234", '3')) println(interfaceEqual("Sephiroth", 7)) + println(obfuscatedFunc()) + + r := imported.Receiver{Field: "field value"} + println(obfuscatedMethod(r)) + println(unobfuscatedMethod(r)) + println(obfuscatedPointerMethod(&r, "another value")) + + typ := reflect.TypeOf(new(error)).Elem() + type emptyInterface struct { + _ *rtype + ptr unsafe.Pointer + } + rtyp := (*rtype)(((*emptyInterface)(unsafe.Pointer(&typ))).ptr) + println("rtype_ptrTo non-nil", rtype_ptrTo(rtyp) != nil) + println("rtype_NumMethod", rtype_NumMethod(rtyp)) + println(renamedFunc()) println(tagline()) println(imported.ByteIndex("01234", '3')) @@ -82,6 +121,22 @@ func ObfuscatedFuncImpl() string { return "obfuscated func" } +type Receiver struct{ + Field string +} + +func (r Receiver) obfuscatedMethod() string { + return "obfuscated method: " + r.Field +} + +func (r *Receiver) obfuscatedPointerMethod(extra string) string { + return "obfuscated pointer method: " + r.Field + " plus " + extra +} + +func (r Receiver) UnobfuscatedMethod() string { + return "unobfuscated method: " + r.Field +} + //go:linkname renamedFunc madeup.newName func renamedFunc() string { return "renamed func" @@ -105,6 +160,11 @@ func chungify() string { 3 false obfuscated func +obfuscated method: field value +unobfuscated method: field value +obfuscated pointer method: field value plus another value +rtype_ptrTo non-nil true +rtype_NumMethod 1 renamed func featuring Dante from the Devil May Cry series 3