reverse.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. // Copyright (c) 2019, The Garble Authors.
  2. // See LICENSE for licensing information.
  3. package main
  4. import (
  5. "bufio"
  6. "flag"
  7. "fmt"
  8. "go/ast"
  9. "go/parser"
  10. "go/types"
  11. "io"
  12. "os"
  13. "path/filepath"
  14. "strings"
  15. )
  16. // commandReverse implements "garble reverse".
  17. func commandReverse(args []string) error {
  18. flags, args := splitFlagsFromArgs(args)
  19. if hasHelpFlag(flags) || len(args) == 0 {
  20. fmt.Fprintf(os.Stderr, `
  21. usage: garble [garble flags] reverse [build flags] package [files]
  22. For example, after building an obfuscated program as follows:
  23. garble -literals build -tags=mytag ./cmd/mycmd
  24. One can reverse a captured panic stack trace as follows:
  25. garble -literals reverse -tags=mytag ./cmd/mycmd panic-output.txt
  26. `[1:])
  27. return errJustExit(2)
  28. }
  29. pkg, args := args[0], args[1:]
  30. listArgs := []string{
  31. "-json",
  32. "-deps",
  33. "-export",
  34. }
  35. listArgs = append(listArgs, flags...)
  36. listArgs = append(listArgs, pkg)
  37. // TODO: We most likely no longer need this "list -toolexec" call, since
  38. // we use the original build IDs.
  39. _, err := toolexecCmd("list", listArgs)
  40. defer os.RemoveAll(os.Getenv("GARBLE_SHARED"))
  41. if err != nil {
  42. return err
  43. }
  44. // We don't actually run a main Go command with all flags,
  45. // so if the user gave a non-build flag,
  46. // we need this check to not silently ignore it.
  47. if _, firstUnknown := filterForwardBuildFlags(flags); firstUnknown != "" {
  48. // A bit of a hack to get a normal flag.Parse error.
  49. // Longer term, "reverse" might have its own FlagSet.
  50. return flag.NewFlagSet("", flag.ContinueOnError).Parse([]string{firstUnknown})
  51. }
  52. // A package's names are generally hashed with the action ID of its
  53. // obfuscated build. We recorded those action IDs above.
  54. // Note that we parse Go files directly to obtain the names, since the
  55. // export data only exposes exported names. Parsing Go files is cheap,
  56. // so it's unnecessary to try to avoid this cost.
  57. var replaces []string
  58. for _, lpkg := range cache.ListedPackages {
  59. if !lpkg.ToObfuscate {
  60. continue
  61. }
  62. curPkg = lpkg
  63. addHashedWithPackage := func(str string) {
  64. replaces = append(replaces, hashWithPackage(lpkg, str), str)
  65. }
  66. // Package paths are obfuscated, too.
  67. addHashedWithPackage(lpkg.ImportPath)
  68. var files []*ast.File
  69. for _, goFile := range lpkg.GoFiles {
  70. fullGoFile := filepath.Join(lpkg.Dir, goFile)
  71. file, err := parser.ParseFile(fset, fullGoFile, nil, parser.SkipObjectResolution)
  72. if err != nil {
  73. return err
  74. }
  75. files = append(files, file)
  76. }
  77. tf := newTransformer()
  78. if err := tf.typecheck(files); err != nil {
  79. return err
  80. }
  81. for i, file := range files {
  82. goFile := lpkg.GoFiles[i]
  83. ast.Inspect(file, func(node ast.Node) bool {
  84. switch node := node.(type) {
  85. // Replace names.
  86. // TODO: do var names ever show up in output?
  87. case *ast.FuncDecl:
  88. addHashedWithPackage(node.Name.Name)
  89. case *ast.TypeSpec:
  90. addHashedWithPackage(node.Name.Name)
  91. case *ast.Field:
  92. for _, name := range node.Names {
  93. obj, _ := tf.info.ObjectOf(name).(*types.Var)
  94. if obj == nil || !obj.IsField() {
  95. continue
  96. }
  97. strct := tf.fieldToStruct[obj]
  98. if strct == nil {
  99. panic("could not find for " + name.Name)
  100. }
  101. replaces = append(replaces, hashWithStruct(strct, name.Name), name.Name)
  102. }
  103. case *ast.CallExpr:
  104. // Reverse position information of call sites.
  105. pos := fset.Position(node.Pos())
  106. origPos := fmt.Sprintf("%s:%d", goFile, pos.Offset)
  107. newFilename := hashWithPackage(lpkg, origPos) + ".go"
  108. // Do "obfuscated.go:1", corresponding to the call site's line.
  109. // Most common in stack traces.
  110. replaces = append(replaces,
  111. newFilename+":1",
  112. fmt.Sprintf("%s/%s:%d", lpkg.ImportPath, goFile, pos.Line),
  113. )
  114. // Do "obfuscated.go" as a fallback.
  115. // Most useful in build errors in obfuscated code,
  116. // since those might land on any line.
  117. // Any ":N" line number will end up being useless,
  118. // but at least the filename will be correct.
  119. replaces = append(replaces,
  120. newFilename,
  121. fmt.Sprintf("%s/%s", lpkg.ImportPath, goFile),
  122. )
  123. }
  124. return true
  125. })
  126. }
  127. }
  128. repl := strings.NewReplacer(replaces...)
  129. if len(args) == 0 {
  130. modified, err := reverseContent(os.Stdout, os.Stdin, repl)
  131. if err != nil {
  132. return err
  133. }
  134. if !modified {
  135. return errJustExit(1)
  136. }
  137. return nil
  138. }
  139. // TODO: cover this code in the tests too
  140. anyModified := false
  141. for _, path := range args {
  142. f, err := os.Open(path)
  143. if err != nil {
  144. return err
  145. }
  146. defer f.Close()
  147. modified, err := reverseContent(os.Stdout, f, repl)
  148. if err != nil {
  149. return err
  150. }
  151. anyModified = anyModified || modified
  152. f.Close() // since we're in a loop
  153. }
  154. if !anyModified {
  155. return errJustExit(1)
  156. }
  157. return nil
  158. }
  159. func reverseContent(w io.Writer, r io.Reader, repl *strings.Replacer) (bool, error) {
  160. // Read line by line.
  161. // Reading the entire content at once wouldn't be interactive,
  162. // nor would it support large files well.
  163. // Reading entire lines ensures we don't cut words in half.
  164. // We use bufio.Reader instead of bufio.Scanner,
  165. // to also obtain the newline characters themselves.
  166. br := bufio.NewReader(r)
  167. modified := false
  168. for {
  169. // Note that ReadString can return a line as well as an error if
  170. // we hit EOF without a newline.
  171. // In that case, we still want to process the string.
  172. line, readErr := br.ReadString('\n')
  173. newLine := repl.Replace(line)
  174. if newLine != line {
  175. modified = true
  176. }
  177. if _, err := io.WriteString(w, newLine); err != nil {
  178. return modified, err
  179. }
  180. if readErr == io.EOF {
  181. return modified, nil
  182. }
  183. if readErr != nil {
  184. return modified, readErr
  185. }
  186. }
  187. }