make -literals a string flag to allow users to specify what types of literals will be obfuscated

pull/102/head
Andrew LeFevre 5 years ago
parent a0c84bdfd4
commit b7cc927ef5

@ -7,6 +7,8 @@ import (
"go/types"
mathrand "math/rand"
"strconv"
"strings"
_ "unsafe"
"golang.org/x/tools/go/ast/astutil"
ah "mvdan.cc/garble/internal/asthelper"
@ -24,7 +26,31 @@ func randObfuscator() obfuscator {
}
// Obfuscate replace literals with obfuscated lambda functions
func Obfuscate(files []*ast.File, info *types.Info, fset *token.FileSet, blacklist map[types.Object]struct{}) []*ast.File {
func Obfuscate(files []*ast.File, info *types.Info, fset *token.FileSet, garbleLiterals string, blacklist map[types.Object]struct{}) []*ast.File {
var obfStrings, obfBytes, obfInts, obfFloats, obfBools bool
litOptions := strings.Split(garbleLiterals, ",")
for _, op := range litOptions {
switch op {
case "all":
obfStrings = true
obfBytes = true
obfInts = true
obfFloats = true
obfBools = true
break
case "strings":
obfStrings = true
case "bytes":
obfBytes = true
case "ints":
obfInts = true
case "floats":
obfFloats = true
case "bools":
obfBools = true
}
}
pre := func(cursor *astutil.Cursor) bool {
switch x := cursor.Node().(type) {
case *ast.GenDecl:
@ -95,8 +121,9 @@ func Obfuscate(files []*ast.File, info *types.Info, fset *token.FileSet, blackli
data[i] = byte(value)
}
cursor.Replace(obfuscateByteArray(data, y.Len()))
if obfBytes {
cursor.Replace(obfuscateByteArray(data, y.Len()))
}
case *types.Slice:
if y.Elem() != byteType {
return true
@ -117,10 +144,10 @@ func Obfuscate(files []*ast.File, info *types.Info, fset *token.FileSet, blackli
data = append(data, byte(value))
}
cursor.Replace(obfuscateByteSlice(data))
if obfBytes {
cursor.Replace(obfuscateByteSlice(data))
}
}
case *ast.BasicLit:
switch cursor.Name() {
case "Values", "Rhs", "Value", "Args", "X", "Y", "Results":
@ -129,9 +156,19 @@ func Obfuscate(files []*ast.File, info *types.Info, fset *token.FileSet, blackli
}
switch x.Kind {
case token.FLOAT, token.INT:
obfuscateNumberLiteral(cursor, info)
case token.INT:
if obfInts {
obfuscateNumberLiteral(cursor, info)
}
case token.FLOAT:
if obfFloats {
obfuscateNumberLiteral(cursor, info)
}
case token.STRING:
if !obfStrings {
return true
}
typeInfo := info.TypeOf(x)
if typeInfo != types.Typ[types.String] && typeInfo != types.Typ[types.UntypedString] {
return true
@ -154,14 +191,17 @@ func Obfuscate(files []*ast.File, info *types.Info, fset *token.FileSet, blackli
return true // we don't want to obfuscate imports etc.
}
obfuscateNumberLiteral(cursor, info)
typeInfo := info.TypeOf(x)
if (isInteger(typeInfo) && obfInts) || (isFloat(typeInfo) && obfFloats) {
obfuscateNumberLiteral(cursor, info)
}
case *ast.Ident:
obj := info.ObjectOf(x)
if obj == nil {
return true
}
if obj == universalTrue || obj == universalFalse {
if obfBools && (obj == universalTrue || obj == universalFalse) {
cursor.Replace(obfuscateBool(x.Name == "true"))
}
}
@ -179,6 +219,12 @@ func Obfuscate(files []*ast.File, info *types.Info, fset *token.FileSet, blackli
return files
}
//go:linkname isInteger go/types.isInteger
func isInteger(typ types.Type) bool
//go:linkname isFloat go/types.isFloat
func isFloat(typ types.Type) bool
func obfuscateString(data string) *ast.CallExpr {
obfuscator := randObfuscator()
block := obfuscator.obfuscate([]byte(data))

@ -11,6 +11,7 @@ import (
"encoding/binary"
"encoding/gob"
"encoding/json"
"errors"
"flag"
"fmt"
"go/ast"
@ -36,7 +37,7 @@ import (
var flagSet = flag.NewFlagSet("garble", flag.ContinueOnError)
var (
flagGarbleLiterals bool
flagGarbleLiterals string
flagGarbleTiny bool
flagDebugDir string
flagSeed string
@ -44,7 +45,7 @@ var (
func init() {
flagSet.Usage = usage
flagSet.BoolVar(&flagGarbleLiterals, "literals", false, "Encrypt all literals with AES, currently only literal strings are supported")
flagSet.StringVar(&flagGarbleLiterals, "literals", "none", "Obfuscate literals and literal expressions \nValid options are 'all', 'strings', 'bytes', 'ints', 'floats', 'bools', or 'none',\nseparated by a comma: '-literals=strings,ints'")
flagSet.BoolVar(&flagGarbleTiny, "tiny", false, "Optimize for binary size, losing the ability to reverse the process")
flagSet.StringVar(&flagDebugDir, "debugdir", "", "Write the garbled source to a given directory: '-debugdir=./debug'")
flagSet.StringVar(&flagSeed, "seed", "", "Provide a custom base64-encoded seed: '-seed=o9WDTZ4CN4w=' \nFor a random seed provide: '-seed=random'")
@ -98,7 +99,7 @@ var (
envGoPrivate = os.Getenv("GOPRIVATE") // complemented by 'go env' later
envGarbleDir = os.Getenv("GARBLE_DIR")
envGarbleLiterals = os.Getenv("GARBLE_LITERALS") == "true"
envGarbleLiterals = os.Getenv("GARBLE_LITERALS")
envGarbleTiny = os.Getenv("GARBLE_TINY") == "true"
envGarbleDebugDir = os.Getenv("GARBLE_DEBUGDIR")
envGarbleSeed = os.Getenv("GARBLE_SEED")
@ -213,6 +214,11 @@ func main1() int {
if err := flagSet.Parse(os.Args[1:]); err != nil {
return 2
}
if err := validateOptions(); err != nil {
fmt.Fprintln(os.Stderr, err)
return 3
}
log.SetPrefix("[garble] ")
args := flagSet.Args()
if len(args) < 1 {
@ -225,6 +231,41 @@ func main1() int {
return 0
}
func validateOptions() error {
var allPassed, nonePassed bool
litOptions := strings.Split(flagGarbleLiterals, ",")
for i, op := range litOptions {
switch op {
case "none":
if allPassed {
return errors.New("error parsing -literals flag: 'all' and 'none' are mutually exclusive")
} else if i != 0 {
return errors.New("error parsing -literals flag: no other options can be passed with 'none'")
}
nonePassed = true
case "all":
if nonePassed {
return errors.New("error parsing -literals flag: 'all' and 'none' are mutually exclusive")
} else if i != 0 {
return errors.New("error parsing -literals flag: no other options can be passed with 'all'")
}
allPassed = true
case "strings", "bytes", "ints", "floats", "bools":
if allPassed {
return errors.New("error parsing -literals flag: no other options can be passed with 'all'")
} else if nonePassed {
return errors.New("error parsing -literals flag: no other options can be passed with 'none'")
}
default:
return fmt.Errorf("error parsing -literals flag: %s", op)
}
}
return nil
}
func mainErr(args []string) error {
// If we recognise an argument, we're not running within -toolexec.
switch cmd := args[0]; cmd {
@ -245,7 +286,7 @@ func mainErr(args []string) error {
return err
}
os.Setenv("GARBLE_DIR", wd)
os.Setenv("GARBLE_LITERALS", fmt.Sprint(flagGarbleLiterals))
os.Setenv("GARBLE_LITERALS", flagGarbleLiterals)
os.Setenv("GARBLE_TINY", fmt.Sprint(flagGarbleTiny))
if flagSeed == "random" {
@ -292,7 +333,7 @@ func mainErr(args []string) error {
modpath, err := exec.Command("go", "list", "-m").Output()
if err == nil {
path := string(bytes.TrimSpace(modpath))
envGoPrivate = path+","+path+"_test"
envGoPrivate = path + "," + path + "_test"
}
}
// Explicitly set GOPRIVATE, since future garble processes won't
@ -400,7 +441,7 @@ func transformCompile(args []string) ([]string, error) {
// them later to remove build information and add additional
// functions to the runtime. However, we only want flags to work on
// private packages.
envGarbleLiterals = false
envGarbleLiterals = "none"
envGarbleDebugDir = ""
} else if !isPrivate(pkgPath) {
return append(flags, paths...), nil
@ -459,8 +500,8 @@ func transformCompile(args []string) ([]string, error) {
blacklist := buildBlacklist(files, info, pkg)
if envGarbleLiterals {
files = literals.Obfuscate(files, info, fset, blacklist)
if envGarbleLiterals != "none" {
files = literals.Obfuscate(files, info, fset, envGarbleLiterals, blacklist)
}
tempDir, err := ioutil.TempDir("", "garble-build")
@ -716,7 +757,7 @@ func buildBlacklist(files []*ast.File, info *types.Info, pkg *types.Package) map
}
visit := func(node ast.Node) bool {
if envGarbleLiterals {
if envGarbleLiterals != "none" {
literals.ConstBlacklist(node, info, blacklist)
}

@ -10,7 +10,7 @@ binsubfloat main$exe '3684433217126772357.33' '-9015867427900753906'
cp stderr normal.stderr
garble -literals build
garble -literals=all build
exec ./main$exe
cmp stderr normal.stderr
@ -23,12 +23,12 @@ binsubstr main$exe 'Skip this block' 'also skip this' 'skip typed const' 'skip t
# Also check that the binary is reproducible.
cp main$exe main_old$exe
rm main$exe
garble -literals build
garble -literals=all build
bincmp main$exe main_old$exe
# Also check that the binary is different from previous builds.
rm main$exe
garble -literals -debugdir=.obf-src -seed=8J+Ri/Cfh6fwn4e+ build
garble -literals=all -debugdir=.obf-src -seed=8J+Ri/Cfh6fwn4e+ build
! bincmp main$exe main_old$exe
exec ./main$exe

Loading…
Cancel
Save