private int GetFinalJumpCount(ASMachine machine, Jumper jumper, List <ASInstruction> cleaned, Dictionary <int, List <ASInstruction> > localReferences, Stack <ASInstruction> valuePushers) { var magicCount = 0; var locals = new List <Local>(); var pushers = new List <ASInstruction>(); bool?isJumping = jumper.RunCondition(machine); // Get the instructions that pushed the values the jump instruction used. int popCount = jumper.GetPopCount(); for (int i = 0; i < popCount; i++) { ASInstruction pusher = valuePushers.Pop(); if (!pushers.Contains(pusher)) { pushers.Add(pusher); // Get the instructions that were pushed by a GetLocal/N. // These are used to determine whether the jump should be kept, since a local register could change within the jump body. if (Local.IsValid(pusher.OP)) { locals.Add((Local)pusher); } else if (Primitive.IsValid(pusher.OP) || pusher.OP == OPCode.Dup) { magicCount++; } } } // Output is not known, keep the instruction. if (isJumping == null) { cleaned.Add(jumper); return(0); } if (pushers.Count != (magicCount + locals.Count)) { // One or more push instructions are wildcards, they have a 'history' of being modified. // Keep this jump instruction, result could change. cleaned.Add(jumper); return(0); } // Gather information about the jump instruction, and it's 'block' of instructions that are being jumped over(if 'isJumping = true'). ASInstruction exit = null; bool isBackwardsJump = false; IEnumerable <ASInstruction> block = null; if (!JumpExits.TryGetValue(jumper, out exit)) { // This jump instruction should not be 'cleaned', keep it. cleaned.Add(jumper); return(0); } if (IsBackwardsJump(jumper)) { isBackwardsJump = true; block = cleaned .Skip(cleaned.IndexOf(exit) + 1) .TakeWhile(i => i != jumper); } else { block = (jumper.Offset > 0 ? GetJumpBlock(jumper) : null); } if (isJumping == true && block != null) { if (isBackwardsJump) { // Check if any of the locals used by the jump instruction is being set within the body. // If the anwser is yes, removing the jump instruction is a bad idea, since the output of the condition could change. foreach (Local local in locals) { foreach (ASInstruction instruction in block) { if (!Local.IsValid(instruction.OP)) { continue; } if (Local.IsGetLocal(instruction.OP)) { continue; } var bodyLocal = (Local)instruction; if (bodyLocal.Register == local.Register) { // Do not remove the jump instruction, condition result may change. cleaned.Add(jumper); return(0); } } } } foreach (KeyValuePair <Jumper, ASInstruction> jumpExit in JumpExits) { if (jumpExit.Key == jumper) { continue; } bool hasEntry = block.Contains(jumpExit.Key); // Does a jump instruction begin somewhere in the block? bool hasExit = block.Contains(jumpExit.Value); // Does the jump instruction end somewhere in the block? ASInstruction afterLast = _instructions[_indices[block.Last()] + 1]; bool isExitAfterLast = (jumpExit.Value == afterLast); // Does the exit of the jump that is in the block come after the final instruction of the current block? if (hasEntry && !hasExit && !isExitAfterLast || hasExit && !hasEntry) { // Keep the jump instruction, since it will corrupt the other jump instruction that is using it. cleaned.Add(jumper); return(0); } } foreach (KeyValuePair <LookUpSwitchIns, ASInstruction[]> switchExit in SwitchExits) { foreach (ASInstruction caseExit in switchExit.Value) { if (block.Contains(caseExit)) { cleaned.Add(jumper); return(0); } } } } foreach (Local local in locals) { List <ASInstruction> references = localReferences[local.Register]; references.Remove(local); } // Remove the instructions that pushed values for the jump instruction that is to be removed. foreach (ASInstruction pusher in pushers) { cleaned.Remove(pusher); } if (isJumping == false || isBackwardsJump) { block = null; } JumpExits.Remove(jumper); return(block?.Count() ?? 0); }
public void Deobfuscate() { var machine = new ASMachine(this, _body.LocalCount); var cleaned = new List <ASInstruction>(_instructions.Count); var valuePushers = new Stack <ASInstruction>(_body.MaxStack); var localReferences = new Dictionary <int, List <ASInstruction> >(); var swappedValues = new Dictionary <ASInstruction, ASInstruction[]>(); KeyValuePair <Jumper, ASInstruction>[] jumpExits = JumpExits.ToArray(); KeyValuePair <LookUpSwitchIns, ASInstruction[]>[] switchExits = SwitchExits.ToArray(); for (int i = 0; i < _instructions.Count; i++) { ASInstruction instruction = _instructions[i]; if (Jumper.IsValid(instruction.OP)) { i += GetFinalJumpCount(machine, (Jumper)instruction, cleaned, localReferences, valuePushers); } else { instruction.Execute(machine); #region Arithmetic Optimization if (Computation.IsValid(instruction.OP)) { object result = machine.Values.Pop(); ASInstruction rightPusher = valuePushers.Pop(); ASInstruction leftPusher = valuePushers.Pop(); if (!Local.IsValid(leftPusher.OP) && !Local.IsValid(rightPusher.OP) && result != null) { // Constant values found, push result instead of having it do the calculation everytime. cleaned.Remove(leftPusher); cleaned.Remove(rightPusher); instruction = Primitive.Create(_abc, result); foreach (KeyValuePair <Jumper, ASInstruction> jumpExit in jumpExits) { if (leftPusher == jumpExit.Value) { JumpExits[jumpExit.Key] = instruction; // Do not break, another jump instruction can share the same exit. } } foreach (KeyValuePair <LookUpSwitchIns, ASInstruction[]> switchExit in switchExits) { ASInstruction[] cases = switchExit.Value; for (int j = 0; j < cases.Length; j++) { ASInstruction exit = cases[j]; if (leftPusher == exit) { cases[j] = instruction; } } } instruction.Execute(machine); } else { // Do not attempt to optimize when a local is being use, as these values can change. valuePushers.Push(leftPusher); valuePushers.Push(rightPusher); machine.Values.Push(result); } } #endregion cleaned.Add(instruction); ASInstruction[] swaps = null; List <ASInstruction> references = null; if (Local.IsValid(instruction.OP)) { var local = (Local)instruction; if (!localReferences.TryGetValue(local.Register, out references)) { references = new List <ASInstruction>(); localReferences[local.Register] = references; } references.Add(instruction); } else if (instruction.OP == OPCode.Swap) { swaps = new ASInstruction[2]; swappedValues[instruction] = swaps; } int popCount = instruction.GetPopCount(); for (int j = 0; j < popCount; j++) { ASInstruction pusher = valuePushers.Pop(); references?.Add(pusher); if (swaps != null) { swaps[j] = pusher; } } int pushCount = instruction.GetPushCount(); for (int j = 0; j < pushCount; j++) { valuePushers.Push(instruction); } } } // Remove dead locals. foreach (KeyValuePair <int, List <ASInstruction> > local in localReferences) { if (local.Key == 0) { continue; // Scope } if (local.Key <= (_body.Method.Parameters.Count)) { continue; // Register == Param #(Non-zero based) } List <ASInstruction> references = localReferences[local.Key]; bool isNeeded = false; foreach (ASInstruction reference in references) { // This checks if the local is being referenced by something else that retreives the value. if (Local.IsValid(reference.OP) && !Local.IsSetLocal(reference.OP)) { isNeeded = true; break; } } if (!isNeeded) { foreach (ASInstruction reference in references) { if (reference.OP == OPCode.Swap) { ASInstruction[] swaps = swappedValues[reference]; foreach (ASInstruction swap in swaps) { cleaned.Remove(swap); } } cleaned.Remove(reference); } } } jumpExits = JumpExits.ToArray(); // Global property could have been updated. foreach (KeyValuePair <Jumper, ASInstruction> jumpExit in jumpExits) { if (IndexOf(jumpExit.Key) > IndexOf(jumpExit.Value)) { // This is not a forward jump instruction, since the exit appears before the jump. continue; } // True if it still has the jump instruction, but the instruction // needed to determine the final instruction to jump is not present. if (cleaned.Contains(jumpExit.Key) && !cleaned.Contains(jumpExit.Value)) { // Start at the index of the instruction that should come after the final instruction to jump. for (int j = (_indices[jumpExit.Value] + 1); j < _instructions.Count; j++) { ASInstruction afterEnd = _instructions[j]; int exitIndex = cleaned.IndexOf(afterEnd); if (exitIndex != -1) // Does this instruction exist in the cleaned output?; Otherwise, get instruction that comes after it. { JumpExits[jumpExit.Key] = cleaned[exitIndex]; break; } } } } _instructions.Clear(); _instructions.AddRange(cleaned); Recalibrate(); }