|
|
|
// Copyright (c) 2020, The Garble Authors.
|
|
|
|
// See LICENSE for licensing information.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"go/ast"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// PosMin is the smallest correct value for the line number.
|
|
|
|
// Source: https://go.googlesource.com/go/+/refs/heads/master/src/cmd/compile/internal/syntax/parser_test.go#229
|
|
|
|
const PosMin = 1
|
|
|
|
|
|
|
|
// detachedDirectives is a list of Go compiler directives which don't need to go
|
|
|
|
// right next to a Go declaration. Unlike all other detached comments, these
|
|
|
|
// need to be kept around as they alter compiler behavior.
|
|
|
|
var detachedDirectives = []string{
|
|
|
|
"// +build",
|
|
|
|
"//go:linkname",
|
|
|
|
"//go:cgo_ldflag",
|
|
|
|
"//go:cgo_dynamic_linker",
|
|
|
|
"//go:cgo_export_static",
|
|
|
|
"//go:cgo_export_dynamic",
|
|
|
|
"//go:cgo_import_static",
|
|
|
|
"//go:cgo_import_dynamic",
|
|
|
|
}
|
|
|
|
|
|
|
|
func isDirective(text string, directives []string) bool {
|
|
|
|
for _, prefix := range directives {
|
|
|
|
if strings.HasPrefix(text, prefix) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func prependComment(group *ast.CommentGroup, comment *ast.Comment) *ast.CommentGroup {
|
|
|
|
if group == nil {
|
|
|
|
return &ast.CommentGroup{List: []*ast.Comment{comment}}
|
|
|
|
}
|
|
|
|
|
|
|
|
group.List = append([]*ast.Comment{comment}, group.List...)
|
|
|
|
return group
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove all comments from CommentGroup except //go: directives.
|
rewrite go:linkname directives with garbled names (#200)
If code includes a linkname directive pointing at a name in an imported
package, like:
//go:linkname localName importedpackage.RemoteName
func localName()
We should rewrite the comment to replace "RemoteName" with its
obfuscated counterpart, if the package in question was obfuscated and
that name was as well.
We already had some code to handle linkname directives, but only to
ensure that "localName" was never obfuscated. This behavior is kept, to
ensure that the directive applies to the right name. In the future, we
could instead rewrite "localName" in the directive, like we do with
"RemoteName".
Add plenty of tests, too. The linkname directive used to be tested in
imports.txt and syntax.txt, but that was hard to maintain as each file
tested different edge cases.
Now that we have build caching, adding one extra testscript file isn't a
big problem anymoree. Add linkname.txt, which is self-explanatory. The
other two scripts also get a bit less complex.
Fixes #197.
4 years ago
|
|
|
// go:linkname directives are removed, since they're collected and rewritten
|
|
|
|
// separately.
|
|
|
|
func clearCommentGroup(group *ast.CommentGroup) *ast.CommentGroup {
|
|
|
|
if group == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var comments []*ast.Comment
|
|
|
|
for _, comment := range group.List {
|
rewrite go:linkname directives with garbled names (#200)
If code includes a linkname directive pointing at a name in an imported
package, like:
//go:linkname localName importedpackage.RemoteName
func localName()
We should rewrite the comment to replace "RemoteName" with its
obfuscated counterpart, if the package in question was obfuscated and
that name was as well.
We already had some code to handle linkname directives, but only to
ensure that "localName" was never obfuscated. This behavior is kept, to
ensure that the directive applies to the right name. In the future, we
could instead rewrite "localName" in the directive, like we do with
"RemoteName".
Add plenty of tests, too. The linkname directive used to be tested in
imports.txt and syntax.txt, but that was hard to maintain as each file
tested different edge cases.
Now that we have build caching, adding one extra testscript file isn't a
big problem anymoree. Add linkname.txt, which is self-explanatory. The
other two scripts also get a bit less complex.
Fixes #197.
4 years ago
|
|
|
if strings.HasPrefix(comment.Text, "//go:") && !strings.HasPrefix(comment.Text, "//go:linkname") {
|
|
|
|
comments = append(comments, &ast.Comment{Text: comment.Text})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(comments) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return &ast.CommentGroup{List: comments}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove all comments from Doc (if any) except //go: directives.
|
|
|
|
func clearNodeComments(node ast.Node) {
|
|
|
|
switch n := node.(type) {
|
|
|
|
case *ast.Field:
|
|
|
|
n.Doc = clearCommentGroup(n.Doc)
|
|
|
|
n.Comment = nil
|
|
|
|
case *ast.ImportSpec:
|
|
|
|
n.Doc = clearCommentGroup(n.Doc)
|
|
|
|
n.Comment = nil
|
|
|
|
case *ast.ValueSpec:
|
|
|
|
n.Doc = clearCommentGroup(n.Doc)
|
|
|
|
n.Comment = nil
|
|
|
|
case *ast.TypeSpec:
|
|
|
|
n.Doc = clearCommentGroup(n.Doc)
|
|
|
|
n.Comment = nil
|
|
|
|
case *ast.GenDecl:
|
|
|
|
n.Doc = clearCommentGroup(n.Doc)
|
|
|
|
case *ast.FuncDecl:
|
|
|
|
n.Doc = clearCommentGroup(n.Doc)
|
|
|
|
case *ast.File:
|
|
|
|
n.Doc = clearCommentGroup(n.Doc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// transformLineInfo removes the comment except go directives and build tags. Converts comments to the node view.
|
|
|
|
// It returns comments not attached to declarations and names of declarations which cannot be renamed.
|
|
|
|
func (tf *transformer) transformLineInfo(file *ast.File, filename string) (detachedComments []string, f *ast.File) {
|
|
|
|
prefix := ""
|
|
|
|
if strings.HasPrefix(filename, "_cgo_") {
|
|
|
|
prefix = "_cgo_"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save build tags and add file name leak protection
|
|
|
|
for _, group := range file.Comments {
|
|
|
|
for _, comment := range group.List {
|
|
|
|
if isDirective(comment.Text, detachedDirectives) {
|
|
|
|
detachedComments = append(detachedComments, comment.Text)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
detachedComments = append(detachedComments, "", "//line "+prefix+":1")
|
|
|
|
file.Comments = nil
|
|
|
|
|
simplify, improve, and test line obfuscation (#239)
First, remove the shuffling of the declarations list within each file.
This is what we used at the very start to shuffle positions. Ever since
we started obfuscating positions via //line comments, that has been
entirely unnecessary.
Second, add a proper test that will fail if we don't obfuscate line
numbers well enough. Filenames were already decently covered by other
tests.
Third, simplify the line obfuscation code. It does not require
astutil.Apply, and ranging over file.Decls is easier.
Finally, also obfuscate the position of top-level vars, since we only
used to do it for top-level funcs. Without that fix, the test would fail
as varLines was unexpectedly sorted.
4 years ago
|
|
|
ast.Inspect(file, func(node ast.Node) bool {
|
|
|
|
clearNodeComments(node)
|
simplify, improve, and test line obfuscation (#239)
First, remove the shuffling of the declarations list within each file.
This is what we used at the very start to shuffle positions. Ever since
we started obfuscating positions via //line comments, that has been
entirely unnecessary.
Second, add a proper test that will fail if we don't obfuscate line
numbers well enough. Filenames were already decently covered by other
tests.
Third, simplify the line obfuscation code. It does not require
astutil.Apply, and ranging over file.Decls is easier.
Finally, also obfuscate the position of top-level vars, since we only
used to do it for top-level funcs. Without that fix, the test would fail
as varLines was unexpectedly sorted.
4 years ago
|
|
|
return true
|
|
|
|
})
|
|
|
|
|
|
|
|
for _, decl := range file.Decls {
|
simplify, improve, and test line obfuscation (#239)
First, remove the shuffling of the declarations list within each file.
This is what we used at the very start to shuffle positions. Ever since
we started obfuscating positions via //line comments, that has been
entirely unnecessary.
Second, add a proper test that will fail if we don't obfuscate line
numbers well enough. Filenames were already decently covered by other
tests.
Third, simplify the line obfuscation code. It does not require
astutil.Apply, and ranging over file.Decls is easier.
Finally, also obfuscate the position of top-level vars, since we only
used to do it for top-level funcs. Without that fix, the test would fail
as varLines was unexpectedly sorted.
4 years ago
|
|
|
var doc **ast.CommentGroup
|
|
|
|
switch decl := decl.(type) {
|
|
|
|
case *ast.FuncDecl:
|
|
|
|
doc = &decl.Doc
|
|
|
|
case *ast.GenDecl:
|
|
|
|
doc = &decl.Doc
|
|
|
|
}
|
|
|
|
newName := ""
|
reimplement import path obfuscation without goobj2 (#242)
We used to rely on a parallel implementation of an object file parser
and writer to be able to obfuscate import paths. After compiling each
package, we would parse the object file, replace the import paths, and
write the updated object file in-place.
That worked well, in most cases. Unfortunately, it had some flaws:
* Complexity. Even when most of the code is maintained in a separate
module, the import_obfuscation.go file was still close to a thousand
lines of code.
* Go compatibility. The object file format changes between Go releases,
so we were supporting Go 1.15, but not 1.16. Fixing the object file
package to work with 1.16 would probably break 1.15 support.
* Bugs. For example, we recently had to add a workaround for #224, since
import paths containing dots after the domain would end up escaped.
Another example is #190, which seems to be caused by the object file
parser or writer corrupting the compiled code and causing segfaults in
some rare edge cases.
Instead, let's drop that method entirely, and force the compiler and
linker to do the work for us. The steps necessary when compiling a
package to obfuscate are:
1) Replace its "package foo" lines with the obfuscated package path. No
need to separate the package path and name, since the obfuscated path
does not contain slashes.
2) Replace the "-p pkg/foo" flag with the obfuscated path.
3) Replace the "import" spec lines with the obfuscated package paths,
for those dependencies which were obfuscated.
4) Replace the "-importcfg [...]" file with a version that uses the
obfuscated paths instead.
The linker also needs that last step, since it also uses an importcfg
file to find object files.
There are three noteworthy drawbacks to this new method:
1) Since we no longer write object files, we can't use them to store
data to be cached. As such, the -debugdir flag goes back to using the
"-a" build flag to always rebuild all packages. On the plus side,
that caching didn't work very well; see #176.
2) The package name "main" remains in all declarations under it, not
just "func main", since we can only rename entire packages. This
seems fine, as it gives little information to the end user.
3) The -tiny mode no longer sets all lines to 0, since it did that by
modifying object files. As a temporary measure, we instead set all
top-level declarations to be on line 1. A TODO is added to hopefully
improve this again in the near future.
The upside is that we get rid of all the issues mentioned before. Plus,
garble now nearly works with Go 1.16, with the exception of two very
minor bugs that look fixable. A follow-up PR will take care of that and
start testing on 1.16.
Fixes #176.
Fixes #190.
4 years ago
|
|
|
if !opts.Tiny {
|
|
|
|
origPos := fmt.Sprintf("%s:%d", filename, fset.Position(decl.Pos()).Offset)
|
|
|
|
newName = hashWith(curPkg.GarbleActionID, origPos) + ".go"
|
|
|
|
// log.Printf("%q hashed with %x to %q", origPos, curPkg.GarbleActionID, newName)
|
reimplement import path obfuscation without goobj2 (#242)
We used to rely on a parallel implementation of an object file parser
and writer to be able to obfuscate import paths. After compiling each
package, we would parse the object file, replace the import paths, and
write the updated object file in-place.
That worked well, in most cases. Unfortunately, it had some flaws:
* Complexity. Even when most of the code is maintained in a separate
module, the import_obfuscation.go file was still close to a thousand
lines of code.
* Go compatibility. The object file format changes between Go releases,
so we were supporting Go 1.15, but not 1.16. Fixing the object file
package to work with 1.16 would probably break 1.15 support.
* Bugs. For example, we recently had to add a workaround for #224, since
import paths containing dots after the domain would end up escaped.
Another example is #190, which seems to be caused by the object file
parser or writer corrupting the compiled code and causing segfaults in
some rare edge cases.
Instead, let's drop that method entirely, and force the compiler and
linker to do the work for us. The steps necessary when compiling a
package to obfuscate are:
1) Replace its "package foo" lines with the obfuscated package path. No
need to separate the package path and name, since the obfuscated path
does not contain slashes.
2) Replace the "-p pkg/foo" flag with the obfuscated path.
3) Replace the "import" spec lines with the obfuscated package paths,
for those dependencies which were obfuscated.
4) Replace the "-importcfg [...]" file with a version that uses the
obfuscated paths instead.
The linker also needs that last step, since it also uses an importcfg
file to find object files.
There are three noteworthy drawbacks to this new method:
1) Since we no longer write object files, we can't use them to store
data to be cached. As such, the -debugdir flag goes back to using the
"-a" build flag to always rebuild all packages. On the plus side,
that caching didn't work very well; see #176.
2) The package name "main" remains in all declarations under it, not
just "func main", since we can only rename entire packages. This
seems fine, as it gives little information to the end user.
3) The -tiny mode no longer sets all lines to 0, since it did that by
modifying object files. As a temporary measure, we instead set all
top-level declarations to be on line 1. A TODO is added to hopefully
improve this again in the near future.
The upside is that we get rid of all the issues mentioned before. Plus,
garble now nearly works with Go 1.16, with the exception of two very
minor bugs that look fixable. A follow-up PR will take care of that and
start testing on 1.16.
Fixes #176.
Fixes #190.
4 years ago
|
|
|
}
|
|
|
|
newPos := fmt.Sprintf("%s%s:1", prefix, newName)
|
|
|
|
|
|
|
|
comment := &ast.Comment{Text: "//line " + newPos}
|
simplify, improve, and test line obfuscation (#239)
First, remove the shuffling of the declarations list within each file.
This is what we used at the very start to shuffle positions. Ever since
we started obfuscating positions via //line comments, that has been
entirely unnecessary.
Second, add a proper test that will fail if we don't obfuscate line
numbers well enough. Filenames were already decently covered by other
tests.
Third, simplify the line obfuscation code. It does not require
astutil.Apply, and ranging over file.Decls is easier.
Finally, also obfuscate the position of top-level vars, since we only
used to do it for top-level funcs. Without that fix, the test would fail
as varLines was unexpectedly sorted.
4 years ago
|
|
|
*doc = prependComment(*doc, comment)
|
|
|
|
}
|
|
|
|
|
simplify, improve, and test line obfuscation (#239)
First, remove the shuffling of the declarations list within each file.
This is what we used at the very start to shuffle positions. Ever since
we started obfuscating positions via //line comments, that has been
entirely unnecessary.
Second, add a proper test that will fail if we don't obfuscate line
numbers well enough. Filenames were already decently covered by other
tests.
Third, simplify the line obfuscation code. It does not require
astutil.Apply, and ranging over file.Decls is easier.
Finally, also obfuscate the position of top-level vars, since we only
used to do it for top-level funcs. Without that fix, the test would fail
as varLines was unexpectedly sorted.
4 years ago
|
|
|
return detachedComments, file
|
|
|
|
}
|