Created
January 16, 2016 14:58
-
-
Save compustar/7c96d656ab0c19b4aeec to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * JavaScript Obfucator 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 2 of the License, or | |
| * (at your option) any later version. | |
| * | |
| * JavaScript Obfucator 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, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
| * | |
| */ | |
| import java.io.BufferedReader; | |
| import java.io.File; | |
| import java.io.FileReader; | |
| import java.io.FileWriter; | |
| import java.io.IOException; | |
| import java.io.PrintWriter; | |
| import java.util.ArrayList; | |
| import java.util.HashMap; | |
| import java.util.HashSet; | |
| import java.util.Iterator; | |
| import java.util.StringTokenizer; | |
| /** | |
| * Obfuscate the JavaScript code. | |
| * @author Shane Ng <starford at gmail dot com> | |
| */ | |
| public class JSO { | |
| public static final String[] reserved = { | |
| "abstract", "else", "instanceof", "switch", "boolean", "enum", "int", | |
| "synchronized", "break", "export", "interface", "this", "byte", "extends", | |
| "long", "throw", "case", "false", "native", "throws", "catch", "final", | |
| "new", "transient", "char", "finally", "null", "true", "class", "float", | |
| "package", "try", "const", "for", "private", "typeof", "continue", "function", | |
| "protected", "var", "debugger", "goto", "public", "void", "default", "if", | |
| "return", "volatile", "delete", "implements", "short", "while", "do", "import", | |
| "static", "with", "double", "in", "super", "undefined", "arguments" | |
| }; | |
| public static final String[] builtIn = { | |
| "alert", "hidden", "outerWidth", "all", "history", "packages", "anchor", | |
| "image", "pageXOffset", "anchors", "images", "pageYOffset", "area", "isNaN", | |
| "parent", "array", "java", "parseFloat", "assign", "JavaArray", "parseInt", | |
| "blur", "JavaClass", "password", "button", "JavaObject", "pkcs11", "checkbox", | |
| "JavaPackage", "plugin", "clearTimeout", "innerHeight", "prompt", | |
| "clientInformation", "innerWidth", "prototype", "close", "layer", "radio", | |
| "closed", "layers", "reset", "confirm", "length", "screenX", "crypto", "link", | |
| "screenY", "date", "location", "scroll", "defaultStatus", "Math", "secure", | |
| "document", "mimeTypes", "select", "element", "name", "self", "elements", "navigate", | |
| "setTimeout", "embed", "navigator", "status", "embeds", "netscape", "String", | |
| "escape", "Number", "submit", "eval", "Object", "sun", "event", "offscreenBuffering", | |
| "taint", "fileUpload", "onblur", "text", "focus", "onerror", "textarea", "form", | |
| "onfocus", "top", "forms", "onload", "toString", "frame", "onunload", "unescape", | |
| "frames", "open", "untaint", "frameRate", "opener", "valueOf", "function", "option", | |
| "window", "getClass", "outerHeight", | |
| "setInterval", "clearInterval", "setTimeout", "clearTimeout" | |
| }; | |
| public static final char[] DELIMITER = {'?', ':', '!', '=', '(', ')', '[', ']', | |
| '{', '}', '\r', '\n', '\t', ' ', '\"', '\'', '<', '>', ',', '.', '/', | |
| '\\', '+', '-', '*', '&', '|', '^', '%', ';' | |
| }; | |
| public static final String[] alpha = { | |
| "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", | |
| "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "$", "_" | |
| }; | |
| public static final HashSet exclusionTokenSet = new HashSet(); | |
| public static final HashSet privatePrefixSet = new HashSet(); | |
| public static int ref = alpha.length; | |
| public static HashMap map = new HashMap(); | |
| public static final String ARG_EXCLUDE_TOKENS = "e="; | |
| public static final String ARG_DESTINATION_DIR = "d="; | |
| public static final String ARG_OBFUSCATE_STRING = "o="; | |
| public static final String ARG_PRIVATE_PREFIX = "p="; | |
| private double[] stringObfuscationParameter = {0, 0, 0.5}; | |
| private String[] file = null; | |
| private String destinationDir = null; | |
| private JSOState state = new JSOState(); | |
| private String delimiter = new String(DELIMITER); | |
| public static void main(String[] args) throws Exception { | |
| ArrayList fileList = new ArrayList(args.length); | |
| String[] file = null; | |
| String destinationDir = null; | |
| double[] stringObfuscationParameter = {0, 0, 0.5}; | |
| if (args.length == 0) { | |
| printUsage(); | |
| return; | |
| } else if (args.length > 1) { | |
| for (int i = 0; i < args.length; i++) { | |
| if (args[i].startsWith(ARG_EXCLUDE_TOKENS)) { | |
| readExclusionTokenSet(args[i].substring(ARG_EXCLUDE_TOKENS.length())); | |
| } else if (args[i].startsWith(ARG_DESTINATION_DIR) && destinationDir == null) { | |
| File dir = new File(args[i].substring(ARG_DESTINATION_DIR.length())); | |
| if (!dir.exists() && !dir.mkdirs()) { | |
| System.err.println("Cannot create the output directory \"" + dir.getName() + "\""); | |
| return; | |
| } else if (dir.exists() && dir.isFile()) { | |
| System.err.println("The output parameter \"" + args[i] + "\" is not a directory"); | |
| return; | |
| } | |
| destinationDir = dir.getName(); | |
| } else if (args[i].startsWith(ARG_OBFUSCATE_STRING)) { | |
| String[] param = args[i].substring(ARG_OBFUSCATE_STRING.length()).split(",", 3); | |
| if (param.length >= 2) { | |
| try { | |
| stringObfuscationParameter[0] = Double.parseDouble(param[0]); | |
| stringObfuscationParameter[1] = Double.parseDouble(param[1]); | |
| if (param.length == 3) { | |
| stringObfuscationParameter[2] = Double.parseDouble(param[2]); | |
| } | |
| } catch (NumberFormatException e) { | |
| System.err.println("The obfuscation parameters are not numbers."); | |
| return; | |
| } | |
| } else { | |
| System.err.println("At least 2 obfuscation parameters are required, e.g. o=0.4,0.7."); | |
| return; | |
| } | |
| } else if (args[i].startsWith(ARG_PRIVATE_PREFIX)) { | |
| String[] prefix = args[i].substring(ARG_PRIVATE_PREFIX.length()).split(","); | |
| for (int j = 0; j < prefix.length; j++) { | |
| if (prefix[j].length() > 0) { | |
| privatePrefixSet.add(prefix[j]); | |
| } | |
| } | |
| } else { | |
| fileList.add(args[i]); | |
| } | |
| } | |
| file = new String[fileList.size()]; | |
| fileList.toArray(file); | |
| } else { | |
| file = new String[]{args[0]}; | |
| } | |
| addExclusionTokenSet(reserved); | |
| addExclusionTokenSet(builtIn); | |
| JSO obfuscator = new JSO(file, destinationDir, stringObfuscationParameter); | |
| obfuscator.run(); | |
| } | |
| private static void printUsage() { | |
| System.err.println("Usage: java JSO <list of javascript file> [options]"); | |
| System.err.println("where the options are:"); | |
| System.err.println("\te=<exception list file>"); | |
| System.err.println("\t\t- filename of the exception list"); | |
| System.err.println("\t\t- exception tokens are delimited by tab, space, dot, comma, "); | |
| System.err.println("\t\t single quote and double quote"); | |
| System.err.println("\td=<destination directory>"); | |
| System.err.println("\t\t- the output directory"); | |
| System.err.println("\t\t- print to the STDOUT if not specified"); | |
| System.err.println("\to=<obfuscation parameters of string literals>"); | |
| System.err.println("\t\t- If it is specified, the characters in string literals will be "); | |
| System.err.println("\t\t encoded to either \\uXXXX (hexidemcial) or \\XXX (octal) format"); | |
| System.err.println("\t\t- The parameters are a 2 or 3 floating point values delimited "); | |
| System.err.println(" by commas. e.g. 0.5,0.3 or 0.5,0.3,0.9"); | |
| System.err.println("\t\t- The values are "); | |
| System.err.println("\t\t * probability to encode a token in a string literal"); | |
| System.err.println("\t\t * probability to encode a character in a candidate token"); | |
| System.err.println("\t\t * probability to encode a character into \\uXXXX format"); | |
| System.err.println("\t\t- The last parameter is set to 0.5 if not specified"); | |
| System.err.println("\tp=<prefix for private members>"); | |
| System.err.println("\t\t- \"private\" prefix to ensure that functions/variables preceeded"); | |
| System.err.println("\t\t by the prefix are obfuscated even when preceded by \"this.\""); | |
| System.err.println(""); | |
| System.err.print("Press Enter to read the examples..."); | |
| try{ | |
| System.in.read(); | |
| } catch (Exception e){} | |
| System.err.println("Examples:"); | |
| System.err.println(""); | |
| System.err.println(" Obfuscate all scripts in the current directory and output to ./out directory:"); | |
| System.err.println("\tjava JSO *.js d=out"); | |
| System.err.println(""); | |
| System.err.println(" Pipe the STDOUT output to x.o.js:"); | |
| System.err.println("\tjava JSO x.js > x.o.js "); | |
| System.err.println(""); | |
| System.err.println(" Obfuscate class member preceded by \"_\" or \"__\" (e.g. this._foo ):"); | |
| System.err.println("\tjava JSO *.js p=_,__"); | |
| System.err.println(""); | |
| System.err.println(" Merge a.js and b.js and pipe the merged output to script.js. Tokens in "); | |
| System.err.println(" exception list, noReplace.txt will not be replaced:"); | |
| System.err.println("\tjava JSO a.js b.js e=noReplace.txt > script.js"); | |
| System.err.println(""); | |
| System.err.println(" Obfuscate the 100% of string literals, 68% of the characters will be encoded. "); | |
| System.err.println(" 50% of the characters will be encoded as \\uXXXX format (default):"); | |
| System.err.println("\tjava JSO x.js o=1,0.68"); | |
| } | |
| public JSO(String[] file, String destinationDir, double[] stringObfuscationParameter){ | |
| this.file = file; | |
| this.destinationDir = destinationDir; | |
| if (stringObfuscationParameter != null && stringObfuscationParameter.length == 3) { | |
| this.stringObfuscationParameter = stringObfuscationParameter; | |
| } | |
| } | |
| public void run() throws IOException { | |
| for (int i = 0; i < file.length; i++) { | |
| BufferedReader in = new BufferedReader(new FileReader(file[i])); | |
| PrintWriter out = null; | |
| if (destinationDir == null) { | |
| out = new PrintWriter(System.out, true); | |
| this.obfuscate(in, out); | |
| out.flush(); | |
| } else { | |
| out = new PrintWriter(new FileWriter(new File(destinationDir + File.separator + file[i]))); | |
| this.obfuscate(in, out); | |
| out.flush(); | |
| out.close(); | |
| } | |
| in.close(); | |
| } | |
| System.err.println(map); | |
| } | |
| public void obfuscate(BufferedReader in, PrintWriter out) throws IOException { | |
| state.reset(); | |
| for (String line = in.readLine(); line != null; line = in.readLine()) { | |
| line = line.trim(); | |
| if (line.length() == 0) { | |
| continue; | |
| } | |
| StringTokenizer st = new StringTokenizer(line, delimiter, true); | |
| if (st.hasMoreTokens()) { | |
| state.setToken(st.nextToken()); | |
| } | |
| for (; state.token != null; state.skipToken()) { | |
| if (st.hasMoreTokens()) { | |
| state.setNextToken(st.nextToken()); | |
| } else { | |
| state.noToken(); | |
| } | |
| boolean doubleSlashed = state.flipFlags(); | |
| if (doubleSlashed) { | |
| break; | |
| } | |
| handleToken(out); | |
| } | |
| if (!state.delimiter && !state.commented) { | |
| out.println(); | |
| } | |
| } | |
| } | |
| private void handleToken(PrintWriter out) { | |
| if (state.token.length() > 0) { | |
| if (state.delimiter) { | |
| if (state.inString() && !state.backslashed && state.c != '\\' && | |
| state.c != '\"' && state.c != '\'') { | |
| state.token = obfuscateQuotedString(state.token); | |
| } | |
| } else { | |
| if (state.inString()) { | |
| if (!state.backslashed) { | |
| state.token = obfuscateQuotedString(state.token); | |
| } | |
| } else if (state.isReplacableToken() && canReplace(state.token)){ | |
| state.token = generateToken(state.token); | |
| } | |
| } | |
| } | |
| if (!state.commented && (state.printToken || state.inString())) { | |
| out.print(state.token); | |
| } | |
| if (state.c == '}' && !state.commented && !state.inString()) { | |
| out.println(); | |
| } | |
| } | |
| private static void readExclusionTokenSet(String file) throws IOException { | |
| BufferedReader in = null; | |
| try { | |
| in = new BufferedReader(new FileReader(file)); | |
| for (String line = in.readLine(); line != null; line = in.readLine()) { | |
| StringTokenizer st = new StringTokenizer(line, "\t ,.\"\'"); | |
| for (; st.hasMoreTokens();) { | |
| exclusionTokenSet.add(st.nextToken()); | |
| } | |
| } | |
| } finally { | |
| if (in != null) { | |
| in.close(); | |
| } | |
| } | |
| } | |
| private String obfuscateQuotedString(String token) { | |
| if (Math.random() < stringObfuscationParameter[0]) { | |
| StringBuffer buffer = new StringBuffer(token.length()); | |
| int n = token.length(); | |
| int pos = 0; | |
| for (int i = 0; i < n; i++) { | |
| if (Math.random() < stringObfuscationParameter[1]) { | |
| buffer.append(token.substring(pos, i)); | |
| encode(token.charAt(i), buffer); | |
| pos = i + 1; | |
| } | |
| } | |
| if (pos < n) { | |
| buffer.append(token.substring(pos)); | |
| } | |
| return buffer.toString(); | |
| } else { | |
| return token; | |
| } | |
| } | |
| private void encode(char c, StringBuffer buffer) { | |
| if (Math.random() < stringObfuscationParameter[2] || c > 077) { | |
| buffer.append("\\u"); | |
| encode(c, 16, 4, buffer); | |
| } else { | |
| buffer.append("\\"); | |
| encode(c, 8, 3, buffer); | |
| } | |
| } | |
| private void encode(char c, int radix, int length, StringBuffer buffer) { | |
| String value = Integer.toString(c, radix); | |
| int n = length - value.length(); | |
| if (n > 0) { | |
| for (int i = 0; i < n; i++) { | |
| buffer.append('0'); | |
| } | |
| buffer.append(value); | |
| } else { | |
| buffer.append(value.substring(-n)); | |
| } | |
| } | |
| private static String generateToken(String token) { | |
| if (map.containsKey(token)) { | |
| return (String) map.get(token); | |
| } else { | |
| String result = null; | |
| do { | |
| StringBuffer buffer = new StringBuffer(token.length()); | |
| for (int i = ref; i > 0; i = i / alpha.length) { | |
| buffer.append(alpha[i % alpha.length]); | |
| } | |
| ref++; | |
| result = buffer.toString(); | |
| } while (exclusionTokenSet.contains(result)); | |
| map.put(token, result); | |
| return result; | |
| } | |
| } | |
| private static boolean canReplace(String token) { | |
| if (token.length() <= 1) { | |
| return false; | |
| } | |
| if (token.length() > 0) { | |
| if (Character.isUpperCase(token.charAt(0))) { | |
| return false; | |
| } | |
| if (Character.isDigit(token.charAt(0))) { | |
| return false; | |
| } | |
| if (exclusionTokenSet.contains(token)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| public static void addExclusionTokenSet(String[] array) { | |
| if (array != null) { | |
| for (int i = 0; i < array.length; i++) { | |
| exclusionTokenSet.add(array[i]); | |
| } | |
| } | |
| } | |
| public static boolean isDelimiter(String token) { | |
| if (token != null && token.length() > 0) { | |
| for (int i = 0; i < DELIMITER.length; i++) { | |
| if (token.charAt(0) == DELIMITER[i]) { | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| } | |
| class JSOState { | |
| boolean dotted = false; | |
| boolean doubleQuoted = false; | |
| boolean singleQuoted = false; | |
| boolean backslashed = false; | |
| boolean commented = false; | |
| boolean printToken = true; | |
| boolean delimiter = false; | |
| String token; | |
| String lastToken; | |
| String nextToken; | |
| char c0 = 0; | |
| char c = 0; | |
| char c2 = 0; | |
| void reset() { | |
| dotted = false; | |
| doubleQuoted = false; | |
| singleQuoted = false; | |
| backslashed = false; | |
| commented = false; | |
| printToken = true; | |
| delimiter = false; | |
| token = null; | |
| lastToken = null; | |
| nextToken = null; | |
| c0 = 0; | |
| c = 0; | |
| c2 = 0; | |
| } | |
| boolean printable() { | |
| return !commented && (printToken || inString()); | |
| } | |
| boolean inString() { | |
| return doubleQuoted || singleQuoted; | |
| } | |
| boolean isReplacableToken() { | |
| if (commented) { | |
| return false; | |
| } | |
| for (Iterator itr = JSO.privatePrefixSet.iterator(); itr.hasNext(); ) { | |
| String prefix = (String) itr.next(); | |
| if (this.token.startsWith(prefix)) return true; | |
| } | |
| return !dotted; | |
| } | |
| boolean delimiterSurrounded() { | |
| return !JSO.isDelimiter(nextToken) && !JSO.isDelimiter(lastToken); | |
| } | |
| boolean isWhitespace(){ | |
| return Character.isWhitespace(c); | |
| } | |
| String setToken(String value) { | |
| String oldToken = lastToken; | |
| lastToken = token; | |
| token = value; | |
| nextToken = null; | |
| if (value != null) { | |
| c0 = c; | |
| c = token == null ? 0 : token.charAt(0); | |
| c2 = 0; | |
| backslashed = c0 == '\\'; | |
| dotted = c0 == '.'; | |
| delimiter = JSO.isDelimiter(token); | |
| printToken = true; | |
| } | |
| return oldToken; | |
| } | |
| String tokenBackslashed() { | |
| String result = null; | |
| int index = 0; | |
| if (c == 'u') { | |
| index = 4; | |
| } else if (Character.isDigit(c)) { | |
| index = 3; | |
| } else { | |
| throw new IllegalStateException("Token not backslashed or invalid JavaScript syntax."); | |
| } | |
| result = token.substring(0, index); | |
| token = token.substring(index); | |
| return result; | |
| } | |
| void setNextToken(String value) { | |
| nextToken = value; | |
| c2 = value.charAt(0); | |
| } | |
| void skipToken() { | |
| this.setToken(nextToken); | |
| } | |
| void noToken() { | |
| nextToken = null; | |
| c2 = 0; | |
| } | |
| boolean flipFlags() { | |
| if (isWhitespace()) { | |
| printToken = delimiterSurrounded(); | |
| } else if (c == '/' && !(singleQuoted || doubleQuoted)) { | |
| if (!commented && c2 == '/') { | |
| return true; | |
| } else if (!commented && c2 == '*' && | |
| !inString()) { | |
| commented = true; | |
| } else if (commented && c0 == '*') { | |
| commented = false; | |
| printToken = false; | |
| } | |
| } else if (c == '\"' && !singleQuoted && !backslashed && !commented) { | |
| doubleQuoted = !doubleQuoted; | |
| } else if (c == '\'' && !doubleQuoted && !backslashed && !commented) { | |
| singleQuoted = !singleQuoted; | |
| } | |
| return false; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment