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, } }