public void Clear() { _indices.Clear(); _opGroups.Clear(); _instructions.Clear(); JumpExits.Clear(); SwitchExits.Clear(); }
public void RemoveRange(int index, int count) { if ((index + count) <= _instructions.Count) { for (int i = 0; i < count; i++) { ASInstruction instruction = _instructions[index]; _indices.Remove(instruction); _instructions.RemoveAt(index); List <ASInstruction> group = _opGroups[instruction.OP]; if (group.Count == 1) { _opGroups.Remove(instruction.OP); } else { group.Remove(instruction); } // TODO: Recalibrate switch exits. Jumper entry = GetJumperEntry(instruction); if (entry != null) { if (index != _instructions.Count) { ASInstruction exit = _instructions[index]; JumpExits[entry] = exit; } else { JumpExits[entry] = null; } } if (Jumper.IsValid(instruction.OP)) { JumpExits.Remove((Jumper)instruction); } else if (instruction.OP == OPCode.LookUpSwitch) { SwitchExits.Remove((LookUpSwitchIns)instruction); } } for (int i = index; i < _indices.Count; i++) { ASInstruction toPull = _instructions[i]; _indices[toPull] -= count; } } }
public void InsertRange(int index, IEnumerable <ASInstruction> collection) { if (index <= _instructions.Count && collection.Any()) { var deadJumps = new Stack <Jumper>(JumpExits .Where(je => je.Value == null) .Select(je => je.Key)); int count = _instructions.Count; if (index == _instructions.Count) { _instructions.AddRange(collection); } else { _instructions.InsertRange(index, collection); } int collectionCount = (_instructions.Count - count); while (deadJumps.Count > 0) { JumpExits[deadJumps.Pop()] = collection.First(); } // We'll try to keep the dictionary organized, even though it really doesn't matter. // Looks nice though. for (int i = ((index + collectionCount) - 1); i >= index; i--) { ASInstruction instruction = _instructions[i]; _indices.Add(instruction, i); List <ASInstruction> instructions = null; if (!_opGroups.TryGetValue(instruction.OP, out instructions)) { instructions = new List <ASInstruction>(); _opGroups.Add(instruction.OP, instructions); } instructions.Add(instruction); } for (int i = (index + collectionCount); i < _instructions.Count; i++) { ASInstruction toPush = _instructions[i]; _indices[toPush] += collectionCount; } } }
public override void WriteTo(FlashWriter output) { var marks = new Dictionary <ASInstruction, long>(); var sharedExits = new Dictionary <ASInstruction, List <ASInstruction> >(); var forwardCaseExits = new Dictionary <ASInstruction, Tuple <LookUpSwitchIns, int> >(); foreach (ASInstruction instruction in _instructions) { long previousPosition = output.Position; marks.Add(instruction, previousPosition); instruction.WriteTo(output); List <ASInstruction> jumpers = null; if (sharedExits.TryGetValue(instruction, out jumpers)) { foreach (ASInstruction jumper in jumpers) { long position = marks[jumper]; var fixedOffset = (uint)(previousPosition - (position + 4)); ((Jumper)jumper).Offset = fixedOffset; Rewrite(output, jumper, position); } sharedExits.Remove(instruction); } Tuple <LookUpSwitchIns, int> switchIdentity = null; if (forwardCaseExits.TryGetValue(instruction, out switchIdentity)) { int caseIndex = switchIdentity.Item2; LookUpSwitchIns lookUpSwitch = switchIdentity.Item1; long position = marks[lookUpSwitch]; var fixedOffset = (uint)(previousPosition - position); if (caseIndex == lookUpSwitch.CaseOffsets.Count) { lookUpSwitch.DefaultOffset = fixedOffset; } else { lookUpSwitch.CaseOffsets[caseIndex] = fixedOffset; } Rewrite(output, lookUpSwitch, position); } if (instruction.OP == OPCode.LookUpSwitch) { bool requiresRewrite = false; var lookUpSwitch = (LookUpSwitchIns)instruction; ASInstruction[] cases = SwitchExits[lookUpSwitch]; for (int i = 0; i < cases.Length; i++) { ASInstruction exit = cases[i]; if (exit.OP != OPCode.Label) { forwardCaseExits.Add(exit, Tuple.Create(lookUpSwitch, i)); } else { requiresRewrite = true; long exitPosition = marks[exit]; long jumpCount = (previousPosition - (exitPosition + 1)); uint fixedOffset = (uint)(uint.MaxValue - jumpCount); if (i == (cases.Length - 1)) { lookUpSwitch.DefaultOffset = fixedOffset; } else { lookUpSwitch.CaseOffsets[i] = fixedOffset; } } } if (requiresRewrite) { Rewrite(output, lookUpSwitch, previousPosition); } } else if (Jumper.IsValid(instruction.OP)) { var jumper = (Jumper)instruction; if (jumper.Offset == 0) { continue; } ASInstruction exit = null; if (!JumpExits.TryGetValue(jumper, out exit)) { // An exit for this jump instruction could not be found, perhaps its' offset exceeds the index limit? continue; } else if (exit.OP != OPCode.Label || !marks.ContainsKey(exit)) // Forward jumps can have a label for an exit, as long as it is located ahead. { jumpers = null; if (!sharedExits.TryGetValue(exit, out jumpers)) { jumpers = new List <ASInstruction>(); sharedExits.Add(exit, jumpers); } jumpers.Add(jumper); } else // Backward jumps must always have a label for an exit, and must be behind this instruction. { long exitPosition = marks[exit]; long jumpCount = (output.Position - (exitPosition + 1)); var fixedOffset = (uint)(uint.MaxValue - jumpCount); jumper.Offset = fixedOffset; Rewrite(output, jumper, previousPosition); } } } }
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); }
private void LoadInstructions() { var caseIndices = new Dictionary <long, int>(); var marks = new Dictionary <long, ASInstruction>(); var sharedExits = new Dictionary <long, List <Jumper> >(); var forwardCaseExits = new Dictionary <long, LookUpSwitchIns>(); using (var input = new FlashReader(_body.Code)) { while (input.IsDataAvailable) { long previousPosition = input.Position; var instruction = ASInstruction.Create(_abc, input); marks[previousPosition] = instruction; _indices.Add(instruction, _indices.Count); _instructions.Add(instruction); List <ASInstruction> instructions = null; if (!_opGroups.TryGetValue(instruction.OP, out instructions)) { instructions = new List <ASInstruction>(); _opGroups.Add(instruction.OP, instructions); } instructions.Add(instruction); List <Jumper> jumpers = null; if (sharedExits.TryGetValue(previousPosition, out jumpers)) { // This is an exit position for a jump instruction, or more. foreach (Jumper jumper in jumpers) { JumpExits.Add(jumper, instruction); } sharedExits.Remove(previousPosition); } LookUpSwitchIns lookUpSwitch = null; if (forwardCaseExits.TryGetValue(previousPosition, out lookUpSwitch)) { int index = caseIndices[previousPosition]; SwitchExits[lookUpSwitch][index] = instruction; caseIndices.Remove(previousPosition); forwardCaseExits.Remove(previousPosition); } if (instruction.OP == OPCode.LookUpSwitch) { lookUpSwitch = (LookUpSwitchIns)instruction; var offsets = new List <uint>(lookUpSwitch.CaseOffsets); offsets.Add(lookUpSwitch.DefaultOffset); var cases = new ASInstruction[offsets.Count]; for (int i = 0; i < offsets.Count; i++) { ASInstruction exit = null; long exitPosition = (previousPosition + offsets[i]); if (exitPosition <= input.Length) { caseIndices.Add(exitPosition, i); forwardCaseExits.Add(exitPosition, lookUpSwitch); } else { cases[i] = exit; exit = marks[(exitPosition - uint.MaxValue) - 1]; } } SwitchExits.Add(lookUpSwitch, cases); } else if (Jumper.IsValid(instruction.OP)) { var jumper = (Jumper)instruction; if (jumper.Offset == 0) { continue; } long exitPosition = (input.Position + jumper.Offset); if (exitPosition == input.Length) { // Jump exit does not exist at this (non-existent)index, do not look for exit. continue; } else if (exitPosition < input.Length) // Forward jump. { jumpers = null; if (!sharedExits.TryGetValue(exitPosition, out jumpers)) { jumpers = new List <Jumper>(); sharedExits.Add(exitPosition, jumpers); } jumpers.Add(jumper); } else // Backwards jump. { var label = (LabelIns)marks[(exitPosition - uint.MaxValue) - 1]; JumpExits.Add(jumper, label); } } } } }
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(); }