9
9
import java .util .ArrayList ;
10
10
import java .util .Arrays ;
11
11
import java .util .Collections ;
12
+ import java .util .stream .IntStream ;
12
13
13
14
public class ConstantTransformer extends ClassTransformer {
14
15
@@ -17,57 +18,103 @@ public ConstantTransformer(Bozar bozar) {
17
18
}
18
19
19
20
private void obfuscateNumbers (ClassNode classNode , MethodNode methodNode ) {
20
- // TODO: Obfuscate longs to enchance ControlFlowTransformer
21
21
Arrays .stream (methodNode .instructions .toArray ())
22
- .filter (ASMUtils :: isPushInt )
22
+ .filter (insn -> ASMUtils . isPushInt ( insn ) || ASMUtils . isPushLong ( insn ) )
23
23
.forEach (insn -> {
24
24
final InsnList insnList = new InsnList ();
25
- int value = ASMUtils .getPushedInt (insn );
26
25
26
+ final ValueType valueType = this .getValueType (insn );
27
+ final long value = switch (valueType ) {
28
+ case INTEGER -> ASMUtils .getPushedInt (insn );
29
+ case LONG -> ASMUtils .getPushedLong (insn );
30
+ };
31
+
32
+ // Randomly selected number obfuscation type
27
33
int type = random .nextInt (2 );
28
34
29
- // Bounds check for number obfuscation
30
- byte shift = 2 ;
31
- if (type == 1 ) {
32
- long l = (long )value << (long )shift ;
33
- if (l > Integer .MAX_VALUE || l < Integer .MIN_VALUE )
34
- type --;
35
- }
35
+ // Bounds check
36
+ final byte shift = 2 ;
37
+ final boolean canShift = switch (valueType ) {
38
+ case INTEGER -> this .canShiftLeft (shift , value , Integer .MIN_VALUE );
39
+ case LONG -> this .canShiftLeft (shift , value , Long .MIN_VALUE );
40
+ };
41
+ if (!canShift && type == 1 )
42
+ type --;
36
43
37
44
// Number obfuscation types
38
45
switch (type ) {
39
- case 0 -> {
46
+ case 0 -> { // XOR
40
47
int xor1 = random .nextInt (Short .MAX_VALUE );
41
- int xor2 = value ^ xor1 ;
42
- insnList .add (ASMUtils .pushInt (xor1 ));
43
- insnList .add (ASMUtils .pushInt (xor2 ));
44
- insnList .add (new InsnNode (IXOR ));
48
+ long xor2 = value ^ xor1 ;
49
+ switch (valueType ) {
50
+ case INTEGER -> {
51
+ insnList .add (ASMUtils .pushInt (xor1 ));
52
+ insnList .add (ASMUtils .pushInt ((int ) xor2 ));
53
+ insnList .add (new InsnNode (IXOR ));
54
+ }
55
+ case LONG -> {
56
+ insnList .add (ASMUtils .pushLong (xor1 ));
57
+ insnList .add (ASMUtils .pushLong (xor2 ));
58
+ insnList .add (new InsnNode (LXOR ));
59
+ }
60
+ }
45
61
}
46
- case 1 -> {
47
- insnList .add (ASMUtils .pushInt (value << shift ));
48
- insnList .add (ASMUtils .pushInt (shift ));
49
- insnList .add (new InsnNode (ISHR ));
62
+ case 1 -> { // Shift
63
+ switch (valueType ) {
64
+ case INTEGER -> {
65
+ insnList .add (ASMUtils .pushInt ((int ) (value << shift )));
66
+ insnList .add (ASMUtils .pushInt (shift ));
67
+ insnList .add (new InsnNode (IUSHR ));
68
+ }
69
+ case LONG -> {
70
+ insnList .add (ASMUtils .pushLong (value << shift ));
71
+ insnList .add (ASMUtils .pushInt (shift ));
72
+ insnList .add (new InsnNode (LUSHR ));
73
+ }
74
+ }
50
75
}
51
76
}
52
77
53
- // Combined obfuscation with Control Flow
54
- // But it generated +750% file bloat with my test file (no libraries), so I don't recommend it
55
- // TODO: Remove this and implement built-in flow obfuscation
56
78
if (this .getBozar ().getConfig ().getOptions ().getConstantObfuscation () == BozarConfig .BozarOptions .ConstantObfuscationOption .FLOW ) {
79
+ final InsnList flow = new InsnList (), afterFlow = new InsnList ();
80
+ final LabelNode label0 = new LabelNode (), label1 = new LabelNode (), label2 = new LabelNode (), label3 = new LabelNode ();
57
81
int index = methodNode .maxLocals + 2 ;
58
- insnList .add (new VarInsnNode (ISTORE , index ));
59
- insnList .add (new VarInsnNode (ILOAD , index ));
60
- insnList .insert ((value == 0 ) ? new InsnNode (ICONST_1 ) : new InsnNode (ICONST_0 ));
61
- var label0 = new LabelNode ();
62
- var label1 = new LabelNode ();
63
- insnList .add (new JumpInsnNode (GOTO , label1 ));
64
- insnList .add (label0 );
65
- insnList .add (new IincInsnNode (index , random .nextInt (Integer .MAX_VALUE )));
66
- insnList .add (new VarInsnNode (ILOAD , index ));
67
- insnList .add (ASMUtils .pushInt (random .nextInt ()));
68
- insnList .add (label1 );
69
- insnList .add (new JumpInsnNode (IF_ICMPEQ , label0 ));
70
- insnList .add (new VarInsnNode (ILOAD , index ));
82
+ long rand0 = random .nextLong (), rand1 = random .nextLong ();
83
+ while (rand0 == rand1 )
84
+ rand1 = random .nextLong ();
85
+
86
+ flow .add (ASMUtils .pushLong (rand0 ));
87
+ flow .add (ASMUtils .pushLong (rand1 ));
88
+ flow .add (new InsnNode (LCMP ));
89
+ flow .add (new VarInsnNode (ISTORE , index ));
90
+ flow .add (new VarInsnNode (ILOAD , index ));
91
+ flow .add (new JumpInsnNode (IFNE , label0 ));
92
+ flow .add (label3 );
93
+ flow .add (switch (valueType ) {
94
+ case INTEGER -> ASMUtils .pushInt (random .nextInt ());
95
+ case LONG -> ASMUtils .pushLong (random .nextLong ());
96
+ });
97
+ flow .add (new JumpInsnNode (GOTO , label1 ));
98
+ flow .add (label0 );
99
+
100
+ int alwaysNegative = 0 ;
101
+ while (alwaysNegative >= 0 ) alwaysNegative = -random .nextInt (Integer .MAX_VALUE );
102
+
103
+ afterFlow .add (label1 );
104
+ afterFlow .add (new VarInsnNode (ILOAD , index ));
105
+ afterFlow .add (ASMUtils .pushInt (random .nextInt (Integer .MAX_VALUE )));
106
+ afterFlow .add (new InsnNode (IADD ));
107
+ afterFlow .add (ASMUtils .pushInt (alwaysNegative ));
108
+ afterFlow .add (new JumpInsnNode (IF_ICMPNE , label2 ));
109
+ afterFlow .add (switch (valueType ) {
110
+ case INTEGER -> new InsnNode (POP );
111
+ case LONG -> new InsnNode (POP2 );
112
+ });
113
+ afterFlow .add (new JumpInsnNode (GOTO , label3 ));
114
+ afterFlow .add (label2 );
115
+
116
+ methodNode .instructions .insertBefore (insn , flow );
117
+ methodNode .instructions .insert (insn , afterFlow );
71
118
}
72
119
73
120
// Replace number instruction with our instructions
@@ -169,4 +216,19 @@ private InsnList convertString(MethodNode methodNode, String str) {
169
216
insnList .add (new MethodInsnNode (INVOKESPECIAL , "java/lang/String" , "<init>" , "([B)V" , false ));
170
217
return insnList ;
171
218
}
219
+
220
+ private boolean canShiftLeft (byte shift , long value , final long minValue ) {
221
+ int power = (int ) (Math .log (-(minValue >> 1 )) / Math .log (2 )) + 1 ;
222
+ return IntStream .range (0 , shift ).allMatch (i -> (value >> power - i ) == 0 );
223
+ }
224
+
225
+ private enum ValueType {
226
+ INTEGER , LONG
227
+ }
228
+
229
+ private ValueType getValueType (AbstractInsnNode insn ) {
230
+ if (ASMUtils .isPushInt (insn )) return ValueType .INTEGER ;
231
+ else if (ASMUtils .isPushLong (insn )) return ValueType .LONG ;
232
+ throw new IllegalArgumentException ("Insn is not a push int/long instruction" );
233
+ }
172
234
}
0 commit comments