|
|
|
// Copyright (c) 2019, The Garble Authors.
|
|
|
|
// See LICENSE for licensing information.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
reverse: support unexported names and package paths (#233)
Unexported names are a bit tricky, since they are not listed in the
export data file. Perhaps unsurprisingly, it's only meant to expose
exported objects.
One option would be to go back to adding an extra header to the export
data file, containing the unexported methods in a map[string]T or
[]string. However, we have an easier route: just parse the Go files and
look up the names directly.
This does mean that we parse the Go files every time "reverse" runs,
even if the build cache is warm, but that should not be an issue.
Parsing Go files without any typechecking is very cheap compared to
everything else we do. Plus, we save having to load go/types information
from the build cache, or having to load extra headers from export files.
It should be noted that the obfuscation process does need type
information, mainly to be careful about which names can be obfuscated
and how they should be obfuscated. Neither is a worry here; all names
belong to a single package, and it doesn't matter if some aren't
actually obfuscated, since the string replacements would simply never
trigger in practice.
The test includes an unexported func, to test the new feature. We also
start reversing the obfuscation of import paths. Now, the test's reverse
output is as follows:
goroutine 1 [running]:
runtime/debug.Stack(0x??, 0x??, 0x??)
runtime/debug/stack.go:24 +0x??
test/main/lib.ExportedLibFunc(0x??, 0x??, 0x??, 0x??)
p.go:6 +0x??
main.unexportedMainFunc(...)
C.go:2
main.main()
z.go:3 +0x??
The only major missing feature is positions and filenames. A follow-up
PR will take care of those.
Updates #5.
4 years ago
|
|
|
"go/ast"
|
|
|
|
"go/parser"
|
|
|
|
"go/token"
|
|
|
|
"io"
|
|
|
|
"os"
|
reverse: support unexported names and package paths (#233)
Unexported names are a bit tricky, since they are not listed in the
export data file. Perhaps unsurprisingly, it's only meant to expose
exported objects.
One option would be to go back to adding an extra header to the export
data file, containing the unexported methods in a map[string]T or
[]string. However, we have an easier route: just parse the Go files and
look up the names directly.
This does mean that we parse the Go files every time "reverse" runs,
even if the build cache is warm, but that should not be an issue.
Parsing Go files without any typechecking is very cheap compared to
everything else we do. Plus, we save having to load go/types information
from the build cache, or having to load extra headers from export files.
It should be noted that the obfuscation process does need type
information, mainly to be careful about which names can be obfuscated
and how they should be obfuscated. Neither is a worry here; all names
belong to a single package, and it doesn't matter if some aren't
actually obfuscated, since the string replacements would simply never
trigger in practice.
The test includes an unexported func, to test the new feature. We also
start reversing the obfuscation of import paths. Now, the test's reverse
output is as follows:
goroutine 1 [running]:
runtime/debug.Stack(0x??, 0x??, 0x??)
runtime/debug/stack.go:24 +0x??
test/main/lib.ExportedLibFunc(0x??, 0x??, 0x??, 0x??)
p.go:6 +0x??
main.unexportedMainFunc(...)
C.go:2
main.main()
z.go:3 +0x??
The only major missing feature is positions and filenames. A follow-up
PR will take care of those.
Updates #5.
4 years ago
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// commandReverse implements "garble reverse".
|
|
|
|
func commandReverse(args []string) error {
|
|
|
|
flags, args := splitFlagsFromArgs(args)
|
|
|
|
mainPkg := "."
|
|
|
|
if len(args) > 0 {
|
|
|
|
mainPkg = args[0]
|
|
|
|
args = args[1:]
|
|
|
|
}
|
|
|
|
|
|
|
|
listArgs := []string{
|
|
|
|
"-json",
|
|
|
|
"-deps",
|
|
|
|
"-export",
|
|
|
|
}
|
|
|
|
listArgs = append(listArgs, flags...)
|
|
|
|
listArgs = append(listArgs, mainPkg)
|
|
|
|
cmd, err := toolexecCmd("list", listArgs)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
stdout, err := cmd.StdoutPipe()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
cmd.Stderr = &stderr
|
|
|
|
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
return fmt.Errorf("go list error: %v", err)
|
|
|
|
}
|
|
|
|
mainPkgPath := ""
|
|
|
|
dec := json.NewDecoder(stdout)
|
|
|
|
var privatePkgPaths []string
|
|
|
|
for dec.More() {
|
|
|
|
var pkg listedPackage
|
|
|
|
if err := dec.Decode(&pkg); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if pkg.Export == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if pkg.Name == "main" {
|
|
|
|
if mainPkgPath != "" {
|
|
|
|
return fmt.Errorf("found two main packages: %s %s", mainPkgPath, pkg.ImportPath)
|
|
|
|
}
|
|
|
|
mainPkgPath = pkg.ImportPath
|
|
|
|
}
|
|
|
|
if isPrivate(pkg.ImportPath) {
|
|
|
|
privatePkgPaths = append(privatePkgPaths, pkg.ImportPath)
|
|
|
|
}
|
|
|
|
buildID, err := buildidOf(pkg.Export)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
reverse: support unexported names and package paths (#233)
Unexported names are a bit tricky, since they are not listed in the
export data file. Perhaps unsurprisingly, it's only meant to expose
exported objects.
One option would be to go back to adding an extra header to the export
data file, containing the unexported methods in a map[string]T or
[]string. However, we have an easier route: just parse the Go files and
look up the names directly.
This does mean that we parse the Go files every time "reverse" runs,
even if the build cache is warm, but that should not be an issue.
Parsing Go files without any typechecking is very cheap compared to
everything else we do. Plus, we save having to load go/types information
from the build cache, or having to load extra headers from export files.
It should be noted that the obfuscation process does need type
information, mainly to be careful about which names can be obfuscated
and how they should be obfuscated. Neither is a worry here; all names
belong to a single package, and it doesn't matter if some aren't
actually obfuscated, since the string replacements would simply never
trigger in practice.
The test includes an unexported func, to test the new feature. We also
start reversing the obfuscation of import paths. Now, the test's reverse
output is as follows:
goroutine 1 [running]:
runtime/debug.Stack(0x??, 0x??, 0x??)
runtime/debug/stack.go:24 +0x??
test/main/lib.ExportedLibFunc(0x??, 0x??, 0x??, 0x??)
p.go:6 +0x??
main.unexportedMainFunc(...)
C.go:2
main.main()
z.go:3 +0x??
The only major missing feature is positions and filenames. A follow-up
PR will take care of those.
Updates #5.
4 years ago
|
|
|
// The action ID, and possibly the export file, will be used
|
|
|
|
// later to reconstruct the mapping of obfuscated names.
|
|
|
|
buildInfo.imports[pkg.ImportPath] = importedPkg{
|
|
|
|
packagefile: pkg.Export,
|
|
|
|
actionID: decodeHash(splitActionID(buildID)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := cmd.Wait(); err != nil {
|
|
|
|
return fmt.Errorf("go list error: %v: %s", err, stderr.Bytes())
|
|
|
|
}
|
|
|
|
|
reverse: support unexported names and package paths (#233)
Unexported names are a bit tricky, since they are not listed in the
export data file. Perhaps unsurprisingly, it's only meant to expose
exported objects.
One option would be to go back to adding an extra header to the export
data file, containing the unexported methods in a map[string]T or
[]string. However, we have an easier route: just parse the Go files and
look up the names directly.
This does mean that we parse the Go files every time "reverse" runs,
even if the build cache is warm, but that should not be an issue.
Parsing Go files without any typechecking is very cheap compared to
everything else we do. Plus, we save having to load go/types information
from the build cache, or having to load extra headers from export files.
It should be noted that the obfuscation process does need type
information, mainly to be careful about which names can be obfuscated
and how they should be obfuscated. Neither is a worry here; all names
belong to a single package, and it doesn't matter if some aren't
actually obfuscated, since the string replacements would simply never
trigger in practice.
The test includes an unexported func, to test the new feature. We also
start reversing the obfuscation of import paths. Now, the test's reverse
output is as follows:
goroutine 1 [running]:
runtime/debug.Stack(0x??, 0x??, 0x??)
runtime/debug/stack.go:24 +0x??
test/main/lib.ExportedLibFunc(0x??, 0x??, 0x??, 0x??)
p.go:6 +0x??
main.unexportedMainFunc(...)
C.go:2
main.main()
z.go:3 +0x??
The only major missing feature is positions and filenames. A follow-up
PR will take care of those.
Updates #5.
4 years ago
|
|
|
// A package's names are generally hashed with the action ID of its
|
|
|
|
// obfuscated build. We recorded those action IDs above.
|
|
|
|
// Note that we parse Go files directly to obtain the names, since the
|
|
|
|
// export data only exposes exported names. Parsing Go files is cheap,
|
|
|
|
// so it's unnecessary to try to avoid this cost.
|
|
|
|
var replaces []string
|
reverse: support unexported names and package paths (#233)
Unexported names are a bit tricky, since they are not listed in the
export data file. Perhaps unsurprisingly, it's only meant to expose
exported objects.
One option would be to go back to adding an extra header to the export
data file, containing the unexported methods in a map[string]T or
[]string. However, we have an easier route: just parse the Go files and
look up the names directly.
This does mean that we parse the Go files every time "reverse" runs,
even if the build cache is warm, but that should not be an issue.
Parsing Go files without any typechecking is very cheap compared to
everything else we do. Plus, we save having to load go/types information
from the build cache, or having to load extra headers from export files.
It should be noted that the obfuscation process does need type
information, mainly to be careful about which names can be obfuscated
and how they should be obfuscated. Neither is a worry here; all names
belong to a single package, and it doesn't matter if some aren't
actually obfuscated, since the string replacements would simply never
trigger in practice.
The test includes an unexported func, to test the new feature. We also
start reversing the obfuscation of import paths. Now, the test's reverse
output is as follows:
goroutine 1 [running]:
runtime/debug.Stack(0x??, 0x??, 0x??)
runtime/debug/stack.go:24 +0x??
test/main/lib.ExportedLibFunc(0x??, 0x??, 0x??, 0x??)
p.go:6 +0x??
main.unexportedMainFunc(...)
C.go:2
main.main()
z.go:3 +0x??
The only major missing feature is positions and filenames. A follow-up
PR will take care of those.
Updates #5.
4 years ago
|
|
|
fset := token.NewFileSet()
|
|
|
|
|
|
|
|
for _, pkgPath := range privatePkgPaths {
|
|
|
|
ipkg := buildInfo.imports[pkgPath]
|
reverse: support unexported names and package paths (#233)
Unexported names are a bit tricky, since they are not listed in the
export data file. Perhaps unsurprisingly, it's only meant to expose
exported objects.
One option would be to go back to adding an extra header to the export
data file, containing the unexported methods in a map[string]T or
[]string. However, we have an easier route: just parse the Go files and
look up the names directly.
This does mean that we parse the Go files every time "reverse" runs,
even if the build cache is warm, but that should not be an issue.
Parsing Go files without any typechecking is very cheap compared to
everything else we do. Plus, we save having to load go/types information
from the build cache, or having to load extra headers from export files.
It should be noted that the obfuscation process does need type
information, mainly to be careful about which names can be obfuscated
and how they should be obfuscated. Neither is a worry here; all names
belong to a single package, and it doesn't matter if some aren't
actually obfuscated, since the string replacements would simply never
trigger in practice.
The test includes an unexported func, to test the new feature. We also
start reversing the obfuscation of import paths. Now, the test's reverse
output is as follows:
goroutine 1 [running]:
runtime/debug.Stack(0x??, 0x??, 0x??)
runtime/debug/stack.go:24 +0x??
test/main/lib.ExportedLibFunc(0x??, 0x??, 0x??, 0x??)
p.go:6 +0x??
main.unexportedMainFunc(...)
C.go:2
main.main()
z.go:3 +0x??
The only major missing feature is positions and filenames. A follow-up
PR will take care of those.
Updates #5.
4 years ago
|
|
|
addReplace := func(str string) {
|
|
|
|
replaces = append(replaces, hashWith(ipkg.actionID, str), str)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Package paths are obfuscated, too.
|
|
|
|
addReplace(pkgPath)
|
|
|
|
|
reverse: support unexported names and package paths (#233)
Unexported names are a bit tricky, since they are not listed in the
export data file. Perhaps unsurprisingly, it's only meant to expose
exported objects.
One option would be to go back to adding an extra header to the export
data file, containing the unexported methods in a map[string]T or
[]string. However, we have an easier route: just parse the Go files and
look up the names directly.
This does mean that we parse the Go files every time "reverse" runs,
even if the build cache is warm, but that should not be an issue.
Parsing Go files without any typechecking is very cheap compared to
everything else we do. Plus, we save having to load go/types information
from the build cache, or having to load extra headers from export files.
It should be noted that the obfuscation process does need type
information, mainly to be careful about which names can be obfuscated
and how they should be obfuscated. Neither is a worry here; all names
belong to a single package, and it doesn't matter if some aren't
actually obfuscated, since the string replacements would simply never
trigger in practice.
The test includes an unexported func, to test the new feature. We also
start reversing the obfuscation of import paths. Now, the test's reverse
output is as follows:
goroutine 1 [running]:
runtime/debug.Stack(0x??, 0x??, 0x??)
runtime/debug/stack.go:24 +0x??
test/main/lib.ExportedLibFunc(0x??, 0x??, 0x??, 0x??)
p.go:6 +0x??
main.unexportedMainFunc(...)
C.go:2
main.main()
z.go:3 +0x??
The only major missing feature is positions and filenames. A follow-up
PR will take care of those.
Updates #5.
4 years ago
|
|
|
lpkg, err := listPackage(pkgPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
reverse: support unexported names and package paths (#233)
Unexported names are a bit tricky, since they are not listed in the
export data file. Perhaps unsurprisingly, it's only meant to expose
exported objects.
One option would be to go back to adding an extra header to the export
data file, containing the unexported methods in a map[string]T or
[]string. However, we have an easier route: just parse the Go files and
look up the names directly.
This does mean that we parse the Go files every time "reverse" runs,
even if the build cache is warm, but that should not be an issue.
Parsing Go files without any typechecking is very cheap compared to
everything else we do. Plus, we save having to load go/types information
from the build cache, or having to load extra headers from export files.
It should be noted that the obfuscation process does need type
information, mainly to be careful about which names can be obfuscated
and how they should be obfuscated. Neither is a worry here; all names
belong to a single package, and it doesn't matter if some aren't
actually obfuscated, since the string replacements would simply never
trigger in practice.
The test includes an unexported func, to test the new feature. We also
start reversing the obfuscation of import paths. Now, the test's reverse
output is as follows:
goroutine 1 [running]:
runtime/debug.Stack(0x??, 0x??, 0x??)
runtime/debug/stack.go:24 +0x??
test/main/lib.ExportedLibFunc(0x??, 0x??, 0x??, 0x??)
p.go:6 +0x??
main.unexportedMainFunc(...)
C.go:2
main.main()
z.go:3 +0x??
The only major missing feature is positions and filenames. A follow-up
PR will take care of those.
Updates #5.
4 years ago
|
|
|
for _, goFile := range lpkg.GoFiles {
|
|
|
|
goFile = filepath.Join(lpkg.Dir, goFile)
|
|
|
|
file, err := parser.ParseFile(fset, goFile, nil, 0)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, decl := range file.Decls {
|
|
|
|
// TODO: Probably do type names too. What else?
|
|
|
|
switch decl := decl.(type) {
|
|
|
|
case *ast.FuncDecl:
|
|
|
|
addReplace(decl.Name.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
repl := strings.NewReplacer(replaces...)
|
|
|
|
|
|
|
|
// TODO: return a non-zero status code if we could not reverse any string.
|
|
|
|
if len(args) == 0 {
|
|
|
|
return reverseContent(os.Stdout, os.Stdin, repl)
|
|
|
|
}
|
|
|
|
for _, path := range args {
|
|
|
|
f, err := os.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
if err := reverseContent(os.Stdout, f, repl); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
f.Close() // since we're in a loop
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func reverseContent(w io.Writer, r io.Reader, repl *strings.Replacer) error {
|
|
|
|
// Read line by line.
|
|
|
|
// Reading the entire content at once wouldn't be interactive,
|
|
|
|
// nor would it support large files well.
|
|
|
|
// Reading entire lines ensures we don't cut words in half.
|
|
|
|
// We use bufio.Reader instead of bufio.Scanner,
|
|
|
|
// to also obtain the newline characters themselves.
|
|
|
|
br := bufio.NewReader(r)
|
|
|
|
for {
|
|
|
|
// Note that ReadString can return a line as well as an error if
|
|
|
|
// we hit EOF without a newline.
|
|
|
|
// In that case, we still want to process the string.
|
|
|
|
line, readErr := br.ReadString('\n')
|
|
|
|
if _, err := repl.WriteString(w, line); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if readErr == io.EOF {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if readErr != nil {
|
|
|
|
return readErr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|