From 3094532eaa68b65fdfb64da62b758cffa749627a Mon Sep 17 00:00:00 2001 From: walon Date: Fri, 9 May 2025 12:55:25 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=A1=E7=AE=97BasicBlock=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E4=B8=94=E8=AE=A1=E7=AE=97=E6=98=AF=E5=90=A6=E5=B1=9E=E4=BA=8E?= =?UTF-8?q?loop=E4=B8=AD=E3=80=82=E6=94=AF=E6=8C=81const=20cache=E7=AD=96?= =?UTF-8?q?=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Editor/Emit/BasicBlockCollection.cs | 300 ++++++++++++++++++ .../BasicBlockObfuscationPassBase.cs | 80 +++++ .../ObfusPasses/ConstObfus/ConstObfusPass.cs | 9 +- 3 files changed, 385 insertions(+), 4 deletions(-) create mode 100644 Editor/Emit/BasicBlockCollection.cs create mode 100644 Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs diff --git a/Editor/Emit/BasicBlockCollection.cs b/Editor/Emit/BasicBlockCollection.cs new file mode 100644 index 0000000..26b9e70 --- /dev/null +++ b/Editor/Emit/BasicBlockCollection.cs @@ -0,0 +1,300 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Remoting.Messaging; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.Emit +{ + public static class LoopDetector + { + + } + + public class BasicBlock + { + public readonly List instructions = new List(); + + public readonly List inBlocks = new List(); + + public readonly List outBlocks = new List(); + + public bool inLoop; + + public void AddTargetBasicBlock(BasicBlock target) + { + if (!outBlocks.Contains(target)) + { + outBlocks.Add(target); + } + if (!target.inBlocks.Contains(this)) + { + target.inBlocks.Add(this); + } + } + } + + public class BasicBlockCollection + { + private readonly MethodDef _method; + + private readonly List _blocks = new List(); + private readonly Dictionary _inst2BlockMap = new Dictionary(); + + public IList Blocks => _blocks; + + public BasicBlockCollection(MethodDef method) + { + _method = method; + HashSet splitPoints = BuildSplitPoint(method); + BuildBasicBlocks(method, splitPoints); + BuildInOutGraph(method); + + var loopBlocks = FindLoopBlocks(_blocks); + foreach (var block in loopBlocks) + { + block.inLoop = true; + } + } + + public BasicBlock GetBasicBlockByInstruction(Instruction inst) + { + return _inst2BlockMap[inst]; + } + + private HashSet BuildSplitPoint(MethodDef method) + { + var insts = method.Body.Instructions; + var splitPoints = new HashSet(); + foreach (ExceptionHandler eh in method.Body.ExceptionHandlers) + { + if (eh.TryStart != null) + { + splitPoints.Add(eh.TryStart); + } + if (eh.TryEnd != null) + { + splitPoints.Add(eh.TryEnd); + } + if (eh.HandlerStart != null) + { + splitPoints.Add(eh.HandlerStart); + } + if (eh.HandlerEnd != null) + { + splitPoints.Add(eh.HandlerEnd); + } + if (eh.FilterStart != null) + { + splitPoints.Add(eh.FilterStart); + } + } + + for (int i = 0, n = insts.Count; i < n; i++) + { + Instruction curInst = insts[i]; + Instruction nextInst = i + 1 < n ? insts[i + 1] : null; + switch (curInst.OpCode.FlowControl) + { + case FlowControl.Branch: + { + if (nextInst != null) + { + splitPoints.Add(nextInst); + } + break; + } + case FlowControl.Cond_Branch: + { + if (nextInst != null) + { + splitPoints.Add(nextInst); + } + if (curInst.Operand is Instruction targetInst) + { + splitPoints.Add(targetInst); + } + else if (curInst.Operand is Instruction[] targetInsts) + { + foreach (var target in targetInsts) + { + splitPoints.Add(target); + } + } + break; + } + case FlowControl.Return: + { + if (nextInst != null) + { + splitPoints.Add(nextInst); + } + break; + } + case FlowControl.Throw: + { + if (nextInst != null) + { + splitPoints.Add(nextInst); + } + break; + } + } + } + return splitPoints; + } + + + private void BuildBasicBlocks(MethodDef method, HashSet splitPoints) + { + var insts = method.Body.Instructions; + + + BasicBlock curBlock = new BasicBlock(); + foreach (Instruction inst in insts) + { + if (splitPoints.Contains(inst) && curBlock.instructions.Count > 0) + { + _blocks.Add(curBlock); + curBlock = new BasicBlock(); + } + curBlock.instructions.Add(inst); + _inst2BlockMap.Add(inst, curBlock); + } + if (curBlock.instructions.Count > 0) + { + _blocks.Add(curBlock); + } + } + + private void BuildInOutGraph(MethodDef method) + { + var insts = method.Body.Instructions; + for (int i = 0, n = _blocks.Count; i < n; i++) + { + BasicBlock curBlock = _blocks[i]; + BasicBlock nextBlock = i + 1 < n ? _blocks[i + 1] : null; + Instruction lastInst = curBlock.instructions.Last(); + switch (lastInst.OpCode.FlowControl) + { + case FlowControl.Branch: + { + Instruction targetInst = (Instruction)lastInst.Operand; + BasicBlock targetBlock = GetBasicBlockByInstruction(targetInst); + curBlock.AddTargetBasicBlock(targetBlock); + break; + } + case FlowControl.Cond_Branch: + { + if (lastInst.Operand is Instruction targetInst) + { + BasicBlock targetBlock = GetBasicBlockByInstruction(targetInst); + curBlock.AddTargetBasicBlock(targetBlock); + } + else if (lastInst.Operand is Instruction[] targetInsts) + { + foreach (var target in targetInsts) + { + BasicBlock targetBlock = GetBasicBlockByInstruction(target); + curBlock.AddTargetBasicBlock(targetBlock); + } + } + else + { + throw new Exception("Invalid operand type for conditional branch"); + } + if (nextBlock != null) + { + curBlock.AddTargetBasicBlock(nextBlock); + } + break; + } + case FlowControl.Return: + case FlowControl.Throw: + { + break; + } + } + } + } + + private static HashSet FindLoopBlocks(List allBlocks) + { + // Tarjan算法找强连通分量 + var sccList = FindStronglyConnectedComponents(allBlocks); + + // 筛选有效循环 + var loopBlocks = new HashSet(); + foreach (var scc in sccList) + { + // 有效循环需满足以下条件之一: + // 1. 分量包含多个块 + // 2. 单个块有自环(跳转自己) + if (scc.Count > 1 || + (scc.Count == 1 && scc[0].outBlocks.Contains(scc[0]))) + { + foreach (var block in scc) + { + loopBlocks.Add(block); + } + } + } + return loopBlocks; + } + + private static List> FindStronglyConnectedComponents(List allBlocks) + { + int index = 0; + var stack = new Stack(); + var indexes = new Dictionary(); + var lowLinks = new Dictionary(); + var onStack = new HashSet(); + var sccList = new List>(); + + foreach (var block in allBlocks.Where(b => !indexes.ContainsKey(b))) + { + StrongConnect(block); + } + + return sccList; + + void StrongConnect(BasicBlock v) + { + indexes[v] = index; + lowLinks[v] = index; + index++; + stack.Push(v); + onStack.Add(v); + + foreach (var w in v.outBlocks) + { + if (!indexes.ContainsKey(w)) + { + StrongConnect(w); + lowLinks[v] = System.Math.Min(lowLinks[v], lowLinks[w]); + } + else if (onStack.Contains(w)) + { + lowLinks[v] = System.Math.Min(lowLinks[v], indexes[w]); + } + } + + if (lowLinks[v] == indexes[v]) + { + var scc = new List(); + BasicBlock w; + do + { + w = stack.Pop(); + onStack.Remove(w); + scc.Add(w); + } while (!w.Equals(v)); + sccList.Add(scc); + } + } + } + } +} diff --git a/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs b/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs new file mode 100644 index 0000000..fe02a35 --- /dev/null +++ b/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs @@ -0,0 +1,80 @@ +using dnlib.DotNet.Emit; +using dnlib.DotNet; +using System.Collections.Generic; +using System.Linq; +using Obfuz.Emit; + +namespace Obfuz.ObfusPasses +{ + public abstract class BasicBlockObfuscationPassBase : ObfuscationPassBase + { + protected abstract bool NeedObfuscateMethod(MethodDef method); + + public override void Process(ObfuscationPassContext ctx) + { + foreach (ModuleDef mod in ctx.toObfuscatedModules) + { + // ToArray to avoid modify list exception + foreach (TypeDef type in mod.GetTypes().ToArray()) + { + if (type.Name.StartsWith("$Obfuz$")) + { + continue; + } + // ToArray to avoid modify list exception + foreach (MethodDef method in type.Methods.ToArray()) + { + if (!method.HasBody || method.Name.StartsWith("$Obfuz$") || !NeedObfuscateMethod(method)) + { + continue; + } + // TODO if isGeneratedBy Obfuscator, continue + ObfuscateData(method); + } + } + } + } + + + protected abstract bool TryObfuscateInstruction(MethodDef callingMethod, Instruction inst, BasicBlock block, int instructionIndex, + List outputInstructions, List totalFinalInstructions); + + private void ObfuscateData(MethodDef method) + { + BasicBlockCollection bbc = new BasicBlockCollection(method); + + IList instructions = method.Body.Instructions; + + var outputInstructions = new List(); + var totalFinalInstructions = new List(); + for (int i = 0; i < instructions.Count; i++) + { + Instruction inst = instructions[i]; + BasicBlock block = bbc.GetBasicBlockByInstruction(inst); + outputInstructions.Clear(); + if (TryObfuscateInstruction(method, inst, block, i, outputInstructions, totalFinalInstructions)) + { + // current instruction may be the target of control flow instruction, so we can't remove it directly. + // we replace it with nop now, then remove it in CleanUpInstructionPass + inst.OpCode = outputInstructions[0].OpCode; + inst.Operand = outputInstructions[0].Operand; + totalFinalInstructions.Add(inst); + for (int k = 1; k < outputInstructions.Count; k++) + { + totalFinalInstructions.Add(outputInstructions[k]); + } + } + else + { + totalFinalInstructions.Add(inst); + } + } + + instructions.Clear(); + foreach (var obInst in totalFinalInstructions) + { + instructions.Add(obInst); + } + } + } +} diff --git a/Editor/ObfusPasses/ConstObfus/ConstObfusPass.cs b/Editor/ObfusPasses/ConstObfus/ConstObfusPass.cs index 92c2c74..9a91ac8 100644 --- a/Editor/ObfusPasses/ConstObfus/ConstObfusPass.cs +++ b/Editor/ObfusPasses/ConstObfus/ConstObfusPass.cs @@ -1,5 +1,6 @@ using dnlib.DotNet; using dnlib.DotNet.Emit; +using Obfuz.Emit; using Obfuz.ObfusPasses.ConstObfus.Policies; using Obfuz.Settings; using System; @@ -13,7 +14,7 @@ using UnityEngine.Assertions; namespace Obfuz.ObfusPasses.ConstObfus { - public class ConstObfusPass : InstructionObfuscationPassBase + public class ConstObfusPass : BasicBlockObfuscationPassBase { private readonly string _configFile; private IObfuscationPolicy _dataObfuscatorPolicy; @@ -40,10 +41,10 @@ namespace Obfuz.ObfusPasses.ConstObfus return _dataObfuscatorPolicy.NeedObfuscateMethod(method); } - protected override bool TryObfuscateInstruction(MethodDef method, Instruction inst, IList instructions, int instructionIndex, + protected override bool TryObfuscateInstruction(MethodDef method, Instruction inst, BasicBlock block, int instructionIndex, List outputInstructions, List totalFinalInstructions) { - bool currentInLoop = false; + bool currentInLoop = block.inLoop; ConstCachePolicy constCachePolicy = _dataObfuscatorPolicy.GetMethodConstCachePolicy(method); switch (inst.OpCode.OperandType) { @@ -127,7 +128,7 @@ namespace Obfuz.ObfusPasses.ConstObfus { if (((IMethod)inst.Operand).FullName == "System.Void System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(System.Array,System.RuntimeFieldHandle)") { - Instruction prevInst = instructions[instructionIndex - 1]; + Instruction prevInst = block.instructions[instructionIndex - 1]; if (prevInst.OpCode.Code == Code.Ldtoken) { IField rvaField = (IField)prevInst.Operand;