-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathJavaObfuscatingVisitor.java
463 lines (396 loc) · 17.5 KB
/
JavaObfuscatingVisitor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
package ru.itmo.bizyaev;
import org.antlr.v4.runtime.RuleContext;
import org.antlr.v4.runtime.tree.*;
import ru.itmo.bizyaev.generated.JavaBasicParser;
import ru.itmo.bizyaev.generated.JavaBasicVisitor;
import java.util.*;
import java.util.stream.Collectors;
/**
* This class implements {@link JavaBasicVisitor} to format and obfuscate a subset of Java language.
*/
public class JavaObfuscatingVisitor extends AbstractParseTreeVisitor<String> implements JavaBasicVisitor<String> {
// Map of all identifier name replacements
private Map<String, String> idReplacementMap;
// Stack of lists of variables to delete from replacement map after the end of the current scope
private Deque<ArrayList<String>> scopeCleanupStack;
// Set of names of fake variables available at this point of code
private Set<String> fakeVars;
// Stack of lists of variables to delete from fake vars set after the end of the current scope
private Deque<ArrayList<String>> fakeVarsStack;
// Last generated obfuscated variable name
private String lastIdReplacement;
// States whether or not current parse context allows variable renaming
private boolean isRenamingAllowed;
// Current indentation level (changes with scope entry / quit)
private int currentIndentLevel;
JavaObfuscatingVisitor() {
idReplacementMap = new HashMap<>();
scopeCleanupStack = new ArrayDeque<>();
fakeVars = new HashSet<>();
fakeVarsStack = new ArrayDeque<>();
lastIdReplacement = "I0100";
isRenamingAllowed = false;
currentIndentLevel = 0;
}
@Override
public String visitMethodDeclaration(JavaBasicParser.MethodDeclarationContext ctx) {
pushScope();
isRenamingAllowed = true;
String type = ctx.typeType() != null ? visit(ctx.typeType()) : "void";
String params = visit(ctx.formalParameters());
String contents = ctx.block() != null ? " " + visit(ctx.block()) : ";";
String result = type + " " + ctx.IDENTIFIER().getText() + params + visit(ctx.optionalBrackets()) + contents;
isRenamingAllowed = false;
popScope();
return result;
}
@Override
public String visitConstructorDeclaration(JavaBasicParser.ConstructorDeclarationContext ctx) {
pushScope();
isRenamingAllowed = true;
String result = String.format("%s%s %s", ctx.IDENTIFIER().getText(),
visit(ctx.formalParameters()), visit(ctx.block()));
isRenamingAllowed = false;
popScope();
return result;
}
@Override
public String visitBlock(JavaBasicParser.BlockContext ctx) {
currentIndentLevel++;
pushScope();
List<String> contents = new ArrayList<>();
for (JavaBasicParser.BlockStatementContext c : ctx.blockStatement()) {
String line = indent() + visit(c);
if (new Random().nextInt(2) == 0) {
contents.add(indent() + generateRandomUselessStatement());
}
contents.add(line);
}
popScope();
currentIndentLevel--;
return String.format("{\n%s\n%s}", String.join("\n", contents), indent());
}
@Override
public String visitCompilationUnit(JavaBasicParser.CompilationUnitContext ctx) {
return ctx.typeDeclaration().stream().map(this::visit).collect(Collectors.joining("\n"));
}
@Override
public String visitVariableDeclaratorId(JavaBasicParser.VariableDeclaratorIdContext ctx) {
return replaceId(ctx.IDENTIFIER(), true) + visit(ctx.optionalBrackets());
}
@Override
public String visitVariableInitializer(JavaBasicParser.VariableInitializerContext ctx) {
return visit(ctx.getChild(0));
}
@Override
public String visitTypeDeclaration(JavaBasicParser.TypeDeclarationContext ctx) {
String mods = ctx.modifier().stream().map(RuleContext::getText).collect(Collectors.joining(" "));
return mods + visit(ctx.classDeclaration()) + ";";
}
@Override
public String visitClassDeclaration(JavaBasicParser.ClassDeclarationContext ctx) {
return "class " + ctx.IDENTIFIER().getText() + visit(ctx.classBody());
}
@Override
public String visitClassBody(JavaBasicParser.ClassBodyContext ctx) {
currentIndentLevel++;
String body = ctx.classBodyDeclaration().stream()
.map((x) -> indent() + visit(x))
.collect(Collectors.joining("\n"));
currentIndentLevel--;
return String.format(" {\n%s\n%s}", body, indent());
}
@Override
public String visitEmptyClassBodyDecl(JavaBasicParser.EmptyClassBodyDeclContext ctx) { return ";"; }
@Override
public String visitBlockClassBodyDecl(JavaBasicParser.BlockClassBodyDeclContext ctx) {
return optionalModifier(ctx.STATIC()) + visit(ctx.block());
}
@Override
public String visitMemberClassBodyDecl(JavaBasicParser.MemberClassBodyDeclContext ctx) {
String mods = ctx.modifier().stream().map(RuleContext::getText).collect(Collectors.joining(" "));
return mods + (!mods.isEmpty() ? " " : "") + visit(ctx.memberDeclaration());
}
@Override
public String visitMemberDeclaration(JavaBasicParser.MemberDeclarationContext ctx) {
return visit(ctx.getChild(0));
}
@Override
public String visitFieldDeclaration(JavaBasicParser.FieldDeclarationContext ctx) {
return String.format("%s %s;", visit(ctx.typeType()), visit(ctx.variableDeclarators()));
}
@Override
public String visitVariableDeclarators(JavaBasicParser.VariableDeclaratorsContext ctx) {
return ctx.variableDeclarator().stream().map(this::visit).collect(Collectors.joining(", "));
}
@Override
public String visitVariableDeclarator(JavaBasicParser.VariableDeclaratorContext ctx) {
String varInit = ctx.variableInitializer() == null ? "" : (" = " + visit(ctx.variableInitializer()));
return visit(ctx.variableDeclaratorId()) + varInit;
}
@Override
public String visitArrayInitializer(JavaBasicParser.ArrayInitializerContext ctx) {
return ctx.variableInitializer().stream().map(this::visit)
.collect(Collectors.joining(", ", "{", "}"));
}
@Override
public String visitFormalParameters(JavaBasicParser.FormalParametersContext ctx) {
return "(" + visitIfNotNull(ctx.formalParameterList()) + ")";
}
@Override
public String visitFormalParameterList(JavaBasicParser.FormalParameterListContext ctx) {
return ctx.formalParameter().stream().map(this::visitFormalParameter).collect(Collectors.joining(", "));
}
@Override
public String visitFormalParameter(JavaBasicParser.FormalParameterContext ctx) {
String finalMod = optionalModifier(ctx.FINAL());
return String.format("%s%s %s", finalMod, visit(ctx.typeType()), visit(ctx.variableDeclaratorId()));
}
@Override
public String visitVarDeclBlockStatement(JavaBasicParser.VarDeclBlockStatementContext ctx) {
String finalMod = optionalModifier(ctx.FINAL());
return String.format("%s%s %s;", finalMod, visit(ctx.typeType()), visit(ctx.variableDeclarators()));
}
@Override
public String visitStatementBlockStatement(JavaBasicParser.StatementBlockStatementContext ctx) {
return visit(ctx.statement());
}
@Override
public String visitTypeDeclBlockStatement(JavaBasicParser.TypeDeclBlockStatementContext ctx) {
return visit(ctx.typeDeclaration());
}
@Override
public String visitNewBlockStatement(JavaBasicParser.NewBlockStatementContext ctx) {
return visit(ctx.block());
}
@Override
public String visitIfStatement(JavaBasicParser.IfStatementContext ctx) {
String failBranch = ctx.failBranch == null ? "" : "else " + visit(ctx.failBranch);
return String.format("if (%s) %s%s", visit(ctx.expression()), visit(ctx.succBranch), failBranch);
}
@Override
public String visitWhileStatement(JavaBasicParser.WhileStatementContext ctx) {
return String.format("while (%s) %s", visit(ctx.expression()), visit(ctx.statement()));
}
@Override
public String visitExpressionStatement(JavaBasicParser.ExpressionStatementContext ctx) {
String ret = ctx.RETURN() == null ? "" : "return";
String exp = visitIfNotNull(ctx.expression());
return ret + (!ret.isEmpty() && !exp.isEmpty() ? " " : "") + exp + ";";
}
@Override
public String visitExpressionList(JavaBasicParser.ExpressionListContext ctx) {
return ctx.expression().stream().map(this::visit).collect(Collectors.joining(", "));
}
@Override
public String visitMethodCall(JavaBasicParser.MethodCallContext ctx) {
String callee = ctx.IDENTIFIER() != null ? ctx.IDENTIFIER().getText() : "this";
return String.format("%s(%s)", callee, visit(ctx.expressionList()));
}
@Override
public String visitPrimaryExpression(JavaBasicParser.PrimaryExpressionContext ctx) {
return visit(ctx.primary());
}
@Override
public String visitCastExpression(JavaBasicParser.CastExpressionContext ctx) {
return String.format("(%s) %s", visit(ctx.typeType()), visit(ctx.expression()));
}
@Override
public String visitDotExpression(JavaBasicParser.DotExpressionContext ctx) {
String afterDot = ctx.IDENTIFIER() != null ? ctx.IDENTIFIER().getText() : visit(ctx.methodCall());
return String.format("%s.%s", visit(ctx.expression()), afterDot);
}
@Override
public String visitSubscriptExpression(JavaBasicParser.SubscriptExpressionContext ctx) {
return String.format("%s[%s]", visit(ctx.ext), visit(ctx.subscript));
}
@Override
public String visitNewExpression(JavaBasicParser.NewExpressionContext ctx) {
String toCreate = visitNotNull(ctx.qualifiedName(), ctx.primitiveType());
String rest = visitNotNull(ctx.arrayCreatorRest(), ctx.classCreatorRest());
return "new " + toCreate + rest;
}
@Override
public String visitMethodCallExpression(JavaBasicParser.MethodCallExpressionContext ctx) {
return visit(ctx.methodCall());
}
@Override
public String visitOpExpression(JavaBasicParser.OpExpressionContext ctx) {
if (ctx.bop != null) {
return String.format("%s %s %s", visit(ctx.expression(0)),
ctx.bop.getText(),
visit(ctx.expression(1)));
} else if (ctx.prefix != null) {
return ctx.prefix.getText() + visit(ctx.expression(0));
} else {
return visit(ctx.expression(0)) + ctx.postfix.getText();
}
}
@Override
public String visitParenthesesPrimary(JavaBasicParser.ParenthesesPrimaryContext ctx) {
return "(" + visit(ctx.expression()) + ")";
}
@Override
public String visitThisPrimary(JavaBasicParser.ThisPrimaryContext ctx) {
return "this";
}
@Override
public String visitLiteralPrimary(JavaBasicParser.LiteralPrimaryContext ctx) {
return ctx.literal().getText();
}
@Override
public String visitIdPrimary(JavaBasicParser.IdPrimaryContext ctx) {
return replaceId(ctx.IDENTIFIER(), false);
}
@Override
public String visitInitArrayCreatorRest(JavaBasicParser.InitArrayCreatorRestContext ctx) {
return multiplyString("[]", ctx.LBRACK().size()) + visit(ctx.arrayInitializer());
}
@Override
public String visitExprArrayCreatorRest(JavaBasicParser.ExprArrayCreatorRestContext ctx) {
String expressions = ctx.expression().stream().map((x) -> "[" + visit(x) + "]").collect(Collectors.joining());
return expressions + visit(ctx.optionalBrackets());
}
@Override
public String visitClassCreatorRest(JavaBasicParser.ClassCreatorRestContext ctx) {
return String.format("(%s)%s", visitIfNotNull(ctx.expressionList()), visit(ctx.classBody()));
}
@Override
public String visitTypeType(JavaBasicParser.TypeTypeContext ctx) {
return visitNotNull(ctx.qualifiedName(), ctx.primitiveType()) + visit(ctx.optionalBrackets());
}
@Override
public String visitQualifiedName(JavaBasicParser.QualifiedNameContext ctx) {
return ctx.getText();
}
@Override
public String visitOptionalBrackets(JavaBasicParser.OptionalBracketsContext ctx) {
return multiplyString("[]", ctx.LBRACK().size());
}
@Override
public String visitLiteral(JavaBasicParser.LiteralContext ctx) {
return ctx.getText();
}
@Override
public String visitModifier(JavaBasicParser.ModifierContext ctx) {
return ctx.getText();
}
@Override
public String visitPrimitiveType(JavaBasicParser.PrimitiveTypeContext ctx) {
return ctx.getText();
}
private String visitNotNull(ParseTree a, ParseTree b) {
return a != null ? visit(a) : visit(b);
}
private String visitIfNotNull(ParseTree a) {
return a == null ? "" : visit(a);
}
private static String optionalModifier(TerminalNode t) {
return t == null ? "" : t.getText() + " ";
}
// Generates new obfuscated variable names
private String nextIdReplacement() {
StringBuilder newIdReplacement = new StringBuilder();
int i = lastIdReplacement.length() - 1;
boolean incrementSucceeded = false;
// Our "digits" are 0, 1, O, I
for (; i >= 0; --i) {
switch (lastIdReplacement.charAt(i)) {
case '0':
newIdReplacement.append('1');
incrementSucceeded = true;
break;
case 'O':
newIdReplacement.append('I');
incrementSucceeded = true;
break;
case '1':
newIdReplacement.append('O');
incrementSucceeded = true;
break;
case 'I':
if (i == 0) {
newIdReplacement.append("0O");
incrementSucceeded = true;
} else {
newIdReplacement.append("0");
}
}
if (incrementSucceeded) break;
}
lastIdReplacement = lastIdReplacement.substring(0, i) + newIdReplacement.reverse().toString();
return lastIdReplacement;
}
private String generateRandomUselessStatement() {
final String[] primitiveIntegerTypes = {"int", "long"};
int action = new Random().nextInt(6);
String type = primitiveIntegerTypes[new Random().nextInt(primitiveIntegerTypes.length)];
String randomVar = getRandomFakeVariable();
if (randomVar == null) {
action = 0;
}
switch (action) {
case 0: // New variable with random value
return String.format("%s %s = %d;", type, newFakeVariable(), new Random().nextInt(32768));
case 1: // New variable assigned from existing fake var
return String.format("%s %s = (%s) %s;", type, newFakeVariable(), type, randomVar);
case 2: // Useless instanceof check
return String.format("%s %s = (Object)%s instanceof Object ? %d : %d;", type, newFakeVariable(),
randomVar, new Random().nextInt(32768), new Random().nextInt(32768));
case 3: // Self-assignment with ternary
return String.format("%s = true ? %s : %s;", randomVar, randomVar, randomVar);
case 4: // Self-assignment
return String.format("%s = %s;", randomVar, randomVar);
case 5: // Increase
return String.format("%s += %d;", randomVar, new Random().nextInt(32768));
default: // Binary "and" with self
return String.format("%s &= %s;", randomVar, randomVar);
}
}
private String getRandomFakeVariable() {
if (fakeVars.isEmpty()) {
return null;
}
List<String> l = new ArrayList<>(fakeVars);
return l.get(new Random().nextInt(l.size()));
}
private String newFakeVariable() {
String name = nextIdReplacement();
fakeVars.add(name);
fakeVarsStack.getLast().add(name);
return name;
}
/* String functions */
static private String multiplyString(String s, int times) {
return new String(new char[times]).replace("\0", s);
}
private String indent() {
final int FORMAT_IDENT_SPACES = 4;
return multiplyString(" ", FORMAT_IDENT_SPACES * currentIndentLevel);
}
/* Variable scope logic for identifier replacement */
private void pushScope() {
scopeCleanupStack.push(new ArrayList<>());
fakeVarsStack.push(new ArrayList<>());
}
private String replaceId(TerminalNode identifier, boolean canCreateNew) {
String varName = identifier.getText();
if (!isRenamingAllowed) {
return varName;
}
if (idReplacementMap.containsKey(varName)) {
return idReplacementMap.get(varName);
} else if (canCreateNew) {
String replacement = nextIdReplacement();
idReplacementMap.put(varName, replacement);
scopeCleanupStack.getLast().add(varName);
return replacement;
} else {
return varName;
}
}
private void popScope() {
idReplacementMap.keySet().removeAll(scopeCleanupStack.pop());
fakeVars.removeAll(fakeVarsStack.pop());
}
}