Skip to content

Commit

Permalink
add trash block generator (burrowers#825)
Browse files Browse the repository at this point in the history
 add trash block generator

For making static code analysis even more difficult, added feature for
generating trash blocks that will never be executed. In combination
with control flow flattening makes it hard to separate trash code from
the real one, plus it causes a large number of trash references to
different methods.

Trash blocks contain 2 types of statements:
1. Function/method call with writing the results into local variables
and passing them to other calls
2. Shuffling or assigning random values to local variables
  • Loading branch information
pagran authored Jan 16, 2024
1 parent c43cf74 commit e8fe80d
Show file tree
Hide file tree
Showing 9 changed files with 816 additions and 16 deletions.
22 changes: 22 additions & 0 deletions internal/ctrlflow/ctrlflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ const (
defaultBlockSplits = 0
defaultJunkJumps = 0
defaultFlattenPasses = 1
defaultTrashBlocks = 0

maxBlockSplits = math.MaxInt32
maxJunkJumps = 256
maxFlattenPasses = 4
maxTrashBlocks = 1024

minTrashBlockStmts = 1
maxTrashBlockStmts = 32
)

type directiveParamMap map[string]string
Expand Down Expand Up @@ -173,6 +178,8 @@ func Obfuscate(fset *token.FileSet, ssaPkg *ssa.Package, files []*ast.File, obfR
return ast.NewIdent(name)
}

var trashGen *trashGenerator

for idx, ssaFunc := range ssaFuncs {
params := ssaParams[idx]

Expand All @@ -184,7 +191,15 @@ func Obfuscate(fset *token.FileSet, ssaPkg *ssa.Package, files []*ast.File, obfR
}
flattenHardening := params.StringSlice("flatten_hardening")

trashBlockCount := params.GetInt("trash_blocks", defaultTrashBlocks, maxTrashBlocks)
if trashBlockCount > 0 && trashGen == nil {
trashGen = newTrashGenerator(ssaPkg.Prog, funcConfig.ImportNameResolver, obfRand)
}

applyObfuscation := func(ssaFunc *ssa.Function) []dispatcherInfo {
if trashBlockCount > 0 {
addTrashBlockMarkers(ssaFunc, trashBlockCount, obfRand)
}
for i := 0; i < split; i++ {
if !applySplitting(ssaFunc, obfRand) {
break // no more candidates for splitting
Expand Down Expand Up @@ -229,6 +244,13 @@ func Obfuscate(fset *token.FileSet, ssaPkg *ssa.Package, files []*ast.File, obfR
funcConfig.SsaValueRemap = nil
}

funcConfig.MarkerInstrCallback = nil
if trashBlockCount > 0 {
funcConfig.MarkerInstrCallback = func(m map[string]types.Type) []ast.Stmt {
return trashGen.Generate(minTrashBlockStmts+obfRand.Intn(maxTrashBlockStmts-minTrashBlockStmts), m)
}
}

astFunc, err := ssa2ast.Convert(ssaFunc, funcConfig)
if err != nil {
return "", nil, nil, err
Expand Down
86 changes: 86 additions & 0 deletions internal/ctrlflow/transform.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package ctrlflow

import (
"go/constant"
"go/token"
"go/types"
mathrand "math/rand"
"strconv"

"golang.org/x/tools/go/ssa"
"mvdan.cc/garble/internal/ssa2ast"
)

type blockMapping struct {
Expand Down Expand Up @@ -218,6 +220,90 @@ func applySplitting(ssaFunc *ssa.Function, obfRand *mathrand.Rand) bool {
return true
}

// randomAlwaysFalseCond generates two random int32 and a random compare operator that always returns false, examples:
// 1350205738 <= 734900678
// 1400381511 >= 1621623831
// 2062290251 < 1908004916
// 1228588894 > 1819094321
// 2094727349 == 955574490
func randomAlwaysFalseCond(obfRand *mathrand.Rand) (*ssa.Const, token.Token, *ssa.Const) {
tokens := []token.Token{token.EQL, token.NEQ, token.LSS, token.LEQ, token.GTR, token.GEQ}

val1, val2 := constant.MakeInt64(int64(obfRand.Int31())), constant.MakeInt64(int64(obfRand.Int31()))

var candidates []token.Token
for _, t := range tokens {
if !constant.Compare(val1, t, val2) {
candidates = append(candidates, t)
}
}

return ssa.NewConst(val1, types.Typ[types.Int]), candidates[obfRand.Intn(len(candidates))], ssa.NewConst(val2, types.Typ[types.Int])
}

// addTrashBlockMarkers adds unreachable blocks with ssa2ast.MarkerInstr to further generate trash statements
func addTrashBlockMarkers(ssaFunc *ssa.Function, count int, obfRand *mathrand.Rand) {
var candidates []*ssa.BasicBlock
for _, block := range ssaFunc.Blocks {
if len(block.Succs) > 0 {
candidates = append(candidates, block)
}
}

if len(candidates) == 0 {
return
}

for i := 0; i < count; i++ {
targetBlock := candidates[obfRand.Intn(len(candidates))]
succsIdx := obfRand.Intn(len(targetBlock.Succs))
succs := targetBlock.Succs[succsIdx]

val1, op, val2 := randomAlwaysFalseCond(obfRand)
phiInstr := &ssa.Phi{
Edges: []ssa.Value{val1},
}
setType(phiInstr, types.Typ[types.Int])

binOpInstr := &ssa.BinOp{
X: phiInstr,
Op: op,
Y: val2,
}
setType(binOpInstr, types.Typ[types.Bool])

jmpInstr := &ssa.If{Cond: binOpInstr}
*binOpInstr.Referrers() = append(*binOpInstr.Referrers(), jmpInstr)

trashBlock := &ssa.BasicBlock{
Comment: "ctrflow.trash." + strconv.Itoa(targetBlock.Index),
Instrs: []ssa.Instruction{
ssa2ast.MarkerInstr,
&ssa.Jump{},
},
}
setBlockParent(trashBlock, ssaFunc)

trashBlockDispatch := &ssa.BasicBlock{
Comment: "ctrflow.trash.cond." + strconv.Itoa(targetBlock.Index),
Instrs: []ssa.Instruction{
phiInstr,
binOpInstr,
jmpInstr,
},
Preds: []*ssa.BasicBlock{targetBlock},
Succs: []*ssa.BasicBlock{trashBlock, succs},
}
setBlockParent(trashBlockDispatch, ssaFunc)
targetBlock.Succs[succsIdx] = trashBlockDispatch

trashBlock.Preds = []*ssa.BasicBlock{trashBlockDispatch, trashBlock}
trashBlock.Succs = []*ssa.BasicBlock{trashBlock}

ssaFunc.Blocks = append(ssaFunc.Blocks, trashBlockDispatch, trashBlock)
}
}

func fixBlockIndexes(ssaFunc *ssa.Function) {
for i, block := range ssaFunc.Blocks {
block.Index = i
Expand Down
Loading

0 comments on commit e8fe80d

Please sign in to comment.