From 8162aa6c3e8f1c7ee175489f6ea6cd8ddfbe87d6 Mon Sep 17 00:00:00 2001 From: "honfika@gmail.com" Date: Thu, 14 Jan 2016 16:38:10 +0100 Subject: [PATCH] linked lavm2instuction list for faster deobfuscation (not ready yet) --- .../avm2/fastavm2/AVM2InstructionItem.java | 156 +++++ .../flash/abc/avm2/fastavm2/FastAVM2List.java | 661 ++++++++++++++++++ .../avm2/fastavm2/FastAVM2ListIterator.java | 116 +++ 3 files changed, 933 insertions(+) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/fastavm2/AVM2InstructionItem.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/fastavm2/FastAVM2List.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/fastavm2/FastAVM2ListIterator.java diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/fastavm2/AVM2InstructionItem.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/fastavm2/AVM2InstructionItem.java new file mode 100644 index 000000000..b4d84d68d --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/fastavm2/AVM2InstructionItem.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.abc.avm2.fastavm2; + +import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * + * @author JPEXS + */ +public class AVM2InstructionItem { + + public AVM2Instruction ins; + + public AVM2InstructionItem prev; + + public AVM2InstructionItem next; + + private AVM2InstructionItem jumpTarget; + + public Set jumpsHere; + + public Set lastInsOf; + + private List containerLastInstructions; + + // 1 means reachable, 2 means reachable and processed + int reachable; + + public boolean excluded; + + public AVM2InstructionItem(AVM2Instruction ins) { + this.ins = ins; + } + + public boolean isJumpTarget() { + return jumpsHere != null && !jumpsHere.isEmpty(); + } + + public int jumpsHereSize() { + return jumpsHere == null ? 0 : jumpsHere.size(); + } + + public boolean isContainerLastInstruction() { + return lastInsOf != null && !lastInsOf.isEmpty(); + } + + public void removeJumpTarget() { + if (jumpTarget == null) { + return; + } + + if (jumpTarget.jumpsHere != null) { + jumpTarget.jumpsHere.remove(this); + } + + jumpTarget = null; + } + + public AVM2InstructionItem getJumpTarget() { + return jumpTarget; + } + + public AVM2Instruction getJumpTargetInstruction() { + return jumpTarget == null ? null : jumpTarget.ins; + } + + public void setJumpTarget(AVM2InstructionItem item) { + removeJumpTarget(); + + if (item == null) { + return; + } + + if (item.jumpsHere == null) { + item.jumpsHere = new HashSet<>(); + } + + item.jumpsHere.add(this); + jumpTarget = item; + } + + public List getContainerLastInstructions() { + return containerLastInstructions; + } + + public void removeContainerLastInstructions() { + if (containerLastInstructions == null) { + return; + } + + for (AVM2InstructionItem lastIns : containerLastInstructions) { + if (lastIns.lastInsOf != null) { + lastIns.lastInsOf.remove(this); + } + } + + containerLastInstructions = null; + } + + public void replaceContainerLastInstruction(AVM2InstructionItem oldItem, AVM2InstructionItem newItem) { + if (containerLastInstructions == null) { + return; + } + + for (int i = 0; i < containerLastInstructions.size(); i++) { + if (containerLastInstructions.get(i) == oldItem) { + containerLastInstructions.set(i, newItem); + if (oldItem.lastInsOf != null) { + oldItem.lastInsOf.remove(this); + } + + newItem.ensureLastInstructionOf().add(this); + } + } + } + + public void setContainerLastInstructions(List lastInstructions) { + removeContainerLastInstructions(); + + for (AVM2InstructionItem lastIns : lastInstructions) { + lastIns.ensureLastInstructionOf().add(this); + } + + containerLastInstructions = lastInstructions; + } + + private Set ensureLastInstructionOf() { + if (lastInsOf == null) { + lastInsOf = new HashSet<>(); + } + + return lastInsOf; + } + + public boolean isExcluded() { + return excluded; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/fastavm2/FastAVM2List.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/fastavm2/FastAVM2List.java new file mode 100644 index 000000000..aee31695b --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/fastavm2/FastAVM2List.java @@ -0,0 +1,661 @@ +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.abc.avm2.fastavm2; + +import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; +import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; +import com.jpexs.decompiler.flash.abc.avm2.instructions.IfTypeIns; +import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.JumpIns; +import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.LookupSwitchIns; +import com.jpexs.decompiler.flash.abc.types.ABCException; +import com.jpexs.decompiler.flash.abc.types.MethodBody; +import com.jpexs.decompiler.graph.GraphSourceItemContainer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * + * @author JPEXS + */ +public class FastAVM2List implements Collection { + + private int size; + + private AVM2InstructionItem firstItem; + + private final Map actionItemMap; + + private final Set actionItemSet; + + public FastAVM2List(MethodBody body) { + // exceptions todo + AVM2Code avm2code = body.getCode(); + List code = avm2code.code; + actionItemMap = new HashMap<>(code.size()); + actionItemSet = new HashSet<>(code.size()); + for (AVM2Instruction action : code) { + insertItemAfter(null, action); + } + + size = code.size(); +// getContainerLastActions(avm2code, actionItemMap); + getJumps(avm2code, actionItemMap); + } + + public final AVM2InstructionItem insertItemBefore(AVM2InstructionItem item, AVM2Instruction action) { + AVM2InstructionItem newItem = new AVM2InstructionItem(action); + return insertItemBefore(item, newItem); + } + + public final AVM2InstructionItem insertItemAfter(AVM2InstructionItem item, AVM2Instruction action) { + AVM2InstructionItem newItem = new AVM2InstructionItem(action); + return insertItemAfter(item, newItem); + } + + public final AVM2InstructionItem insertItemBefore(AVM2InstructionItem item, AVM2InstructionItem newItem) { + insertItemAfter(item.prev, newItem); + if (item == firstItem) { + firstItem = newItem; + } + + return newItem; + } + + public final AVM2InstructionItem insertItemAfter(AVM2InstructionItem item, AVM2InstructionItem newItem) { + if (item == null && firstItem == null) { + firstItem = newItem; + newItem.next = newItem; + newItem.prev = newItem; + } else { + if (item == null) { + // insert to the end + item = firstItem.prev; + } + + AVM2InstructionItem oldNext = item.next; + newItem.prev = item; + newItem.next = oldNext; + item.next = newItem; + oldNext.prev = newItem; + } + + size++; + actionItemMap.put(newItem.ins, newItem); + actionItemSet.add(newItem); + return newItem; + } + + public AVM2InstructionItem removeItem(AVM2InstructionItem item) { + AVM2InstructionItem next = null; + if (item == firstItem) { + if (item.next == item) { + // there is only 1 item + firstItem = null; + } else { + next = item.next; + firstItem = next; + next.prev = item.prev; + item.prev.next = next; + } + } else { + next = item.next; + item.prev.next = next; + next.prev = item.prev; + } + + size--; + actionItemMap.remove(item.ins); + actionItemSet.remove(item); + + item.removeJumpTarget(); + item.removeContainerLastInstructions(); + + if (item.jumpsHere != null) { + for (AVM2InstructionItem item1 : new ArrayList<>(item.jumpsHere)) { + item1.setJumpTarget(item.next); + } + } + + if (item.lastInsOf != null) { + for (AVM2InstructionItem item1 : new ArrayList<>(item.lastInsOf)) { + item1.replaceContainerLastInstruction(item, item.prev); + } + } + + return next; + } + + public void removeItem(int index, int count) { + FastAVM2ListIterator iterator = new FastAVM2ListIterator(this, index); + for (int i = 0; i < count; i++) { + iterator.next(); + iterator.remove(); + } + } + + public AVM2InstructionItem get(int index) { + FastAVM2ListIterator iterator = new FastAVM2ListIterator(this, index); + return iterator.next(); + } + + public void replaceJumpTargets(AVM2InstructionItem target, AVM2InstructionItem newTarget) { + if (target.jumpsHere != null) { + for (AVM2InstructionItem item : new ArrayList<>(target.jumpsHere)) { + item.setJumpTarget(newTarget); + } + } + } + +// private void getContainerLastActions(AVM2Code actions, Map actionItemMap) { +// AVM2InstructionItem item = firstItem; +// if (item == null) { +// return; +// } +// +// do { +// AVM2Instruction action = item.ins; +// if (action instanceof GraphSourceItemContainer) { +// item.setContainerLastInstructions(getContainerLastActions(actions, action, actionItemMap)); +// } +// +// item = item.next; +// } while (item != firstItem); +// } +// private List getContainerLastActions(AVM2Code actions, AVM2Instruction action, Map actionItemMap) { +// GraphSourceItemContainer container = (GraphSourceItemContainer) action; +// List sizes = container.getContainerSizes(); +// long endAddress = action.getAddress() + container.getHeaderSize(); +// List lasts = new ArrayList<>(sizes.size()); +// for (long size : sizes) { +// endAddress += size; +// long lastActionAddress = getNearAddress(actions.code, endAddress - 1, false); +// AVM2Instruction lastAction = null; +// if (lastActionAddress != -1) { +// lastAction = actions.getByAddress(lastActionAddress); +// } +// +// if (lastAction != null) { +// lasts.add(actionItemMap.get(lastAction)); +// } else { +// lasts.add(null); +// } +// } +// return lasts; +// } + private long getNearAddress(List instructions, long address, boolean next) { + int min = 0; + int max = instructions.size() - 1; + + while (max >= min) { + int mid = (min + max) / 2; + long midValue = instructions.get(mid).getAddress(); + if (midValue == address) { + return address; + } else if (midValue < address) { + min = mid + 1; + } else { + max = mid - 1; + } + } + + return next + ? (min < instructions.size() ? instructions.get(min).getAddress() : -1) + : (max >= 0 ? instructions.get(max).getAddress() : -1); + } + + private void getJumps(AVM2Code actions, Map actionItemMap) { + AVM2InstructionItem item = firstItem; + if (item == null) { + return; + } + + do { + AVM2Instruction action = item.ins; + long target = -1; + if (action.definition instanceof IfTypeIns) { + target = action.getTargetAddress(); + } else if (action.definition instanceof LookupSwitchIns) { + // todo + } + if (target >= 0) { + AVM2Instruction targetAction = actions.adr2ins(target); + item.setJumpTarget(actionItemMap.get(targetAction)); + } + + item = item.next; + } while (item != firstItem); + } + + private void updateActionAddressesAndLengths() { + AVM2InstructionItem item = firstItem; + if (item == null) { + return; + } + + long offset = item.ins.getAddress(); + do { + AVM2Instruction action = item.ins; + action.setAddress(offset); + offset += action.getBytesLength(); + item = item.next; + } while (item != firstItem); + } + + private void updateJumps() { + AVM2InstructionItem item = firstItem; + if (item == null) { + return; + } + + long endAddress = item.prev.ins.getAddress(); + do { + AVM2Instruction action = item.ins; + if (action.definition instanceof IfTypeIns) { + AVM2Instruction target = item.getJumpTargetInstruction(); + long offset; + if (target != null) { + offset = target.getAddress() - action.getAddress() - action.getBytesLength(); + } else { + offset = endAddress - action.getAddress() - action.getBytesLength(); + } + action.setTargetOffset((int) offset); + } else if (action.definition instanceof LookupSwitchIns) { + // todo + } + + item = item.next; + } while (item != firstItem); + } + + private void updateContainerSizes() { + AVM2InstructionItem item = firstItem; + if (item == null) { + return; + } + + do { + AVM2Instruction action = item.ins; + if (action instanceof GraphSourceItemContainer) { + GraphSourceItemContainer container = (GraphSourceItemContainer) action; + List lastActions = item.getContainerLastInstructions(); + long startAddress = action.getAddress() + container.getHeaderSize(); + for (int j = 0; j < lastActions.size(); j++) { + AVM2Instruction lastAction = lastActions.get(j).ins; + int length = (int) (lastAction.getAddress() + lastAction.getBytesLength() - startAddress); + container.setContainerSize(j, length); + startAddress += length; + } + } + + item = item.next; + } while (item != firstItem); + } + + public AVM2InstructionItem getContainer(AVM2InstructionItem item) { + while (!(item.ins instanceof GraphSourceItemContainer) && item != firstItem) { + item = item.prev; + } + + if (item.ins instanceof GraphSourceItemContainer) { + return item; + } + + return null; + } + + public void removeZeroJumps() { + AVM2InstructionItem item = firstItem; + if (item == null) { + return; + } + + do { + AVM2Instruction ins = item.ins; + if (ins.definition instanceof JumpIns) { + if (item.getJumpTarget() == item.next && item.getJumpTarget() != firstItem) { + item = removeItem(item); + continue; + } + } + + item = item.next; + } while (item != firstItem); + } + + public void removeUnreachableActions() { + AVM2InstructionItem item = firstItem; + if (item == null) { + return; + } + + updateReachableFlags(null, null); + + do { + if (item.reachable == 0) { + item = removeItem(item); + continue; + } + + item = item.next; + } while (item != firstItem); + } + + public void removeIncludedActions() { + AVM2InstructionItem item = firstItem; + if (item == null) { + return; + } + + do { + if (!item.excluded) { + item = removeItem(item); + continue; + } + + item = item.next; + } while (item != firstItem); + } + + public int getUnreachableActionCount(AVM2InstructionItem jump, AVM2InstructionItem jumpTarget) { + AVM2InstructionItem item = firstItem; + if (item == null) { + return 0; + } + + updateReachableFlags(jump, jumpTarget); + jump.reachable = 0; + + int count = 0; + do { + if (item.reachable == 0) { + count++; + } + + item = item.next; + } while (item != firstItem); + + return count; + } + + private void clearReachableFlags() { + AVM2InstructionItem item = firstItem; + if (item == null) { + return; + } + + do { + item.reachable = 0; + item = item.next; + } while (item != firstItem); + } + + public void setExcludedFlags(boolean value) { + AVM2InstructionItem item = firstItem; + if (item == null) { + return; + } + + do { + item.excluded = value; + item = item.next; + } while (item != firstItem); + } + + private void updateReachableFlags(AVM2InstructionItem jump, AVM2InstructionItem jumpTarget) { + if (firstItem == null) { + return; + } + + clearReachableFlags(); + + firstItem.reachable = 1; + AVM2InstructionItem firstItem2 = firstItem; + boolean modified = true; + while (modified) { + modified = false; + AVM2InstructionItem item = firstItem2; + do { + AVM2InstructionItem next = item.next; + //AVM2InstructionItem alternativeNext = null; + AVM2Instruction action = item.ins; + if (item.reachable == 1) { + item.reachable = 2; + modified = true; + + if (item == firstItem2) { + firstItem2 = next; + } + + if (item == jump) { + if (jumpTarget.reachable == 0) { + jumpTarget.reachable = 1; + //alternativeNext = jumpTarget; + } + } else { + + if (!action.isExit() && !(action.definition instanceof JumpIns)) { + if (next.reachable == 0) { + next.reachable = 1; + } + } + + if (action instanceof GraphSourceItemContainer) { + for (AVM2InstructionItem lastActionItem : item.getContainerLastInstructions()) { + if (lastActionItem != null && lastActionItem.next != null && lastActionItem.next.reachable == 0) { + lastActionItem.next.reachable = 1; + //alternativeNext = lastActionItem.next; + } + } + } + + AVM2InstructionItem target = item.getJumpTarget(); + if (target != null) { + if (target.reachable == 0) { + target.reachable = 1; + //alternativeNext = target; + } + } + } + } + + //item = alternativeNext == null || next.reachable == 1 ? next : alternativeNext; + item = next; + } while (item != firstItem); + } + } + + public void updateActions(MethodBody body) { + AVM2Code result = new AVM2Code(size); + AVM2InstructionItem item = firstItem; + if (item == null) { + body.setCode(result); + body.exceptions = new ABCException[0]; + return; + } + + List resultList = result.code; + do { + resultList.add(item.ins); + item = item.next; + } while (item != firstItem); + + updateActionAddressesAndLengths(); + updateJumps(); + updateContainerSizes(); + } + + public AVM2InstructionItem first() { + return firstItem; + } + + public AVM2InstructionItem last() { + return firstItem == null ? null : firstItem.prev; + } + + public void toMethodBody(MethodBody body) { + updateActions(body); + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public boolean contains(Object o) { + if (o instanceof AVM2InstructionItem) { + return actionItemSet.contains(o); + } else if (o instanceof AVM2Instruction) { + return actionItemMap.containsKey((AVM2Instruction) o); + } + + return false; + } + + @Override + public FastAVM2ListIterator iterator() { + return new FastAVM2ListIterator(this); + } + + @Override + public Object[] toArray() { + Object[] result = new Object[size]; + + AVM2InstructionItem item = firstItem; + if (item == null) { + return result; + } + + int i = 0; + do { + result[i] = item.ins; + item = item.next; + i++; + } while (item != firstItem); + return null; + } + + @Override + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + if (a.length != size) { + a = (T[]) new AVM2InstructionItem[size]; + } + + AVM2InstructionItem item = firstItem; + if (item == null) { + return a; + } + + int i = 0; + do { + a[i] = (T) item; + item = item.next; + i++; + } while (item != firstItem); + return null; + } + + @Override + public boolean add(AVM2InstructionItem e) { + insertItemAfter(null, e); + return true; + } + + @Override + public boolean remove(Object o) { + AVM2InstructionItem item = null; + if (o instanceof AVM2InstructionItem) { + item = (AVM2InstructionItem) o; + } else if (o instanceof AVM2Instruction) { + item = actionItemMap.get((AVM2Instruction) o); + } + + if (item == null) { + return false; + } + + removeItem(item); + return true; + } + + @Override + public boolean containsAll(Collection c) { + for (Object c1 : c) { + if (!contains(c1)) { + return false; + } + } + + return true; + } + + @Override + public boolean addAll(Collection c) { + for (AVM2InstructionItem c1 : c) { + insertItemAfter(null, c1); + } + + return true; + } + + @Override + public boolean removeAll(Collection c) { + boolean result = false; + for (Object c1 : c) { + result |= remove(c1); + } + + return result; + } + + @Override + public boolean retainAll(Collection c) { + AVM2InstructionItem item = firstItem; + if (item == null) { + return false; + } + + boolean modified = false; + do { + if (!c.contains(item)) { + item = removeItem(item); + modified = true; + continue; + } + + item = item.next; + } while (item != firstItem); + return modified; + } + + @Override + public void clear() { + firstItem = null; + size = 0; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/fastavm2/FastAVM2ListIterator.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/fastavm2/FastAVM2ListIterator.java new file mode 100644 index 000000000..e040b119a --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/fastavm2/FastAVM2ListIterator.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.abc.avm2.fastavm2; + +import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; +import java.util.Iterator; + +/** + * + * @author JPEXS + */ +public final class FastAVM2ListIterator implements Iterator { + + private AVM2InstructionItem item; + + private final FastAVM2List list; + + private boolean started = false; + + FastAVM2ListIterator(FastAVM2List list) { + item = list.first(); + this.list = list; + } + + FastAVM2ListIterator(FastAVM2List list, int index) { + item = list.first(); + this.list = list; + for (int i = 0; i < index; i++) { + if (!hasNext()) { + throw new Error("Invalid index"); + } + + next(); + } + } + + @Override + public boolean hasNext() { + return item != null && (!started || item != list.first()); + } + + @Override + public AVM2InstructionItem next() { + AVM2InstructionItem result = item; + item = item.next; + started = true; + /*if (!list.contains(result)) { + throw new Error(); + }*/ + + return result; + } + + public AVM2InstructionItem prev() { + item = item.prev; + if (item == list.first()) { + started = false; + } + + /*if (!list.contains(item)) { + throw new Error(); + }*/ + return item; + } + + public void setCurrent(AVM2InstructionItem item) { + this.item = item; + if (item == list.first()) { + started = false; + } + } + + @Override + public void remove() { + item = list.removeItem(item.prev); + } + + public void add(AVM2Instruction ins) { + item = list.insertItemAfter(item.prev, ins).next; + } + + public void add(AVM2InstructionItem insItem) { + item = list.insertItemAfter(item.prev, insItem).next; + } + + public void addBefore(AVM2InstructionItem insItem) { + list.insertItemBefore(item.prev, insItem); + } + + public AVM2InstructionItem peek(int index) { + AVM2InstructionItem item = this.item; + for (int i = 0; i < index; i++) { + if (item == null) { + break; + } + + item = item.next; + } + + return item; + } +}