Skip to content

Commit

Permalink
add trash block generator
Browse files Browse the repository at this point in the history
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 committed Jan 2, 2024
1 parent 3a9c9aa commit 9cf4b87
Show file tree
Hide file tree
Showing 9 changed files with 756 additions and 16 deletions.
23 changes: 23 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,14 @@ func Obfuscate(fset *token.FileSet, ssaPkg *ssa.Package, files []*ast.File, obfR
funcConfig.SsaValueRemap = nil
}

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

astFunc, err := ssa2ast.Convert(ssaFunc, funcConfig)
if err != nil {
return "", nil, nil, err
Expand Down
80 changes: 80 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,84 @@ func applySplitting(ssaFunc *ssa.Function, obfRand *mathrand.Rand) bool {
return true
}

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 9cf4b87

Please sign in to comment.