支持控制流混淆

dev
walon 2025-06-22 19:33:28 +08:00
parent ac9c96b4b9
commit 7a7ef72728
15 changed files with 1031 additions and 35 deletions

View File

@ -212,11 +212,21 @@ namespace Obfuz.Emit
}
break;
}
case FlowControl.Call:
case FlowControl.Next:
{
if (nextBlock != null)
{
curBlock.AddTargetBasicBlock(nextBlock);
}
break;
}
case FlowControl.Return:
case FlowControl.Throw:
{
break;
}
default: throw new NotSupportedException($"Unsupported flow control: {lastInst.OpCode.FlowControl} in method {method.FullName}");
}
}
}

View File

@ -3,6 +3,7 @@ using dnlib.DotNet.Emit;
using Obfuz.Utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using UnityEngine.Assertions;
@ -50,21 +51,34 @@ namespace Obfuz.Emit
}
}
class EvalStackState
{
public bool visited;
public readonly List<EvalDataTypeWithSig> inputStackDatas = new List<EvalDataTypeWithSig>();
public readonly List<EvalDataTypeWithSig> runStackDatas = new List<EvalDataTypeWithSig>();
}
class EvalStackCalculator
{
private readonly MethodDef _method;
private readonly BasicBlockCollection _basicBlocks;
private readonly Dictionary<Instruction, InstructionParameterInfo> _instructionParameterInfos = new Dictionary<Instruction, InstructionParameterInfo>();
private readonly Dictionary<Instruction, EvalDataType> _evalStackTopDataTypeAfterInstructions = new Dictionary<Instruction, EvalDataType>();
private readonly Dictionary<BasicBlock, EvalStackState> _blockEvalStackStates;
public EvalStackCalculator(MethodDef method)
{
_method = method;
_basicBlocks = new BasicBlockCollection(method, false);
_blockEvalStackStates = _basicBlocks.Blocks.ToDictionary(b => b, b => new EvalStackState());
SimulateRunAllBlocks();
}
public BasicBlockCollection BasicBlockCollection => _basicBlocks;
public bool TryGetParameterInfo(Instruction inst, out InstructionParameterInfo info)
{
return _instructionParameterInfos.TryGetValue(inst, out info);
@ -75,12 +89,9 @@ namespace Obfuz.Emit
return _evalStackTopDataTypeAfterInstructions.TryGetValue(inst, out result);
}
class EvalStackState
public EvalStackState GetEvalStackState(BasicBlock basicBlock)
{
public bool visited;
public readonly List<EvalDataTypeWithSig> inputStackDatas = new List<EvalDataTypeWithSig>();
public readonly List<EvalDataTypeWithSig> runStackDatas = new List<EvalDataTypeWithSig>();
return _blockEvalStackStates[basicBlock];
}
private void PushStack(List<EvalDataTypeWithSig> datas, TypeSig type)
@ -163,6 +174,8 @@ namespace Obfuz.Emit
}
case ElementType.Var:
case ElementType.MVar:
PushStack(datas, new EvalDataTypeWithSig(EvalDataType.ValueType, type));
break;
case ElementType.ValueArray:
case ElementType.R:
case ElementType.CModOpt:
@ -193,6 +206,11 @@ namespace Obfuz.Emit
datas.Add(type);
}
private void PushStackObject(List<EvalDataTypeWithSig> datas)
{
datas.Add(new EvalDataTypeWithSig(EvalDataType.Ref, _method.Module.CorLibTypes.Object));
}
private EvalDataType CalcBasicBinOpRetType(EvalDataType op1, EvalDataType op2)
{
switch (op1)
@ -252,7 +270,6 @@ namespace Obfuz.Emit
private void SimulateRunAllBlocks()
{
Dictionary<BasicBlock, EvalStackState> blockEvalStackStates = _basicBlocks.Blocks.ToDictionary(b => b, b => new EvalStackState());
bool methodHasReturnValue = _method.ReturnType.RemovePinnedAndModifiers().ElementType != ElementType.Void;
CilBody body = _method.Body;
@ -260,15 +277,23 @@ namespace Obfuz.Emit
{
foreach (ExceptionHandler handler in body.ExceptionHandlers)
{
if (handler.IsCatch)
{
BasicBlock bb = _basicBlocks.GetBasicBlockByInstruction(handler.HandlerStart);
blockEvalStackStates[bb].runStackDatas.Add(new EvalDataTypeWithSig(EvalDataType.Ref, null)); // Exception object is pushed onto the stack.
}
else if (handler.IsFilter)
if (handler.IsFilter)
{
BasicBlock bb = _basicBlocks.GetBasicBlockByInstruction(handler.FilterStart);
blockEvalStackStates[bb].runStackDatas.Add(new EvalDataTypeWithSig(EvalDataType.Ref, null)); // Exception object is pushed onto the stack.
var inputStackDatas = _blockEvalStackStates[bb].inputStackDatas;
if (inputStackDatas.Count == 0)
{
inputStackDatas.Add(new EvalDataTypeWithSig(EvalDataType.Ref, handler.CatchType.ToTypeSig()));
}
}
if (handler.IsCatch || handler.IsFilter)
{
BasicBlock bb = _basicBlocks.GetBasicBlockByInstruction(handler.HandlerStart);
var inputStackDatas = _blockEvalStackStates[bb].inputStackDatas;
if (inputStackDatas.Count == 0)
{
inputStackDatas.Add(new EvalDataTypeWithSig(EvalDataType.Ref, handler.CatchType.ToTypeSig()));
}
}
}
}
@ -281,12 +306,13 @@ namespace Obfuz.Emit
? (IList<TypeSig>)_method.GenericParameters.Select(p => (TypeSig)new GenericMVar(p.Number)).ToList()
: null;
var gac = new GenericArgumentContext(methodTypeGenericArgument, methodMethodGenericArgument);
var corLibTypes = _method.Module.CorLibTypes;
var blockWalkStack = new Stack<BasicBlock>(_basicBlocks.Blocks.Reverse());
while (blockWalkStack.Count > 0)
{
BasicBlock block = blockWalkStack.Pop();
EvalStackState state = blockEvalStackStates[block];
EvalStackState state = _blockEvalStackStates[block];
if (state.visited)
continue;
state.visited = true;
@ -350,7 +376,7 @@ namespace Obfuz.Emit
}
case Code.Ldnull:
{
PushStack(newPushedDatas, EvalDataType.I);
PushStackObject(newPushedDatas);
break;
}
case Code.Ldc_I4_M1:
@ -493,7 +519,7 @@ namespace Obfuz.Emit
case Code.Ldind_Ref:
{
Assert.IsTrue(stackSize > 0);
PushStack(newPushedDatas, EvalDataType.Ref);
PushStackObject(newPushedDatas);
break;
}
case Code.Ldind_R4:
@ -676,7 +702,7 @@ namespace Obfuz.Emit
}
case Code.Ldstr:
{
PushStack(newPushedDatas, EvalDataType.Ref);
PushStack(newPushedDatas, new EvalDataTypeWithSig(EvalDataType.Ref, corLibTypes.String));
break;
}
case Code.Newobj:
@ -692,7 +718,10 @@ namespace Obfuz.Emit
}
case Code.Isinst:
{
PushStack(newPushedDatas, EvalDataType.Ref);
Assert.IsTrue(stackSize > 0);
var obj = stackDatas[stackSize - 1];
Assert.IsTrue(obj.type == EvalDataType.Ref);
PushStack(newPushedDatas, obj);
break;
}
case Code.Unbox:
@ -710,7 +739,7 @@ namespace Obfuz.Emit
case Code.Box:
{
Assert.IsTrue(stackSize > 0);
PushStack(newPushedDatas, EvalDataType.Ref);
PushStackObject(newPushedDatas);
break;
}
case Code.Throw:
@ -745,7 +774,7 @@ namespace Obfuz.Emit
case Code.Newarr:
{
Assert.IsTrue(stackSize > 0);
PushStack(newPushedDatas, EvalDataType.Ref);
PushStack(newPushedDatas, new SZArraySig(((ITypeDefOrRef)inst.Operand).ToTypeSig()));
break;
}
case Code.Ldlen:
@ -798,7 +827,7 @@ namespace Obfuz.Emit
case Code.Ldelem_Ref:
{
Assert.IsTrue(stackSize >= 2);
PushStack(newPushedDatas, EvalDataType.Ref);
PushStackObject(newPushedDatas);
break;
}
case Code.Ldelem:
@ -914,7 +943,7 @@ namespace Obfuz.Emit
}
foreach (BasicBlock outBb in block.outBlocks)
{
EvalStackState outState = blockEvalStackStates[outBb];
EvalStackState outState = _blockEvalStackStates[outBb];
if (outState.visited)
{
if (stackDatas.Count != outState.inputStackDatas.Count)

View File

@ -253,7 +253,7 @@ namespace Obfuz.ObfusPasses.CallObfus
{
if (!_methodRuleCache.TryGetValue(method, out var rule))
{
rule = _configParser.GetMethodRule(method, s_default);
rule = _configParser.GetMethodRule(method, _global);
_methodRuleCache[method] = rule;
}
return rule;

View File

@ -116,7 +116,7 @@ namespace Obfuz.ObfusPasses.ControlFlowObfus
{
if (!_methodRuleCache.TryGetValue(method, out var rule))
{
rule = _xmlParser.GetMethodRule(method, s_default);
rule = _xmlParser.GetMethodRule(method, _global);
_methodRuleCache[method] = rule;
}
return rule;

View File

@ -14,6 +14,12 @@ namespace Obfuz.ObfusPasses.ControlFlowObfus
public EncryptionScopeInfo encryptionScope;
public DefaultMetadataImporter importer;
public ModuleConstFieldAllocator constFieldAllocator;
public int minInstructionCountOfBasicBlockToObfuscate;
public IRandom CreateRandom()
{
return encryptionScope.localRandomCreator(MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(method));
}
}
internal class ControlFlowObfusPass : ObfuscationMethodPassBase
@ -54,7 +60,6 @@ namespace Obfuz.ObfusPasses.ControlFlowObfus
//Debug.Log($"Obfuscating method: {method.FullName} with EvalStackObfusPass");
ObfuscationPassContext ctx = ObfuscationPassContext.Current;
var calc = new BasicBlockCollection(method, false);
var encryptionScope = ctx.encryptionScopeProvider.GetScope(method.Module);
var ruleData = _obfuscationPolicy.GetObfuscationRuleData(method);
var localRandom = encryptionScope.localRandomCreator(MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(method));
@ -66,8 +71,9 @@ namespace Obfuz.ObfusPasses.ControlFlowObfus
constFieldAllocator = ctx.constFieldAllocator.GetModuleAllocator(method.Module),
localRandom = localRandom,
importer = ctx.moduleEntityManager.GetDefaultModuleMetadataImporter(method.Module, ctx.encryptionScopeProvider),
minInstructionCountOfBasicBlockToObfuscate = _settings.minInstructionCountOfBasicBlockToObfuscate,
};
_obfuscator.Obfuscate(calc, obfusMethodCtx);
_obfuscator.Obfuscate(method, obfusMethodCtx);
}
}
}

View File

@ -1,12 +1,20 @@
using Obfuz.Emit;
using dnlib.DotNet;
using UnityEngine;
namespace Obfuz.ObfusPasses.ControlFlowObfus
{
class DefaultObfuscator : ObfuscatorBase
{
public override bool Obfuscate(BasicBlockCollection basicBlocks, ObfusMethodContext ctx)
public override bool Obfuscate(MethodDef method, ObfusMethodContext ctx)
{
//Debug.Log($"Obfuscating method: {method.FullName} with ControlFlowObfusPass");
var mcfc = new MethodControlFlowCalculator(method, ctx.CreateRandom(), ctx.constFieldAllocator, ctx.minInstructionCountOfBasicBlockToObfuscate);
if (!mcfc.TryObfus())
{
Debug.LogWarning($"not obfuscate method: {method.FullName}");
return false;
}
return true;
}
}
}

View File

@ -1,14 +1,15 @@
using Obfuz.Emit;
using dnlib.DotNet;
using Obfuz.Emit;
namespace Obfuz.ObfusPasses.ControlFlowObfus
{
interface IObfuscator
{
bool Obfuscate(BasicBlockCollection basicBlocks, ObfusMethodContext ctx);
bool Obfuscate(MethodDef method, ObfusMethodContext ctx);
}
abstract class ObfuscatorBase : IObfuscator
{
public abstract bool Obfuscate(BasicBlockCollection basicBlocks, ObfusMethodContext ctx);
public abstract bool Obfuscate(MethodDef method, ObfusMethodContext ctx);
}
}

View File

@ -0,0 +1,895 @@
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using Obfuz.Data;
using Obfuz.Emit;
using Obfuz.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using UnityEngine;
using UnityEngine.Assertions;
namespace Obfuz.ObfusPasses.ControlFlowObfus
{
class MethodControlFlowCalculator
{
class BasicBlockInputOutputArguments
{
public readonly List<Local> locals = new List<Local>();
public BasicBlockInputOutputArguments()
{
}
public BasicBlockInputOutputArguments(MethodDef method, List<EvalDataTypeWithSig> inputStackDatas)
{
ICorLibTypes corLibTypes = method.Module.CorLibTypes;
foreach (var data in inputStackDatas)
{
Local local = new Local(GetLocalTypeSig(corLibTypes, data));
locals.Add(local);
method.Body.Variables.Add(local);
}
}
private TypeSig GetLocalTypeSig(ICorLibTypes corLibTypes, EvalDataTypeWithSig type)
{
switch (type.type)
{
case EvalDataType.Int32: return corLibTypes.Int32;
case EvalDataType.Int64: return corLibTypes.Int64;
case EvalDataType.Float: return corLibTypes.Single;
case EvalDataType.Double: return corLibTypes.Double;
case EvalDataType.I: return corLibTypes.IntPtr;
case EvalDataType.Ref: Assert.IsNotNull(type.typeSig); return type.typeSig;
case EvalDataType.ValueType: Assert.IsNotNull(type.typeSig); return type.typeSig;
case EvalDataType.Token: throw new System.NotSupportedException("Token type is not supported in BasicBlockInputOutputArguments");
default: throw new System.NotSupportedException("not supported EvalDataType");
}
}
}
class BasicBlockInfo
{
public BlockGroup group;
//public int order;
public bool isSaveStackBlock;
public BasicBlockInfo prev;
public BasicBlockInfo next;
public List<Instruction> instructions;
public List<EvalDataTypeWithSig> inputStackDatas;
public List<EvalDataTypeWithSig> outputStackDatas;
public List<BasicBlockInfo> inBasicBlocks = new List<BasicBlockInfo>();
public List<BasicBlockInfo> outBasicBlocks = new List<BasicBlockInfo>();
public BasicBlockInputOutputArguments inputArgs;
public BasicBlockInputOutputArguments outputArgs;
public Instruction FirstInstruction => instructions[0];
public Instruction LastInstruction => instructions[instructions.Count - 1];
public Instruction GroupFirstInstruction => group.basicBlocks[0].FirstInstruction;
//public void InsertNext(BasicBlockInfo nextBb)
//{
// if (next != null)
// {
// next.prev = nextBb;
// nextBb.next = next;
// }
// nextBb.prev = this;
// next = nextBb;
//}
public void InsertBefore(BasicBlockInfo prevBb)
{
prev.next = prevBb;
prevBb.prev = prev;
prevBb.next = this;
this.prev = prevBb;
}
public void AddOutBasicBlock(BasicBlockInfo outBb)
{
if (!outBasicBlocks.Contains(outBb))
{
outBasicBlocks.Add(outBb);
outBb.inBasicBlocks.Add(this);
}
}
public void ClearInBasicBlocks()
{
foreach (var inBb in inBasicBlocks)
{
inBb.outBasicBlocks.Remove(this);
}
inBasicBlocks.Clear();
}
public void RetargetInBasicBlocksTo(BasicBlockInfo prevBb, Dictionary<Instruction, BasicBlockInfo> inst2bb)
{
var oldInBlocks = new List<BasicBlockInfo>(inBasicBlocks);
ClearInBasicBlocks();
foreach (var oldInBb in oldInBlocks)
{
oldInBb.AddOutBasicBlock(prevBb);
}
// inBB => saveBb => cur
foreach (BasicBlockInfo inBb in prevBb.inBasicBlocks)
{
if (inBb.instructions.Count == 0)
{
// empty block, no need to retarget
continue;
}
Instruction lastInst = inBb.instructions.Last();
if (lastInst.Operand is Instruction targetInst)
{
if (inst2bb.TryGetValue(targetInst, out BasicBlockInfo targetBb) && targetBb == this)
{
// retarget to prevBb
lastInst.Operand = prevBb.FirstInstruction;
}
}
else if (lastInst.Operand is Instruction[] targetInsts)
{
for (int i = 0; i < targetInsts.Length; i++)
{
targetInst = targetInsts[i];
if (inst2bb.TryGetValue(targetInst, out BasicBlockInfo targetBb) && targetBb == this)
{
targetInsts[i] = prevBb.FirstInstruction;
}
}
}
}
}
}
private readonly MethodDef _method;
private readonly IRandom _random;
private readonly ModuleConstFieldAllocator _constFieldAllocator;
private readonly int _minInstructionCountOfBasicBlockToObfuscate;
private readonly BasicBlockInfo _bbHead;
public MethodControlFlowCalculator(MethodDef method, IRandom random, ModuleConstFieldAllocator constFieldAllocator, int minInstructionCountOfBasicBlockToObfuscate)
{
_method = method;
_random = random;
_constFieldAllocator = constFieldAllocator;
_minInstructionCountOfBasicBlockToObfuscate = minInstructionCountOfBasicBlockToObfuscate;
_bbHead = new BasicBlockInfo()
{
instructions = new List<Instruction>(),
inputStackDatas = new List<EvalDataTypeWithSig>(),
outputStackDatas = new List<EvalDataTypeWithSig>(),
};
}
private void BuildBasicBlockLink(EvalStackCalculator evc)
{
BasicBlockInfo prev = _bbHead;
var bb2bb = new Dictionary<BasicBlock, BasicBlockInfo>();
foreach (BasicBlock bb in evc.BasicBlockCollection.Blocks)
{
EvalStackState ess = evc.GetEvalStackState(bb);
var newBB = new BasicBlockInfo
{
prev = prev,
next = null,
instructions = bb.instructions,
inputStackDatas = ess.inputStackDatas,
outputStackDatas = ess.runStackDatas,
};
prev.next = newBB;
prev = newBB;
bb2bb.Add(bb, newBB);
}
foreach (BasicBlock bb in evc.BasicBlockCollection.Blocks)
{
BasicBlockInfo bbi = bb2bb[bb];
foreach (var inBb in bb.inBlocks)
{
bbi.inBasicBlocks.Add(bb2bb[inBb]);
}
foreach (var outBb in bb.outBlocks)
{
bbi.outBasicBlocks.Add(bb2bb[outBb]);
}
}
// let _bbHead point to the first basic block
//_bbHead.instructions.Add(Instruction.Create(OpCodes.Br, _bbHead.next.FirstInstruction));
_bbHead.next.inBasicBlocks.Add(_bbHead);
_bbHead.outBasicBlocks.Add(_bbHead.next);
}
private bool CheckNotContainsNotSupportedEvalStackData()
{
for (BasicBlockInfo cur = _bbHead; cur != null; cur = cur.next)
{
foreach (var data in cur.inputStackDatas)
{
if (data.type == EvalDataType.Unknown || data.type == EvalDataType.Token)
{
Debug.LogError($"NotSupported EvalStackData found in method: {_method.FullName}, type: {data.type}");
return false;
}
}
}
return true;
}
private void WalkInputArgumentGroup(BasicBlockInfo cur, BasicBlockInputOutputArguments inputArgs)
{
if (cur.inputArgs != null)
{
Assert.AreEqual(cur.inputArgs, inputArgs, "input arguments not match");
return;
}
cur.inputArgs = inputArgs;
foreach (BasicBlockInfo inputBB in cur.inBasicBlocks)
{
if (inputBB.outputArgs != null)
{
Assert.AreEqual(inputBB.outputArgs, inputArgs, $"Input BB {inputBB} outputArgs does not match in method: {_method.FullName}");
continue;
}
inputBB.outputArgs = cur.inputArgs;
foreach (var outBB in inputBB.outBasicBlocks)
{
WalkInputArgumentGroup(outBB, inputArgs);
}
}
}
private readonly BasicBlockInputOutputArguments emptyEvalStackArgs = new BasicBlockInputOutputArguments();
private void ComputeInputOutputArguments()
{
for (BasicBlockInfo cur = _bbHead; cur != null; cur = cur.next)
{
if (cur.inputArgs == null)
{
if (cur.inputStackDatas.Count == 0)
{
cur.inputArgs = emptyEvalStackArgs;
}
else
{
var inputArgs = new BasicBlockInputOutputArguments(_method, cur.inputStackDatas);
WalkInputArgumentGroup(cur, inputArgs);
}
}
if (cur.outputArgs == null && cur.outputStackDatas.Count == 0)
{
cur.outputArgs = emptyEvalStackArgs;
}
}
for (BasicBlockInfo cur = _bbHead; cur != null; cur = cur.next)
{
if (cur.inputArgs == null)
{
throw new System.Exception($"Input arguments for BasicBlock {cur} in method {_method.FullName} is null");
}
if (cur.outputArgs == null)
{
if (cur.instructions.Count > 0)
{
Code lastInstCode = cur.LastInstruction.OpCode.Code;
Assert.IsTrue(lastInstCode == Code.Throw || lastInstCode == Code.Rethrow);
cur.outputStackDatas = new List<EvalDataTypeWithSig>();
}
cur.outputArgs = emptyEvalStackArgs;
}
}
}
private BasicBlockInfo CreateSaveStackBasicBlock(BasicBlockInfo to)
{
if (to.group == null)
{
throw new Exception($"BasicBlock {to} in method {_method.FullName} does not belong to any group. This should not happen.");
}
var saveLocalBasicBlock = new BasicBlockInfo
{
group = to.group,
isSaveStackBlock = true,
inputStackDatas = to.inputStackDatas,
inputArgs = to.inputArgs,
outputStackDatas = new List<EvalDataTypeWithSig>(),
outputArgs = emptyEvalStackArgs,
instructions = new List<Instruction>(),
};
var locals = to.inputArgs.locals;
if (locals.Count > 0)
{
to.instructions.InsertRange(0, locals.Select(l => Instruction.Create(OpCodes.Ldloc, l)));
}
for (int i = locals.Count - 1; i >= 0; i--)
{
saveLocalBasicBlock.instructions.Add(Instruction.Create(OpCodes.Stloc, locals[i]));
}
to.inputArgs = emptyEvalStackArgs;
to.inputStackDatas = new List<EvalDataTypeWithSig>();
BlockGroup group = to.group;
group.basicBlocks.Insert(group.basicBlocks.IndexOf(to), saveLocalBasicBlock);
group.switchMachineCases.Add(new SwitchMachineCase { index = -1, prepareBlock = saveLocalBasicBlock, targetBlock = to});
saveLocalBasicBlock.instructions.Add(Instruction.Create(OpCodes.Ldsfld, (FieldDef)null));
saveLocalBasicBlock.instructions.Add(Instruction.Create(OpCodes.Br, group.switchMachineInst));
return saveLocalBasicBlock;
}
private void AdjustInputOutputEvalStack()
{
Dictionary<Instruction, BasicBlockInfo> inst2bb = BuildInstructionToBasicBlockInfoDic();
for (BasicBlockInfo cur = _bbHead.next; cur != null; cur = cur.next)
{
if (cur.inputArgs.locals.Count == 0 && cur.instructions.Count < _minInstructionCountOfBasicBlockToObfuscate)
{
// small block, no need to save stack
continue;
}
BasicBlockInfo saveBb = CreateSaveStackBasicBlock(cur);
cur.InsertBefore(saveBb);
cur.RetargetInBasicBlocksTo(saveBb, inst2bb);
//saveBb.AddOutBasicBlock(cur);
}
}
private void InsertSwitchMachineBasicBlockForGroups(BlockGroup rootGroup)
{
Dictionary<Instruction, BasicBlockInfo> inst2bb = BuildInstructionToBasicBlockInfoDic();
InsertSwitchMachineBasicBlockForGroup(rootGroup, inst2bb);
}
//private void ShuffleBasicBlocks0(List<BasicBlockInfo> bbs)
//{
// int n = bbs.Count;
// if (n <= 1)
// {
// return;
// }
// var firstSection = new List<BasicBlockInfo>() { bbs[0] };
// var sectionsExcludeFirstLast = new List<List<BasicBlockInfo>>();
// List<BasicBlockInfo> currentSection = firstSection;
// for (int i = 1; i < n; i++)
// {
// BasicBlockInfo cur = bbs[i];
// if (cur.inputArgs.locals.Count == 0)
// {
// currentSection = new List<BasicBlockInfo>() { cur };
// sectionsExcludeFirstLast.Add(currentSection);
// }
// else
// {
// currentSection.Add(cur);
// }
// }
// if (sectionsExcludeFirstLast.Count <= 1)
// {
// return;
// }
// var lastSection = sectionsExcludeFirstLast.Last();
// sectionsExcludeFirstLast.RemoveAt(sectionsExcludeFirstLast.Count - 1);
// RandomUtil.ShuffleList(sectionsExcludeFirstLast, _random);
// bbs.Clear();
// bbs.AddRange(firstSection);
// bbs.AddRange(sectionsExcludeFirstLast.SelectMany(section => section));
// bbs.AddRange(lastSection);
// Assert.AreEqual(n, bbs.Count, "Shuffled basic blocks count should be the same as original count");
//}
private void ShuffleBasicBlocks(List<BasicBlockInfo> bbs)
{
// TODO
//int n = bbs.Count;
//BasicBlockInfo groupPrev = bbs[0].prev;
//BasicBlockInfo groupNext = bbs[n - 1].next;
////RandomUtil.ShuffleList(bbs, _random);
//ShuffleBasicBlocks0(bbs);
//BasicBlockInfo prev = groupPrev;
//for (int i = 0; i < n; i++)
//{
// BasicBlockInfo cur = bbs[i];
// cur.prev = prev;
// prev.next = cur;
// prev = cur;
//}
//prev.next = groupNext;
//if (groupNext != null)
//{
// groupNext.prev = prev;
//}
}
private void InsertSwitchMachineBasicBlockForGroup(BlockGroup group, Dictionary<Instruction, BasicBlockInfo> inst2bb)
{
if (group.subGroups != null && group.subGroups.Count > 0)
{
foreach (var subGroup in group.subGroups)
{
InsertSwitchMachineBasicBlockForGroup(subGroup, inst2bb);
}
}
else if (group.switchMachineCases.Count > 0)
{
Assert.IsTrue(group.basicBlocks.Count > 0, "Group should contain at least one basic block");
BasicBlockInfo firstBlock = group.basicBlocks[0];
var firstCase = group.switchMachineCases[0];
//Assert.AreEqual(firstCase.prepareBlock, firstBlock, "First case prepare block should be the first basic block in group");
Assert.IsTrue(firstCase.targetBlock.inputArgs.locals.Count == 0);
Assert.IsTrue(firstCase.targetBlock.inputStackDatas.Count == 0);
var instructions = new List<Instruction>()
{
Instruction.Create(OpCodes.Ldsfld, (FieldDef)null),
group.switchMachineInst,
Instruction.Create(OpCodes.Br, firstCase.targetBlock.FirstInstruction),
};
if (firstCase.prepareBlock != firstBlock || firstBlock.inputStackDatas.Count != 0)
{
instructions.Insert(0, Instruction.Create(OpCodes.Br, firstBlock.FirstInstruction));
}
var switchMachineBb = new BasicBlockInfo()
{
group = group,
inputArgs = firstBlock.inputArgs,
outputArgs = emptyEvalStackArgs,
inputStackDatas = firstBlock.inputStackDatas,
outputStackDatas = new List<EvalDataTypeWithSig>(),
instructions = instructions,
};
firstBlock.InsertBefore(switchMachineBb);
group.basicBlocks.Insert(0, switchMachineBb);
ShuffleBasicBlocks(group.basicBlocks);
List<Instruction> switchTargets = (List<Instruction>)group.switchMachineInst.Operand;
RandomUtil.ShuffleList(group.switchMachineCases, _random);
for (int i = 0, n = group.switchMachineCases.Count; i < n; i++)
{
SwitchMachineCase switchMachineCase = group.switchMachineCases[i];
switchMachineCase.index = i;
List<Instruction> prepareBlockInstructions = switchMachineCase.prepareBlock.instructions;
Instruction setBranchIndexInst = prepareBlockInstructions[prepareBlockInstructions.Count - 2];
Assert.AreEqual(setBranchIndexInst.OpCode, OpCodes.Ldsfld, "first instruction of prepareBlock should be Ldsfld");
//setBranchIndexInst.Operand = i;
var indexField = _constFieldAllocator.Allocate(i);
setBranchIndexInst.Operand = indexField;
switchTargets.Add(switchMachineCase.targetBlock.FirstInstruction);
}
// after shuffle
Assert.IsTrue(instructions.Count == 3 || instructions.Count == 4, "Switch machine basic block should contain 3 or 4 instructions");
Assert.AreEqual(Code.Ldsfld, instructions[instructions.Count - 3].OpCode.Code, "First instruction should be Ldsfld");
instructions[instructions.Count - 3].Operand = _constFieldAllocator.Allocate(firstCase.index);
}
}
private bool IsPrevBasicBlockControlFlowNextToThis(BasicBlockInfo cur)
{
Instruction lastInst = cur.prev.LastInstruction;
switch (lastInst.OpCode.FlowControl)
{
case FlowControl.Cond_Branch:
case FlowControl.Call:
case FlowControl.Next:
case FlowControl.Break:
{
return true;
}
default: return false;
}
}
private void InsertBrInstructionForConjoinedBasicBlocks()
{
for (BasicBlockInfo cur = _bbHead.next.next; cur != null; cur = cur.next)
{
if (cur.group == cur.prev.group && IsPrevBasicBlockControlFlowNextToThis(cur))
{
cur.prev.instructions.Add(Instruction.Create(OpCodes.Br, cur.FirstInstruction));
}
}
}
private Dictionary<Instruction, BasicBlockInfo> BuildInstructionToBasicBlockInfoDic()
{
var inst2bb = new Dictionary<Instruction, BasicBlockInfo>();
for (BasicBlockInfo cur = _bbHead.next; cur != null; cur = cur.next)
{
foreach (var inst in cur.instructions)
{
inst2bb[inst] = cur;
}
}
return inst2bb;
}
private class SwitchMachineCase
{
public int index;
public BasicBlockInfo prepareBlock;
public BasicBlockInfo targetBlock;
}
private class BlockGroup
{
public BlockGroup parent;
public List<Instruction> instructions;
public List<BlockGroup> subGroups;
public List<BasicBlockInfo> basicBlocks;
public Instruction switchMachineInst;
public List<SwitchMachineCase> switchMachineCases;
public BlockGroup(List<Instruction> instructions, Dictionary<Instruction, BlockGroup> inst2group)
{
this.instructions = instructions;
UpdateInstructionGroup(inst2group);
}
public BlockGroup(BlockGroup parent, List<Instruction> instructions, Dictionary<Instruction, BlockGroup> inst2group)
{
this.instructions = instructions;
UpdateInstructionGroup(parent, inst2group);
}
public BlockGroup RootParent => parent == null ? this : parent.RootParent;
public void SetParent(BlockGroup newParent)
{
if (parent != null)
{
Assert.IsTrue(parent != newParent, "Parent group should not be the same as new parent");
Assert.IsTrue(parent.subGroups.Contains(this), "Parent group should already contain this group");
parent.subGroups.Remove(this);
}
parent = newParent;
if (newParent.subGroups == null)
{
newParent.subGroups = new List<BlockGroup>();
}
Assert.IsFalse(newParent.subGroups.Contains(this), "New parent group should not already contain this group");
newParent.subGroups.Add(this);
}
private void UpdateInstructionGroup(Dictionary<Instruction, BlockGroup> inst2group)
{
foreach (var inst in instructions)
{
if (inst2group.TryGetValue(inst, out BlockGroup existGroup))
{
if (this != existGroup)
{
BlockGroup rootParent = existGroup.RootParent;
if (rootParent != this)
{
rootParent.SetParent(this);
}
}
}
else
{
inst2group[inst] = this;
}
}
}
private void UpdateInstructionGroup(BlockGroup parentGroup, Dictionary<Instruction, BlockGroup> inst2group)
{
foreach (var inst in instructions)
{
BlockGroup existGroup = inst2group[inst];
Assert.AreEqual(parentGroup, existGroup, "Instruction group parent should be the same as parent group");
inst2group[inst] = this;
}
SetParent(parentGroup);
}
public void SplitInstructionsNotInAnySubGroupsToIndividualGroups(Dictionary<Instruction, BlockGroup> inst2group)
{
if (subGroups == null || subGroups.Count == 0 || instructions.Count == 0)
{
return;
}
foreach (var subGroup in subGroups)
{
subGroup.SplitInstructionsNotInAnySubGroupsToIndividualGroups(inst2group);
}
var finalGroupList = new List<BlockGroup>();
var curGroupInstructions = new List<Instruction>();
var firstInst2SubGroup = subGroups.ToDictionary(g => g.instructions[0]);
foreach (var inst in instructions)
{
BlockGroup group = inst2group[inst];
if (group == this)
{
curGroupInstructions.Add(inst);
}
else
{
if (curGroupInstructions.Count > 0)
{
finalGroupList.Add(new BlockGroup(this, curGroupInstructions, inst2group));
curGroupInstructions = new List<Instruction>();
}
if (firstInst2SubGroup.TryGetValue(inst, out var subGroup))
{
finalGroupList.Add(subGroup);
}
}
}
if (curGroupInstructions.Count > 0)
{
finalGroupList.Add(new BlockGroup(this, curGroupInstructions, inst2group));
}
this.subGroups = finalGroupList;
}
public void ComputeBasicBlocks(Dictionary<Instruction, BasicBlockInfo> inst2bb)
{
if (subGroups == null || subGroups.Count == 0)
{
basicBlocks = new List<BasicBlockInfo>();
foreach (var inst in instructions)
{
BasicBlockInfo block = inst2bb[inst];
if (block.group != null)
{
if (block.group != this)
{
throw new Exception("BasicBlockInfo group should be the same as this BlockGroup");
}
}
else
{
block.group = this;
basicBlocks.Add(block);
}
}
switchMachineInst = Instruction.Create(OpCodes.Switch, new List<Instruction>());
switchMachineCases = new List<SwitchMachineCase>();
return;
}
foreach (var subGroup in subGroups)
{
subGroup.ComputeBasicBlocks(inst2bb);
}
}
}
private class TryBlockGroup : BlockGroup
{
public TryBlockGroup(List<Instruction> instructions, Dictionary<Instruction, BlockGroup> inst2group) : base(instructions, inst2group)
{
}
}
private class ExceptionHandlerGroup : BlockGroup
{
public readonly ExceptionHandler exceptionHandler;
public ExceptionHandlerGroup(ExceptionHandler exceptionHandler, List<Instruction> instructions, Dictionary<Instruction, BlockGroup> inst2group) : base(instructions, inst2group)
{
this.exceptionHandler = exceptionHandler;
}
}
private class ExceptionFilterGroup : BlockGroup
{
public readonly ExceptionHandler exceptionHandler;
public ExceptionFilterGroup(ExceptionHandler exceptionHandler, List<Instruction> instructions, Dictionary<Instruction, BlockGroup> inst2group) : base(instructions, inst2group)
{
this.exceptionHandler = exceptionHandler;
}
}
private class ExceptionHandlerWithFilterGroup : BlockGroup
{
public readonly ExceptionHandler exceptionHandler;
//public readonly ExceptionFilterGroup filterGroup;
//public readonly ExceptionHandlerGroup handlerGroup;
public ExceptionHandlerWithFilterGroup(ExceptionHandler exceptionHandler, List<Instruction> filterInstructions, List<Instruction> handlerInstructions, List<Instruction> allInstructions, Dictionary<Instruction, BlockGroup> inst2group) : base(allInstructions, inst2group)
{
this.exceptionHandler = exceptionHandler;
var filterGroup = new ExceptionFilterGroup(exceptionHandler, filterInstructions, inst2group);
var handlerGroup = new ExceptionHandlerGroup(exceptionHandler, handlerInstructions, inst2group);
}
}
class TryBlockInfo
{
public Instruction tryStart;
public Instruction tryEnd;
public TryBlockGroup blockGroup;
}
private Dictionary<Instruction, int> BuildInstruction2Index()
{
IList<Instruction> instructions = _method.Body.Instructions;
var inst2Index = new Dictionary<Instruction, int>(instructions.Count);
for (int i = 0; i < instructions.Count; i++)
{
Instruction inst = instructions[i];
inst2Index.Add(inst, i);
}
return inst2Index;
}
private BlockGroup SplitBasicBlockGroup()
{
Dictionary<Instruction, int> inst2Index = BuildInstruction2Index();
var inst2blockGroup = new Dictionary<Instruction, BlockGroup>();
List<Instruction> instructions = (List<Instruction>)_method.Body.Instructions;
var tryBlocks = new List<TryBlockInfo>();
foreach (var ex in _method.Body.ExceptionHandlers)
{
TryBlockInfo tryBlock = tryBlocks.Find(tryBlocks => tryBlocks.tryStart == ex.TryStart && tryBlocks.tryEnd == ex.TryEnd);
if (tryBlock == null)
{
int startIndex = inst2Index[ex.TryStart];
int endIndex = ex.TryEnd != null ? inst2Index[ex.TryEnd] : inst2Index.Count;
TryBlockGroup blockGroup = new TryBlockGroup(instructions.GetRange(startIndex, endIndex - startIndex), inst2blockGroup);
tryBlock = new TryBlockInfo
{
tryStart = ex.TryStart,
tryEnd = ex.TryEnd,
blockGroup = blockGroup,
};
tryBlocks.Add(tryBlock);
}
if (ex.FilterStart != null)
{
int filterStartIndex = inst2Index[ex.FilterStart];
int filterEndIndex = ex.HandlerStart != null ? inst2Index[ex.HandlerStart] : inst2Index.Count;
int handlerStartIndex = filterEndIndex;
int handlerEndIndex = ex.HandlerEnd != null ? inst2Index[ex.HandlerEnd] : inst2Index.Count;
var filterHandlerGroup = new ExceptionHandlerWithFilterGroup(ex,
instructions.GetRange(filterStartIndex, filterEndIndex - filterStartIndex),
instructions.GetRange(handlerStartIndex, handlerEndIndex - handlerStartIndex),
instructions.GetRange(filterStartIndex, handlerEndIndex - filterStartIndex), inst2blockGroup);
}
else
{
int handlerStartIndex = inst2Index[ex.HandlerStart];
int handlerEndIndex = ex.HandlerEnd != null ? inst2Index[ex.HandlerEnd] : inst2Index.Count;
ExceptionHandlerGroup handlerGroup = new ExceptionHandlerGroup(ex, instructions.GetRange(handlerStartIndex, handlerEndIndex - handlerStartIndex), inst2blockGroup);
}
}
var rootGroup = new BlockGroup(new List<Instruction>(instructions), inst2blockGroup);
rootGroup.SplitInstructionsNotInAnySubGroupsToIndividualGroups(inst2blockGroup);
rootGroup.ComputeBasicBlocks(BuildInstructionToBasicBlockInfoDic());
return rootGroup;
}
private void FixInstructionTargets()
{
var inst2bb = BuildInstructionToBasicBlockInfoDic();
foreach (var ex in _method.Body.ExceptionHandlers)
{
if (ex.TryStart != null)
{
ex.TryStart = inst2bb[ex.TryStart].GroupFirstInstruction;
}
if (ex.TryEnd != null)
{
ex.TryEnd = inst2bb[ex.TryEnd].GroupFirstInstruction;
}
if (ex.HandlerStart != null)
{
ex.HandlerStart = inst2bb[ex.HandlerStart].GroupFirstInstruction;
}
if (ex.HandlerEnd != null)
{
ex.HandlerEnd = inst2bb[ex.HandlerEnd].GroupFirstInstruction;
}
if (ex.FilterStart != null)
{
ex.FilterStart = inst2bb[ex.FilterStart].GroupFirstInstruction;
}
}
//foreach (var inst in inst2bb.Keys)
//{
// if (inst.Operand is Instruction targetInst)
// {
// inst.Operand = inst2bb[targetInst].FirstInstruction;
// }
// else if (inst.Operand is Instruction[] targetInsts)
// {
// for (int i = 0; i < targetInsts.Length; i++)
// {
// targetInsts[i] = inst2bb[targetInsts[i]].FirstInstruction;
// }
// }
//}
}
private void BuildInstructions()
{
IList<Instruction> methodInstructions = _method.Body.Instructions;
methodInstructions.Clear();
for (BasicBlockInfo cur = _bbHead.next; cur != null; cur = cur.next)
{
foreach (Instruction inst in cur.instructions)
{
methodInstructions.Add(inst);
}
}
_method.Body.InitLocals = true;
//_method.Body.MaxStack = Math.Max(_method.Body.MaxStack , (ushort)1); // TODO: set to a reasonable value
//_method.Body.KeepOldMaxStack = true;
//_method.Body.UpdateInstructionOffsets();
}
public bool TryObfus()
{
// TODO: TEMP
//if (_method.Body.HasExceptionHandlers)
//{
// return false;
//}
var evc = new EvalStackCalculator(_method);
BuildBasicBlockLink(evc);
if (!CheckNotContainsNotSupportedEvalStackData())
{
Debug.LogError($"Method {_method.FullName} contains unsupported EvalStackData, obfuscation skipped.");
return false;
}
BlockGroup rootGroup = SplitBasicBlockGroup();
if (rootGroup.basicBlocks != null && rootGroup.basicBlocks.Count == 1)
{
return false;
}
ComputeInputOutputArguments();
AdjustInputOutputEvalStack();
InsertBrInstructionForConjoinedBasicBlocks();
InsertSwitchMachineBasicBlockForGroups(rootGroup);
FixInstructionTargets();
BuildInstructions();
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 144b6474de40382498899f8b1c7f92a3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -126,7 +126,7 @@ namespace Obfuz.ObfusPasses.EvalStackObfus
{
if (!_methodRuleCache.TryGetValue(method, out var rule))
{
rule = _xmlParser.GetMethodRule(method, s_default);
rule = _xmlParser.GetMethodRule(method, _global);
_methodRuleCache[method] = rule;
}
return rule;

View File

@ -126,7 +126,7 @@ namespace Obfuz.ObfusPasses.ExprObfus
{
if (!_methodRuleCache.TryGetValue(method, out var rule))
{
rule = _xmlParser.GetMethodRule(method, s_default);
rule = _xmlParser.GetMethodRule(method, _global);
_methodRuleCache[method] = rule;
}
return rule;

View File

@ -7,12 +7,15 @@ namespace Obfuz.Settings
public class ControlFlowObfuscationSettingsFacade
{
public int minInstructionCountOfBasicBlockToObfuscate;
public List<string> ruleFiles;
}
[Serializable]
public class ControlFlowObfuscationSettings
{
public int minInstructionCountOfBasicBlockToObfuscate = 3;
[Tooltip("rule config xml files")]
public string[] ruleFiles;
@ -20,6 +23,7 @@ namespace Obfuz.Settings
{
return new ControlFlowObfuscationSettingsFacade
{
minInstructionCountOfBasicBlockToObfuscate = minInstructionCountOfBasicBlockToObfuscate,
ruleFiles = new List<string>(ruleFiles ?? Array.Empty<string>()),
};
}

View File

@ -1,7 +1,9 @@
using System;
using NUnit.Framework;
using System;
namespace Obfuz.Utils
{
internal static class MathUtil
{
//public static int ModInverseOdd32(int sa)

View File

@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace Obfuz.Utils
{
static class RandomUtil
{
public static void ShuffleList<T>(List<T> list, IRandom random)
{
int n = list.Count;
for (int i = n - 1; i > 0; i--)
{
int j = random.NextInt(i + 1);
T temp = list[i];
list[i] = list[j];
list[j] = temp;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d482c078394711d428e627843d2481d7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: