From f3a2d0ada1a8e8928e1475c95a7957e5e57d30ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=F8=EDk?= Date: Sat, 22 Jun 2013 17:44:35 +0200 Subject: [PATCH] Issue #147 Escape filenames from invalid identifiers Issue #132 Rename identifiers renames strings Issue #130 Renaming identifiers in SymbolClass tags --- trunk/src/com/jpexs/decompiler/flash/SWF.java | 92 +++++++++++++++++-- .../com/jpexs/decompiler/flash/abc/ABC.java | 84 ++++++++++------- .../decompiler/flash/abc/ScriptPack.java | 51 +++++++++- .../flash/action/swf4/ActionGetTime.java | 1 - .../jpexs/decompiler/flash/graph/Graph.java | 52 +++++------ .../jpexs/decompiler/flash/gui/MainFrame.java | 5 +- .../decompiler/flash/helpers/Helper.java | 57 ++++++++++++ 7 files changed, 266 insertions(+), 76 deletions(-) diff --git a/trunk/src/com/jpexs/decompiler/flash/SWF.java b/trunk/src/com/jpexs/decompiler/flash/SWF.java index 8c8ed7042..b0d0a39a6 100644 --- a/trunk/src/com/jpexs/decompiler/flash/SWF.java +++ b/trunk/src/com/jpexs/decompiler/flash/SWF.java @@ -26,6 +26,7 @@ import com.jpexs.decompiler.flash.action.swf4.ActionGetVariable; import com.jpexs.decompiler.flash.action.swf4.ActionIf; import com.jpexs.decompiler.flash.action.swf4.ActionPush; import com.jpexs.decompiler.flash.action.swf4.ActionSetVariable; +import com.jpexs.decompiler.flash.action.swf4.ConstantIndex; import com.jpexs.decompiler.flash.action.swf4.Null; import com.jpexs.decompiler.flash.action.swf5.ActionCallFunction; import com.jpexs.decompiler.flash.action.swf5.ActionCallMethod; @@ -1055,6 +1056,7 @@ public class SWF { private HashMap allVariableNames = new HashMap<>(); private HashSet allVariableNamesStr = new HashSet<>(); private List allFunctions = new ArrayList<>(); + private HashMap allStrings = new HashMap<>(); private String fooString(String orig, boolean firstUppercase, int rndSize) { boolean exists; @@ -1129,7 +1131,7 @@ public class SWF { return null; } - private static void getVariables(ConstantPool constantPool, List localData, Stack stack, List output, ActionGraphSource code, int ip, int lastIp, HashMap variables, List functions, List visited) { + private static void getVariables(ConstantPool constantPool, List localData, Stack stack, List output, ActionGraphSource code, int ip, int lastIp, HashMap variables, List functions, HashMap strings, List visited) { boolean debugMode = false; while ((ip > -1) && ip < code.size()) { if (visited.contains(ip)) { @@ -1174,7 +1176,7 @@ public class SWF { ip = code.adr2pos(addr); addr += size; int nextip = code.adr2pos(addr); - getVariables(variables, functions, new ActionGraphSource(code.getActions().subList(ip, nextip), code.version, new HashMap(), new HashMap(), new HashMap()), 0); + getVariables(variables, functions, strings, new ActionGraphSource(code.getActions().subList(ip, nextip), code.version, new HashMap(), new HashMap(), new HashMap()), 0); ip = nextip; } ((GraphSourceItemContainer) ins).translateContainer(new ArrayList>(), stack, output, new HashMap(), new HashMap(), new HashMap()); @@ -1206,6 +1208,18 @@ public class SWF { break; } + if (ins instanceof ActionPush) { + if (!stack.isEmpty()) { + GraphTargetItem top = stack.peek(); + if (top instanceof DirectValueTreeItem) { + DirectValueTreeItem dvt = (DirectValueTreeItem) top; + if ((dvt.value instanceof String) || (dvt.value instanceof ConstantIndex)) { + strings.put(dvt, constantPool); + } + } + } + } + if (ins.isBranch() || ins.isJump()) { if (ins instanceof ActionIf) { stack.pop(); @@ -1216,7 +1230,7 @@ public class SWF { @SuppressWarnings("unchecked") Stack brStack = (Stack) stack.clone(); if (b >= 0) { - getVariables(constantPool, localData, brStack, output, code, b, ip, variables, functions, visited); + getVariables(constantPool, localData, brStack, output, code, b, ip, variables, functions, strings, visited); } else { if (debugMode) { System.out.println("Negative branch:" + b); @@ -1230,20 +1244,20 @@ public class SWF { }; } - private static void getVariables(HashMap variables, List functions, ActionGraphSource code, int addr) { + private static void getVariables(HashMap variables, List functions, HashMap strings, ActionGraphSource code, int addr) { List localData = Helper.toList(new HashMap(), new HashMap(), new HashMap()); try { - getVariables(null, localData, new Stack(), new ArrayList(), code, code.adr2pos(addr), 0, variables, functions, new ArrayList()); + getVariables(null, localData, new Stack(), new ArrayList(), code, code.adr2pos(addr), 0, variables, functions, strings, new ArrayList()); } catch (Exception ex) { Logger.getLogger(SWF.class.getName()).log(Level.SEVERE, "Getting variables error", ex); } } - private HashMap getVariables(HashMap variables, List functions, ASMSource src) { + private HashMap getVariables(HashMap variables, List functions, HashMap strings, ASMSource src) { HashMap ret = new HashMap<>(); List actions = src.getActions(version); actionsMap.put(src, actions); - getVariables(variables, functions, new ActionGraphSource(actions, version, new HashMap(), new HashMap(), new HashMap()), 0); + getVariables(variables, functions, strings, new ActionGraphSource(actions, version, new HashMap(), new HashMap(), new HashMap()), 0); return ret; } private HashMap> actionsMap = new HashMap<>(); @@ -1252,7 +1266,7 @@ public class SWF { for (Object o : objs) { if (o instanceof ASMSource) { informListeners("getVariables", path + "/" + o.toString()); - getVariables(allVariableNames, allFunctions, (ASMSource) o); + getVariables(allVariableNames, allFunctions, allStrings, (ASMSource) o); } if (o instanceof Container) { getVariables(((Container) o).getSubItems(), path + "/" + o.toString()); @@ -1260,10 +1274,56 @@ public class SWF { } } + public int deobfuscateAS3Identifiers() { + HashMap namesMap = new HashMap<>(); + for (Tag tag : tags) { + if (tag instanceof ABCContainerTag) { + ((ABCContainerTag) tag).getABC().deobfuscateIdentifiers(namesMap); + } + } + for (Tag tag : tags) { + if (tag instanceof SymbolClassTag) { + SymbolClassTag sc = (SymbolClassTag) tag; + for (int i = 0; i < sc.classNames.length; i++) { + String pkg = null; + String name = ""; + if (sc.classNames[i].contains(".")) { + pkg = sc.classNames[i].substring(0, sc.classNames[i].lastIndexOf(".")); + name = sc.classNames[i].substring(sc.classNames[i].lastIndexOf(".") + 1); + } else { + name = sc.classNames[i]; + } + boolean changed = false; + if ((pkg != null) && (!pkg.equals(""))) { + if (namesMap.containsKey(pkg)) { + changed = true; + pkg = namesMap.get(pkg); + } + } + if (namesMap.containsKey(name)) { + changed = true; + name = namesMap.get(name); + } + if (changed) { + String newClassName = ""; + if (pkg == null) { + newClassName = name; + } else { + newClassName = pkg + "." + name; + } + sc.classNames[i] = newClassName; + } + } + } + } + return namesMap.size(); + } + public int deobfuscateAS2Identifiers() { actionsMap = new HashMap<>(); allFunctions = new ArrayList<>(); allVariableNames = new HashMap<>(); + allStrings = new HashMap<>(); List objs = new ArrayList<>(); int ret = 0; objs.addAll(tags); @@ -1288,10 +1348,26 @@ public class SWF { String name = ti.toStringNoH(allVariableNames.get(ti)); allVariableNamesStr.add(name); } + //Ommit variables from all strings + HashMap stringsNoVar = new HashMap<>(); + for (DirectValueTreeItem ti : allStrings.keySet()) { + if (!allVariableNames.containsKey(ti)) { + stringsNoVar.put(ti, allStrings.get(ti)); + } + } for (DirectValueTreeItem ti : allVariableNames.keySet()) { String name = ti.toStringNoH(allVariableNames.get(ti)); String changed = deobfuscateName(deobfuscated, name, false); if (changed != null) { + /*boolean addNew=false; + for (DirectValueTreeItem snv : stringsNoVar.keySet()) { + if (stringsNoVar.get(snv) == allVariableNames.get(ti)) { //Same constantpool + if(snv.toStringNoH(stringsNoVar.get(snv)).equals(name)){ //Same string + addNew = true; + break; + } + } + }*/ ActionPush pu = (ActionPush) ti.src; if (pu.replacement == null) { pu.replacement = new ArrayList<>(); diff --git a/trunk/src/com/jpexs/decompiler/flash/abc/ABC.java b/trunk/src/com/jpexs/decompiler/flash/abc/ABC.java index a98c084f9..40412ca9b 100644 --- a/trunk/src/com/jpexs/decompiler/flash/abc/ABC.java +++ b/trunk/src/com/jpexs/decompiler/flash/abc/ABC.java @@ -34,6 +34,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Random; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -96,33 +97,38 @@ public class ABC { } } - public int deobfuscateIdentifiers(HashMap namesMap) { - int ret = 0; - for (int i = 1; i < instance_info.length; i++) { - if (instance_info[i].name_index != 0) { - if (deobfuscateName(namesMap, constants.constant_multiname[instance_info[i].name_index].name_index, true)) { - ret++; + public Set getStringUsages() { + Set ret = new HashSet<>(); + for (MethodBody body : bodies) { + for (AVM2Instruction ins : body.code.code) { + for (int i = 0; i < ins.definition.operands.length; i++) { + if (ins.definition.operands[i] == AVM2Code.DAT_STRING_INDEX) { + ret.add(ins.operands[i]); + } } } - if (instance_info[i].super_index != 0) { - if (deobfuscateName(namesMap, constants.constant_multiname[instance_info[i].super_index].name_index, true)) { - ret++; - } - } - } - for (int i = 1; i < constants.constant_multiname.length; i++) { - if (deobfuscateName(namesMap, constants.constant_multiname[i].name_index, false)) { - ret++; - } - } - for (int i = 1; i < constants.constant_namespace.length; i++) { - if (deobfuscateNameSpace(namesMap, constants.constant_namespace[i].name_index)) { - ret++; - } } return ret; } + public void deobfuscateIdentifiers(HashMap namesMap) { + Set stringUsages = getStringUsages(); + for (int i = 1; i < instance_info.length; i++) { + if (instance_info[i].name_index != 0) { + constants.constant_multiname[instance_info[i].name_index].name_index = deobfuscateName(stringUsages, namesMap, constants.constant_multiname[instance_info[i].name_index].name_index, true); + } + if (instance_info[i].super_index != 0) { + constants.constant_multiname[instance_info[i].super_index].name_index = deobfuscateName(stringUsages, namesMap, constants.constant_multiname[instance_info[i].super_index].name_index, true); + } + } + for (int i = 1; i < constants.constant_multiname.length; i++) { + constants.constant_multiname[i].name_index = deobfuscateName(stringUsages, namesMap, constants.constant_multiname[i].name_index, false); + } + for (int i = 1; i < constants.constant_namespace.length; i++) { + constants.constant_namespace[i].name_index = deobfuscateNameSpace(stringUsages, namesMap, constants.constant_namespace[i].name_index); + } + } + public ABC(InputStream is) throws IOException { ABCInputStream ais = new ABCInputStream(is); minor_version = ais.readU16(); @@ -732,15 +738,16 @@ public class ABC { return isValid; } - public boolean deobfuscateNameSpace(HashMap namesMap, int strIndex) { + public int deobfuscateNameSpace(Set stringUsages, HashMap namesMap, int strIndex) { if (strIndex <= 0) { - return false; + return strIndex; } String s = constants.constant_string[strIndex]; boolean isValid = isValidNSPart(s); if (!isValid) { + String newName; if (namesMap.containsKey(s)) { - constants.constant_string[strIndex] = namesMap.get(s); + newName = constants.constant_string[strIndex] = namesMap.get(s); } else { String parts[] = null; if (s.contains(".")) { @@ -759,16 +766,22 @@ public class ABC { ret += parts[p]; } } - constants.constant_string[strIndex] = ret; - namesMap.put(s, constants.constant_string[strIndex]); + newName = ret; + namesMap.put(s, newName); } + if (stringUsages.contains(strIndex)) { + strIndex = constants.addString(newName); + } else { + constants.constant_string[strIndex] = newName; + } + } - return !isValid; + return strIndex; } - public boolean deobfuscateName(HashMap namesMap, int strIndex, boolean firstUppercase) { + public int deobfuscateName(Set stringUsages, HashMap namesMap, int strIndex, boolean firstUppercase) { if (strIndex <= 0) { - return false; + return strIndex; } String s = constants.constant_string[strIndex]; boolean isValid = true; @@ -793,14 +806,21 @@ public class ABC { } if (!isValid) { + String newname; if (namesMap.containsKey(s)) { - constants.constant_string[strIndex] = namesMap.get(s); + newname = namesMap.get(s); } else { - constants.constant_string[strIndex] = fooString(constants.constant_string[strIndex], firstUppercase, DEFAULT_FOO_SIZE); + newname = fooString(constants.constant_string[strIndex], firstUppercase, DEFAULT_FOO_SIZE); + } + if (stringUsages.contains(strIndex)) { //this name is already referenced as String + strIndex = constants.addString(s); //add new index + } + constants.constant_string[strIndex] = newname; + if (!namesMap.containsKey(s)) { namesMap.put(s, constants.constant_string[strIndex]); } } - return !isValid; + return strIndex; } private void checkMultinameUsedInMethod(int multinameIndex, int methodInfo, List ret, int classIndex, int traitIndex, boolean isStatic, boolean isInitializer, Traits traits, int parentTraitIndex) { diff --git a/trunk/src/com/jpexs/decompiler/flash/abc/ScriptPack.java b/trunk/src/com/jpexs/decompiler/flash/abc/ScriptPack.java index 86eba5e67..1345df553 100644 --- a/trunk/src/com/jpexs/decompiler/flash/abc/ScriptPack.java +++ b/trunk/src/com/jpexs/decompiler/flash/abc/ScriptPack.java @@ -18,6 +18,7 @@ package com.jpexs.decompiler.flash.abc; import com.jpexs.decompiler.flash.abc.types.Multiname; import com.jpexs.decompiler.flash.abc.types.Namespace; +import com.jpexs.decompiler.flash.helpers.Helper; import com.jpexs.decompiler.flash.tags.ABCContainerTag; import java.io.File; import java.io.FileOutputStream; @@ -41,6 +42,30 @@ public class ScriptPack { this.traitIndices = traitIndices; } + public String getPathPackage() { + String packageName = ""; + for (int t : traitIndices) { + Multiname name = abc.script_info[scriptIndex].traits.traits[t].getName(abc); + Namespace ns = name.getNamespace(abc.constants); + if ((ns.kind == Namespace.KIND_PACKAGE) || (ns.kind == Namespace.KIND_PACKAGE_INTERNAL)) { + packageName = ns.getName(abc.constants); + } + } + return packageName; + } + + public String getPathScriptName() { + String scriptName = ""; + for (int t : traitIndices) { + Multiname name = abc.script_info[scriptIndex].traits.traits[t].getName(abc); + Namespace ns = name.getNamespace(abc.constants); + if ((ns.kind == Namespace.KIND_PACKAGE) || (ns.kind == Namespace.KIND_PACKAGE_INTERNAL)) { + scriptName = name.getName(abc.constants, new ArrayList()); + } + } + return scriptName; + } + public String getPath() { String packageName = ""; String scriptName = ""; @@ -55,15 +80,31 @@ public class ScriptPack { return packageName + "." + scriptName; } + private static String makeDirPath(String packageName) { + if (packageName.equals("")) { + return ""; + } + String pathParts[]; + if (packageName.contains(".")) { + pathParts = packageName.split("\\."); + } else { + pathParts = new String[]{packageName}; + } + for (int i = 0; i < pathParts.length; i++) { + pathParts[i] = Helper.makeFileName(pathParts[i]); + } + return Helper.joinStrings(pathParts, File.separator); + } + public void export(String directory, List abcList, boolean pcode, boolean paralel) throws IOException { - String path = getPath(); - String scriptName = path.substring(path.lastIndexOf(".") + 1); - String packageName = path.substring(0, path.lastIndexOf(".")); - File outDir = new File(directory + File.separatorChar + packageName.replace('.', File.separatorChar)); + String scriptName = getPathScriptName(); + String packageName = getPathPackage(); + + File outDir = new File(directory + File.separatorChar + makeDirPath(packageName)); if (!outDir.exists()) { outDir.mkdirs(); } - String fileName = outDir.toString() + File.separator + scriptName + ".as"; + String fileName = outDir.toString() + File.separator + Helper.makeFileName(scriptName) + ".as"; try (FileOutputStream fos = new FileOutputStream(fileName)) { for (int t : traitIndices) { Multiname name = abc.script_info[scriptIndex].traits.traits[t].getName(abc); diff --git a/trunk/src/com/jpexs/decompiler/flash/action/swf4/ActionGetTime.java b/trunk/src/com/jpexs/decompiler/flash/action/swf4/ActionGetTime.java index fc23757b0..37731c1b6 100644 --- a/trunk/src/com/jpexs/decompiler/flash/action/swf4/ActionGetTime.java +++ b/trunk/src/com/jpexs/decompiler/flash/action/swf4/ActionGetTime.java @@ -18,7 +18,6 @@ package com.jpexs.decompiler.flash.action.swf4; import com.jpexs.decompiler.flash.action.Action; import com.jpexs.decompiler.flash.action.treemodel.GetTimeTreeItem; -import com.jpexs.decompiler.flash.action.treemodel.SimpleActionTreeItem; import com.jpexs.decompiler.flash.graph.GraphTargetItem; import java.util.HashMap; import java.util.List; diff --git a/trunk/src/com/jpexs/decompiler/flash/graph/Graph.java b/trunk/src/com/jpexs/decompiler/flash/graph/Graph.java index d852a3093..53f6a2cf6 100644 --- a/trunk/src/com/jpexs/decompiler/flash/graph/Graph.java +++ b/trunk/src/com/jpexs/decompiler/flash/graph/Graph.java @@ -752,7 +752,7 @@ public class Graph { } } } - + if (!visited.contains(part)) { visited.add(part); } @@ -840,11 +840,11 @@ public class Graph { private void getLoops(GraphPart part, List loops, List stopPart) { clearLoops(loops); - getLoops(part, loops, stopPart, true,1); + getLoops(part, loops, stopPart, true, 1); clearLoops(loops); } - private void getLoops(GraphPart part, List loops, List stopPart, boolean first,int level) { + private void getLoops(GraphPart part, List loops, List stopPart, boolean first, int level) { boolean debugMode = false; if (stopPart == null) { @@ -853,7 +853,7 @@ public class Graph { if (part == null) { return; } - + if (debugMode) { System.err.println("getloops: " + part); } @@ -913,13 +913,13 @@ public class Graph { stopPart2.add(next); } if (next != part.nextParts.get(0)) { - getLoops(part.nextParts.get(0), loops, stopPart2, false,level+1); + getLoops(part.nextParts.get(0), loops, stopPart2, false, level + 1); } if (next != part.nextParts.get(1)) { - getLoops(part.nextParts.get(1), loops, stopPart2, false,level+1); + getLoops(part.nextParts.get(1), loops, stopPart2, false, level + 1); } if (next != null) { - getLoops(next, loops, stopPart, false,level); + getLoops(next, loops, stopPart, false, level); } } if (part.nextParts.size() > 2) { @@ -940,15 +940,15 @@ public class Graph { } } if (next != p) { - getLoops(p, loops, stopPart2, false,level+1); + getLoops(p, loops, stopPart2, false, level + 1); } } if (next != null) { - getLoops(next, loops, stopPart, false,level); + getLoops(next, loops, stopPart, false, level); } } if (part.nextParts.size() == 1) { - getLoops(part.nextParts.get(0), loops, stopPart, false,level); + getLoops(part.nextParts.get(0), loops, stopPart, false, level); } @@ -996,23 +996,23 @@ public class Graph { /*if (cand.path.equals(cand2.path)) { found = cand2; } else {*/ - int lev1=Integer.MAX_VALUE; - int lev2=Integer.MAX_VALUE; - for(int i=0;i namesMap = new HashMap<>(); - for (ABCContainerTag tag : abcPanel.list) { - cnt += tag.getABC().deobfuscateIdentifiers(namesMap); - } + cnt = swf.deobfuscateAS3Identifiers(); } else { cnt = swf.deobfuscateAS2Identifiers(); } diff --git a/trunk/src/com/jpexs/decompiler/flash/helpers/Helper.java b/trunk/src/com/jpexs/decompiler/flash/helpers/Helper.java index 47b80c4ff..cdfa83d6c 100644 --- a/trunk/src/com/jpexs/decompiler/flash/helpers/Helper.java +++ b/trunk/src/com/jpexs/decompiler/flash/helpers/Helper.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; +import java.util.BitSet; import java.util.List; import java.util.Stack; import java.util.logging.Level; @@ -356,4 +357,60 @@ public class Helper { } return f; } + private static BitSet fileNameInvalidChars; + private static final List invalidFilenamesParts; + + static { + BitSet toEncode = new BitSet(256); + + for (int i = 0; i < 32; i++) { + toEncode.set(i); + } + + toEncode.set('\\'); + toEncode.set('/'); + toEncode.set(':'); + toEncode.set('*'); + toEncode.set('?'); + toEncode.set('"'); + toEncode.set('<'); + toEncode.set('>'); + toEncode.set('|'); + + fileNameInvalidChars = toEncode; + + //windows reserved filenames: + invalidFilenamesParts = new ArrayList<>(); + invalidFilenamesParts.add("CON"); + invalidFilenamesParts.add("PRN"); + invalidFilenamesParts.add("AUX"); + invalidFilenamesParts.add("CLOCK$"); + invalidFilenamesParts.add("NUL"); + for (int i = 1; i <= 9; i++) { + invalidFilenamesParts.add("COM" + i); + invalidFilenamesParts.add("LPT" + i); + } + } + + public static String makeFileName(String str) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < str.length(); i++) { + int ch = (int) str.charAt(i); + if (ch < 256 && fileNameInvalidChars.get(ch)) { + sb.append("%").append(String.format("%02X", ch)); + } else { + sb.append((char) ch); + } + } + str = sb.toString(); + str = "." + str + "."; + for (String inv : invalidFilenamesParts) { + str = Pattern.compile("\\." + Pattern.quote(inv) + "\\.", Pattern.CASE_INSENSITIVE).matcher(str).replaceAll("._" + inv + "."); + } + str = str.substring(1, str.length() - 1); //remove dots + if (str.equals("")) { + str = "unnamed"; + } + return str; + } }