mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-05-31 00:15:22 +00:00
Issue #698 Better obfuscated identifiers handling, separated reserved keyword for AS2/AS3
This commit is contained in:
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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 IdentifiersDeobfuscation {
|
||||
|
||||
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";
|
||||
|
||||
//http://help.adobe.com/en_US/AS2LCR/Flash_10.0/help.html?content=00000477.html
|
||||
public static final String[] reservedWordsAS2= {
|
||||
//is "add" really a keyword? documentation says yes, but I can create "add" variable in CS6...
|
||||
//"add",
|
||||
"and","break","case","catch","class","continue","default","delete","do","dynamic","else",
|
||||
"eq","extends","false","finally","for","function","ge","get","gt","if","ifFrameLoaded","implements",
|
||||
"import","in","instanceof","interface","intrinsic","le",
|
||||
//is "it" really a keyword? documentation says yes, but I can create "it" variable in CS6...
|
||||
//"it",
|
||||
"ne","new","not","null","on","onClipEvent",
|
||||
"or","private","public","return","set","static","super","switch","tellTarget","this","throw","try",
|
||||
"typeof","undefined","var","void","while","with"
|
||||
};
|
||||
|
||||
//http://www.adobe.com/devnet/actionscript/learning/as3-fundamentals/syntax.html
|
||||
public static final String[] reservedWordsAS3 = {
|
||||
"as", "break", "case", "catch", "class", "const", "continue", "default", "delete", "do", "else",
|
||||
"extends", "false", "finally", "for", "function", "if", "implements", "import", "in", "instanceof",
|
||||
"interface", "internal", "is", "new", "null", "package", "private", "protected", "public",
|
||||
"return", "super", "switch", "this", "throw",
|
||||
//is "to" really a keyword? documentation says yes, but I can create "to" variable...
|
||||
// "to",
|
||||
"true", "try", "typeof", "use", "var",
|
||||
"void","while","with"
|
||||
};
|
||||
//syntactic keywords - can be used as identifiers, but that have special meaning in certain contexts
|
||||
public static final String[] syntacticKeywordsAS3= {"each", "get", "set", "namespace", "include", "dynamic", "final", "native", "override", "static"};
|
||||
|
||||
public static boolean isReservedWord(String s,boolean as3) {
|
||||
if (s == null) {
|
||||
return false;
|
||||
}
|
||||
String reservedWords[] = as3?reservedWordsAS3:reservedWordsAS2;
|
||||
for (String rw : reservedWords) {
|
||||
if (rw.equals(s.trim())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String fooString(boolean as3,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 (isReservedWord(ret,as3)) {
|
||||
exists = true;
|
||||
rndSize += 1;
|
||||
continue;
|
||||
}
|
||||
if (deobfuscated.containsValue(ret)) {
|
||||
exists = true;
|
||||
rndSize += 1;
|
||||
continue;
|
||||
}
|
||||
} while (exists);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void deobfuscateInstanceNames(boolean as3,HashMap<String, String> namesMap, RenameType renameType, List<Tag> tags, Map<String, String> selected) {
|
||||
for (Tag t : tags) {
|
||||
if (t instanceof DefineSpriteTag) {
|
||||
deobfuscateInstanceNames(as3,namesMap, renameType, ((DefineSpriteTag) t).subTags, selected);
|
||||
}
|
||||
if (t instanceof PlaceObjectTypeTag) {
|
||||
PlaceObjectTypeTag po = (PlaceObjectTypeTag) t;
|
||||
String name = po.getInstanceName();
|
||||
if (name != null) {
|
||||
String changedName = deobfuscateName(as3,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(as3,className, namesMap, renameType, selected);
|
||||
if (changedClassName != null) {
|
||||
po.setClassName(changedClassName);
|
||||
((Tag) po).setModified(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String deobfuscatePackage(boolean as3,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(as3,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(boolean as3,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(as3,pkg, namesMap, renameType, selected);
|
||||
if (changedPkg != null) {
|
||||
changed = true;
|
||||
pkg = changedPkg;
|
||||
}
|
||||
}
|
||||
String changedName = deobfuscateName(as3,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(boolean as3,String s, String... exceptions) {
|
||||
boolean isValid = true;
|
||||
|
||||
for (String e : exceptions) {
|
||||
if (e.equals(s)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isReservedWord(s,as3)) {
|
||||
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(boolean as3,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(as3,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(as3,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(boolean as3, 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(as3, s, validExceptions)) {
|
||||
nameCache.put(s, s);
|
||||
return s;
|
||||
}
|
||||
String ret = makeObfuscatedIdentifier(s);
|
||||
nameCache.put(s, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static String printNamespace(boolean as3,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(as3,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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user