Issue #698 Better obfuscated identifiers handling, separated reserved keyword for AS2/AS3

This commit is contained in:
Jindra Petřík
2014-11-04 20:51:39 +01:00
parent b4aeb0f80f
commit 342247d5d2
20 changed files with 2124 additions and 2281 deletions
@@ -103,28 +103,7 @@ public class Action implements GraphSourceItem {
* Length of action data
*/
public int actionLength;
private long address;
public static final String[] reservedWords = {
"as", "break", "case", "catch", "class", "const", "continue", "default", "delete", "do", "else",
"extends", "false", "finally", "for", "function", "if", "implements", "import", "in", "instanceof",
"interface", "internal", "is", "native", "new", "null", "package", "private", "protected", "public",
"return", "super", "switch", "this", "throw",/*to*/ "true", "try", "typeof", "use", "var", /*"void",*/ "while",
"with",
//syntactic keywords - can be used as identifiers, but that have special meaning in certain contexts
"each", "get", "set", "namespace", "#include", "include", "dynamic", "final", "native", "override", "static", "in"};
public static boolean isReservedWord(String s) {
if (s == null) {
return false;
}
for (String rw : reservedWords) {
if (rw.equals(s.trim())) {
return true;
}
}
return false;
}
private long address;
/**
* Names of ActionScript properties
@@ -1,334 +0,0 @@
/*
* Copyright (C) 2010-2014 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.action;
import com.jpexs.decompiler.flash.abc.RenameType;
import com.jpexs.decompiler.flash.tags.DefineSpriteTag;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.regex.Pattern;
/**
*
* @author JPEXS
*/
public class Deobfuscation {
private final Random rnd = new Random();
private final int DEFAULT_FOO_SIZE = 10;
public HashSet<String> allVariableNamesStr = new HashSet<>();
private final HashMap<String, Integer> typeCounts = new HashMap<>();
public static final String VALID_FIRST_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_";
public static final String VALID_NEXT_CHARACTERS = VALID_FIRST_CHARACTERS + "0123456789";
public static final String FOO_CHARACTERS = "bcdfghjklmnpqrstvwz";
public static final String FOO_JOIN_CHARACTERS = "aeiouy";
private String fooString(HashMap<String, String> deobfuscated, String orig, boolean firstUppercase, int rndSize) {
boolean exists;
String ret;
loopfoo:
do {
exists = false;
int len = 3 + rnd.nextInt(rndSize - 3);
ret = "";
for (int i = 0; i < len; i++) {
String c = "";
if ((i % 2) == 0) {
c = "" + FOO_CHARACTERS.charAt(rnd.nextInt(FOO_CHARACTERS.length()));
} else {
c = "" + FOO_JOIN_CHARACTERS.charAt(rnd.nextInt(FOO_JOIN_CHARACTERS.length()));
}
if (i == 0 && firstUppercase) {
c = c.toUpperCase(Locale.ENGLISH);
}
ret += c;
}
if (allVariableNamesStr.contains(ret)) {
exists = true;
rndSize += 1;
continue loopfoo;
}
if (Action.isReservedWord(ret)) {
exists = true;
rndSize += 1;
continue;
}
if (deobfuscated.containsValue(ret)) {
exists = true;
rndSize += 1;
continue;
}
} while (exists);
return ret;
}
public void deobfuscateInstanceNames(HashMap<String, String> namesMap, RenameType renameType, List<Tag> tags, Map<String, String> selected) {
for (Tag t : tags) {
if (t instanceof DefineSpriteTag) {
deobfuscateInstanceNames(namesMap, renameType, ((DefineSpriteTag) t).subTags, selected);
}
if (t instanceof PlaceObjectTypeTag) {
PlaceObjectTypeTag po = (PlaceObjectTypeTag) t;
String name = po.getInstanceName();
if (name != null) {
String changedName = deobfuscateName(name, false, "instance", namesMap, renameType, selected);
if (changedName != null) {
po.setInstanceName(changedName);
((Tag) po).setModified(true);
}
}
String className = po.getClassName();
if (className != null) {
String changedClassName = deobfuscateNameWithPackage(className, namesMap, renameType, selected);
if (changedClassName != null) {
po.setClassName(changedClassName);
((Tag) po).setModified(true);
}
}
}
}
}
public String deobfuscatePackage(String pkg, HashMap<String, String> namesMap, RenameType renameType, Map<String, String> selected) {
if (namesMap.containsKey(pkg)) {
return namesMap.get(pkg);
}
String[] parts = null;
if (pkg.contains(".")) {
parts = pkg.split("\\.");
} else {
parts = new String[]{pkg};
}
String ret = "";
boolean isChanged = false;
for (int p = 0; p < parts.length; p++) {
if (p > 0) {
ret += ".";
}
String partChanged = deobfuscateName(parts[p], false, "package", namesMap, renameType, selected);
if (partChanged != null) {
ret += partChanged;
isChanged = true;
} else {
ret += parts[p];
}
}
if (isChanged) {
namesMap.put(pkg, ret);
return ret;
}
return null;
}
public String deobfuscateNameWithPackage(String n, HashMap<String, String> namesMap, RenameType renameType, Map<String, String> selected) {
String pkg = null;
String name = "";
if (n.contains(".")) {
pkg = n.substring(0, n.lastIndexOf('.'));
name = n.substring(n.lastIndexOf('.') + 1);
} else {
name = n;
}
boolean changed = false;
if ((pkg != null) && (!pkg.isEmpty())) {
String changedPkg = deobfuscatePackage(pkg, namesMap, renameType, selected);
if (changedPkg != null) {
changed = true;
pkg = changedPkg;
}
}
String changedName = deobfuscateName(name, true, "class", namesMap, renameType, selected);
if (changedName != null) {
changed = true;
name = changedName;
}
if (changed) {
String newClassName = "";
if (pkg == null) {
newClassName = name;
} else {
newClassName = pkg + "." + name;
}
return newClassName;
}
return null;
}
public static boolean isValidName(String s, String... exceptions) {
boolean isValid = true;
for (String e : exceptions) {
if (e.equals(s)) {
return true;
}
}
if (Action.isReservedWord(s)) {
isValid = false;
}
if (isValid) {
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) > 127) {
isValid = false;
break;
}
}
}
if (isValid) {
Pattern pat = Pattern.compile("^[" + Pattern.quote(VALID_FIRST_CHARACTERS) + "]" + "[" + Pattern.quote(VALID_FIRST_CHARACTERS + VALID_NEXT_CHARACTERS) + "]*$");
if (!pat.matcher(s).matches()) {
isValid = false;
}
}
return isValid;
}
public String deobfuscateName(String s, boolean firstUppercase, String usageType, HashMap<String, String> namesMap, RenameType renameType, Map<String, String> selected) {
boolean isValid = true;
if (usageType == null) {
usageType = "name";
}
if (selected != null) {
if (selected.containsKey(s)) {
return selected.get(s);
}
}
isValid = isValidName(s);
if (!isValid) {
if (namesMap.containsKey(s)) {
return namesMap.get(s);
} else {
Integer cnt = typeCounts.get(usageType);
if (cnt == null) {
cnt = 0;
}
String ret = null;
if (renameType == RenameType.TYPENUMBER) {
boolean found;
do {
found = false;
cnt++;
ret = usageType + "_" + cnt;
found = allVariableNamesStr.contains(ret);
} while (found);
typeCounts.put(usageType, cnt);
} else if (renameType == RenameType.RANDOMWORD) {
ret = fooString(namesMap, s, firstUppercase, DEFAULT_FOO_SIZE);
}
namesMap.put(s, ret);
return ret;
}
}
return null;
}
public static String makeObfuscatedIdentifier(String s) {
return "\u00A7" + escapeOIdentifier(s) + "\u00A7";
}
private static final Map<String, String> nameCache = new HashMap<>();
/**
* Ensures identifier is valid and if not, uses paragraph syntax
*
* @param s Identifier
* @param validExceptions Exceptions which are valid (e.g. some reserved
* words)
* @return
*/
public static String printIdentifier(String s, String... validExceptions) {
if (s.startsWith("\u00A7") && s.endsWith("\u00A7")) { //Assuming already printed - TODO:detect better
return s;
}
if (nameCache.containsKey(s)) {
return nameCache.get(s);
}
if (isValidName(s, validExceptions)) {
nameCache.put(s, s);
return s;
}
String ret = makeObfuscatedIdentifier(s);
nameCache.put(s, ret);
return ret;
}
public static String printNamespace(String pkg, String... validNameExceptions) {
if (nameCache.containsKey(pkg)) {
return nameCache.get(pkg);
}
if (pkg.isEmpty()) {
nameCache.put(pkg, pkg);
return pkg;
}
String[] parts = null;
if (pkg.contains(".")) {
parts = pkg.split("\\.");
} else {
parts = new String[]{pkg};
}
String ret = "";
for (int i = 0; i < parts.length; i++) {
if (i > 0) {
ret += ".";
}
ret += printIdentifier(parts[i], validNameExceptions);
}
nameCache.put(pkg, ret);
return ret;
}
public static String escapeOIdentifier(String s) {
StringBuilder ret = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '\n') {
ret.append("\\n");
} else if (c == '\r') {
ret.append("\\r");
} else if (c == '\t') {
ret.append("\\t");
} else if (c == '\b') {
ret.append("\\b");
} else if (c == '\t') {
ret.append("\\t");
} else if (c == '\f') {
ret.append("\\f");
} else if (c == '\\') {
ret.append("\\\\");
} else if (c == '\u00A7') {
ret.append("\\\u00A7");
} else {
ret.append(c);
}
}
return ret.toString();
}
}
@@ -17,7 +17,7 @@
package com.jpexs.decompiler.flash.action.model;
import com.jpexs.decompiler.flash.SourceGeneratorLocalData;
import com.jpexs.decompiler.flash.action.Deobfuscation;
import com.jpexs.decompiler.flash.IdentifiersDeobfuscation;
import com.jpexs.decompiler.flash.action.swf5.ActionCallFunction;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
import com.jpexs.decompiler.graph.CompilationException;
@@ -49,7 +49,7 @@ public class CallFunctionActionItem extends ActionItem {
@Override
public GraphTextWriter appendTo(GraphTextWriter writer, LocalData localData) throws InterruptedException {
String paramStr = "";
writer.append(Deobfuscation.printIdentifier(((DirectValueActionItem) functionName).toStringNoQuotes(localData)));
writer.append(IdentifiersDeobfuscation.printIdentifier(false,((DirectValueActionItem) functionName).toStringNoQuotes(localData)));
writer.spaceBeforeCallParenthesies(arguments.size());
writer.append("(");
for (int t = 0; t < arguments.size(); t++) {
@@ -17,7 +17,7 @@
package com.jpexs.decompiler.flash.action.model;
import com.jpexs.decompiler.flash.SourceGeneratorLocalData;
import com.jpexs.decompiler.flash.action.Deobfuscation;
import com.jpexs.decompiler.flash.IdentifiersDeobfuscation;
import com.jpexs.decompiler.flash.action.swf5.ActionCallMethod;
import com.jpexs.decompiler.flash.ecma.Undefined;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
@@ -73,7 +73,7 @@ public class CallMethodActionItem extends ActionItem {
scriptObject.toString(writer, localData);
}
writer.append(".");
writer.append(Deobfuscation.printIdentifier(methodName.toStringNoQuotes(localData)));
writer.append(IdentifiersDeobfuscation.printIdentifier(false,methodName.toStringNoQuotes(localData)));
} else {
scriptObject.toString(writer, localData);
}
@@ -17,7 +17,7 @@
package com.jpexs.decompiler.flash.action.model;
import com.jpexs.decompiler.flash.SourceGeneratorLocalData;
import com.jpexs.decompiler.flash.action.Deobfuscation;
import com.jpexs.decompiler.flash.IdentifiersDeobfuscation;
import com.jpexs.decompiler.flash.action.swf5.ActionDefineLocal;
import com.jpexs.decompiler.flash.action.swf5.ActionDefineLocal2;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
@@ -76,8 +76,8 @@ public class DefineLocalActionItem extends ActionItem implements SetTypeActionIt
public GraphTextWriter appendTo(GraphTextWriter writer, LocalData localData) throws InterruptedException {
writer.append("var ");
if (((name instanceof DirectValueActionItem)) && (((DirectValueActionItem) name).isString()) && (!Deobfuscation.isValidName(((DirectValueActionItem) name).toStringNoQuotes(localData), "this", "super"))) {
writer.append(Deobfuscation.makeObfuscatedIdentifier(((DirectValueActionItem) name).toStringNoQuotes(localData)));
if (((name instanceof DirectValueActionItem)) && (((DirectValueActionItem) name).isString()) && (!IdentifiersDeobfuscation.isValidName(false,((DirectValueActionItem) name).toStringNoQuotes(localData), "this", "super"))) {
writer.append(IdentifiersDeobfuscation.makeObfuscatedIdentifier(((DirectValueActionItem) name).toStringNoQuotes(localData)));
} else {
stripQuotes(name, localData, writer);
}
@@ -12,13 +12,14 @@
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.␍ */
* License along with this library.
*/
package com.jpexs.decompiler.flash.action.model;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SourceGeneratorLocalData;
import com.jpexs.decompiler.flash.action.Action;
import com.jpexs.decompiler.flash.action.Deobfuscation;
import com.jpexs.decompiler.flash.IdentifiersDeobfuscation;
import com.jpexs.decompiler.flash.action.parser.script.ActionSourceGenerator;
import com.jpexs.decompiler.flash.action.parser.script.VariableActionItem;
import com.jpexs.decompiler.flash.action.swf4.RegisterNumber;
@@ -82,15 +83,15 @@ public class FunctionActionItem extends ActionItem {
if (calculatedFunctionName != null) {
writer.append(" ");
String fname = calculatedFunctionName.toStringNoQuotes(localData);
if (!Deobfuscation.isValidName(fname)) {
writer.append(Deobfuscation.makeObfuscatedIdentifier(fname));
if (!IdentifiersDeobfuscation.isValidName(false,fname)) {
writer.append(IdentifiersDeobfuscation.makeObfuscatedIdentifier(fname));
} else {
calculatedFunctionName.appendToNoQuotes(writer, localData);
}
} else if (!functionName.isEmpty()) {
writer.append(" ");
if (!Deobfuscation.isValidName(functionName)) {
writer.append(Deobfuscation.makeObfuscatedIdentifier(functionName));
if (!IdentifiersDeobfuscation.isValidName(false,functionName)) {
writer.append(IdentifiersDeobfuscation.makeObfuscatedIdentifier(functionName));
} else {
writer.append(functionName);
}
@@ -106,8 +107,8 @@ public class FunctionActionItem extends ActionItem {
if (pname == null || pname.isEmpty()) {
pname = new RegisterNumber(regStart + p).translate();
}
if (!Deobfuscation.isValidName(pname)) {
writer.append(Deobfuscation.makeObfuscatedIdentifier(pname));
if (!IdentifiersDeobfuscation.isValidName(false,pname)) {
writer.append(IdentifiersDeobfuscation.makeObfuscatedIdentifier(pname));
}
writer.append(pname);
}
@@ -12,11 +12,12 @@
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.␍ */
* License along with this library.
*/
package com.jpexs.decompiler.flash.action.model;
import com.jpexs.decompiler.flash.SourceGeneratorLocalData;
import com.jpexs.decompiler.flash.action.Deobfuscation;
import com.jpexs.decompiler.flash.IdentifiersDeobfuscation;
import com.jpexs.decompiler.flash.action.swf5.ActionGetMember;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
import com.jpexs.decompiler.graph.CompilationException;
@@ -49,7 +50,7 @@ public class GetMemberActionItem extends ActionItem {
@Override
public GraphTextWriter appendTo(GraphTextWriter writer, LocalData localData) throws InterruptedException {
object.toString(writer, localData);
if ((!(memberName instanceof DirectValueActionItem)) || (!((DirectValueActionItem) memberName).isString()) || (!Deobfuscation.isValidName(((DirectValueActionItem) memberName).toStringNoQuotes(localData)))) {
if ((!(memberName instanceof DirectValueActionItem)) || (!((DirectValueActionItem) memberName).isString()) || (!IdentifiersDeobfuscation.isValidName(false,((DirectValueActionItem) memberName).toStringNoQuotes(localData)))) {
writer.append("[");
memberName.toString(writer, localData);
return writer.append("]");
@@ -17,7 +17,7 @@
package com.jpexs.decompiler.flash.action.model;
import com.jpexs.decompiler.flash.SourceGeneratorLocalData;
import com.jpexs.decompiler.flash.action.Deobfuscation;
import com.jpexs.decompiler.flash.IdentifiersDeobfuscation;
import com.jpexs.decompiler.flash.action.swf4.ActionGetVariable;
import com.jpexs.decompiler.flash.ecma.Undefined;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
@@ -59,8 +59,8 @@ public class GetVariableActionItem extends ActionItem {
@Override
public GraphTextWriter appendTo(GraphTextWriter writer, LocalData localData) throws InterruptedException {
if (((name instanceof DirectValueActionItem)) && (((DirectValueActionItem) name).isString()) && (!Deobfuscation.isValidName(((DirectValueActionItem) name).toStringNoQuotes(localData), "this", "super"))) {
return writer.append(Deobfuscation.makeObfuscatedIdentifier(((DirectValueActionItem) name).toStringNoQuotes(localData)));
if (((name instanceof DirectValueActionItem)) && (((DirectValueActionItem) name).isString()) && (!IdentifiersDeobfuscation.isValidName(false,((DirectValueActionItem) name).toStringNoQuotes(localData), "this", "super"))) {
return writer.append(IdentifiersDeobfuscation.makeObfuscatedIdentifier(((DirectValueActionItem) name).toStringNoQuotes(localData)));
} else if ((!(name instanceof DirectValueActionItem)) || (!((DirectValueActionItem) name).isString())) {
writer.append("eval(");
name.appendTo(writer, localData);
@@ -12,11 +12,12 @@
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.␍ */
* License along with this library.
*/
package com.jpexs.decompiler.flash.action.model;
import com.jpexs.decompiler.flash.SourceGeneratorLocalData;
import com.jpexs.decompiler.flash.action.Deobfuscation;
import com.jpexs.decompiler.flash.IdentifiersDeobfuscation;
import com.jpexs.decompiler.flash.action.parser.script.ActionSourceGenerator;
import com.jpexs.decompiler.flash.action.swf4.ActionPush;
import com.jpexs.decompiler.flash.action.swf4.RegisterNumber;
@@ -84,7 +85,7 @@ public class SetMemberActionItem extends ActionItem implements SetTypeActionItem
public GraphTextWriter appendTo(GraphTextWriter writer, LocalData localData) throws InterruptedException {
object.toString(writer, localData);
if ((!(objectName instanceof DirectValueActionItem)) || (!((DirectValueActionItem) objectName).isString()) || (!Deobfuscation.isValidName(((DirectValueActionItem) objectName).toStringNoQuotes(localData)))) {
if ((!(objectName instanceof DirectValueActionItem)) || (!((DirectValueActionItem) objectName).isString()) || (!IdentifiersDeobfuscation.isValidName(false,((DirectValueActionItem) objectName).toStringNoQuotes(localData)))) {
writer.append("[");
objectName.toString(writer, localData);
writer.append("]");
@@ -17,7 +17,7 @@
package com.jpexs.decompiler.flash.action.model;
import com.jpexs.decompiler.flash.SourceGeneratorLocalData;
import com.jpexs.decompiler.flash.action.Deobfuscation;
import com.jpexs.decompiler.flash.IdentifiersDeobfuscation;
import com.jpexs.decompiler.flash.action.parser.script.ActionSourceGenerator;
import com.jpexs.decompiler.flash.action.swf4.ActionPush;
import com.jpexs.decompiler.flash.action.swf4.ActionSetVariable;
@@ -73,8 +73,8 @@ public class SetVariableActionItem extends ActionItem implements SetTypeActionIt
@Override
public GraphTextWriter appendTo(GraphTextWriter writer, LocalData localData) throws InterruptedException {
if (((name instanceof DirectValueActionItem)) && (((DirectValueActionItem) name).isString()) && (!Deobfuscation.isValidName(((DirectValueActionItem) name).toStringNoQuotes(localData), "this", "super"))) {
writer.append(Deobfuscation.makeObfuscatedIdentifier(((DirectValueActionItem) name).toStringNoQuotes(localData)));
if (((name instanceof DirectValueActionItem)) && (((DirectValueActionItem) name).isString()) && (!IdentifiersDeobfuscation.isValidName(false,((DirectValueActionItem) name).toStringNoQuotes(localData), "this", "super"))) {
writer.append(IdentifiersDeobfuscation.makeObfuscatedIdentifier(((DirectValueActionItem) name).toStringNoQuotes(localData)));
writer.append(" = ");
return value.toString(writer, localData);
} else if ((!(name instanceof DirectValueActionItem)) || (!((DirectValueActionItem) name).isString())) {