obfuscate literals via constant folding

Constants don't need to be added to ignoreObjs anymore,
because go/types now does this work for us.

Fixes #360
pull/427/head
lu4p 3 years ago committed by GitHub
parent b5bef981ee
commit a645929151
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,12 +5,13 @@ go 1.17
require (
github.com/google/go-cmp v0.5.6
github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4
golang.org/x/mod v0.5.0
golang.org/x/tools v0.1.5
golang.org/x/mod v0.5.1
golang.org/x/tools v0.1.7
)
require (
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/errgo.v2 v2.1.0 // indirect
)

@ -7,33 +7,32 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 h1:Ha8xCaq6ln1a+R91Km45Oq6lPXj2Mla6CRJYcuV2h1w=
github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0 h1:UG21uOlmZabA4fW5i7ZX6bjw1xELEGg/ZLgZq9auk/Q=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

@ -6,6 +6,7 @@ package literals
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
mathrand "math/rand"
@ -38,49 +39,45 @@ func Obfuscate(file *ast.File, info *types.Info, fset *token.FileSet, ignoreObj
pre := func(cursor *astutil.Cursor) bool {
switch x := cursor.Node().(type) {
case *ast.GenDecl:
if x.Tok != token.CONST {
return true
// constants are obfuscated by replacing all references with the obfuscated value
if x.Tok == token.CONST {
return false
}
for _, spec := range x.Specs {
spec := spec.(*ast.ValueSpec) // guaranteed for Tok==CONST
if len(spec.Values) == 0 {
// skip constants with inferred values
return false
}
}
return true
}
for _, name := range spec.Names {
obj := info.ObjectOf(name)
post := func(cursor *astutil.Cursor) bool {
node, ok := cursor.Node().(ast.Expr)
if !ok {
return true
}
// We only obfuscate const declarations with typed string values.
if obj.Type() != types.Typ[types.String] {
return false
}
typeAndValue := info.Types[node]
if !typeAndValue.IsValue() {
return true
}
// The object cannot be obfuscated, e.g. a value that needs to be constant
if ignoreObj[obj] {
return false
}
}
if typeAndValue.Type == types.Typ[types.String] && typeAndValue.Value != nil {
value := constant.StringVal(typeAndValue.Value)
if len(value) == 0 || len(value) > maxSizeBytes {
return true
}
x.Tok = token.VAR
// constants are not possible if we want to obfuscate literals, therefore
// move all constant blocks which only contain strings to variables
}
return true
}
cursor.Replace(withPos(obfuscateString(value), node.Pos()))
post := func(cursor *astutil.Cursor) bool {
switch x := cursor.Node().(type) {
case *ast.CompositeLit:
byteType := types.Universe.Lookup("byte").Type()
return true
}
if len(x.Elts) == 0 || len(x.Elts) > maxSizeBytes {
if node, ok := node.(*ast.CompositeLit); ok {
if len(node.Elts) == 0 || len(node.Elts) > maxSizeBytes {
return true
}
byteType := types.Universe.Lookup("byte").Type()
var arrayLen int64
switch y := info.TypeOf(x.Type).(type) {
switch y := info.TypeOf(node.Type).(type) {
case *types.Array:
if y.Elem() != byteType {
return true
@ -97,74 +94,73 @@ func Obfuscate(file *ast.File, info *types.Info, fset *token.FileSet, ignoreObj
return true
}
data := make([]byte, 0, len(x.Elts))
data := make([]byte, 0, len(node.Elts))
for _, el := range x.Elts {
lit, ok := el.(*ast.BasicLit)
if !ok {
for _, el := range node.Elts {
elType := info.Types[el]
if elType.Value == nil || elType.Value.Kind() != constant.Int {
return true
}
var value byte
if lit.Kind == token.CHAR {
val, err := strconv.Unquote(lit.Value)
if err != nil {
panic(fmt.Sprintf("cannot unquote character: %v", err))
}
value = byte(val[0])
} else {
val, err := strconv.ParseUint(lit.Value, 0, 8)
if err != nil {
panic(fmt.Sprintf("cannot parse integer: %v", err))
}
value = byte(val)
value, ok := constant.Uint64Val(elType.Value)
if !ok {
panic(fmt.Sprintf("cannot parse byte value: %v", elType.Value))
}
data = append(data, value)
data = append(data, byte(value))
}
if arrayLen > 0 {
cursor.Replace(withPos(obfuscateByteArray(data, arrayLen), x.Pos()))
cursor.Replace(withPos(obfuscateByteArray(data, arrayLen), node.Pos()))
} else {
cursor.Replace(withPos(obfuscateByteSlice(data), x.Pos()))
cursor.Replace(withPos(obfuscateByteSlice(data), node.Pos()))
}
}
return true
case *ast.BasicLit:
switch cursor.Name() {
case "Values", "Rhs", "Value", "Args", "X", "Y", "Results", "Elts":
default:
return true // we don't want to obfuscate imports etc.
}
return true
}
if x.Kind != token.STRING {
return true
}
if len(x.Value) > maxSizeBytes {
return true
}
typeInfo := info.TypeOf(x)
if typeInfo != types.Typ[types.String] && typeInfo != types.Typ[types.UntypedString] {
return true
}
value, err := strconv.Unquote(x.Value)
if err != nil {
panic(fmt.Sprintf("cannot unquote string: %v", err))
}
// Imports which are used might be marked as unused by only looking at the ast,
// because packages can declare a name different from the last element of their import path.
//
// package main
//
// import "github.com/user/somepackage"
//
// func main(){
// // this line uses github.com/user/somepackage
// anotherpackage.Foo()
// }
//
// TODO: remove this check and detect used imports with go/types somehow
prevUsedImports := make(map[string]bool)
for _, imp := range file.Imports {
path, err := strconv.Unquote(imp.Path.Value)
if err != nil {
panic(err)
}
prevUsedImports[path] = astutil.UsesImport(file, path)
}
if len(value) == 0 {
return true
}
file = astutil.Apply(file, pre, post).(*ast.File)
cursor.Replace(withPos(obfuscateString(value), x.Pos()))
// some imported constants might not be needed anymore, remove unnessecary imports
for _, imp := range file.Imports {
path, err := strconv.Unquote(imp.Path.Value)
if err != nil {
panic(err)
}
if !prevUsedImports[path] || astutil.UsesImport(file, path) {
continue
}
return true
if !astutil.DeleteImport(fset, file, path) {
panic(fmt.Sprintf("cannot delete unused import: %v", path))
}
}
return astutil.Apply(file, pre, post).(*ast.File)
return file
}
// withPos sets any token.Pos fields under node which affect printing to pos.
@ -266,53 +262,3 @@ func obfuscateByteArray(data []byte, length int64) *ast.CallExpr {
return ah.LambdaCall(arrayType, block)
}
// RecordUsedAsConstants records identifiers used in constant expressions.
func RecordUsedAsConstants(node ast.Node, info *types.Info, ignoreObj map[types.Object]bool) {
visit := func(node ast.Node) bool {
ident, ok := node.(*ast.Ident)
if !ok {
return true
}
// Only record *types.Const objects.
// Other objects, such as builtins or type names,
// must not be recorded as they would be false positives.
obj := info.ObjectOf(ident)
if _, ok := obj.(*types.Const); ok {
ignoreObj[obj] = true
}
return true
}
switch x := node.(type) {
// in a slice or array composite literal all explicit keys must be constant representable
case *ast.CompositeLit:
if _, ok := x.Type.(*ast.ArrayType); !ok {
break
}
for _, elt := range x.Elts {
if kv, ok := elt.(*ast.KeyValueExpr); ok {
ast.Inspect(kv.Key, visit)
}
}
// in an array type the length must be a constant representable
case *ast.ArrayType:
if x.Len != nil {
ast.Inspect(x.Len, visit)
}
// in a const declaration all values must be constant representable
case *ast.GenDecl:
if x.Tok != token.CONST {
break
}
for _, spec := range x.Specs {
spec := spec.(*ast.ValueSpec)
for _, val := range spec.Values {
ast.Inspect(val, visit)
}
}
}
}

@ -8,7 +8,6 @@ import (
"go/ast"
"go/token"
mathrand "math/rand"
"os"
)
// obfuscator takes a byte slice and converts it to a ast.BlockStmt
@ -25,7 +24,6 @@ var (
shuffle{},
seed{},
}
envGarbleSeed = os.Getenv("GARBLE_SEED")
)
// If math/rand.Seed() is not called, the generator behaves as if seeded by rand.Seed(1),

@ -1070,10 +1070,6 @@ func (tf *transformer) prefillIgnoreObjects(files []*ast.File) {
tf.ignoreObjects = make(map[types.Object]bool)
visit := func(node ast.Node) bool {
if opts.ObfuscateLiterals {
literals.RecordUsedAsConstants(node, tf.info, tf.ignoreObjects)
}
call, ok := node.(*ast.CallExpr)
if !ok {
return true

@ -4,8 +4,8 @@ garble -literals build
exec ./main$exe
cmp stderr main.stderr
binsubstr main$exe 'Skip this block' 'also skip this' 'skip typed const' 'skip typed var' 'skip typed var assign' 'stringTypeField strType' 'stringType lambda func return' 'testMap1 key' 'testMap2 key' 'testMap3 key' 'testMap1 value' 'testMap3 value' 'testMap1 new value' 'testMap3 new value' 'stringType func param' 'stringType return' 'skip untyped const'
! binsubstr main$exe 'garbleDecrypt' 'Lorem' 'dolor' 'first assign' 'second assign' 'First Line' 'Second Line' 'map value' 'obfuscated with shadowed builtins' '1: literal in' 'an array' '2: literal in' 'a slice' 'to obfuscate' 'also obfuscate' 'stringTypeField String'
binsubstr main$exe 'skip typed const' 'skip typed var' 'skip typed var assign' 'stringTypeField strType' 'stringType lambda func return' 'testMap2 key' 'testMap3 key' 'testMap1 value' 'testMap3 value' 'testMap1 new value' 'testMap3 new value' 'stringType func param' 'stringType return' 'skip untyped const'
! binsubstr main$exe 'garbleDecrypt' 'Lorem' 'dolor' 'first assign' 'second assign' 'First Line' 'Second Line' 'map value' 'obfuscated with shadowed builtins' '1: literal in' 'an array' '2: literal in' 'a slice' 'to obfuscate' 'also obfuscate' 'stringTypeField String' 'testMap1 key' 'Obfuscate this block' 'also obfuscate this'
[short] stop # checking that the build is reproducible is slow
@ -78,14 +78,14 @@ const (
i = 1
boolean = true
skip1 = "Skip this block"
mixedBlock = "Obfuscate this block"
)
const (
foo = iota
bar
skip2 = "also skip this"
iotaBlock = "also obfuscate this"
)
// We used to conver this to a var in an attempt of obfuscating the literal.
@ -131,7 +131,7 @@ func main() {
testMap["map key"] = "new value"
println(testMap["map key"])
println("another literal")
println(skip1, skip2)
println(mixedBlock, iotaBlock)
println(i, foo, bar)
typedTest()
constantTest()
@ -267,7 +267,6 @@ func stringTypeFunc(s stringType) stringType {
return "stringType return" // skip
}
// obfuscating this broke before
const (
iota0 uint8 = iota
@ -357,7 +356,7 @@ second assign
to obfuscate also obfuscate
new value
another literal
Skip this block also skip this
Obfuscate this block also obfuscate this
1 0 1
skip untyped const
skip typed const skip typed var skip typed var assign

Loading…
Cancel
Save