Created
August 5, 2019 11:55
-
-
Save Runemoro/89e11901e9f12a9402cd1178e771b62e to your computer and use it in GitHub Desktop.
This file contains 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
import com.google.caja.lexer.*; | |
import com.google.caja.parser.*; | |
import com.google.caja.parser.js.*; | |
import com.google.caja.render.JsPrettyPrinter; | |
import com.google.caja.reporting.EchoingMessageQueue; | |
import com.google.caja.reporting.MessageContext; | |
import com.google.caja.reporting.RenderContext; | |
import com.google.caja.util.Pair; | |
import java.io.FileReader; | |
import java.io.PrintWriter; | |
import java.util.*; | |
public class MopeDeobfuscator { | |
public static void main(String[] args) throws Exception { | |
CharProducer charProducer = CharProducer.Factory.create(new FileReader("obfuscated.js"), InputSource.UNKNOWN); | |
JsLexer lexer = new JsLexer(charProducer); | |
JsTokenQueue tokenQueue = new JsTokenQueue(lexer, InputSource.UNKNOWN); | |
Parser parser = new Parser(tokenQueue, new EchoingMessageQueue(new PrintWriter(System.out), new MessageContext())); | |
Block code = parser.parse(); | |
// Read strings | |
Declaration stringsDeclaration = (Declaration) code.children().get(0); | |
List<String> strings = new ArrayList<>(); | |
for (Expression stringExpression : ((ArrayConstructor) stringsDeclaration.getInitializer()).children()) { | |
strings.add(new String(Base64.getDecoder().decode(((StringLiteral) stringExpression).getUnquotedValue()))); | |
} | |
code.removeChild(code.children().get(0)); | |
// Rotate strings | |
int n = ((IntegerLiteral) ((SpecialOperation) ((ExpressionStmt) code.children().get(0)).getExpression()).children().get(2)).getValue().intValue(); | |
while (n-- > 0) { | |
strings.add(strings.remove(0)); | |
} | |
code.removeChild(code.children().get(0)); | |
// Get string function name | |
String stringFunction = ((Declaration) code.children().get(0)).getIdentifierName(); | |
code.removeChild(code.children().get(0)); | |
// Replace strings | |
code.acceptPostOrder(ancestors -> { | |
if (ancestors.node instanceof Operation) { | |
Operation node = (Operation) ancestors.node; | |
if (node.getOperator() != Operator.FUNCTION_CALL) { | |
return true; | |
} | |
Expression function = node.children().get(0); | |
if (function instanceof Reference && ((Reference) function).getIdentifierName().equals(stringFunction)) { | |
String string = strings.get(Long.decode(((StringLiteral) node.children().get(1)).getUnquotedValue()).intValue()); | |
replace(ancestors, new StringLiteral(FilePosition.UNKNOWN, string)); | |
} | |
} | |
return true; | |
}, null); | |
// Replace square brackets with member access when possible | |
code.acceptPostOrder(ancestors -> { | |
if (ancestors.node instanceof Operation) { | |
Operation node = (Operation) ancestors.node; | |
if (node.getOperator() != Operator.SQUARE_BRACKET) { | |
return true; | |
} | |
Expression key = node.children().get(1); | |
if (key instanceof StringLiteral) { | |
String name = ((StringLiteral) key).getUnquotedValue(); | |
if (!ParserBase.isJavascriptIdentifier(name)) { | |
return true; | |
} | |
Reference reference = new Reference(new Identifier(key.getFilePosition(), name)); | |
replace(ancestors, new SpecialOperation(FilePosition.UNKNOWN, Operator.MEMBER_ACCESS, Arrays.asList(node.children().get(0), reference))); | |
} | |
} | |
return true; | |
}, null); | |
// Replace complicated boolean literals | |
code.acceptPostOrder(ancestors -> { | |
if (ancestors.node instanceof Operation) { | |
Operation node = (Operation) ancestors.node; | |
if (node.getOperator() != Operator.NOT) { | |
return true; | |
} | |
Expression operand = node.children().get(0); | |
if (operand instanceof ArrayConstructor && operand.children().isEmpty()) { | |
replace(ancestors, new BooleanLiteral(FilePosition.UNKNOWN, false)); | |
} else if (operand instanceof NumberLiteral) { | |
replace(ancestors, new BooleanLiteral(FilePosition.UNKNOWN, ((NumberLiteral) operand).doubleValue() == 0)); | |
} else if (operand instanceof BooleanLiteral) { | |
replace(ancestors, new BooleanLiteral(FilePosition.UNKNOWN, !((BooleanLiteral) operand).value)); | |
} | |
} | |
return true; | |
}, null); | |
boolean[] needsPass = {true}; | |
while (needsPass[0]) { | |
needsPass[0] = false; | |
// Replace && and || with if statement when possible | |
code.acceptPostOrder(ancestors -> { | |
if (!(ancestors.node instanceof ExpressionStmt)) { | |
return true; | |
} | |
Expression expression = ((ExpressionStmt) ancestors.node).getExpression(); | |
if (!(expression instanceof Operation)) { | |
return true; | |
} | |
Operation operation = (Operation) expression; | |
if (operation.getOperator() == Operator.LOGICAL_AND || operation.getOperator() == Operator.LOGICAL_OR) { | |
Expression condition = operation.children().get(0); | |
if (operation.getOperator() == Operator.LOGICAL_OR) { | |
condition = new SimpleOperation(FilePosition.UNKNOWN, Operator.NOT, Collections.singletonList(condition)); | |
} | |
Statement ifBranch = new ExpressionStmt(operation.children().get(1)); | |
replace(ancestors, new Conditional(FilePosition.UNKNOWN, Collections.singletonList(new Pair<>(condition, ifBranch)), null)); | |
needsPass[0] = true; | |
} | |
return true; | |
}, null); | |
// Make statement bodies blocks | |
code.acceptPostOrder(ancestors -> { | |
if (ancestors.node instanceof Conditional) { | |
Conditional node = (Conditional) ancestors.node; | |
for (ParseTreeNode child : node.children()) { | |
if (child instanceof Statement && !(child instanceof Block)) { | |
node.replaceChild(toBlock((Statement) child), child); | |
needsPass[0] = true; | |
} | |
} | |
} | |
if (ancestors.node instanceof Loop) { | |
Loop node = (Loop) ancestors.node; | |
Statement body = node.getBody(); | |
if (!(body instanceof Block)) { | |
node.replaceChild(toBlock(body), body); | |
needsPass[0] = true; | |
} | |
} | |
return true; | |
}, null); | |
// Convert ternary operation in statement to if-else statement | |
code.acceptPostOrder(ancestors -> { | |
if (ancestors.node instanceof ExpressionStmt) { | |
Expression expression = ((ExpressionStmt) ancestors.node).getExpression(); | |
if (expression instanceof Operation && ((Operation) expression).getOperator() == Operator.TERNARY) { | |
Operation operation = (Operation) expression; | |
Expression condition = operation.children().get(0); | |
Statement ifBranch = new ExpressionStmt(operation.children().get(1)); | |
Statement elseBranch = new ExpressionStmt(operation.children().get(2)); | |
replace(ancestors, new Conditional(FilePosition.UNKNOWN, Collections.singletonList(new Pair<>(condition, ifBranch)), elseBranch)); | |
needsPass[0] = true; | |
} | |
} | |
return true; | |
}, null); | |
} | |
// Split multi-variable declarations | |
code.acceptPostOrder(ancestors -> { | |
if (!(ancestors.node instanceof MultiDeclaration)) { | |
return true; | |
} | |
MultiDeclaration node = (MultiDeclaration) ancestors.node; | |
MutableParseTreeNode parent = (AbstractParseTreeNode) ancestors.parent.node; | |
if (!(parent instanceof Block)) { | |
return true; | |
} | |
for (Declaration declaration : node.children()) { | |
parent.insertBefore(declaration, node); | |
} | |
parent.removeChild(node); | |
return true; | |
}, null); | |
// Remove self-defence code | |
code.removeChild(code.children().get(0)); | |
code.removeChild(code.children().get(0)); | |
code.removeChild(code.children().get(0)); | |
// Remove logging-removal code | |
code.removeChild(code.children().get(0)); | |
code.removeChild(code.children().get(0)); | |
code.removeChild(code.children().get(0)); | |
System.out.print(toString(code)); | |
} | |
private static Block toBlock(Statement statement) { | |
if (statement instanceof ExpressionStmt) { | |
Block block = new Block(); | |
Expression expression = ((ExpressionStmt) statement).getExpression(); | |
while (expression instanceof Operation && ((Operation) expression).getOperator() == Operator.COMMA) { | |
block.prepend(new ExpressionStmt((Expression) expression.children().get(1))); | |
expression = (Expression) expression.children().get(0); | |
} | |
block.prepend(new ExpressionStmt(expression)); | |
return block; | |
} else { | |
return new Block(FilePosition.UNKNOWN, Collections.singletonList(statement)); | |
} | |
} | |
private static void replace(AncestorChain<?> ancestors, ParseTreeNode replacement) { | |
((AbstractParseTreeNode) ancestors.parent.node).replaceChild(replacement, ancestors.node); | |
} | |
private static String toString(Block code) { | |
StringBuilder stringBuilder = new StringBuilder(); | |
JsPrettyPrinter printer = new JsPrettyPrinter(stringBuilder); | |
printer.setLineLengthLimit(Integer.MAX_VALUE); | |
code.renderBody(new RenderContext(printer)); | |
printer.noMoreTokens(); | |
return stringBuilder.toString(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment