Skip to content

Commit 48560c1

Browse files
committed
Long obfuscation & built-in flow obfuscation for ConstantTransformer
1 parent 539e49f commit 48560c1

File tree

1 file changed

+97
-35
lines changed

1 file changed

+97
-35
lines changed

src/main/java/com/vimasig/bozar/obfuscator/transformer/impl/ConstantTransformer.java

+97-35
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.ArrayList;
1010
import java.util.Arrays;
1111
import java.util.Collections;
12+
import java.util.stream.IntStream;
1213

1314
public 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

Comments
 (0)