Skip to content

Commit

Permalink
obfuscate literals via constant folding
Browse files Browse the repository at this point in the history
Constants don't need to be added to ignoreObjs anymore,
because go/types now does this work for us.

Fixes burrowers#360
  • Loading branch information
lu4p committed Nov 27, 2021
1 parent b5bef98 commit a645929
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 157 deletions.
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
21 changes: 10 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
208 changes: 77 additions & 131 deletions internal/literals/literals.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package literals
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
mathrand "math/rand"
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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)
}
}
}
}
2 changes: 0 additions & 2 deletions internal/literals/obfuscators.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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),
Expand Down
4 changes: 0 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit a645929

Please sign in to comment.