mirror of
https://github.com/burrowers/garble.git
synced 2025-09-26 20:01:16 +08:00

Some programs which could automatically reverse string literals obfuscated with `-literals` exist. They currently work by emulating the string literal decryption functions we insert. We prevent this naive emulation from succeeding by making the decryption functions dependent on global state. This can still be broken with enough effort, we are curious which approach reverse-engineers come up with next, we certainly still have some ideas to make this harder. Fixes #926 --------- Co-authored-by: Paul Scheduikat <lu4p@pm.me>
248 lines
6.5 KiB
Go
248 lines
6.5 KiB
Go
package literals
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/token"
|
|
mathrand "math/rand"
|
|
"strconv"
|
|
|
|
ah "mvdan.cc/garble/internal/asthelper"
|
|
)
|
|
|
|
const (
|
|
// minStructCount is the minimum number of proxyStructs initialized in the dispatcher.
|
|
minStructCount = 4
|
|
// maxStructCount is the maximum number of proxyStructs initialized in the dispatcher.
|
|
maxStructCount = 8
|
|
//minChildCount defines the minimum number of child elements that can be assigned to proxy structs.
|
|
minChildCount = 1
|
|
// maxChildCount defines the maximum number of child elements that can be assigned to proxy structs.
|
|
maxChildCount = 3
|
|
// minJunkValueCount defines the minimum number of junk values that can be added to a structure.
|
|
minJunkValueCount = 1
|
|
// maxJunkValueCount defines the maximum number of junk values that can be added to a structure.
|
|
maxJunkValueCount = 3
|
|
// minJunkArraySize defines the minimum size of a randomized byte array for junk data generation.
|
|
minJunkArraySize = 1
|
|
// maxJunkArraySize defines the maximum size of a randomized byte array for junk data generation.
|
|
maxJunkArraySize = 8
|
|
)
|
|
|
|
// proxyValue represents a named field with a type and value used in a proxy structure.
|
|
type proxyValue struct {
|
|
name string
|
|
typ, val ast.Expr
|
|
}
|
|
|
|
type proxyStruct struct {
|
|
typeName, name string
|
|
isPointer bool
|
|
|
|
values []*proxyValue
|
|
|
|
children []*proxyStruct
|
|
parent *proxyStruct
|
|
}
|
|
|
|
type proxyDispatcher struct {
|
|
rand *mathrand.Rand
|
|
nameFunc NameProviderFunc
|
|
|
|
root *proxyStruct
|
|
flattenStructs []*proxyStruct
|
|
}
|
|
|
|
func (d *proxyDispatcher) initialize() {
|
|
flattenStructs := make([]*proxyStruct, d.rand.Intn(maxStructCount-minStructCount)+minStructCount)
|
|
for i := 0; i < len(flattenStructs); i++ {
|
|
flattenStructs[i] = &proxyStruct{
|
|
typeName: d.nameFunc(d.rand, "proxyStructName"+strconv.Itoa(i)),
|
|
name: d.nameFunc(d.rand, "proxyStructFieldName"+strconv.Itoa(i)),
|
|
isPointer: d.rand.Intn(2) == 0,
|
|
}
|
|
}
|
|
|
|
root := &proxyStruct{
|
|
name: d.nameFunc(d.rand, "rootStructName"),
|
|
typeName: d.nameFunc(d.rand, "rootStructType"),
|
|
parent: nil,
|
|
}
|
|
d.root = root
|
|
|
|
unassigned := append([]*proxyStruct(nil), flattenStructs...)
|
|
queue := []*proxyStruct{root}
|
|
for len(unassigned) > 0 && len(queue) > 0 {
|
|
current := queue[0]
|
|
queue = queue[1:]
|
|
|
|
childCount := d.rand.Intn(maxChildCount-minChildCount) + minChildCount
|
|
if childCount > len(unassigned) {
|
|
childCount = len(unassigned)
|
|
}
|
|
|
|
for i := 0; i < childCount; i++ {
|
|
child := unassigned[0]
|
|
unassigned = unassigned[1:]
|
|
|
|
child.parent = current
|
|
current.children = append(current.children, child)
|
|
queue = append(queue, child)
|
|
}
|
|
}
|
|
|
|
d.flattenStructs = append(flattenStructs, root)
|
|
}
|
|
|
|
// buildPath creates an AST expression that represents a field access path
|
|
// from the root of a nested struct hierarchy to a specific field.
|
|
// Example: root.child.grandchild.fieldName
|
|
func buildPath(strct *proxyStruct, valueName string) ast.Expr {
|
|
var stack []*proxyStruct
|
|
for s := strct; s != nil; s = s.parent {
|
|
stack = append(stack, s)
|
|
}
|
|
|
|
var expr ast.Expr = ast.NewIdent(stack[len(stack)-1].name)
|
|
for i := len(stack) - 2; i >= 0; i-- {
|
|
expr = &ast.SelectorExpr{
|
|
X: expr,
|
|
Sel: ast.NewIdent(stack[i].name),
|
|
}
|
|
}
|
|
|
|
return &ast.SelectorExpr{
|
|
X: expr,
|
|
Sel: ast.NewIdent(valueName),
|
|
}
|
|
}
|
|
|
|
func (d *proxyDispatcher) HideValue(val, typ ast.Expr) ast.Expr {
|
|
if d.root == nil {
|
|
d.initialize()
|
|
}
|
|
|
|
strct := d.flattenStructs[d.rand.Intn(len(d.flattenStructs))]
|
|
valueName := d.nameFunc(d.rand, strct.name+"_"+strct.typeName+"_"+strconv.Itoa(len(strct.values)))
|
|
strct.values = append(strct.values, &proxyValue{
|
|
name: valueName,
|
|
typ: typ,
|
|
val: val,
|
|
})
|
|
return buildPath(strct, valueName)
|
|
}
|
|
|
|
func (d *proxyDispatcher) generateStructLiteral(s *proxyStruct) ast.Expr {
|
|
var fields []ast.Expr
|
|
for _, child := range s.children {
|
|
expr := d.generateStructLiteral(child)
|
|
if child.isPointer {
|
|
expr = &ast.UnaryExpr{
|
|
Op: token.AND,
|
|
X: expr,
|
|
}
|
|
}
|
|
fields = append(fields, &ast.KeyValueExpr{
|
|
Key: ast.NewIdent(child.name),
|
|
Value: expr,
|
|
})
|
|
}
|
|
|
|
for _, val := range s.values {
|
|
fields = append(fields, &ast.KeyValueExpr{
|
|
Key: ast.NewIdent(val.name),
|
|
Value: val.val,
|
|
})
|
|
}
|
|
|
|
d.rand.Shuffle(len(fields), func(i, j int) {
|
|
fields[i], fields[j] = fields[j], fields[i]
|
|
})
|
|
|
|
return &ast.CompositeLit{
|
|
Type: ast.NewIdent(s.typeName),
|
|
Elts: fields,
|
|
}
|
|
}
|
|
|
|
// junkValue generates and returns a proxyValue containing a randomized byte array of variable size
|
|
func (d *proxyDispatcher) junkValue() *proxyValue {
|
|
size := d.rand.Intn(maxJunkArraySize-minJunkArraySize+1) + minJunkArraySize
|
|
data := make([]byte, size)
|
|
d.rand.Read(data)
|
|
|
|
dummyDataExpr := &ast.CompositeLit{
|
|
Type: &ast.ArrayType{
|
|
Len: ah.IntLit(size),
|
|
Elt: ast.NewIdent("byte"),
|
|
},
|
|
Elts: ah.DataToArray(data).Elts,
|
|
}
|
|
return &proxyValue{
|
|
name: d.nameFunc(d.rand, "junkValue"),
|
|
typ: &ast.ArrayType{
|
|
Len: ah.IntLit(size),
|
|
Elt: ast.NewIdent("byte"),
|
|
},
|
|
val: dummyDataExpr,
|
|
}
|
|
}
|
|
|
|
func (d *proxyDispatcher) AddToFile(file *ast.File) {
|
|
if d.root == nil {
|
|
return
|
|
}
|
|
|
|
for _, strct := range d.flattenStructs {
|
|
dummyCount := d.rand.Intn(maxJunkValueCount-minJunkValueCount+1) + minJunkValueCount
|
|
for i := 0; i < dummyCount; i++ {
|
|
strct.values = append(strct.values, d.junkValue())
|
|
}
|
|
|
|
structType := &ast.StructType{Fields: &ast.FieldList{}}
|
|
for _, child := range strct.children {
|
|
var typ ast.Expr = ast.NewIdent(child.typeName)
|
|
if child.isPointer {
|
|
typ = &ast.StarExpr{X: typ}
|
|
}
|
|
structType.Fields.List = append(structType.Fields.List, &ast.Field{
|
|
Names: []*ast.Ident{ast.NewIdent(child.name)},
|
|
Type: typ,
|
|
})
|
|
}
|
|
|
|
for _, value := range strct.values {
|
|
structType.Fields.List = append(structType.Fields.List, &ast.Field{
|
|
Names: []*ast.Ident{ast.NewIdent(value.name)},
|
|
Type: value.typ,
|
|
})
|
|
}
|
|
|
|
d.rand.Shuffle(len(structType.Fields.List), func(i, j int) {
|
|
structType.Fields.List[i], structType.Fields.List[j] = structType.Fields.List[j], structType.Fields.List[i]
|
|
})
|
|
|
|
file.Decls = append(file.Decls, &ast.GenDecl{
|
|
Tok: token.TYPE,
|
|
Specs: []ast.Spec{&ast.TypeSpec{
|
|
Name: ast.NewIdent(strct.typeName),
|
|
Type: structType,
|
|
}},
|
|
})
|
|
}
|
|
|
|
file.Decls = append(file.Decls, &ast.GenDecl{
|
|
Tok: token.VAR,
|
|
Specs: []ast.Spec{&ast.ValueSpec{
|
|
Names: []*ast.Ident{ast.NewIdent(d.root.name)},
|
|
Values: []ast.Expr{d.generateStructLiteral(d.root)},
|
|
}},
|
|
})
|
|
}
|
|
|
|
func newProxyDispatcher(rand *mathrand.Rand, nameFunc NameProviderFunc) *proxyDispatcher {
|
|
return &proxyDispatcher{
|
|
rand: rand,
|
|
nameFunc: nameFunc,
|
|
}
|
|
}
|