Skip to content

Instantly share code, notes, and snippets.

@Runemoro
Created August 5, 2019 11:55
Show Gist options
  • Save Runemoro/89e11901e9f12a9402cd1178e771b62e to your computer and use it in GitHub Desktop.
Save Runemoro/89e11901e9f12a9402cd1178e771b62e to your computer and use it in GitHub Desktop.
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