skip over comments when obfuscating assembly

github.com/bytedance/sonic has many comments in its assembly files,
and one in particular caused us trouble:

	internal/rt/asm_amd64.s:2:// Code generated by asm2asm, DO NOT EDIT·

Since we looked for "middle dot" characters anywhere,
we would try to load the package "EDIT", which clearly does not exist.

To fix that, start skipping over comments. Thankfully,
Go's assembly syntax only supports "//" line comments,
so it's enough to read one line at a time and skip them quickly.
We also longer need a regular expression to find #include lines.

While here, two other minor bits of cleanup.
First, rename transformGo to transformGoFile, for clarity.
Second, use ".s" extensions for obfuscated assembly filenames,
just like we do with the comiler and ".go" files.

Updates #621.
pull/640/head
Daniel Martí 3 years ago committed by lu4p
parent 5ede145791
commit 1c4fe53fc1

@ -4,6 +4,7 @@
package main package main
import ( import (
"bufio"
"bytes" "bytes"
cryptorand "crypto/rand" cryptorand "crypto/rand"
"encoding/base64" "encoding/base64"
@ -612,8 +613,6 @@ var transformFuncs = map[string]func([]string) ([]string, error){
"link": transformLink, "link": transformLink,
} }
var rxIncludeHeader = regexp.MustCompile(`#include\s+"([^"]+)"`)
func transformAsm(args []string) ([]string, error) { func transformAsm(args []string) ([]string, error) {
flags, paths := splitFlagsFromFiles(args, ".s") flags, paths := splitFlagsFromFiles(args, ".s")
@ -631,7 +630,7 @@ func transformAsm(args []string) ([]string, error) {
newPaths := make([]string, 0, len(paths)) newPaths := make([]string, 0, len(paths))
if !slices.Contains(args, "-gensymabis") { if !slices.Contains(args, "-gensymabis") {
for _, path := range paths { for _, path := range paths {
name := hashWithPackage(curPkg, filepath.Base(path)) name := hashWithPackage(curPkg, filepath.Base(path)) + ".s"
pkgDir := filepath.Join(sharedTempDir, curPkg.obfuscatedImportPath()) pkgDir := filepath.Join(sharedTempDir, curPkg.obfuscatedImportPath())
newPath := filepath.Join(pkgDir, name) newPath := filepath.Join(pkgDir, name)
newPaths = append(newPaths, newPath) newPaths = append(newPaths, newPath)
@ -641,70 +640,90 @@ func transformAsm(args []string) ([]string, error) {
const missingHeader = "missing header path" const missingHeader = "missing header path"
newHeaderPaths := make(map[string]string) newHeaderPaths := make(map[string]string)
var buf bytes.Buffer var buf, includeBuf bytes.Buffer
for _, path := range paths { for _, path := range paths {
// Read the entire file into memory. buf.Reset()
// If we find issues with large files, we can use bufio. f, err := os.Open(path)
content, err := os.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
offset := 0 defer f.Close() // in case of error
for _, match := range rxIncludeHeader.FindAllSubmatchIndex(content, -1) { scanner := bufio.NewScanner(f)
start, end := offset+match[2], offset+match[3] for scanner.Scan() {
path := string(content[start:end]) line := scanner.Text()
if strings.ContainsAny(path, "\n\"") {
// If we failed to keep track of offsets, we could see a header // First, handle hash directives without leading whitespaces.
// path that contains quotes or newlines, which should not happen.
return nil, fmt.Errorf("bad offset tracking? %q", path) // #include "foo.h"
} if quoted := strings.TrimPrefix(line, "#include"); quoted != line {
newPath := newHeaderPaths[path] quoted = strings.TrimSpace(quoted)
switch newPath { path, err := strconv.Unquote(quoted)
case missingHeader: // no need to try again if err != nil {
continue
case "": // first time we see this header
buf.Reset()
content, err := os.ReadFile(path)
if errors.Is(err, fs.ErrNotExist) {
newHeaderPaths[path] = missingHeader
continue // a header file provided by Go or the system
} else if err != nil {
return nil, err return nil, err
} }
replaceAsmNames(&buf, content) newPath := newHeaderPaths[path]
switch newPath {
case missingHeader: // no need to try again
buf.WriteString(line)
buf.WriteByte('\n')
continue
case "": // first time we see this header
includeBuf.Reset()
content, err := os.ReadFile(path)
if errors.Is(err, fs.ErrNotExist) {
newHeaderPaths[path] = missingHeader
buf.WriteString(line)
buf.WriteByte('\n')
continue // a header file provided by Go or the system
} else if err != nil {
return nil, err
}
replaceAsmNames(&includeBuf, content)
// For now, we replace `foo.h` or `dir/foo.h` with `garbled_foo.h`. // For now, we replace `foo.h` or `dir/foo.h` with `garbled_foo.h`.
// The different name ensures we don't use the unobfuscated file. // The different name ensures we don't use the unobfuscated file.
// This is far from perfect, but does the job for the time being. // This is far from perfect, but does the job for the time being.
// In the future, use a randomized name. // In the future, use a randomized name.
basename := filepath.Base(path) basename := filepath.Base(path)
newPath = "garbled_" + basename newPath = "garbled_" + basename
if _, err := writeSourceFile(basename, newPath, buf.Bytes()); err != nil { if _, err := writeSourceFile(basename, newPath, includeBuf.Bytes()); err != nil {
return nil, err return nil, err
}
newHeaderPaths[path] = newPath
} }
newHeaderPaths[path] = newPath buf.WriteString("#include ")
buf.WriteString(strconv.Quote(newPath))
buf.WriteByte('\n')
continue
}
// Leave "//" comments unchanged; they might be directives.
if strings.HasPrefix(strings.TrimSpace(line), "// ") {
buf.WriteString(line)
buf.WriteByte('\n')
continue
} }
offset += len(newPath) - len(path)
// TODO: copying the bytes in a loop like this is far from optimal. // Anything else is regular assembly; replace the names.
var newContent []byte replaceAsmNames(&buf, []byte(line))
newContent = append(newContent, content[:start]...) buf.WriteByte('\n')
newContent = append(newContent, newPath...) }
newContent = append(newContent, content[end:]...) if err := scanner.Err(); err != nil {
content = newContent return nil, err
} }
buf.Reset()
replaceAsmNames(&buf, content)
// With assembly files, we obfuscate the filename in the temporary // With assembly files, we obfuscate the filename in the temporary
// directory, as assembly files do not support `/*line` directives. // directory, as assembly files do not support `/*line` directives.
// TODO(mvdan): per cmd/asm/internal/lex, they do support `#line`.
basename := filepath.Base(path) basename := filepath.Base(path)
newName := hashWithPackage(curPkg, basename) newName := hashWithPackage(curPkg, basename) + ".s"
if path, err := writeSourceFile(basename, newName, buf.Bytes()); err != nil { if path, err := writeSourceFile(basename, newName, buf.Bytes()); err != nil {
return nil, err return nil, err
} else { } else {
newPaths = append(newPaths, path) newPaths = append(newPaths, path)
} }
f.Close() // do not keep len(paths) files open
} }
return append(flags, newPaths...), nil return append(flags, newPaths...), nil
@ -933,7 +952,7 @@ func transformCompile(args []string) ([]string, error) {
} }
} }
tf.handleDirectives(file.Comments) tf.handleDirectives(file.Comments)
file = tf.transformGo(file) file = tf.transformGoFile(file)
if newPkgPath != "" { if newPkgPath != "" {
file.Name.Name = newPkgPath file.Name.Name = newPkgPath
} }
@ -1700,8 +1719,8 @@ func (tf *transformer) removeUnnecessaryImports(file *ast.File) {
} }
} }
// transformGo obfuscates the provided Go syntax file. // transformGoFile obfuscates the provided Go syntax file.
func (tf *transformer) transformGo(file *ast.File) *ast.File { func (tf *transformer) transformGoFile(file *ast.File) *ast.File {
// Only obfuscate the literals here if the flag is on // Only obfuscate the literals here if the flag is on
// and if the package in question is to be obfuscated. // and if the package in question is to be obfuscated.
// //

@ -60,6 +60,10 @@ func main() {
#include "extra/garble_define2_amd64.h" #include "extra/garble_define2_amd64.h"
// A comment may include many·specialasm·runes and it's okay.
// Or the same with leading whitespace:
// A comment may include many·specialasm·runes and it's okay.
TEXT ·privateAdd(SB),$0-16 TEXT ·privateAdd(SB),$0-16
JMP testwith·many·dotsmainimported·PublicAdd(SB) JMP testwith·many·dotsmainimported·PublicAdd(SB)

Loading…
Cancel
Save