From e71cb69dd82f6a08dc7402d37c7a6ca38120591b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Sun, 2 Oct 2022 17:31:35 +0100 Subject: [PATCH] support obfuscating the time package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This failed at link time because transformAsm did not know how to handle the fact that the runtime package's assembly code implements the `time.now` function via: TEXT time·now(SB),NOSPLIT,$16-24 First, we need transformAsm to happen for all packages, not just the ones that we are obfuscating. This is because the runtime can implement APIs in other packages which are themselves obfuscated, whereas runtime may not itself be getting obfuscated. This is currently the case with `GOGARBLE=*` as we do not yet support obfuscating the runtime. Second, we need to teach replaceAsmNames to handle qualified names with import paths. Not just to look up the right package information for the name, but also to obfuscate the package path if necessary. Third, we need to relax the Deps requirement on listPackage, since the runtime package and its dependencies are always implicit dependencies. This is a big step towards being able to obfuscate the runtime, as there is now just one package left that we cannot obfuscate outside the runtime. Updates #193. --- main.go | 84 ++++++++++++++++++++++++++++++++++--------------------- shared.go | 22 +++++++++++---- 2 files changed, 69 insertions(+), 37 deletions(-) diff --git a/main.go b/main.go index 4ae6323..7e3860b 100644 --- a/main.go +++ b/main.go @@ -587,14 +587,10 @@ var transformFuncs = map[string]func([]string) ([]string, error){ var rxIncludeHeader = regexp.MustCompile(`#include\s+"([^"]+)"`) func transformAsm(args []string) ([]string, error) { - if !curPkg.ToObfuscate { - return args, nil // we're not obfuscating this package - } - flags, paths := splitFlagsFromFiles(args, ".s") // When assembling, the import path can make its way into the output object file. - if curPkg.Name != "main" { + if curPkg.Name != "main" && curPkg.ToObfuscate { flags = flagSetValue(flags, "-p", curPkg.obfuscatedImportPath()) } @@ -693,10 +689,17 @@ func replaceAsmNames(buf *bytes.Buffer, remaining []byte) { // Luckily, all func names in Go assembly files are immediately followed // by the unicode "middle dot", like: // - // TEXT ·privateAdd(SB),$0-24 + // TEXT ·privateAdd(SB),$0-24 + // TEXT runtime∕internal∕sys·Ctz64(SB), NOSPLIT, $0-12 const middleDot = '·' middleDotLen := utf8.RuneLen(middleDot) + // Note that import paths in assembly, like `runtime∕internal∕sys` above, + // use a Unicode slash rather than the ASCII one used by Go and `go list`. + // We need to convert to ASCII to find the right package information. + const asmPkgSlash = '∕' + const goPkgSlash = '/' + for { i := bytes.IndexRune(remaining, middleDot) if i < 0 { @@ -705,28 +708,46 @@ func replaceAsmNames(buf *bytes.Buffer, remaining []byte) { break } - // We want to replace "OP ·foo" and "OP $·foo", - // but not "OP somepkg·foo" just yet. - // "somepkg" is often runtime, syscall, etc. - // We don't obfuscate any of those for now. - // - // TODO: we'll likely need to deal with this - // when we start obfuscating the runtime. - // When we do, note that we can't hash with curPkg. - localName := false - if i >= 0 { - switch remaining[i-1] { - case ' ', '\t', '$', ',', '(': - localName = true + // The package name ends at the first rune which cannot be part of a Go + // import path, such as a comma or space. + pkgStart := i + for pkgStart >= 0 { + c, size := utf8.DecodeLastRune(remaining[:pkgStart]) + if !unicode.IsLetter(c) && c != '_' && c != asmPkgSlash && !unicode.IsDigit(c) { + break + } + pkgStart -= size + } + asmPkgPath := string(remaining[pkgStart:i]) + goPkgPath := strings.ReplaceAll(asmPkgPath, string(asmPkgSlash), string(goPkgSlash)) + + // Write the bytes before our unqualified `·foo` or qualified `pkg·foo`. + buf.Write(remaining[:pkgStart]) + + // If the name was qualified, fetch the package, and write the + // obfuscated import path if needed. + lpkg := curPkg + if asmPkgPath != "" { + var err error + lpkg, err = listPackage(goPkgPath) + if err != nil { + panic(err) // shouldn't happen + } + if lpkg.ToObfuscate { + // Note that we don't need to worry about asmPkgSlash here, + // because our obfuscated import paths contain no slashes right now. + buf.WriteString(lpkg.obfuscatedImportPath()) + } else { + buf.WriteString(asmPkgPath) } } - i += middleDotLen - buf.Write(remaining[:i]) - remaining = remaining[i:] + // Write the middle dot and advance the remaining slice. + buf.WriteRune(middleDot) + remaining = remaining[i+middleDotLen:] - // The name ends at the first rune which cannot be part - // of a Go identifier, such as a comma or space. + // The declared name ends at the first rune which cannot be part of a Go + // identifier, such as a comma or space. nameEnd := 0 for nameEnd < len(remaining) { c, size := utf8.DecodeRune(remaining[nameEnd:]) @@ -738,16 +759,15 @@ func replaceAsmNames(buf *bytes.Buffer, remaining []byte) { name := string(remaining[:nameEnd]) remaining = remaining[nameEnd:] - if !localName { + if lpkg.ToObfuscate { + 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, curPkg.GarbleActionID, newName) + } + buf.WriteString(newName) + } else { buf.WriteString(name) - continue - } - - newName := hashWithPackage(curPkg, 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, curPkg.GarbleActionID, newName) } - buf.WriteString(newName) } } diff --git a/shared.go b/shared.go index bdbc7b5..9a4b78d 100644 --- a/shared.go +++ b/shared.go @@ -289,9 +289,6 @@ func appendListedPackages(packages []string, withDeps bool) error { // // TODO: investigate and resolve each one of these var cannotObfuscate = map[string]bool{ - // "undefined reference" errors at link time - "time": true, - // "//go:linkname must refer to declared function or variable" "syscall": true, @@ -393,15 +390,30 @@ func listPackage(path string) (*listedPackage, error) { log.Printf("listed %d missing runtime-linknamed packages in %s", len(missing), debugSince(startTime)) return pkg, nil } - // Packages other than runtime can list any package, - // as long as they depend on it directly or indirectly. if !ok { return nil, fmt.Errorf("path not found in listed packages: %s", path) } + + // Packages other than runtime can list any package, + // as long as they depend on it directly or indirectly. for _, dep := range curPkg.Deps { if dep == pkg.ImportPath { return pkg, nil } } + + // As a special case, any package can list runtime or its dependencies, + // since those are always an implicit dependency. + // We need to handle this ourselves as runtime does not appear in Deps. + // TODO: it might be faster to bring back a "runtimeAndDeps" map or func. + if pkg.ImportPath == "runtime" { + return pkg, nil + } + for _, dep := range cache.ListedPackages["runtime"].Deps { + if dep == pkg.ImportPath { + return pkg, nil + } + } + return nil, fmt.Errorf("refusing to list non-dependency package: %s", path) }