99import java .util .ArrayList ;
1010import java .util .Arrays ;
1111import java .util .Collections ;
12+ import java .util .stream .IntStream ;
1213
1314public class ConstantTransformer extends ClassTransformer {
1415
@@ -17,57 +18,103 @@ public ConstantTransformer(Bozar bozar) {
1718 }
1819
1920 private void obfuscateNumbers (ClassNode classNode , MethodNode methodNode ) {
20- // TODO: Obfuscate longs to enchance ControlFlowTransformer
2121 Arrays .stream (methodNode .instructions .toArray ())
22- .filter (ASMUtils :: isPushInt )
22+ .filter (insn -> ASMUtils . isPushInt ( insn ) || ASMUtils . isPushLong ( insn ) )
2323 .forEach (insn -> {
2424 final InsnList insnList = new InsnList ();
25- int value = ASMUtils .getPushedInt (insn );
2625
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
2733 int type = random .nextInt (2 );
2834
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 --;
3643
3744 // Number obfuscation types
3845 switch (type ) {
39- case 0 -> {
46+ case 0 -> { // XOR
4047 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+ }
4561 }
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+ }
5075 }
5176 }
5277
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
5678 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 ();
5781 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 );
71118 }
72119
73120 // Replace number instruction with our instructions
@@ -169,4 +216,19 @@ private InsnList convertString(MethodNode methodNode, String str) {
169216 insnList .add (new MethodInsnNode (INVOKESPECIAL , "java/lang/String" , "<init>" , "([B)V" , false ));
170217 return insnList ;
171218 }
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+ }
172234}
0 commit comments