forked from burrowers/garble
-
Notifications
You must be signed in to change notification settings - Fork 0
/
map.go
147 lines (117 loc) · 3.61 KB
/
map.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Copyright (c) 2019, The Garble Authors.
// See LICENSE for licensing information.
package main
import (
"encoding/json"
"flag"
"fmt"
"go/ast"
"go/types"
"os"
"golang.org/x/tools/go/types/objectpath"
)
// commandMap implements "garble map".
func commandMap(args []string) error {
flags, pkgs := splitFlagsFromArgs(args)
if hasHelpFlag(flags) || len(args) == 0 {
fmt.Fprint(os.Stderr, `
usage: garble [garble flags] map [build flags] packages...
For example, after building an obfuscated program as follows:
garble -literals build -tags=mytag ./cmd/mycmd
One can obtain an obfuscation map as follows:
garble -literals map -tags=mytag ./cmd/mycmd
`[1:])
return errJustExit(2)
}
listArgs := []string{
"-json",
"-deps",
"-export",
}
listArgs = append(listArgs, flags...)
listArgs = append(listArgs, pkgs...)
// TODO: We most likely no longer need this "list -toolexec" call, since
// we use the original build IDs.
_, err := toolexecCmd("list", listArgs)
defer os.RemoveAll(os.Getenv("GARBLE_SHARED"))
if err != nil {
return err
}
// We don't actually run a main Go command with all flags,
// so if the user gave a non-build flag,
// we need this check to not silently ignore it.
if _, firstUnknown := filterForwardBuildFlags(flags); firstUnknown != "" {
// A bit of a hack to get a normal flag.Parse error.
// Longer term, "map" might have its own FlagSet.
return flag.NewFlagSet("", flag.ContinueOnError).Parse([]string{firstUnknown})
}
// 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.
type obfuscatedPackageInfo struct {
Path string `json:"path"`
Objects map[objectpath.Path]string `json:"objects"`
}
result := make(map[string]obfuscatedPackageInfo, len(sharedCache.ListedPackages))
for _, lpkg := range sharedCache.ListedPackages {
if !lpkg.ToObfuscate {
continue
}
tf := transformer{
curPkg: lpkg,
origImporter: importerForPkg(lpkg),
}
objectMap := make(map[objectpath.Path]string)
result[lpkg.ImportPath] = obfuscatedPackageInfo{
Path: hashWithPackage(&tf, lpkg, lpkg.ImportPath),
Objects: objectMap,
}
files, err := parseFiles(lpkg.Dir, lpkg.CompiledGoFiles)
if err != nil {
return err
}
tf.pkg, tf.info, err = typecheck(lpkg.ImportPath, files, tf.origImporter)
if err != nil {
return err
}
tf.curPkgCache, err = loadPkgCache(lpkg, tf.pkg, files, tf.info, nil)
if err != nil {
return err
}
tf.fieldToStruct = computeFieldToStruct(tf.info)
var encoder objectpath.Encoder
visited := make(map[types.Object]bool) // Avoid duplicated work.
for _, file := range files {
ast.Inspect(file, func(node ast.Node) bool {
switch node := node.(type) {
case ast.Stmt:
// Skip statements as local objects have no object path.
return false
case *ast.Ident:
obj := tf.info.ObjectOf(node)
if obj == nil || obj.Pkg() != tf.pkg || visited[obj] {
return true
}
visited[obj] = true
obfuscated := tf.obfuscateObjectName(obj)
if obfuscated == obj.Name() {
return true
}
// This is probably costlier than obfuscation:
// run it only when necessary.
path, err := encoder.For(obj)
if err != nil {
return true
}
objectMap[path] = obfuscated
default:
return true
}
return true
})
}
}
return json.NewEncoder(os.Stdout).Encode(result)
}