More robust reflection detection

Functions which use reflection on one of their parameters are,
now added to knownReflectAPIs automatically.

This makes most explicit hints for reflection redundant.
Simple protobuf code now works correctly when obfuscated.

Fixes #162
Fixes #373
pull/399/head
lu4p 4 years ago
parent c76c4196af
commit aafd845418

@ -110,20 +110,8 @@ to document the current shortcomings of this tool.
be required by interfaces and reflection. This area is a work in progress; see be required by interfaces and reflection. This area is a work in progress; see
[#3](https://github.com/burrowers/garble/issues/3). [#3](https://github.com/burrowers/garble/issues/3).
* It can be hard for garble to know what types will be used with * If your program breaks due to an issue with reflection please [open an issue](https://github.com/burrowers/garble/issues/new),
[reflection](https://golang.org/pkg/reflect). we'll try to resolve new edge cases.
Its detection will improve over time with [#162](https://github.com/burrowers/garble/issues/162)
Until then, if your program breaks due to the obfuscation of field names,
you can add an explicit hint:
```go
type Message struct {
Command string
Args string
}
// Never obfuscate the Message type.
var _ = reflect.TypeOf(Message{})
```
* Go declarations exported for cgo via `//export` are not obfuscated. * Go declarations exported for cgo via `//export` are not obfuscated.

@ -7,6 +7,7 @@ import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"encoding/gob"
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
@ -629,16 +630,6 @@ func transformCompile(args []string) ([]string, error) {
// generating it. // generating it.
flags = append(flags, "-dwarf=false") flags = append(flags, "-dwarf=false")
if (curPkg.ImportPath == "runtime" && opts.Tiny) || curPkg.ImportPath == "runtime/internal/sys" {
// Even though these packages aren't private, we will still process
// them later to remove build information and strip code from the
// runtime. However, we only want flags to work on private packages.
opts.GarbleLiterals = false
opts.DebugDir = ""
} else if !curPkg.Private {
return append(flags, paths...), nil
}
for i, path := range paths { for i, path := range paths {
if filepath.Base(path) == "_gomod_.go" { if filepath.Base(path) == "_gomod_.go" {
// never include module info // never include module info
@ -647,13 +638,6 @@ func transformCompile(args []string) ([]string, error) {
} }
} }
flags = alterTrimpath(flags)
newImportCfg, err := processImportCfg(flags)
if err != nil {
return nil, err
}
var files []*ast.File var files []*ast.File
for _, path := range paths { for _, path := range paths {
file, err := parser.ParseFile(fset, path, nil, parser.ParseComments) file, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
@ -662,6 +646,35 @@ func transformCompile(args []string) ([]string, error) {
} }
files = append(files, file) files = append(files, file)
} }
tf := newTransformer()
if err := tf.typecheck(files); err != nil {
return nil, err
}
flags = alterTrimpath(flags)
newImportCfg, err := processImportCfg(flags)
if err != nil {
return nil, err
}
if err = loadKnownReflectAPIs(curPkg); err != nil {
return nil, err
}
tf.findReflectFunctions(files)
if err := saveKnownReflectAPIs(curPkg); err != nil {
return nil, err
}
if (curPkg.ImportPath == "runtime" && opts.Tiny) || curPkg.ImportPath == "runtime/internal/sys" {
// Even though these packages aren't private, we will still process
// them later to remove build information and strip code from the
// runtime. However, we only want flags to work on private packages.
opts.GarbleLiterals = false
opts.DebugDir = ""
} else if !curPkg.Private {
return append(flags, paths...), nil
}
// Literal obfuscation uses math/rand, so seed it deterministically. // Literal obfuscation uses math/rand, so seed it deterministically.
randSeed := opts.Seed randSeed := opts.Seed
@ -671,11 +684,6 @@ func transformCompile(args []string) ([]string, error) {
// log.Printf("seeding math/rand with %x\n", randSeed) // log.Printf("seeding math/rand with %x\n", randSeed)
mathrand.Seed(int64(binary.BigEndian.Uint64(randSeed))) mathrand.Seed(int64(binary.BigEndian.Uint64(randSeed)))
tf := newTransformer()
if err := tf.typecheck(files); err != nil {
return nil, err
}
tf.prefillIgnoreObjects(files) tf.prefillIgnoreObjects(files)
// log.Println(flags) // log.Println(flags)
@ -1015,58 +1023,132 @@ func processImportCfg(flags []string) (newImportCfg string, _ error) {
return newCfg.Name(), nil return newCfg.Name(), nil
} }
type knownReflect struct {
pkgPath string
name string
}
type funcFullName = string type funcFullName = string
type reflectParameterPosition = int
// knownReflectAPIs is a static record of what std APIs use reflection on their // knownReflectAPIs is a static record of what std APIs use reflection on their
// parameters, so we can avoid obfuscating types used with them. // parameters, so we can avoid obfuscating types used with them.
// //
// For now, this table is manually maintained, until we automatically detect and
// propagate the uses of reflect in std and third party APIs.
// If we end up maintaining this table for a long time, we should probably
// improve the process via either code generation or sanity checks.
//
// TODO: record which parameters get used with reflection, as it's often not all
// of them.
//
// TODO: we're not including fmt.Printf, as it would have many false positives, // TODO: we're not including fmt.Printf, as it would have many false positives,
// unless we were smart enough to detect which arguments get used as %#v or %T. // unless we were smart enough to detect which arguments get used as %#v or %T.
// var knownReflectAPIs = map[funcFullName][]reflectParameterPosition{
// TODO: this should also support third party APIs; see "reflect.TypeOf": {0},
// https://github.com/burrowers/garble/issues/162 "reflect.ValueOf": {0},
var knownReflectAPIs = map[funcFullName]bool{ }
"reflect.TypeOf": true,
"reflect.ValueOf": true, func loadKnownReflectAPIs(currPkg *listedPackage) error {
for path := range importCfgEntries {
"encoding/json.Marshal": true, pkg, err := listPackage(path)
"encoding/json.MarshalIndent": true, if err != nil {
"encoding/json.Unmarshal": true, return err
"(*encoding/json.Decoder).Decode": true, }
"(*encoding/json.Encoder).Encode": true,
// this function literal is used for the deferred close
"encoding/gob.Register": true, err = func() error {
"encoding/gob.RegisterName": true, filename := strings.TrimSuffix(pkg.Export, "-d") + "-garble-d"
"(*encoding/gob.Decoder).Decode": true, f, err := os.Open(filename)
"(*encoding/gob.Encoder).Encode": true, if err != nil {
return err
"encoding/xml.Marshal": true, }
"encoding/xml.MarshalIndent": true, defer f.Close()
"encoding/xml.Unmarshal": true,
"(*encoding/xml.Decoder).Decode": true, // Decode appends new entries to the existing map
"(*encoding/xml.Encoder).Encode": true, return gob.NewDecoder(f).Decode(&knownReflectAPIs)
"(*encoding/xml.Decoder).DecodeElement": true, }()
"(*encoding/xml.Encoder).EncodeElement": true, if err != nil {
return err
"(*text/template.Template).Execute": true, }
}
"(*html/template.Template).Execute": true,
return nil
"net/rpc.Register": true, }
"net/rpc.RegisterName": true,
func saveKnownReflectAPIs(currPkg *listedPackage) error {
filename := strings.TrimSuffix(currPkg.Export, "-d") + "-garble-d"
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
return gob.NewEncoder(f).Encode(knownReflectAPIs)
}
func (tf *transformer) findReflectFunctions(files []*ast.File) {
visitReflect := func(node ast.Node) {
funcDecl, ok := node.(*ast.FuncDecl)
if !ok {
return
}
funcObj := tf.info.ObjectOf(funcDecl.Name).(*types.Func)
var paramNames []string
for _, param := range funcDecl.Type.Params.List {
for _, name := range param.Names {
paramNames = append(paramNames, name.Name)
}
}
ast.Inspect(funcDecl, func(node ast.Node) bool {
call, ok := node.(*ast.CallExpr)
if !ok {
return true
}
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
fnType, _ := tf.info.ObjectOf(sel.Sel).(*types.Func)
if fnType == nil || fnType.Pkg() == nil {
return true
}
fullName := fnType.FullName()
var identifiers []string
for _, argPos := range knownReflectAPIs[fullName] {
arg := call.Args[argPos]
ident, ok := arg.(*ast.Ident)
if !ok {
continue
}
obj := tf.info.ObjectOf(ident)
if obj.Parent() == funcObj.Scope() {
identifiers = append(identifiers, ident.Name)
}
}
if identifiers == nil {
return true
}
var argumentPosReflect []int
for _, ident := range identifiers {
for paramPos, paramName := range paramNames {
if ident == paramName {
argumentPosReflect = append(argumentPosReflect, paramPos)
}
}
}
knownReflectAPIs[funcObj.FullName()] = argumentPosReflect
return true
})
}
lenPrevKnownReflectAPIs := len(knownReflectAPIs)
for _, file := range files {
for _, decl := range file.Decls {
visitReflect(decl)
}
}
// if a new reflectAPI is found we need to Re-evaluate all functions which might be using that API
if len(knownReflectAPIs) > lenPrevKnownReflectAPIs {
tf.findReflectFunctions(files)
}
} }
// prefillIgnoreObjects collects objects which should not be obfuscated, // prefillIgnoreObjects collects objects which should not be obfuscated,
@ -1085,23 +1167,30 @@ func (tf *transformer) prefillIgnoreObjects(files []*ast.File) {
if !ok { if !ok {
return true return true
} }
sel, ok := call.Fun.(*ast.SelectorExpr)
ident, ok := call.Fun.(*ast.Ident)
if !ok { if !ok {
return true sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
ident = sel.Sel
} }
fnType, _ := tf.info.ObjectOf(sel.Sel).(*types.Func)
fnType, _ := tf.info.ObjectOf(ident).(*types.Func)
if fnType == nil || fnType.Pkg() == nil { if fnType == nil || fnType.Pkg() == nil {
return true return true
} }
fullName := fnType.FullName() fullName := fnType.FullName()
// log.Printf("%s: %s", fset.Position(node.Pos()), fullName) // log.Printf("%s: %s", fset.Position(node.Pos()), fullName)
if knownReflectAPIs[fullName] { for _, argPos := range knownReflectAPIs[fullName] {
for _, arg := range call.Args { arg := call.Args[argPos]
argType := tf.info.TypeOf(arg) argType := tf.info.TypeOf(arg)
tf.recordIgnore(argType, tf.pkg.Path()) tf.recordIgnore(argType, tf.pkg.Path())
}
} }
return true return true
} }
for _, file := range files { for _, file := range files {

@ -4,7 +4,7 @@ garble build
exec ./main exec ./main
cmp stdout main.stdout cmp stdout main.stdout
! binsubstr main$exe 'garble_main.go' 'test/main' 'importedpkg.' 'DownstreamObfuscated' 'SiblingObfuscated' 'IndirectObfuscated' 'IndirectNamedWithoutReflect' 'AliasIndirectNamedWithReflect' 'AliasIndirectNamedWithoutReflect' ! binsubstr main$exe 'garble_main.go' 'test/main' 'importedpkg.' 'DownstreamObfuscated' 'SiblingObfuscated' 'IndirectObfuscated' 'IndirectNamedWithoutReflect' 'AliasIndirectNamedWithReflect' 'AliasIndirectNamedWithoutReflect' 'FmtTypeField'
binsubstr main$exe 'ReflectInDefined' 'ExportedField2' 'unexportedField2' 'IndirectUnobfuscated' 'IndirectNamedWithReflect' binsubstr main$exe 'ReflectInDefined' 'ExportedField2' 'unexportedField2' 'IndirectUnobfuscated' 'IndirectNamedWithReflect'
[short] stop # no need to verify this with -short [short] stop # no need to verify this with -short
@ -95,6 +95,9 @@ func main() {
// TODO: don't obfuscate the embedded field name here // TODO: don't obfuscate the embedded field name here
// printfWithoutPackage("%#v\n", importedpkg.ReflectEmbeddingAlias{}) // printfWithoutPackage("%#v\n", importedpkg.ReflectEmbeddingAlias{})
indirectReflection(IndirectReflection{})
fmt.Println(FmtType{})
} }
type EmbeddingIndirect struct { type EmbeddingIndirect struct {
@ -126,6 +129,18 @@ type RecursiveStruct struct {
// This could crash or hang if we don't deal with loops. // This could crash or hang if we don't deal with loops.
var _ = reflect.TypeOf(RecursiveStruct{}) var _ = reflect.TypeOf(RecursiveStruct{})
type IndirectReflection struct {
ReflectionField string
}
func indirectReflection(v interface{}) {
fmt.Println(reflect.TypeOf(v).Field(0).Name)
}
type FmtType struct {
FmtTypeField int
}
-- importedpkg/imported.go -- -- importedpkg/imported.go --
package importedpkg package importedpkg
@ -236,3 +251,5 @@ Hello Dave.
{sibling} {sibling}
{{indirect-with} {indirect-without} {}} {{indirect-with} {indirect-without} {}}
IndirectNamedWithReflect{IndirectUnobfuscated:"indirect-with"} IndirectNamedWithReflect{IndirectUnobfuscated:"indirect-with"}
ReflectionField
{0}

Loading…
Cancel
Save