Esempio n. 1
0
        /// <summary>
        /// Update the state of the given branch targets (of the given bytecode).
        /// Add modified branch targets to the given agenda.
        /// </summary>
        private static void UpdateBranchTarget(ByteCode byteCode, ByteCode branchTarget, bool canShareNewState, StackSlot[] newStack, VariableSlot[] newVariableState, Stack <ByteCode> agenda)
        {
            if ((branchTarget.StackBefore == null) && (branchTarget.VariablesBefore == null))
            {
                // Branch target has not been processed at all
                if (canShareNewState)
                {
                    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);
                return;
            }

            // If we get here, the branch target has been processed before.
            // See the the stack size is the same and merge the state where needed.
            if ((branchTarget.StackBefore == null) || (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.
            var modified = false;

            // Merge stacks - modify the target
            for (var i = 0; i < newStack.Length; i++)
            {
                var oldDefs = branchTarget.StackBefore[i].Definitions;
                var 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 (var i = 0; i < newVariableState.Length; i++)
            {
                var oldSlot = branchTarget.VariablesBefore[i];
                var newSlot = newVariableState[i];
                if (oldSlot.UnknownDefinition)
                {
                    continue;
                }

                if (newSlot.UnknownDefinition)
                {
                    branchTarget.VariablesBefore[i] = newSlot;
                    modified = true;
                }
                else
                {
                    var oldDefs = oldSlot.Definitions;
                    var newDefs = oldDefs.Union(newSlot.Definitions);
                    if (newDefs.Length > oldDefs.Length)
                    {
                        branchTarget.VariablesBefore[i] = new VariableSlot(newDefs, false);
                        modified = true;
                    }
                }
            }

            if (modified)
            {
                agenda.Push(branchTarget);
            }
        }
Esempio n. 2
0
        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 List <StackSlot>();
                    handlerStart.VariablesBefore = VariableSlot.MakeFullState(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.Add(new StackSlot(ldexception));
                    }
                    agenda.Push(handlerStart);

                    if (ex.HandlerType == ExceptionHandlerType.Filter)
                    {
                        ByteCode filterStart = instrToByteCode[ex.FilterStart];
                        filterStart.StackBefore     = new List <StackSlot>();
                        filterStart.VariablesBefore = VariableSlot.MakeFullState(varCount);
                        ByteCode ldexception = new ByteCode()
                        {
                            Code      = ILCode.Ldexception,
                            Operand   = ex.CatchType,
                            PopCount  = 0,
                            PushCount = 1
                        };
                        // TODO: ldexceptions[ex] = ldexception;
                        filterStart.StackBefore.Add(new StackSlot(ldexception));
                        agenda.Push(filterStart);
                    }
                }
            }

            body[0].StackBefore     = new List <StackSlot>();
            body[0].VariablesBefore = VariableSlot.MakeEmptyState(varCount);
            agenda.Push(body[0]);

            // Process agenda
            while (agenda.Count > 0)
            {
                ByteCode byteCode = agenda.Pop();

                // Calculate new stack
                List <StackSlot> newStack = StackSlot.CloneStack(byteCode.StackBefore, byteCode.PopCount);
                for (int i = 0; i < byteCode.PushCount; i++)
                {
                    newStack.Add(new StackSlot(byteCode));
                }

                // Calculate new variable state
                VariableSlot[] newVariableState = VariableSlot.CloneVariableState(byteCode.VariablesBefore);
                if (byteCode.Code == ILCode.Stloc)
                {
                    int varIndex = ((VariableReference)byteCode.Operand).Index;
                    newVariableState[varIndex] = new VariableSlot(byteCode);
                }

                // After the leave, finally block might have touched the variables
                if (byteCode.Code == ILCode.Leave)
                {
                    newVariableState = VariableSlot.MakeFullState(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.CloneStack(newStack, 0);
                            branchTarget.VariablesBefore = VariableSlot.CloneVariableState(newVariableState);
                        }
                        agenda.Push(branchTarget);
                    }
                    else
                    {
                        if (branchTarget.StackBefore.Count != newStack.Count)
                        {
                            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.Count; i++)
                        {
                            ByteCode[] oldPushedBy = branchTarget.StackBefore[i].PushedBy;
                            ByteCode[] newPushedBy = oldPushedBy.Union(newStack[i].PushedBy);
                            if (newPushedBy.Length > oldPushedBy.Length)
                            {
                                branchTarget.StackBefore[i] = new StackSlot(newPushedBy, 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];
                            // All can not be unioned further
                            if (!oldSlot.StoredByAll)
                            {
                                if (newSlot.StoredByAll)
                                {
                                    branchTarget.VariablesBefore[i] = newSlot;
                                    modified = true;
                                }
                                else
                                {
                                    ByteCode[] oldStoredBy = oldSlot.StoredBy;
                                    ByteCode[] newStoredBy = oldStoredBy.Union(newSlot.StoredBy);
                                    if (newStoredBy.Length > oldStoredBy.Length)
                                    {
                                        branchTarget.VariablesBefore[i] = new VariableSlot(newStoredBy, false);
                                        modified = true;
                                    }
                                }
                            }
                        }

                        if (modified)
                        {
                            agenda.Push(branchTarget);
                        }
                    }
                }
            }

            // Occasionally the compilers or obfuscators generate unreachable code (which migt 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.Count;
                for (int i = byteCode.StackBefore.Count - popCount; i < byteCode.StackBefore.Count; 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].PushedBy, tmpVar);
                    foreach (ByteCode pushedBy in byteCode.StackBefore[i].PushedBy)
                    {
                        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.PushedBy.Length == 1 && slot.PushedBy[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.Count; 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].PushedBy, 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);
        }
Esempio n. 3
0
        /// <summary>
        /// Analyse the instructions in the method code and convert them to a ByteCode list.
        /// </summary>
        private List <ByteCode> StackAnalysis()
        {
            // Map from instruction to bytecode.
            var instrToByteCode = new Dictionary <Instruction, ByteCode>();

            // Create temporary structure for the stack analysis
            var body = new List <ByteCode>(codeAttr.Code.Length);

            foreach (var inst in codeAttr.Instructions)
            {
                var first = true;
                foreach (var byteCode in Create(inst, module))
                {
                    if (first)
                    {
                        instrToByteCode[inst] = byteCode;
                        first = false;
                    }
                    body.Add(byteCode);
                }
            }
            // Connect bytecodes to the next
            for (var i = 0; i < body.Count - 1; i++)
            {
                body[i].Next = body[i + 1];
            }

            var agenda         = new Stack <ByteCode>();
            var localVarsCount = codeAttr.MaxLocals; // methodDef.GetParametersLocalVariableSlots();

            // All bytecodes that are the start of an exception handler.
            var exceptionHandlerStarts = new HashSet <ByteCode>(validExceptionHandlers.Select(eh => instrToByteCode[eh.Handler]));
            var exceptionTryStarts     = new DefaultDictionary <ByteCode, List <ExceptionHandler> >(x => new List <ExceptionHandler>());

            foreach (var eh in validExceptionHandlers)
            {
                exceptionTryStarts[instrToByteCode[eh.Start]].Add(eh);
            }

            // Add known states
            var ldExceptionByHandlerPc = new Dictionary <int, ByteCode>();

            foreach (var ex in validExceptionHandlers)
            {
                ByteCode ldexception;
                if (ldExceptionByHandlerPc.TryGetValue(ex.HandlerPc, out ldexception))
                {
                    // Re-use ldexception (that we've created for the same handler PC for another exception handler before)
                }
                else
                {
                    // No handler at handlerPc processed before, do that now
                    var handlerStart = instrToByteCode[ex.Handler];
                    handlerStart.StackBefore     = new StackSlot[0];
                    handlerStart.VariablesBefore = VariableSlot.MakeUnknownState(localVarsCount);
                    {
                        // Catch handlers start with the exeption on the stack
                        ldexception = new ByteCode {
                            Code            = AstCode.Ldexception,
                            Operand         = ex.CatchType,
                            PopCount        = 0,
                            PushCount       = 1,
                            Offset          = handlerStart.Offset,
                            Next            = handlerStart,
                            StackBefore     = new StackSlot[0],
                            VariablesBefore = handlerStart.VariablesBefore
                        };
                        handlerStart.StackBefore = new[] { new StackSlot(new[] { ldexception }, null) };
                    }
                    ldExceptionByHandlerPc[ex.HandlerPc] = ldexception;
                    agenda.Push(handlerStart);
                }
                // Store ldexception by exception handler
                ldexceptions[ex] = ldexception;
            }

            // At the start of the method the stack is empty and all local variables have unknown state
            body[0].StackBefore     = new StackSlot[0];
            body[0].VariablesBefore = VariableSlot.MakeUnknownState(localVarsCount);
            agenda.Push(body[0]);

            // Process agenda
            while (agenda.Count > 0)
            {
                var byteCode = agenda.Pop();

                // Calculate new stack
                var newStack = byteCode.CreateNewStack();

                // Calculate new variable state
                var newVariableState = VariableSlot.CloneVariableState(byteCode.VariablesBefore);
                if (byteCode.IsVariableDefinition)
                {
                    newVariableState[((LocalVariableReference)byteCode.Operand).Index] = new VariableSlot(new[] { byteCode }, false);
                }

                // After the leave, finally block might have touched the variables
                if (byteCode.Code == AstCode.Leave)
                {
                    newVariableState = VariableSlot.MakeUnknownState(localVarsCount);
                }

                // Find all successors
                var branchTargets = FindBranchTargets(byteCode, instrToByteCode, exceptionHandlerStarts);

                // Apply the state to successors
                foreach (var branchTarget in branchTargets)
                {
                    UpdateBranchTarget(byteCode, branchTarget, (branchTargets.Count == 1), newStack, newVariableState, agenda);
                }

                // Apply state to handlers when a branch target is the start of an exception handler
                foreach (var branchTarget in branchTargets.Where(exceptionTryStarts.ContainsKey))
                {
                    // The branch target is the start of a try block.
                    UpdateTryStartBranchTarget(branchTarget, exceptionTryStarts[branchTarget], instrToByteCode, newVariableState, agenda);
                }
            }

            // Occasionally the compilers or obfuscators generate unreachable code (which might be intentonally invalid)
            // I believe it is safe to just remove it
            body.RemoveAll(b => b.StackBefore == null);

            // Generate temporary variables to replace stack
            foreach (var byteCode in body)
            {
                var argIdx   = 0;
                var popCount = byteCode.PopCount ?? byteCode.StackBefore.Length;
                for (var i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++)
                {
                    var tmpVar = new AstGeneratedVariable(string.Format("arg_{0:X2}_{1}", byteCode.Offset, argIdx), null);
                    byteCode.StackBefore[i] = new StackSlot(byteCode.StackBefore[i].Definitions, tmpVar);
                    foreach (var pushedBy in byteCode.StackBefore[i].Definitions)
                    {
                        if (pushedBy.StoreTo == null)
                        {
                            pushedBy.StoreTo = new List <AstVariable>(1);
                        }
                        pushedBy.StoreTo.Add(tmpVar);
                    }
                    argIdx++;
                }
            }

            // Try to use single temporary variable insted of several if possible (especially useful for dup)
            // This has to be done after all temporary variables are assigned so we know about all loads
            foreach (var byteCode in body)
            {
                if ((byteCode.StoreTo == null) || (byteCode.StoreTo.Count <= 1))
                {
                    continue;
                }

                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
                    var tmpVar = new AstGeneratedVariable(string.Format("expr_{0:X2}", byteCode.Offset), locVars.Select(x => x.OriginalName).FirstOrDefault());
                    byteCode.StoreTo = new List <AstVariable> {
                        tmpVar
                    };
                    foreach (var bc in body)
                    {
                        for (var 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 (var byteCode in body)
            {
                if (byteCode.Operand is Instruction[])
                {
                    byteCode.Operand = (from target in (Instruction[])byteCode.Operand select instrToByteCode[target].Label(true)).ToArray();
                }
                else if (byteCode.Operand is Instruction)
                {
                    byteCode.Operand = instrToByteCode[(Instruction)byteCode.Operand].Label(true);
                }
                else if (byteCode.Operand is LookupSwitchData)
                {
                    var data = (LookupSwitchData)byteCode.Operand;
                    byteCode.Operand = data.Pairs.Select(x => new AstLabelKeyPair(instrToByteCode[x.Target].Label(true), x.Match)).ToArray();
                }
            }

            // Convert parameters to ILVariables
            ConvertParameters(body);

            // Replace temporary opcodes
            foreach (var byteCode in body)
            {
                switch (byteCode.Code)
                {
                case AstCode.Dup_x1:
                case AstCode.Dup_x2:
                case AstCode.Dup2:
                case AstCode.Dup2_x1:
                case AstCode.Dup2_x2:
                case AstCode.Swap:
                    byteCode.Code = AstCode.Dup;
                    break;

                case AstCode.Pop2:
                    byteCode.Code = AstCode.Pop;
                    break;
                }
            }

            return(body);
        }