Rewrite renaming logic for private names and reduce length of public names (#135)

1. Now private names are obfuscated based on the counter in scope of the package.
2. The length of public names is reduced to 4 bytes.
pull/138/head
pagran 5 years ago committed by GitHub
parent 0d182a3dbd
commit 90fa325da7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -6,8 +6,11 @@ package main
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
@ -44,17 +47,34 @@ type privateImports struct {
privateNames []string
}
func obfuscateImports(objPath, importCfgPath string) (map[string]string, error) {
func appendPrivateNameMap(nameMap map[string]string, packageDirectory string) error {
file, err := os.Open(filepath.Join(packageDirectory, garbleMapFile))
if errors.Is(err, os.ErrNotExist) {
return nil
}
if err != nil {
return err
}
defer file.Close()
if err := json.NewDecoder(file).Decode(&nameMap); err != nil {
return err
}
return nil
}
func obfuscateImports(objPath, importCfgPath string) (garbledImports, privateNameMap map[string]string, err error) {
importCfg, err := goobj2.ParseImportCfg(importCfgPath)
if err != nil {
return nil, err
return nil, nil, err
}
mainPkg, err := goobj2.Parse(objPath, "main", importCfg)
if err != nil {
return nil, fmt.Errorf("error parsing main objfile: %v", err)
return nil, nil, fmt.Errorf("error parsing main objfile: %v", err)
}
pkgs := []pkgInfo{{mainPkg, objPath, true}}
privateNameMap = make(map[string]string)
// build list of imported packages that are private
for pkgPath, info := range importCfg {
// if the '-tiny' flag is passed, we will strip filename
@ -62,16 +82,22 @@ func obfuscateImports(objPath, importCfgPath string) (map[string]string, error)
if private := isPrivate(pkgPath); envGarbleTiny || private {
pkg, err := goobj2.Parse(info.Path, pkgPath, importCfg)
if err != nil {
return nil, fmt.Errorf("error parsing objfile %s at %s: %v", pkgPath, info.Path, err)
return nil, nil, fmt.Errorf("error parsing objfile %s at %s: %v", pkgPath, info.Path, err)
}
pkgs = append(pkgs, pkgInfo{pkg, info.Path, private})
packageDir := filepath.Dir(info.Path)
if err := appendPrivateNameMap(privateNameMap, packageDir); err != nil {
return nil, nil, fmt.Errorf("error parsing name map %s at %s: %v", pkgPath, info.Path, err)
}
}
}
var sb strings.Builder
var buf bytes.Buffer
garbledImports := make(map[string]string)
garbledImports = make(map[string]string)
for _, p := range pkgs {
// log.Printf("++ Obfuscating object file for %s ++", p.pkg.ImportPath)
for _, am := range p.pkg.ArchiveMembers {
@ -147,16 +173,16 @@ func obfuscateImports(objPath, importCfgPath string) (map[string]string, error)
}
if err := p.pkg.Write(p.path); err != nil {
return nil, fmt.Errorf("error writing objfile %s at %s: %v", p.pkg.ImportPath, p.path, err)
return nil, nil, fmt.Errorf("error writing objfile %s at %s: %v", p.pkg.ImportPath, p.path, err)
}
}
// garble importcfg so the linker knows where to find garbled imports
if err := garbleImportCfg(importCfgPath, importCfg, garbledImports); err != nil {
return nil, err
return nil, nil, err
}
return garbledImports, nil
return garbledImports, privateNameMap, nil
}
// stripPCLinesAndNames removes all filename and position info

@ -19,6 +19,8 @@ import (
"go/printer"
"go/token"
"go/types"
"golang.org/x/mod/module"
"golang.org/x/tools/go/ast/astutil"
"io"
"io/ioutil"
"log"
@ -28,9 +30,7 @@ import (
"path/filepath"
"runtime"
"strings"
"golang.org/x/mod/module"
"golang.org/x/tools/go/ast/astutil"
"unicode"
"mvdan.cc/garble/internal/literals"
)
@ -112,6 +112,8 @@ var (
seed []byte
)
const garbleMapFile = "garble.map"
func saveListedPackages(w io.Writer, flags, patterns []string) error {
args := []string{"list", "-json", "-deps", "-export"}
args = append(args, flags...)
@ -517,6 +519,8 @@ func transformCompile(args []string) ([]string, error) {
}
}
privateNameMap := make(map[string]string)
// TODO: randomize the order and names of the files
newPaths := make([]string, 0, len(files))
for i, file := range files {
@ -550,7 +554,7 @@ func transformCompile(args []string) ([]string, error) {
if !envGarbleTiny {
extraComments, file = transformLineInfo(file)
}
file = transformGo(file, info, blacklist)
file = transformGo(file, info, blacklist, privateNameMap, pkgPath)
// Uncomment for some quick debugging. Do not delete.
// fmt.Fprintf(os.Stderr, "\n-- %s/%s --\n", pkgPath, origName)
@ -594,6 +598,20 @@ func transformCompile(args []string) ([]string, error) {
newPaths = append(newPaths, tempFile.Name())
}
if len(privateNameMap) > 0 {
outputDirectory := filepath.Dir(flagValue(flags, "-o"))
file, err := os.Create(filepath.Join(outputDirectory, garbleMapFile))
if err != nil {
return nil, err
}
defer file.Close()
if err := json.NewEncoder(file).Encode(privateNameMap); err != nil {
return nil, err
}
}
return append(flags, newPaths...), nil
}
@ -697,7 +715,7 @@ func buildidOf(path string) (string, error) {
}
func hashWith(salt, value string) string {
const length = 8
const length = 4
d := sha256.New()
io.WriteString(d, salt)
@ -711,6 +729,37 @@ func hashWith(salt, value string) string {
return "z" + sum[:length]
}
func buildNameCharset() []rune {
var charset []rune
for _, r := range unicode.Letter.R16 {
for c := r.Lo; c <= r.Hi; c += r.Stride {
charset = append(charset, rune(c))
}
}
for _, r := range unicode.Digit.R16 {
for c := r.Lo; c <= r.Hi; c += r.Stride {
charset = append(charset, rune(c))
}
}
return charset
}
var privateNameCharset = buildNameCharset()
func encodeIntToName(i int) string {
builder := strings.Builder{}
builder.WriteByte('_')
for i > 0 {
charIdx := i % len(privateNameCharset)
i -= charIdx + 1
builder.WriteRune(privateNameCharset[charIdx])
}
return builder.String()
}
// buildBlacklist collects all the objects in a package which are known to be
// used with reflect.TypeOf or reflect.ValueOf. Since we obfuscate one package
// at a time, we only detect those if the type definition and the reflect usage
@ -776,7 +825,7 @@ func buildBlacklist(files []*ast.File, info *types.Info, pkg *types.Package) map
}
// transformGo garbles the provided Go syntax node.
func transformGo(file *ast.File, info *types.Info, blacklist map[types.Object]struct{}) *ast.File {
func transformGo(file *ast.File, info *types.Info, blacklist map[types.Object]struct{}, privateNameMap map[string]string, pkgPath string) *ast.File {
// Shuffle top level declarations
mathrand.Shuffle(len(file.Decls), func(i, j int) {
decl1 := file.Decls[i]
@ -886,8 +935,24 @@ func transformGo(file *ast.File, info *types.Info, blacklist map[types.Object]st
}
buildID = id
}
// The exported names cannot be shortened as counter synchronization between packages is not currently implemented
if token.IsExported(node.Name) {
node.Name = hashWith(buildID, node.Name)
return true
}
fullName := pkgPath + "." + node.Name
if name, ok := privateNameMap[fullName]; ok {
node.Name = name
return true
}
name := encodeIntToName(len(privateNameMap) + 1)
// orig := node.Name
node.Name = hashWith(buildID, node.Name)
privateNameMap[fullName] = name
node.Name = name
// log.Printf("%q hashed with %q to %q", orig, buildID, node.Name)
return true
}
@ -947,7 +1012,7 @@ func transformLink(args []string) ([]string, error) {
importCfgPath := flagValue(flags, "-importcfg")
// there should only ever be one archive/object file passed to the linker,
// the file for the main package or entrypoint
garbledImports, err := obfuscateImports(paths[0], importCfgPath)
garbledImports, privateNameMap, err := obfuscateImports(paths[0], importCfgPath)
if err != nil {
return nil, err
}
@ -976,9 +1041,13 @@ func transformLink(args []string) ([]string, error) {
pkgPath = buildInfo.firstImport
}
if id := buildInfo.imports[pkgPath].buildID; id != "" {
// If the name is not in the map file, it means that the name was not obfuscated or is public
newName, ok := privateNameMap[pkg+"."+name]
if !ok {
newName = hashWith(id, name)
}
garbledPkg := garbledImports[pkg]
name = hashWith(id, name)
flags = append(flags, fmt.Sprintf("-X=%s.%s=%s", garbledPkg, name, str))
flags = append(flags, fmt.Sprintf("-X=%s.%s=%s", garbledPkg, newName, str))
}
})

@ -8,6 +8,11 @@ cmp stderr main.stderr
[short] stop # no need to verify this with -short
garble -tiny build -ldflags='-X=main.unexportedVersion=v1.0.0 -X=domain.test/main/imported.ExportedVar=replaced'
exec ./main
cmp stderr main.stderr
! binsubstr main$exe 'unexportedVersion'
exec go build -ldflags='-X=main.unexportedVersion=v1.0.0 -X=domain.test/main/imported.ExportedVar=replaced'
exec ./main
cmp stderr main.stderr

Loading…
Cancel
Save