mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-06-09 23:25:05 +00:00
930 lines
39 KiB
Java
930 lines
39 KiB
Java
/*
|
|
* Copyright (C) 2010-2014 JPEXS
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package com.jpexs.decompiler.flash.action;
|
|
|
|
import com.jpexs.decompiler.flash.AppStrings;
|
|
import com.jpexs.decompiler.flash.DisassemblyListener;
|
|
import com.jpexs.decompiler.flash.SWFInputStream;
|
|
import com.jpexs.decompiler.flash.action.model.ConstantPool;
|
|
import com.jpexs.decompiler.flash.action.model.DirectValueActionItem;
|
|
import com.jpexs.decompiler.flash.action.special.ActionEnd;
|
|
import com.jpexs.decompiler.flash.action.special.ActionNop;
|
|
import com.jpexs.decompiler.flash.action.special.ActionStore;
|
|
import com.jpexs.decompiler.flash.action.swf4.ActionEquals;
|
|
import com.jpexs.decompiler.flash.action.swf4.ActionIf;
|
|
import com.jpexs.decompiler.flash.action.swf4.ActionJump;
|
|
import com.jpexs.decompiler.flash.action.swf4.ActionPush;
|
|
import com.jpexs.decompiler.flash.action.swf5.ActionConstantPool;
|
|
import com.jpexs.decompiler.flash.action.swf5.ActionDefineFunction;
|
|
import com.jpexs.decompiler.flash.action.swf5.ActionEquals2;
|
|
import com.jpexs.decompiler.flash.action.swf5.ActionStoreRegister;
|
|
import com.jpexs.decompiler.flash.action.swf7.ActionDefineFunction2;
|
|
import com.jpexs.decompiler.flash.configuration.Configuration;
|
|
import com.jpexs.decompiler.flash.ecma.EcmaScript;
|
|
import com.jpexs.decompiler.flash.ecma.Null;
|
|
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
|
|
import com.jpexs.decompiler.graph.Graph;
|
|
import com.jpexs.decompiler.graph.GraphSourceItem;
|
|
import com.jpexs.decompiler.graph.GraphSourceItemContainer;
|
|
import com.jpexs.decompiler.graph.GraphTargetItem;
|
|
import com.jpexs.decompiler.graph.NotCompileTimeItem;
|
|
import com.jpexs.decompiler.graph.TranslateException;
|
|
import com.jpexs.decompiler.graph.model.LocalData;
|
|
import com.jpexs.helpers.CancellableWorker;
|
|
import com.jpexs.helpers.Helper;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Queue;
|
|
import java.util.Scanner;
|
|
import java.util.Stack;
|
|
import java.util.TreeMap;
|
|
import java.util.concurrent.Callable;
|
|
import java.util.concurrent.ExecutionException;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.TimeoutException;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
/**
|
|
* Class for reading data from SWF file
|
|
*
|
|
* @author JPEXS
|
|
*/
|
|
public class ActionListReader {
|
|
|
|
private static final Logger logger = Logger.getLogger(SWFInputStream.class.getName());
|
|
|
|
/**
|
|
* Reads list of actions from the stream. Reading ends with
|
|
* ActionEndFlag(=0) or end of the stream.
|
|
*
|
|
* @param listeners
|
|
* @param sis
|
|
* @param version
|
|
* @param ip
|
|
* @param endIp
|
|
* @param path
|
|
* @return List of actions
|
|
* @throws IOException
|
|
* @throws java.lang.InterruptedException
|
|
* @throws java.util.concurrent.TimeoutException
|
|
*/
|
|
public static List<Action> readActionListTimeout(final List<DisassemblyListener> listeners, final SWFInputStream sis, final int version, final int ip, final int endIp, final String path) throws IOException, InterruptedException, TimeoutException {
|
|
try {
|
|
return CancellableWorker.call(new Callable<List<Action>>() {
|
|
|
|
@Override
|
|
public List<Action> call() throws IOException, InterruptedException {
|
|
return readActionList(listeners, sis, version, ip, endIp, path);
|
|
}
|
|
}, Configuration.decompilationTimeoutSingleMethod.get(), TimeUnit.SECONDS);
|
|
} catch (ExecutionException ex) {
|
|
Throwable cause = ex.getCause();
|
|
if (cause instanceof InterruptedException) {
|
|
throw (InterruptedException) cause;
|
|
} else if (cause instanceof InterruptedException) {
|
|
throw (IOException) cause;
|
|
} else {
|
|
Logger.getLogger(ActionListReader.class.getName()).log(Level.SEVERE, null, ex);
|
|
}
|
|
}
|
|
return new ArrayList<>();
|
|
}
|
|
|
|
/**
|
|
* Reads list of actions from the stream. Reading ends with
|
|
* ActionEndFlag(=0) or end of the stream.
|
|
*
|
|
* @param listeners
|
|
* @param sis
|
|
* @param version
|
|
* @param ip
|
|
* @param endIp
|
|
* @param path
|
|
* @return List of actions
|
|
* @throws IOException
|
|
* @throws java.lang.InterruptedException
|
|
*/
|
|
private static List<Action> readActionList(List<DisassemblyListener> listeners, SWFInputStream sis, int version, int ip, int endIp, String path) throws IOException, InterruptedException {
|
|
ConstantPool cpool = new ConstantPool();
|
|
|
|
// Map of the actions. Use TreeMap to sort the keys in ascending order
|
|
Map<Long, Action> actionMap = new TreeMap<>();
|
|
Map<Long,Long> nextOffsets = new HashMap<>();
|
|
Action entryAction = readActionListAtPos(listeners, cpool,
|
|
sis, actionMap, nextOffsets,
|
|
ip, ip, endIp, version, path, false, new ArrayList<Long>());
|
|
|
|
List<Action> actions = new ArrayList<>();
|
|
if (actionMap.isEmpty()) {
|
|
return actions;
|
|
}
|
|
|
|
Map<Action, List<Action>> containerLastActions = new HashMap<>();
|
|
List<Long> addresses = new ArrayList<>(actionMap.keySet());
|
|
getContainerLastActions(actionMap, addresses, containerLastActions);
|
|
|
|
// jump to the entry action when it is diffrent from the first action in the map
|
|
long index = addresses.get(0);
|
|
if (index != -1 && entryAction != actionMap.get(index)) {
|
|
ActionJump jump = new ActionJump(0);
|
|
int size = jump.getTotalActionLength();
|
|
jump.setJumpOffset((int) (entryAction.getAddress() - size));
|
|
actions.add(jump);
|
|
}
|
|
|
|
// remove nulls
|
|
index = getNearAddress(addresses, index, true);
|
|
while (index > -1) {
|
|
Action action = actionMap.get(index);
|
|
long nextOffset = nextOffsets.get(index);
|
|
long nextIndex = getNearAddress(addresses, index + 1, true);
|
|
actions.add(action);
|
|
if (nextIndex != -1 && nextOffset != nextIndex) {
|
|
if (!action.isExit() && !(action instanceof ActionJump)) {
|
|
ActionJump jump = new ActionJump(0);
|
|
jump.setAddress(action.getAddress(), version);
|
|
int size = jump.getTotalActionLength();
|
|
jump.setJumpOffset((int) (nextOffset - action.getAddress() - size));
|
|
actions.add(jump);
|
|
}
|
|
}
|
|
index = nextIndex;
|
|
}
|
|
|
|
// Map for storing the targers of the "jump" actions
|
|
// "jump" action can be ActionIf, ActionJump and any ActionStore
|
|
Map<Action, Action> jumps = new HashMap<>();
|
|
getJumps(actions, jumps);
|
|
|
|
long endAddress = updateAddresses(actions, 0, version);
|
|
|
|
// add end action
|
|
Action lastAction = actions.get(actions.size() - 1);
|
|
Action aEnd = new ActionEnd();
|
|
if (!(lastAction instanceof ActionEnd)) {
|
|
aEnd.setAddress(endAddress, version);
|
|
actions.add(aEnd);
|
|
} else {
|
|
endAddress -= aEnd.getTotalActionLength();
|
|
}
|
|
|
|
updateJumps(actions, jumps, containerLastActions, endAddress, version);
|
|
updateActionStores(actions, jumps);
|
|
updateContainerSizes(actions, containerLastActions);
|
|
updateActionLengths(actions, version);
|
|
|
|
if (Configuration.autoDeobfuscate.get()) {
|
|
try {
|
|
actions = deobfuscateActionList(listeners, actions, version, 0, path);
|
|
updateActionLengths(actions, version);
|
|
removeZeroJumps(actions, version);
|
|
} catch (OutOfMemoryError | StackOverflowError | TranslateException ex) {
|
|
// keep orignal (not deobfuscated) actions
|
|
Logger.getLogger(ActionListReader.class.getName()).log(Level.SEVERE, null, ex);
|
|
}
|
|
}
|
|
|
|
return actions;
|
|
}
|
|
|
|
/**
|
|
* Reads list of actions from the stream. Reading ends with
|
|
* ActionEndFlag(=0) or end of the stream.
|
|
*
|
|
* @param listeners
|
|
* @param actions
|
|
* @param version
|
|
* @param ip
|
|
* @param path
|
|
* @return List of actions
|
|
* @throws IOException
|
|
* @throws java.lang.InterruptedException
|
|
*/
|
|
private static List<Action> deobfuscateActionList(List<DisassemblyListener> listeners, List<Action> actions, int version, int ip, String path) throws IOException, InterruptedException {
|
|
if (actions.isEmpty()) {
|
|
return actions;
|
|
}
|
|
|
|
Action lastAction = actions.get(actions.size() - 1);
|
|
int endIp = (int) lastAction.getAddress();
|
|
|
|
List<Action> retdups = new ArrayList<>(endIp);
|
|
for (int i = 0; i < endIp; i++) {
|
|
Action a = new ActionNop();
|
|
a.setAddress(i, version);
|
|
retdups.add(a);
|
|
}
|
|
List<Action> actionMap = new ArrayList<>(endIp);
|
|
for (int i = 0; i <= endIp; i++) {
|
|
actionMap.add(null);
|
|
}
|
|
for (Action a : actions) {
|
|
actionMap.set((int) a.getAddress(), a);
|
|
}
|
|
|
|
ConstantPool cpool = new ConstantPool();
|
|
|
|
Stack<GraphTargetItem> stack = new Stack<>();
|
|
|
|
ActionLocalData localData = new ActionLocalData();
|
|
|
|
int maxRecursionLevel = 0;
|
|
for (int i = 0; i < actions.size(); i++) {
|
|
Action a = actions.get(i);
|
|
if (a instanceof ActionIf || a instanceof GraphSourceItemContainer) {
|
|
maxRecursionLevel++;
|
|
}
|
|
if (a instanceof ActionIf) {
|
|
ActionIf aif = (ActionIf) a;
|
|
aif.ignoreUsed = false;
|
|
aif.jumpUsed = false;
|
|
}
|
|
}
|
|
|
|
deobfustaceActionListAtPosRecursive(listeners, new ArrayList<GraphTargetItem>(), new HashMap<Long, List<GraphSourceItemContainer>>(), localData, stack, cpool, actionMap, ip, retdups, ip, endIp, path, new HashMap<Integer, Integer>(), false, new HashMap<Integer, HashMap<String, GraphTargetItem>>(), version, 0, maxRecursionLevel);
|
|
|
|
List<Action> ret = new ArrayList<>();
|
|
Action last = null;
|
|
for (Action a : retdups) {
|
|
if (a != last) {
|
|
ret.add(a);
|
|
}
|
|
last = a;
|
|
}
|
|
ret = Action.removeNops(0, ret, version, path);
|
|
List<Action> reta = new ArrayList<>();
|
|
for (Object o : ret) {
|
|
if (o instanceof Action) {
|
|
reta.add((Action) o);
|
|
}
|
|
}
|
|
return reta;
|
|
}
|
|
|
|
private static long getNearAddress(List<Long> addresses, long address, boolean next) {
|
|
int min = 0;
|
|
int max = addresses.size() - 1;
|
|
|
|
while (max >= min) {
|
|
int mid = (min + max) / 2;
|
|
long midValue = addresses.get(mid);
|
|
if(midValue == address)
|
|
return address;
|
|
else if (midValue < address)
|
|
min = mid + 1;
|
|
else
|
|
max = mid - 1;
|
|
}
|
|
|
|
return next
|
|
? (min < addresses.size() ? addresses.get(min) : -1)
|
|
: (max > 0 ? addresses.get(max) : -1);
|
|
}
|
|
|
|
private static Map<Long, Action> actionListToMap(List<Action> actions) {
|
|
Map<Long, Action> map = new HashMap<>(actions.size());
|
|
for (Action a : actions) {
|
|
long address = a.getAddress();
|
|
// There are multiple actions in the same address (2nd action is a jump for obfuscated code)
|
|
// So this check is required
|
|
if (!map.containsKey(address)) {
|
|
map.put(a.getAddress(), a);
|
|
}
|
|
}
|
|
return map;
|
|
}
|
|
|
|
private static void getJumps(List<Action> actions, Map<Action, Action> jumps) {
|
|
Map<Long, Action> actionMap = actionListToMap(actions);
|
|
for (Action a : actions) {
|
|
long target = -1;
|
|
if (a instanceof ActionIf) {
|
|
ActionIf aIf = (ActionIf) a;
|
|
target = aIf.getAddress() + a.getTotalActionLength() + aIf.getJumpOffset();
|
|
} else if (a instanceof ActionJump) {
|
|
ActionJump aJump = (ActionJump) a;
|
|
target = aJump.getAddress() + a.getTotalActionLength() + aJump.getJumpOffset();
|
|
} else if (a instanceof ActionStore) {
|
|
ActionStore aStore = (ActionStore) a;
|
|
int storeSize = aStore.getStoreSize();
|
|
// skip storeSize + 1 actions (+1 is the current action)
|
|
Action targetAction = a;
|
|
for (int i = 0; i <= storeSize; i++) {
|
|
long address = targetAction.getAddress() + targetAction.getTotalActionLength();
|
|
targetAction = actionMap.get(address);
|
|
if (targetAction == null) {
|
|
break;
|
|
}
|
|
}
|
|
jumps.put(a, targetAction);
|
|
}
|
|
if (target >= 0) {
|
|
Action targetAction = actionMap.get(target);
|
|
jumps.put(a, targetAction);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void getContainerLastActions(Map<Long, Action> actionMap, List<Long> addresses, Map<Action, List<Action>> lastActions) {
|
|
for (Long address : actionMap.keySet()) {
|
|
Action a = actionMap.get(address);
|
|
|
|
if (a instanceof GraphSourceItemContainer) {
|
|
GraphSourceItemContainer container = (GraphSourceItemContainer) a;
|
|
List<Long> sizes = container.getContainerSizes();
|
|
long endAddress = a.getAddress() + container.getHeaderSize();
|
|
List<Action> lasts = new ArrayList<>(sizes.size());
|
|
for (long size : sizes) {
|
|
endAddress += size;
|
|
long lastActionIndex = getNearAddress(addresses, endAddress - 1, false);
|
|
Action lastAction = null;
|
|
if (lastActionIndex != -1) {
|
|
lastAction = actionMap.get(lastActionIndex);
|
|
}
|
|
lasts.add(lastAction);
|
|
}
|
|
lastActions.put(a, lasts);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static long updateAddresses(List<Action> actions, long address, int version) {
|
|
for (int i = 0; i < actions.size(); i++) {
|
|
Action a = actions.get(i);
|
|
a.setAddress(address, version);
|
|
int length = a.getBytes(version).length;
|
|
if ((i != actions.size() - 1) && (a instanceof ActionEnd)) {
|
|
// placeholder for jump action
|
|
length = new ActionJump(0).getTotalActionLength();
|
|
}
|
|
address += length;
|
|
}
|
|
return address;
|
|
}
|
|
|
|
private static void updateActionLengths(List<Action> actions, int version) {
|
|
for (int i = 0; i < actions.size(); i++) {
|
|
Action a = actions.get(i);
|
|
int length = a.getBytes(version).length;
|
|
a.actionLength = length - 1 - ((a.actionCode >= 0x80) ? 2 : 0);
|
|
}
|
|
}
|
|
|
|
private static void updateActionStores(List<Action> actions, Map<Action, Action> jumps) {
|
|
Map<Long, Action> actionMap = actionListToMap(actions);
|
|
for (int i = 0; i < actions.size(); i++) {
|
|
Action a = actions.get(i);
|
|
if (a instanceof ActionStore) {
|
|
ActionStore aStore = (ActionStore) a;
|
|
Action nextActionAfterStore = jumps.get(a);
|
|
Action a1 = a;
|
|
List<Action> store = new ArrayList<>();
|
|
while (true) {
|
|
long address = a1.getAddress() + a1.getTotalActionLength();
|
|
a1 = actionMap.get(address);
|
|
if (a1 == null || a1 == nextActionAfterStore) {
|
|
break;
|
|
}
|
|
store.add(a1);
|
|
}
|
|
aStore.setStore(store);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void updateContainerSizes(List<Action> actions, Map<Action, List<Action>> containerLastActions) {
|
|
for (int i = 0; i < actions.size(); i++) {
|
|
Action a = actions.get(i);
|
|
if (a instanceof GraphSourceItemContainer) {
|
|
GraphSourceItemContainer container = (GraphSourceItemContainer) a;
|
|
List<Action> lastActions = containerLastActions.get(a);
|
|
long startAddress = a.getAddress() + container.getHeaderSize();
|
|
for (int j = 0; j < lastActions.size(); j++) {
|
|
Action lastAction = lastActions.get(j);
|
|
int length = (int) (lastAction.getAddress() + lastAction.getTotalActionLength() - startAddress);
|
|
container.setContainerSize(j, length);
|
|
startAddress += length;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void replaceJumpTargets(Map<Action, Action> jumps, Action oldTarget, Action newTarget) {
|
|
for (Action a : jumps.keySet()) {
|
|
if (jumps.get(a) == oldTarget) {
|
|
jumps.put(a, newTarget);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void replaceContainerLastActions(Map<Action, List<Action>> containerLastActions, Action oldTarget, Action newTarget) {
|
|
for (Action a : containerLastActions.keySet()) {
|
|
List<Action> targets = containerLastActions.get(a);
|
|
for (int i = 0; i < targets.size(); i++) {
|
|
if (targets.get(i) == oldTarget) {
|
|
targets.set(i, newTarget);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void updateJumps(List<Action> actions, Map<Action, Action> jumps, Map<Action, List<Action>> containerLastActions, long endAddress, int version) {
|
|
if (actions.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < actions.size(); i++) {
|
|
Action a = actions.get(i);
|
|
if ((i != actions.size() - 1) && (a instanceof ActionEnd)) {
|
|
ActionJump aJump = new ActionJump(0);
|
|
aJump.setJumpOffset((int) (endAddress - a.getAddress() - aJump.getTotalActionLength()));
|
|
aJump.setAddress(a.getAddress(), version);
|
|
replaceJumpTargets(jumps, a, aJump);
|
|
replaceContainerLastActions(containerLastActions, a, aJump);
|
|
a = aJump;
|
|
actions.set(i, a);
|
|
} else if (a instanceof ActionIf) {
|
|
ActionIf aIf = (ActionIf) a;
|
|
Action target = jumps.get(a);
|
|
long offset;
|
|
if (target != null) {
|
|
offset = target.getAddress() - a.getAddress() - a.getTotalActionLength();
|
|
} else {
|
|
offset = endAddress - a.getAddress() - a.getTotalActionLength();
|
|
}
|
|
aIf.setJumpOffset((int) offset);
|
|
} else if (a instanceof ActionJump) {
|
|
ActionJump aJump = (ActionJump) a;
|
|
Action target = jumps.get(a);
|
|
long offset;
|
|
if (target != null) {
|
|
offset = target.getAddress() - a.getAddress() - a.getTotalActionLength();
|
|
} else {
|
|
offset = endAddress - a.getAddress() - a.getTotalActionLength();
|
|
}
|
|
aJump.setJumpOffset((int) offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void removeZeroJumps(List<Action> actions, int version) {
|
|
for (int i = 0; i < actions.size(); i++) {
|
|
Action a = actions.get(i);
|
|
if (a instanceof ActionJump) {
|
|
ActionJump aJump = (ActionJump) a;
|
|
if (aJump.getJumpOffset() == 0) {
|
|
if (removeAction(actions, i, version, false)) {
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes an action from the action list, and updates all references
|
|
*
|
|
* @param actions
|
|
* @param index
|
|
* @param version
|
|
* @param removeWhenLast
|
|
* @return
|
|
*/
|
|
private static boolean removeAction(List<Action> actions, int index, int version, boolean removeWhenLast) {
|
|
|
|
if (index < 0 || actions.size() <= index) {
|
|
return false;
|
|
}
|
|
|
|
long startIp = actions.get(0).getAddress();
|
|
Action lastAction = actions.get(actions.size() - 1);
|
|
long endAddress = lastAction.getAddress() + lastAction.getTotalActionLength();
|
|
|
|
Map<Long, Action> actionMap = new TreeMap<>();
|
|
for (Action a : actions) {
|
|
actionMap.put(a.getAddress(), a);
|
|
}
|
|
List<Long> addresses = new ArrayList<>(actionMap.keySet());
|
|
|
|
Map<Action, List<Action>> containerLastActions = new HashMap<>();
|
|
getContainerLastActions(actionMap, addresses, containerLastActions);
|
|
|
|
Map<Action, Action> jumps = new HashMap<>();
|
|
getJumps(actions, jumps);
|
|
|
|
Action prevAction = index > 0 ? actions.get(index - 1) : null;
|
|
Action nextAction = index + 1 < actions.size() ? actions.get(index + 1) : null;
|
|
Action actionToRemove = actions.get(index);
|
|
for (Action a : containerLastActions.keySet()) {
|
|
List<Action> lastActions = containerLastActions.get(a);
|
|
for (int i = 0; i < lastActions.size(); i++) {
|
|
if (lastActions.get(i) == actionToRemove) {
|
|
if (!removeWhenLast) {
|
|
return false;
|
|
}
|
|
lastActions.set(i, prevAction);
|
|
}
|
|
}
|
|
}
|
|
for (Action a : jumps.keySet()) {
|
|
Action targetAction = jumps.get(a);
|
|
if (targetAction == actionToRemove) {
|
|
jumps.put(a, nextAction);
|
|
}
|
|
}
|
|
if (containerLastActions.containsKey(actionToRemove)) {
|
|
containerLastActions.remove(actionToRemove);
|
|
}
|
|
if (jumps.containsKey(actionToRemove)) {
|
|
jumps.remove(actionToRemove);
|
|
}
|
|
|
|
actions.remove(index);
|
|
|
|
updateAddresses(actions, startIp, version);
|
|
updateJumps(actions, jumps, containerLastActions, endAddress, version);
|
|
updateActionStores(actions, jumps);
|
|
updateContainerSizes(actions, containerLastActions);
|
|
updateActionLengths(actions, version);
|
|
|
|
return true;
|
|
}
|
|
|
|
private static Action readActionListAtPos(List<DisassemblyListener> listeners, ConstantPool cpool,
|
|
SWFInputStream sis, Map<Long, Action> actions, Map<Long, Long> nextOffsets,
|
|
long ip, long startIp, long endIp, int version, String path, boolean indeterminate, List<Long> visitedContainers) throws IOException {
|
|
|
|
Action entryAction = null;
|
|
|
|
if (visitedContainers.contains(ip)) {
|
|
return null;
|
|
}
|
|
visitedContainers.add(ip);
|
|
|
|
Queue<Long> jumpQueue = new LinkedList<>();
|
|
jumpQueue.add(ip);
|
|
while (!jumpQueue.isEmpty()) {
|
|
ip = jumpQueue.remove();
|
|
while (endIp == -1 || endIp > ip) {
|
|
sis.seek((int) ip);
|
|
|
|
Action a;
|
|
if ((a = sis.readAction(cpool)) == null) {
|
|
break;
|
|
}
|
|
|
|
int actionLengthWithHeader = a.getTotalActionLength();
|
|
|
|
// unknown action, replace with jump
|
|
if (a instanceof ActionNop) {
|
|
ActionJump aJump = new ActionJump(0);
|
|
int jumpLength = aJump.getTotalActionLength();
|
|
aJump.setAddress(a.getAddress(), version);
|
|
aJump.setJumpOffset(actionLengthWithHeader - jumpLength);
|
|
a = aJump;
|
|
actionLengthWithHeader = a.getTotalActionLength();
|
|
}
|
|
|
|
if (entryAction == null) {
|
|
entryAction = a;
|
|
}
|
|
|
|
Action existingAction = actions.get(ip);
|
|
if (existingAction != null) {
|
|
break;
|
|
}
|
|
|
|
actions.put(ip, a);
|
|
nextOffsets.put(ip, ip + actionLengthWithHeader);
|
|
|
|
long pos = sis.getPos();
|
|
long length = pos + sis.available();
|
|
for (int i = 0; i < listeners.size(); i++) {
|
|
listeners.get(i).progress(AppStrings.translate("disassemblingProgress.reading"), pos, length);
|
|
}
|
|
|
|
a.setAddress(ip, version, false);
|
|
|
|
if (a instanceof ActionPush && cpool != null) {
|
|
((ActionPush) a).constantPool = cpool.constants;
|
|
} else if (a instanceof ActionConstantPool) {
|
|
if (cpool == null) {
|
|
cpool = new ConstantPool();
|
|
}
|
|
cpool.setNew(((ActionConstantPool) a).constantPool);
|
|
} else if (a instanceof ActionIf) {
|
|
ActionIf aIf = (ActionIf) a;
|
|
long nIp = ip + actionLengthWithHeader + aIf.getJumpOffset();
|
|
if (nIp >= 0) {
|
|
jumpQueue.add(nIp);
|
|
}
|
|
} else if (a instanceof ActionJump) {
|
|
ActionJump aJump = (ActionJump) a;
|
|
long nIp = ip + actionLengthWithHeader + aJump.getJumpOffset();
|
|
if (nIp >= 0) {
|
|
jumpQueue.add(nIp);
|
|
}
|
|
break;
|
|
} else if (a instanceof GraphSourceItemContainer) {
|
|
GraphSourceItemContainer cnt = (GraphSourceItemContainer) a;
|
|
String cntName = cnt.getName();
|
|
String newPath = path + (cntName == null ? "" : "/" + cntName);
|
|
for (long size : cnt.getContainerSizes()) {
|
|
if (size != 0) {
|
|
long ip2 = ip + actionLengthWithHeader;
|
|
//long endIp2 = ip + actionLengthWithHeader + size;
|
|
readActionListAtPos(listeners, cpool,
|
|
sis, actions, nextOffsets,
|
|
ip2, startIp, endIp, version, newPath, indeterminate, visitedContainers);
|
|
actionLengthWithHeader += size;
|
|
}
|
|
}
|
|
}
|
|
|
|
ip += actionLengthWithHeader;
|
|
|
|
if (a.isExit()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return entryAction;
|
|
}
|
|
|
|
private static void deobfustaceActionListAtPosRecursive(List<DisassemblyListener> listeners, List<GraphTargetItem> output, HashMap<Long, List<GraphSourceItemContainer>> containers, ActionLocalData localData, Stack<GraphTargetItem> stack, ConstantPool cpool, List<Action> actions, int ip, List<Action> ret, int startIp, int endip, String path, Map<Integer, Integer> visited, boolean indeterminate, Map<Integer, HashMap<String, GraphTargetItem>> decisionStates, int version, int recursionLevel, int maxRecursionLevel) throws IOException, InterruptedException {
|
|
boolean debugMode = false;
|
|
boolean decideBranch = false;
|
|
|
|
if (recursionLevel > maxRecursionLevel + 1) {
|
|
throw new TranslateException("deobfustaceActionListAtPosRecursive max recursion level reached.");
|
|
}
|
|
|
|
Action a;
|
|
Scanner sc = new Scanner(System.in);
|
|
loopip:
|
|
while (((endip == -1) || (endip > ip)) && (a = actions.get(ip)) != null) {
|
|
if (Thread.currentThread().isInterrupted()) {
|
|
throw new InterruptedException();
|
|
}
|
|
|
|
int actionLen = a.getTotalActionLength();
|
|
if (!visited.containsKey(ip)) {
|
|
visited.put(ip, 0);
|
|
}
|
|
int curVisited = visited.get(ip);
|
|
curVisited++;
|
|
visited.put(ip, curVisited);
|
|
for (int i = 0; i < listeners.size(); i++) {
|
|
listeners.get(i).progress(AppStrings.translate("disassemblingProgress.deobfuscating"), ip, actions.size());
|
|
}
|
|
int info = a.getTotalActionLength();
|
|
|
|
if (a instanceof ActionPush) {
|
|
if (cpool != null) {
|
|
((ActionPush) a).constantPool = cpool.constants;
|
|
}
|
|
}
|
|
|
|
if (debugMode) {
|
|
String atos = a.getASMSource(new ArrayList<GraphSourceItem>(), new ArrayList<Long>(), cpool.constants, version, ScriptExportMode.PCODE);
|
|
if (a instanceof GraphSourceItemContainer) {
|
|
atos = a.toString();
|
|
}
|
|
System.err.println("readActionListAtPos ip: " + (ip - startIp) + " (0x" + Helper.formatAddress(ip - startIp) + ") " + " action(len " + a.actionLength + "): " + atos + (a.isIgnored() ? " (ignored)" : "") + " stack:" + Helper.stackToString(stack, LocalData.create(cpool)) + " " + Helper.byteArrToString(a.getBytes(version)));
|
|
System.err.print("variables: ");
|
|
for (Map.Entry<String, GraphTargetItem> v : localData.variables.entrySet()) {
|
|
System.err.print("'" + v + "' = " + v.getValue().toString(LocalData.create(cpool)) + ", ");
|
|
}
|
|
System.err.println();
|
|
String add = "";
|
|
if (a instanceof ActionIf) {
|
|
add = " change: " + ((ActionIf) a).getJumpOffset();
|
|
}
|
|
if (a instanceof ActionJump) {
|
|
add = " change: " + ((ActionJump) a).getJumpOffset();
|
|
}
|
|
System.err.println(add);
|
|
}
|
|
|
|
int newip = -1;
|
|
|
|
if (a instanceof ActionConstantPool) {
|
|
if (cpool == null) {
|
|
cpool = new ConstantPool();
|
|
}
|
|
cpool.setNew(((ActionConstantPool) a).constantPool);
|
|
}
|
|
ActionIf aif = null;
|
|
boolean goaif = false;
|
|
if (!a.isIgnored()) {
|
|
String varname = null;
|
|
if (a instanceof StoreTypeAction) {
|
|
StoreTypeAction sta = (StoreTypeAction) a;
|
|
varname = sta.getVariableName(stack, cpool);
|
|
}
|
|
|
|
try {
|
|
if (a instanceof ActionIf) {
|
|
aif = (ActionIf) a;
|
|
|
|
GraphTargetItem top = stack.pop();
|
|
int nip = ip + actionLen + aif.getJumpOffset();
|
|
|
|
if (decideBranch) {
|
|
System.out.print("newip " + nip + ", ");
|
|
System.out.print("Action: jump(j),ignore(i),compute(c)?");
|
|
String next = sc.next();
|
|
switch (next) {
|
|
case "j":
|
|
newip = nip;
|
|
break;
|
|
case "i":
|
|
break;
|
|
case "c":
|
|
goaif = true;
|
|
break;
|
|
}
|
|
} else if (top.isCompileTime() && (!top.hasSideEffect())) {
|
|
if (debugMode) {
|
|
System.err.print("is compiletime -> ");
|
|
}
|
|
if (EcmaScript.toBoolean(top.getResult())) {
|
|
newip = nip;
|
|
aif.jumpUsed = true;
|
|
if (debugMode) {
|
|
System.err.println("jump");
|
|
}
|
|
} else {
|
|
aif.ignoreUsed = true;
|
|
if (debugMode) {
|
|
System.err.println("ignore");
|
|
}
|
|
}
|
|
} else {
|
|
if (debugMode) {
|
|
System.err.println("goaif");
|
|
}
|
|
goaif = true;
|
|
}
|
|
} else if (a instanceof ActionJump) {
|
|
newip = ip + actionLen + ((ActionJump) a).getJumpOffset();
|
|
} else if (!(a instanceof GraphSourceItemContainer)) {
|
|
//return in for..in, TODO:Handle this better way
|
|
if (((a instanceof ActionEquals) || (a instanceof ActionEquals2)) && (stack.size() == 1) && (stack.peek() instanceof DirectValueActionItem)) {
|
|
stack.push(new DirectValueActionItem(null, 0, new Null(), new ArrayList<String>()));
|
|
}
|
|
if ((a instanceof ActionStoreRegister) && stack.isEmpty()) {
|
|
stack.push(new DirectValueActionItem(null, 0, new Null(), new ArrayList<String>()));
|
|
}
|
|
a.translate(localData, stack, output, Graph.SOP_USE_STATIC/*Graph.SOP_SKIP_STATIC*/, path);
|
|
}
|
|
} catch (RuntimeException ex) {
|
|
logger.log(Level.SEVERE, "Disassembly exception", ex);
|
|
break;
|
|
}
|
|
|
|
HashMap<String, GraphTargetItem> vars = localData.variables;
|
|
if (varname != null) {
|
|
GraphTargetItem varval = vars.get(varname);
|
|
if (varval != null && varval.isCompileTime() && indeterminate) {
|
|
vars.put(varname, new NotCompileTimeItem(null, varval));
|
|
}
|
|
}
|
|
}
|
|
int nopos = -1;
|
|
for (int i = 0; i < actionLen; i++) {
|
|
if (a instanceof ActionNop) {
|
|
int prevPos = (int) a.getAddress();
|
|
a = new ActionNop();
|
|
a.setAddress(prevPos, version);
|
|
nopos++;
|
|
if (nopos > 0) {
|
|
a.setAddress(a.getAddress() + 1, version);
|
|
}
|
|
|
|
}
|
|
ret.set(ip + i, a);
|
|
}
|
|
|
|
if (a instanceof GraphSourceItemContainer) {
|
|
GraphSourceItemContainer cnt = (GraphSourceItemContainer) a;
|
|
if (a instanceof Action) {
|
|
long endAddr = a.getAddress() + cnt.getHeaderSize();
|
|
String cntName = cnt.getName();
|
|
List<List<GraphTargetItem>> output2s = new ArrayList<>();
|
|
for (long size : cnt.getContainerSizes()) {
|
|
if (size == 0) {
|
|
output2s.add(new ArrayList<GraphTargetItem>());
|
|
continue;
|
|
}
|
|
ActionLocalData localData2;
|
|
List<GraphTargetItem> output2 = new ArrayList<>();
|
|
if ((cnt instanceof ActionDefineFunction) || (cnt instanceof ActionDefineFunction2)) {
|
|
localData2 = new ActionLocalData();
|
|
} else {
|
|
localData2 = localData;
|
|
}
|
|
deobfustaceActionListAtPosRecursive(listeners, output2, containers, localData2, new Stack<GraphTargetItem>(), cpool, actions, (int) endAddr, ret, startIp, (int) (endAddr + size), path + (cntName == null ? "" : "/" + cntName), visited, indeterminate, decisionStates, version, recursionLevel + 1, maxRecursionLevel);
|
|
output2s.add(output2);
|
|
endAddr += size;
|
|
}
|
|
cnt.translateContainer(output2s, stack, output, localData.regNames, localData.variables, localData.functions);
|
|
ip = (int) endAddr;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (a instanceof ActionEnd) {
|
|
break;
|
|
}
|
|
if (goaif) {
|
|
aif.ignoreUsed = true;
|
|
aif.jumpUsed = true;
|
|
indeterminate = true;
|
|
|
|
HashMap<String, GraphTargetItem> vars = localData.variables;
|
|
boolean stateChanged = false;
|
|
if (decisionStates.containsKey(ip)) {
|
|
HashMap<String, GraphTargetItem> oldstate = decisionStates.get(ip);
|
|
if (oldstate.size() != vars.size()) {
|
|
stateChanged = true;
|
|
} else {
|
|
for (String k : vars.keySet()) {
|
|
if (!oldstate.containsKey(k)) {
|
|
stateChanged = true;
|
|
break;
|
|
}
|
|
if (!vars.get(k).isCompileTime() && oldstate.get(k).isCompileTime()) {
|
|
stateChanged = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
HashMap<String, GraphTargetItem> curstate = new HashMap<>();
|
|
curstate.putAll(vars);
|
|
decisionStates.put(ip, curstate);
|
|
|
|
if ((!stateChanged) && curVisited > 1) {
|
|
List<Integer> branches = new ArrayList<>();
|
|
branches.add(ip + actionLen + aif.getJumpOffset());
|
|
branches.add(ip + actionLen);
|
|
for (int br : branches) {
|
|
int visc = 0;
|
|
if (visited.containsKey(br)) {
|
|
visc = visited.get(br);
|
|
}
|
|
if (visc == 0) {//<curVisited){
|
|
ip = br;
|
|
continue loopip;
|
|
}
|
|
}
|
|
break loopip;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
Stack<GraphTargetItem> substack = (Stack<GraphTargetItem>) stack.clone();
|
|
deobfustaceActionListAtPosRecursive(listeners, output, containers, prepareLocalBranch(localData), substack, cpool, actions, ip + actionLen + aif.getJumpOffset(), ret, startIp, endip, path, visited, indeterminate, decisionStates, version, recursionLevel + 1, maxRecursionLevel);
|
|
}
|
|
|
|
if (newip > -1) {
|
|
ip = newip;
|
|
} else {
|
|
ip += info;
|
|
}
|
|
|
|
if (a.isExit()) {
|
|
break;
|
|
}
|
|
}
|
|
for (DisassemblyListener listener : listeners) {
|
|
listener.progress(AppStrings.translate("disassemblingProgress.deobfuscating"), ip, actions.size());
|
|
}
|
|
}
|
|
|
|
private static ActionLocalData prepareLocalBranch(ActionLocalData localData) {
|
|
|
|
return new ActionLocalData(new HashMap<>(localData.regNames),
|
|
new HashMap<>(localData.variables), new HashMap<>(localData.functions));
|
|
}
|
|
}
|