List <ILNode> ConvertToAst(List <ByteCode> body) { List <ILNode> ast = new List <ILNode>(); // Convert stack-based IL code to ILAst tree foreach (ByteCode byteCode in body) { ILRange ilRange = new ILRange() { From = byteCode.Offset, To = byteCode.EndOffset }; if (byteCode.StackBefore == null) { // Unreachable code continue; } ILExpression expr = new ILExpression(byteCode.Code, byteCode.Operand); expr.ILRanges.Add(ilRange); if (byteCode.Prefixes != null && byteCode.Prefixes.Length > 0) { ILExpressionPrefix[] prefixes = new ILExpressionPrefix[byteCode.Prefixes.Length]; for (int i = 0; i < prefixes.Length; i++) { prefixes[i] = new ILExpressionPrefix((ILCode)byteCode.Prefixes[i].OpCode.Code, byteCode.Prefixes[i].Operand); } expr.Prefixes = prefixes; } // Label for this instruction if (byteCode.Label != null) { ast.Add(byteCode.Label); } // Reference arguments using temporary variables int popCount = byteCode.PopCount ?? byteCode.StackBefore.Length; for (int i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) { StackSlot slot = byteCode.StackBefore[i]; expr.Arguments.Add(new ILExpression(ILCode.Ldloc, slot.LoadFrom)); } // Store the result to temporary variable(s) if needed if (byteCode.StoreTo == null || byteCode.StoreTo.Count == 0) { ast.Add(expr); } else if (byteCode.StoreTo.Count == 1) { ast.Add(new ILExpression(ILCode.Stloc, byteCode.StoreTo[0], expr)); } else { ILVariable tmpVar = new ILVariable() { Name = "expr_" + byteCode.Offset.ToString("X2"), IsGenerated = true }; ast.Add(new ILExpression(ILCode.Stloc, tmpVar, expr)); foreach (ILVariable storeTo in byteCode.StoreTo.AsEnumerable().Reverse()) { ast.Add(new ILExpression(ILCode.Stloc, storeTo, new ILExpression(ILCode.Ldloc, tmpVar))); } } } return(ast); }
void HandleAwait(List <ILNode> newBody, out ILVariable awaiterVar, out FieldDefinition awaiterField, out int targetStateID) { // Handle the instructions prior to the exit out of the method to detect what is being awaited. // (analyses the last instructions in newBody and removes the analyzed instructions from newBody) if (doFinallyBodies != null) { // stloc(<>t__doFinallyBodies, ldc.i4(0)) ILExpression dfbInitExpr; if (!newBody.LastOrDefault().MatchStloc(doFinallyBodies, out dfbInitExpr)) { throw new SymbolicAnalysisFailedException(); } int val; if (!(dfbInitExpr.Match(ILCode.Ldc_I4, out val) && val == 0)) { throw new SymbolicAnalysisFailedException(); } newBody.RemoveAt(newBody.Count - 1); // remove doFinallyBodies assignment } // call(AsyncTaskMethodBuilder::AwaitUnsafeOnCompleted, ldflda(StateMachine::<>t__builder, ldloc(this)), ldloca(CS$0$0001), ldloc(this)) ILExpression callAwaitUnsafeOnCompleted = newBody.LastOrDefault() as ILExpression; newBody.RemoveAt(newBody.Count - 1); // remove AwaitUnsafeOnCompleted call if (callAwaitUnsafeOnCompleted == null || callAwaitUnsafeOnCompleted.Code != ILCode.Call) { throw new SymbolicAnalysisFailedException(); } string methodName = ((MethodReference)callAwaitUnsafeOnCompleted.Operand).Name; if (methodName != "AwaitUnsafeOnCompleted" && methodName != "AwaitOnCompleted") { throw new SymbolicAnalysisFailedException(); } if (callAwaitUnsafeOnCompleted.Arguments.Count != 3) { throw new SymbolicAnalysisFailedException(); } if (!callAwaitUnsafeOnCompleted.Arguments[1].Match(ILCode.Ldloca, out awaiterVar)) { throw new SymbolicAnalysisFailedException(); } // stfld(StateMachine::<>u__$awaiter6, ldloc(this), ldloc(CS$0$0001)) FieldReference awaiterFieldRef; ILExpression loadThis, loadAwaiterVar; if (!newBody.LastOrDefault().Match(ILCode.Stfld, out awaiterFieldRef, out loadThis, out loadAwaiterVar)) { throw new SymbolicAnalysisFailedException(); } newBody.RemoveAt(newBody.Count - 1); // remove awaiter field assignment awaiterField = awaiterFieldRef.ResolveWithinSameModule(); if (!(awaiterField != null && loadThis.MatchThis() && loadAwaiterVar.MatchLdloc(awaiterVar))) { throw new SymbolicAnalysisFailedException(); } // stfld(StateMachine::<>1__state, ldloc(this), ldc.i4(0)) if (!MatchStateAssignment(newBody.LastOrDefault(), out targetStateID)) { throw new SymbolicAnalysisFailedException(); } newBody.RemoveAt(newBody.Count - 1); // remove awaiter field assignment }
List <ILNode> ConvertToAst(List <ByteCode> body, HashSet <ExceptionHandler> ehs) { List <ILNode> ast = new List <ILNode>(); while (ehs.Any()) { ILTryCatchBlock tryCatchBlock = new ILTryCatchBlock(); // Find the first and widest scope int tryStart = ehs.Min(eh => eh.TryStart.Offset); int tryEnd = ehs.Where(eh => eh.TryStart.Offset == tryStart).Max(eh => eh.TryEnd.Offset); var handlers = ehs.Where(eh => eh.TryStart.Offset == tryStart && eh.TryEnd.Offset == tryEnd).OrderBy(eh => eh.TryStart.Offset).ToList(); // Remember that any part of the body migt have been removed due to unreachability // Cut all instructions up to the try block { int tryStartIdx = 0; while (tryStartIdx < body.Count && body[tryStartIdx].Offset < tryStart) { tryStartIdx++; } ast.AddRange(ConvertToAst(body.CutRange(0, tryStartIdx))); } // Cut the try block { HashSet <ExceptionHandler> nestedEHs = new HashSet <ExceptionHandler>(ehs.Where(eh => (tryStart <= eh.TryStart.Offset && eh.TryEnd.Offset < tryEnd) || (tryStart < eh.TryStart.Offset && eh.TryEnd.Offset <= tryEnd))); ehs.ExceptWith(nestedEHs); int tryEndIdx = 0; while (tryEndIdx < body.Count && body[tryEndIdx].Offset < tryEnd) { tryEndIdx++; } tryCatchBlock.TryBlock = new ILBlock(ConvertToAst(body.CutRange(0, tryEndIdx), nestedEHs)); } // Cut all handlers tryCatchBlock.CatchBlocks = new List <ILTryCatchBlock.CatchBlock>(); foreach (ExceptionHandler eh in handlers) { int handlerEndOffset = eh.HandlerEnd == null ? methodDef.Body.CodeSize : eh.HandlerEnd.Offset; int startIdx = 0; while (startIdx < body.Count && body[startIdx].Offset < eh.HandlerStart.Offset) { startIdx++; } int endIdx = 0; while (endIdx < body.Count && body[endIdx].Offset < handlerEndOffset) { endIdx++; } HashSet <ExceptionHandler> nestedEHs = new HashSet <ExceptionHandler>(ehs.Where(e => (eh.HandlerStart.Offset <= e.TryStart.Offset && e.TryEnd.Offset < handlerEndOffset) || (eh.HandlerStart.Offset < e.TryStart.Offset && e.TryEnd.Offset <= handlerEndOffset))); ehs.ExceptWith(nestedEHs); List <ILNode> handlerAst = ConvertToAst(body.CutRange(startIdx, endIdx - startIdx), nestedEHs); if (eh.HandlerType == ExceptionHandlerType.Catch) { ILTryCatchBlock.CatchBlock catchBlock = new ILTryCatchBlock.CatchBlock() { ExceptionType = eh.CatchType, Body = handlerAst }; // Handle the automatically pushed exception on the stack ByteCode ldexception = ldexceptions[eh]; if (ldexception.StoreTo == null || ldexception.StoreTo.Count == 0) { // Exception is not used catchBlock.ExceptionVariable = null; } else if (ldexception.StoreTo.Count == 1) { ILExpression first = catchBlock.Body[0] as ILExpression; if (first != null && first.Code == ILCode.Pop && first.Arguments[0].Code == ILCode.Ldloc && first.Arguments[0].Operand == ldexception.StoreTo[0]) { // The exception is just poped - optimize it all away; if (context.Settings.AlwaysGenerateExceptionVariableForCatchBlocks) { catchBlock.ExceptionVariable = new ILVariable() { Name = "ex_" + eh.HandlerStart.Offset.ToString("X2"), IsGenerated = true } } ; else { catchBlock.ExceptionVariable = null; } catchBlock.Body.RemoveAt(0); } else { catchBlock.ExceptionVariable = ldexception.StoreTo[0]; } } else { ILVariable exTemp = new ILVariable() { Name = "ex_" + eh.HandlerStart.Offset.ToString("X2"), IsGenerated = true }; catchBlock.ExceptionVariable = exTemp; foreach (ILVariable storeTo in ldexception.StoreTo) { catchBlock.Body.Insert(0, new ILExpression(ILCode.Stloc, storeTo, new ILExpression(ILCode.Ldloc, exTemp))); } } tryCatchBlock.CatchBlocks.Add(catchBlock); } else if (eh.HandlerType == ExceptionHandlerType.Finally) { tryCatchBlock.FinallyBlock = new ILBlock(handlerAst); } else if (eh.HandlerType == ExceptionHandlerType.Fault) { tryCatchBlock.FaultBlock = new ILBlock(handlerAst); } else { // TODO: ExceptionHandlerType.Filter } } ehs.ExceptWith(handlers); ast.Add(tryCatchBlock); } // Add whatever is left ast.AddRange(ConvertToAst(body)); return(ast); }
public readonly ILVariable LoadFrom; // Variable used for storage of the value public StackSlot(ByteCode[] definitions, ILVariable loadFrom) { this.Definitions = definitions; this.LoadFrom = loadFrom; }
List <ByteCode> StackAnalysis(MethodDefinition methodDef) { Dictionary <Instruction, ByteCode> instrToByteCode = new Dictionary <Instruction, ByteCode>(); // Create temporary structure for the stack analysis List <ByteCode> body = new List <ByteCode>(methodDef.Body.Instructions.Count); List <Instruction> prefixes = null; foreach (Instruction inst in methodDef.Body.Instructions) { if (inst.OpCode.OpCodeType == OpCodeType.Prefix) { if (prefixes == null) { prefixes = new List <Instruction>(1); } prefixes.Add(inst); continue; } ILCode code = (ILCode)inst.OpCode.Code; object operand = inst.Operand; ILCodeUtil.ExpandMacro(ref code, ref operand, methodDef.Body); ByteCode byteCode = new ByteCode() { Offset = inst.Offset, EndOffset = inst.Next != null ? inst.Next.Offset : methodDef.Body.CodeSize, Code = code, Operand = operand, PopCount = inst.GetPopDelta(methodDef), PushCount = inst.GetPushDelta() }; if (prefixes != null) { instrToByteCode[prefixes[0]] = byteCode; byteCode.Offset = prefixes[0].Offset; byteCode.Prefixes = prefixes.ToArray(); prefixes = null; } else { instrToByteCode[inst] = byteCode; } body.Add(byteCode); } for (int i = 0; i < body.Count - 1; i++) { body[i].Next = body[i + 1]; } Stack <ByteCode> agenda = new Stack <ByteCode>(); int varCount = methodDef.Body.Variables.Count; var exceptionHandlerStarts = new HashSet <ByteCode>(methodDef.Body.ExceptionHandlers.Select(eh => instrToByteCode[eh.HandlerStart])); // Add known states if (methodDef.Body.HasExceptionHandlers) { foreach (ExceptionHandler ex in methodDef.Body.ExceptionHandlers) { ByteCode handlerStart = instrToByteCode[ex.HandlerStart]; handlerStart.StackBefore = new StackSlot[0]; handlerStart.VariablesBefore = VariableSlot.MakeUknownState(varCount); if (ex.HandlerType == ExceptionHandlerType.Catch || ex.HandlerType == ExceptionHandlerType.Filter) { // Catch and Filter handlers start with the exeption on the stack ByteCode ldexception = new ByteCode() { Code = ILCode.Ldexception, Operand = ex.CatchType, PopCount = 0, PushCount = 1 }; ldexceptions[ex] = ldexception; handlerStart.StackBefore = new StackSlot[] { new StackSlot(new [] { ldexception }, null) }; } agenda.Push(handlerStart); if (ex.HandlerType == ExceptionHandlerType.Filter) { ByteCode filterStart = instrToByteCode[ex.FilterStart]; ByteCode ldexception = new ByteCode() { Code = ILCode.Ldexception, Operand = ex.CatchType, PopCount = 0, PushCount = 1 }; // TODO: ldexceptions[ex] = ldexception; filterStart.StackBefore = new StackSlot[] { new StackSlot(new [] { ldexception }, null) }; filterStart.VariablesBefore = VariableSlot.MakeUknownState(varCount); agenda.Push(filterStart); } } } body[0].StackBefore = new StackSlot[0]; body[0].VariablesBefore = VariableSlot.MakeUknownState(varCount); agenda.Push(body[0]); // Process agenda while (agenda.Count > 0) { ByteCode byteCode = agenda.Pop(); // Calculate new stack StackSlot[] newStack = StackSlot.ModifyStack(byteCode.StackBefore, byteCode.PopCount ?? byteCode.StackBefore.Length, byteCode.PushCount, byteCode); // Calculate new variable state VariableSlot[] newVariableState = VariableSlot.CloneVariableState(byteCode.VariablesBefore); if (byteCode.IsVariableDefinition) { newVariableState[((VariableReference)byteCode.Operand).Index] = new VariableSlot(new [] { byteCode }, false); } // After the leave, finally block might have touched the variables if (byteCode.Code == ILCode.Leave) { newVariableState = VariableSlot.MakeUknownState(varCount); } // Find all successors List <ByteCode> branchTargets = new List <ByteCode>(); if (!byteCode.Code.IsUnconditionalControlFlow()) { if (exceptionHandlerStarts.Contains(byteCode.Next)) { // Do not fall though down to exception handler // It is invalid IL as per ECMA-335 §12.4.2.8.1, but some obfuscators produce it } else { branchTargets.Add(byteCode.Next); } } if (byteCode.Operand is Instruction[]) { foreach (Instruction inst in (Instruction[])byteCode.Operand) { ByteCode target = instrToByteCode[inst]; branchTargets.Add(target); // The target of a branch must have label if (target.Label == null) { target.Label = new ILLabel() { Name = target.Name }; } } } else if (byteCode.Operand is Instruction) { ByteCode target = instrToByteCode[(Instruction)byteCode.Operand]; branchTargets.Add(target); // The target of a branch must have label if (target.Label == null) { target.Label = new ILLabel() { Name = target.Name }; } } // Apply the state to successors foreach (ByteCode branchTarget in branchTargets) { if (branchTarget.StackBefore == null && branchTarget.VariablesBefore == null) { if (branchTargets.Count == 1) { branchTarget.StackBefore = newStack; branchTarget.VariablesBefore = newVariableState; } else { // Do not share data for several bytecodes branchTarget.StackBefore = StackSlot.ModifyStack(newStack, 0, 0, null); branchTarget.VariablesBefore = VariableSlot.CloneVariableState(newVariableState); } agenda.Push(branchTarget); } else { if (branchTarget.StackBefore.Length != newStack.Length) { throw new Exception("Inconsistent stack size at " + byteCode.Name); } // Be careful not to change our new data - it might be reused for several branch targets. // In general, be careful that two bytecodes never share data structures. bool modified = false; // Merge stacks - modify the target for (int i = 0; i < newStack.Length; i++) { ByteCode[] oldDefs = branchTarget.StackBefore[i].Definitions; ByteCode[] newDefs = oldDefs.Union(newStack[i].Definitions); if (newDefs.Length > oldDefs.Length) { branchTarget.StackBefore[i] = new StackSlot(newDefs, null); modified = true; } } // Merge variables - modify the target for (int i = 0; i < newVariableState.Length; i++) { VariableSlot oldSlot = branchTarget.VariablesBefore[i]; VariableSlot newSlot = newVariableState[i]; if (!oldSlot.UnknownDefinition) { if (newSlot.UnknownDefinition) { branchTarget.VariablesBefore[i] = newSlot; modified = true; } else { ByteCode[] oldDefs = oldSlot.Definitions; ByteCode[] newDefs = oldDefs.Union(newSlot.Definitions); if (newDefs.Length > oldDefs.Length) { branchTarget.VariablesBefore[i] = new VariableSlot(newDefs, false); modified = true; } } } } if (modified) { agenda.Push(branchTarget); } } } } // Occasionally the compilers or obfuscators generate unreachable code (which might be intentonally invalid) // I belive it is safe to just remove it body.RemoveAll(b => b.StackBefore == null); // Genertate temporary variables to replace stack foreach (ByteCode byteCode in body) { int argIdx = 0; int popCount = byteCode.PopCount ?? byteCode.StackBefore.Length; for (int i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) { ILVariable tmpVar = new ILVariable() { Name = string.Format("arg_{0:X2}_{1}", byteCode.Offset, argIdx), IsGenerated = true }; byteCode.StackBefore[i] = new StackSlot(byteCode.StackBefore[i].Definitions, tmpVar); foreach (ByteCode pushedBy in byteCode.StackBefore[i].Definitions) { if (pushedBy.StoreTo == null) { pushedBy.StoreTo = new List <ILVariable>(1); } pushedBy.StoreTo.Add(tmpVar); } argIdx++; } } // Try to use single temporary variable insted of several if possilbe (especially useful for dup) // This has to be done after all temporary variables are assigned so we know about all loads foreach (ByteCode byteCode in body) { if (byteCode.StoreTo != null && byteCode.StoreTo.Count > 1) { var locVars = byteCode.StoreTo; // For each of the variables, find the location where it is loaded - there should be preciesly one var loadedBy = locVars.Select(locVar => body.SelectMany(bc => bc.StackBefore).Single(s => s.LoadFrom == locVar)).ToList(); // We now know that all the variables have a single load, // Let's make sure that they have also a single store - us if (loadedBy.All(slot => slot.Definitions.Length == 1 && slot.Definitions[0] == byteCode)) { // Great - we can reduce everything into single variable ILVariable tmpVar = new ILVariable() { Name = string.Format("expr_{0:X2}", byteCode.Offset), IsGenerated = true }; byteCode.StoreTo = new List <ILVariable>() { tmpVar }; foreach (ByteCode bc in body) { for (int i = 0; i < bc.StackBefore.Length; i++) { // Is it one of the variable to be merged? if (locVars.Contains(bc.StackBefore[i].LoadFrom)) { // Replace with the new temp variable bc.StackBefore[i] = new StackSlot(bc.StackBefore[i].Definitions, tmpVar); } } } } } } // Split and convert the normal local variables ConvertLocalVariables(body); // Convert branch targets to labels foreach (ByteCode byteCode in body) { if (byteCode.Operand is Instruction[]) { List <ILLabel> newOperand = new List <ILLabel>(); foreach (Instruction target in (Instruction[])byteCode.Operand) { newOperand.Add(instrToByteCode[target].Label); } byteCode.Operand = newOperand.ToArray(); } else if (byteCode.Operand is Instruction) { byteCode.Operand = instrToByteCode[(Instruction)byteCode.Operand].Label; } } // Convert parameters to ILVariables ConvertParameters(body); return(body); }
public static bool MatchStloc(this ILNode node, ILVariable expectedVar, out ILExpression expr) { ILVariable v; return(node.Match(ILCode.Stloc, out v, out expr) && v == expectedVar); }
public static bool MatchLdloca(this ILNode node, ILVariable expectedVar) { ILVariable v; return(node.Match(ILCode.Ldloca, out v) && v == expectedVar); }