NP管理器DEX加固解混淆
carbofish

通过一道SCTF的安卓题记一次反混淆

静态调用混淆 动态调用混淆 引用混淆 字符串混淆 数字混淆 控制流混淆

最近在打 sctf 的时候,遇到了一题安卓题,用的 NP 管理器的控制流混淆,用了 switch 来打乱程序的执行流,还有一些比较麻烦的混淆,如果一步步调试的话可能工作量比较大,于是想到了利用 ASM 框架和混淆特征,来实现一个 NP混淆的反混淆器

1. Dex 转 Jar

虽然 Dex 可以转成 smali 后修改再编译回去,但是笔者没有找到像 OW2 ASM 那样成熟的字节码编辑框架,所以最后选择了 先用 d2j-dex2jar 转换成 jar 后在用 ASM 去反混淆

我选择了 Github 上一个比较成熟的 Java 反混淆器,虽然没有为 NP定制的 Transformer,但是好在写自定义的 Transformer 比较方便
https://github.com/java-deobfuscator/deobfuscator

然后使用 d2j-dex2jar 把 dex 转换成 jar,开始编写反混淆器

2. 反混淆部分

控制流混淆

image.png

这个会在每个 case 结束前对 var0 进行赋值,来让人难以推断下一个 case是什么,而且一般还与数字混淆配合在一起
比如 这种静态变量 组合运算
image.png
还有调用静态函数获取值
image.png
还有先把下一个 case 的值计算完之后存在局部变量里,然后跳转前再读取出来的
image.png
这里的 var7_23 读取了上面运算出来的值
控制流混淆笔者没有什么好的办法,而且提前算出来的一些数字似乎会有一些 case 是不可达的,但是分析后发现函数要实现功能这些块应该是肯定会执行的不到的
目前的方法是对 switch 建立一个 label 索引表,然后把对 var7_23 赋值的内容根据索引表找出下一跳的 Label,然后把 goto 的内容修改为索引表中的 Label,最后去掉 switch,让反编译器去优化控制流

数字混淆

上面分析控制流混淆的时候已经分析过了三种数字混淆,初次之外还有
image.png
这种在嵌套在其他表达式中的数字混淆
对于数组混淆笔者的解决办法是利用 VM 去执行,这样可以最大程度上避免精度造成的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
Integer num1 = getTargetInteger((FieldInsnNode) start);  
Integer num2 = getTargetInteger((FieldInsnNode) getNextN(start, 1));
MethodNode calcMethod = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "carbo_calc", "()Ljava/lang/Integer;", null, null);
calcMethod.visitCode();
calcMethod.visitLdcInsn(num1);
calcMethod.visitLdcInsn(num2);
calcMethod.visitInsn(getNextN(start, 2).getOpcode());
calcMethod.visitLdcInsn(Utils.getIntValue(getNextN(start, 3)));
calcMethod.visitInsn(getNextN(start, 4).getOpcode());
calcMethod.visitInsn(IRETURN);
mathClz.methods.add(calcMethod);
Integer result = vm.execute(mathClz, calcMethod).getReturnValue().asInt();
mathClz.methods.clear();

FakeJump 不可达跳转混淆

还有一些这种静态变量组成的计算式 组成的条件跳转,其中参杂着一些假的跳转
image.png
举个例子,分别找到这两个静态变量的值,然后得到算式
image.png
image.png
-909 | 639 * 5237 = -901
计算出来后的值 -901 不满足跳转条件,这里就是一个不可达的区块,很容易干扰阅读

变量引用混淆

NP为了隐藏调用的变量,在 np.protect 下生成了几个类根据传入的参数通过反射获取变量或者调用方法
image.png

image.png

这里还有一些 byte[] 组成的字符串,我们先写一个方法来把这里数字还原成字符串
先来分析这种 byte[] 形式字符串的字节码特征,我们使用 Recaf 反编译

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
new java/lang/String // 初始化一个 String Object
dup
bipush 6 // 往栈顶压入数字 6
newarray byte // 一个 TypeInsnNode,申明数组
dup
iconst_0 // 数组下标 0
bipush 73 // 数组第一个元素的的值
i2b // 把栈顶的 73 转换为 byte 类型,等同于 (byte)73
bastore // 等于是存入数组
dup
iconst_1
bipush 108
i2b
bastore
dup
iconst_2
bipush 79
i2b
bastore
dup
iconst_3
bipush 84
i2b
bastore
dup
iconst_4
bipush 68
i2b
bastore
dup
iconst_5
bipush 121
i2b
bastore
invokespecial java/lang/String.<init> ([B)V // 调用 new String(xxx)
astore v1 // 保存到 v1 变量里
goto B

然后就可以写一个方法根据特征还原 String 类

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
public boolean fixNPByteString(AbstractInsnNode start, MethodNode methodNode, boolean checkFlag) throws UnsupportedEncodingException {  

AbstractInsnNode check = start;
if (!(check instanceof TypeInsnNode && check.getOpcode() == NEW && ((TypeInsnNode) check).desc.equals("java/lang/String"))) return false;
check = getNextN(start, 1);
if (!(check instanceof InsnNode && check.getOpcode() == DUP)) return false;
check = getNextN(start, 2);
if (!(check instanceof IntInsnNode && check.getOpcode() == BIPUSH) && !(check instanceof InsnNode && check.getOpcode() >= ICONST_0 && check.getOpcode() <= ICONST_5)) return false;
if (checkFlag) return true;
int length = -1;
if (check instanceof IntInsnNode) length = ((IntInsnNode) check).operand;
else length = check.getOpcode() - ICONST_0;
check = start;
toRemove = new ArrayList<>();
toRemove.addAll(Arrays.asList(
check,
getNextN(check, 1),
getNextN(check, 2),
getNextN(check, 3)
));
check = getNextN(start, 4);
byte[] bytes = new byte[length];
for (int i = 0; i < length; i++) {
toRemove.addAll(Arrays.asList(
check,
getNextN(check, 1),
getNextN(check, 2),
getNextN(check, 3),
getNextN(check, 4)
));
int bit = -1;
if (getNextN(check, 2) instanceof IntInsnNode) {
bit = ((IntInsnNode) getNextN(check, 2)).operand;
} else {
bit = check.getOpcode() - ICONST_0;
}
bytes[i] = (byte) bit;
check = getNextN(check, 5);
}
String s = new String(bytes, "UTF-8");
// System.out.println(String.format("Got a string.. %s", s));
if (!(check instanceof MethodInsnNode && ((MethodInsnNode) check).owner.equals("java/lang/String")
&& ((MethodInsnNode) check).name.equals("<init>"))) return false;
toRemove.add(check);
methodNode.instructions.insertBefore(check, new LdcInsnNode(s));
toRemove.forEach(insn -> {methodNode.instructions.remove(insn);});
return true;
}

效果
image.png

可以根据每个 case 的字符串个数判断这是 一个 String 对象还是一个 变量引用
然后就可以写一个方法把它们转换回 FieldInsnNode

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
public Map<Integer, FieldResult> getFieldMap(Map<LabelNode, Integer> iLmap, MethodNode methodNode) {  
boolean foundSwitch = false;
Map<Integer, FieldResult> result = new HashMap<>();
for (AbstractInsnNode insn : methodNode.instructions) {
if (insn instanceof LookupSwitchInsnNode) foundSwitch = true;
if (!foundSwitch) continue;
if (insn instanceof LabelNode) {
LabelNode thisLabel = (LabelNode) insn;
if (iLmap.containsKey(thisLabel)) {
// 开始往下寻找
ArrayList<String> ldcList = new ArrayList<>();
AbstractInsnNode ptr = insn.getNext();
while (ptr != null && !(ptr instanceof LabelNode)) {
if (ptr instanceof LdcInsnNode) ldcList.add((String) ((LdcInsnNode) ptr).cst);
ptr = ptr.getNext();
}
if (!ldcList.isEmpty()) {
if (ldcList.size() == 1) {
// 字符串对象
result.put(iLmap.get(thisLabel), new FieldResult(ldcList.get(0)));
System.out.printf("Found new field %s\n", ldcList.get(0));
} else {
String desc = ldcList.get(2);
StringBuilder param = new StringBuilder();
if (desc.startsWith("[")) desc = desc;
else if (typeMap.containsKey(desc)) desc = typeMap.get(desc);
else {
desc = String.format("L%s;", desc.replace(".", "/"));
}
FieldInsnNode fieldInsnNode = new FieldInsnNode(
GETSTATIC,
ldcList.get(0).replace(".", "/"),
ldcList.get(1),
desc
);
System.out.printf("Found new field: %s %s %s\n", ldcList.get(0), ldcList.get(1), desc);
result.put(iLmap.get(thisLabel), new FieldResult(fieldInsnNode));
}
}
}
}
}
return result;
}
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
public boolean patchNPField(AbstractInsnNode start, MethodNode methodNode, ClassNode classNode, boolean checkFlag) {  
if (!isNPFieldCall(start)) return false;
AbstractInsnNode check = start;
MethodInsnNode method = (MethodInsnNode) check;
check = check.getPrevious();

if (!Utils.isInteger(check)) return false;
if (checkFlag) return true;

int val = Utils.getIntValue(check);

if (NPFieldMap.containsKey(val)) {
FieldResult fieldResult = NPFieldMap.get(val);
List<AbstractInsnNode> toRemove = Arrays.asList(
start.getPrevious()
);

if (fieldResult.type.equals("str")) {
methodNode.instructions.set(start, new LdcInsnNode(fieldResult.strObj));
System.out.printf("Fixed a string field %s\n", fieldResult.strObj);
} else {
FieldInsnNode fieldInsnNode = new FieldInsnNode(GETSTATIC, fieldResult.fieldInsnNode.owner, fieldResult.fieldInsnNode.name, fieldResult.fieldInsnNode.desc);
methodNode.instructions.set(start, fieldInsnNode);
System.out.printf("Fixed a field %s %s %s\n", fieldResult.fieldInsnNode.owner, fieldResult.fieldInsnNode.name, fieldResult.fieldInsnNode.desc);
}

toRemove.forEach(insn -> methodNode.instructions.remove(insn));
} else {
System.out.printf("%d not in the field map...\n", val);
}

return true;

}

效果
image.png

image.png
image.png

方法调用混淆

和上面的方法引用一样,先吧数字值和对应的方法调用对应起来
image.png

然后与方法混淆不同的是,原本函数调用的参数被魔改成了一个 Object 数组传递过去,想要还原得先吧 Object 数组解析出来,然后恢复原本的函数调用

image.png

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
ldc 72724 // 第一个参数 72724
aconst_null // 第二个参数 null
iconst_1 // Object 数组长度
anewarray java/lang/Object // 申明新建数组
dup
iconst_0 // 元素下标
getstatic kotlinx/coroutines/channels/ۨ۠ۤ۟.۟۠ۨۧۢ I // FieldInsnNode 获取那个
sipush -26811 // 对应上面那个被异或的值
ixor // 异或
invokestatic java/lang/Integer.valueOf (I)Ljava/lang/Integer; // 转换成 int 类型
aastore // 数组保存
invokestatic np/protect/\u06E5ۣۢۢ.n (ILjava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object; // 调用这个方法
checkcast java/lang/Boolean
invokevirtual java/lang/Boolean.booleanValue ()Z
istore i23
ldc 72724
aconst_null
iconst_1
anewarray java/lang/Object
dup
iconst_0
getstatic kotlinx/coroutines/channels/ۨ۠ۤ۟.۟۠ۨۧۢ I
sipush -26811
ixor
invokestatic java/lang/Integer.valueOf (I)Ljava/lang/Integer;
aastore
invokestatic np/protect/\u06E5ۣۢۢ.n (ILjava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
checkcast java/lang/Boolean
invokevirtual java/lang/Boolean.booleanValue ()Z
istore i24

和上面的变量引用混淆一样,建立一个对应表后可以根据传递的第一个参数来还原
这张图可以很明显的看出删减的内容
image.png
还原前
image.png
还原后
image.png

3. 总结

反混淆结果

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
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
package com.example.bbandroid;

import androidx.cardview.widget.ۤۦ۠ۧ;
import androidx.constraintlayout.core.motion.parse.۟ۥۡۦ۟;
import androidx.constraintlayout.core.widgets.analyzer.۟ۡۧ۠ۢ;
import androidx.core.view.autofill.۟ۢۦۧۥ;
import androidx.legacy.coreutils.۟ۥۤۦۤ;
import androidx.lifecycle.viewmodel.۟ۦۡ۟ۥ;
import androidx.localbroadcastmanager.ۥۣۡ۠;
import androidx.startup.ۣ۟ۧ۠ۧ;
import androidx.vectordrawable.۟ۡ۠ۤۨ;
import androidx.versionedparcelable.۟ۧۤ۟ۧ;
import com.google.android.material.appbar.ۣ۟ۧ۠;
import com.google.android.material.datepicker.ۦۣۧۥ;
import com.google.android.material.imageview.ۣ۟۟ۨۥ;
import com.google.android.material.theme.ۢۧ۟ۢ;
import com.google.android.material.transition.platform.۟۠۟ۧۡ;
import java.util.Arrays;
import kotlin.comparisons.۟ۦۤ۠ۢ;
import kotlin.enums.۟ۥۧۡ۠;
import kotlin.internal.jdk8.ۦۤۤۡ;
import kotlinx.coroutines.android.ۥۣ۠۟;
import kotlinx.coroutines.sync.ۦۨۦۤ;

public class strange {
private static final char[] ALPHABET;
private static final int[] LOOKUP;
private static final short[] short;

static {
int var0 = 1754379;
char[] var3 = null;
int[] var4 = null;
int var1 = 0;
int var2 = 0;

while (true) {
switch (var0) {
case 56419:
throw new ArithmeticException("divide by zero");
case 56544:
var3 = ALPHABET;
var0 = 1747810;
continue;
case 1747810:
if (var2 < var3.length) {
۟ۡۧ۠ۢ.ۣ۟ۧۧ۟();
var0 = 1754375;
continue;
}
case 1752551:
ۢۧ۟ۢ.ۣ۟ۥۢۤ();
String var9 = "ۣۤ";
var0 = 56447;
continue;
case 1748613:
var0 = 1755336;
continue;
case 1748796:
LOOKUP[61] = 0;
var0 = 1753508;
continue;
case 1749574:
LOOKUP[var3[var2]] = var2;
var0 = 1749729;
continue;
case 1749729:
var1 = var2 + 1;
ۦۤۤۡ.ۨۥۢ۟ = 7;
var0 = 1749729;
continue;
case 1750562:
case 1750625:
var0 = 56544;
continue;
case 1750813:
var4 = new int[256];
String var8 = "ۧۨۧ";
break;
case 1751554:
var0 = 1754379;
continue;
case 1751648:
ALPHABET = ۦۣۧۥ.ۣۣۡ۟(short, 0, 64, 3052).toCharArray();
۟ۥۡۦ۟.ۣ۟ۡ۟۠();
var0 = 1754379;
continue;
case 1752648:
var0 = 1751554;
continue;
case 1753508:
if (oOo0.isPortInUse(27042)) {
var0 = 56419;
continue;
}
case 1751775:
var0 = 1755624;
continue;
case 1754375:
Arrays.fill(var4, -1);
String var7 = "ۣۡ۟";
var0 = 1748613;
continue;
case 1754379:
short = new short[]{
2946,
2947,
2972,
2973,
2974,
2975,
2968,
2984,
2985,
2986,
2987,
2980,
2981,
2982,
2983,
2976,
2948,
2949,
2950,
2951,
2944,
3001,
3002,
3005,
3006,
3007,
3000,
3011,
3003,
2996,
2997,
2998,
2957,
2958,
2989,
2990,
2991,
2959,
2952,
2953,
2954,
2955,
2945,
2969,
2970,
3034,
3035,
3028,
3029,
3015,
2971,
2964,
2965,
2966,
3036,
3037,
3038,
3039,
3032,
3033,
2977,
2978,
2979,
3004
};
var0 = 1751648;
continue;
case 1754662:
LOOKUP = var4;
ۣ۟۟ۨۥ.ۤۢۡ = 22;
String var6 = "ۣۡ۟";
break;
case 1755336:
var0 = 56544;
var2 = 0;
continue;
case 1755560:
String var5 = "ۥۥۨ";
var0 = 1750562;
var2 = var1;
continue;
case 1755624:
return;
default:
continue;
}

var0 = 1755560;
}
}

public strange() {
int var1 = 56474;
Integer var2 = null;

while (true) {
switch (var1) {
case 56474:
if (۟ۦۡ۟ۥ.۟ۥۥۥۧ() <= 0) {
ۤۦ۠ۧ.۠ۦۨ();
var1 = 56474;
break;
}
case 1754440:
String var4 = "ۥۣۨ";
var1 = 1751618;
break;
case 1746751:
System.out.println(var2);
String var3 = "ۥۣۨ";
var1 = 1752736;
break;
case 1748859:
var2 = Integer.valueOf(۟ۢۦۧۥ.ۣۣ۟۟ۧ("LV4FmlkaVyEOjznZ4"));
var1 = 1746784 + (۟۠۟ۧۡ.۟ۦۣۡۨ | ۦۨۦۤ.ۥۣۣ۟);
break;
case 1750817:
var1 = 56474;
break;
case 1752736:
return;
}
}
}

public static String encode(byte[] var0) {
boolean var24 = false;
int var18 = 0;
int var20 = 0;
int var19 = 0;
int var11 = 0;
int var10 = 0;
int var16 = 0;
int var17 = 0;
int var14 = 0;
int var13 = 0;
char[] var26 = null;
byte var2 = 0;
char var5 = 0;
byte var6 = 0;
char var4 = 0;
char var3 = 0;
char var1 = 0;
int var9 = 0;
int var8 = 0;
int var21 = 0;
int var15 = 0;
int var12 = 0;
int var7 = 1748833;
StringBuilder var28 = null;
boolean var23 = false;

while (true) {
label246:
while (true) {
label244:
while (true) {
label242:
while (true) {
label238:
while (true) {
label223:
while (true) {
label219:
while (true) {
label213:
while (true) {
label203:
while (true) {
label197:
while (true) {
label191:
while (true) {
switch (var7) {
case 56356:
var7 = 56474;
continue;
case 56390:
var7 = 2 + var9;
if (var7 < var0.length) {
byte var42 = var0[var7];
var7 = 255 & var42;
String var69 = "ۢۨۢ";
break label246;
}
case 1751682:
ۥۣۡ۠.۟ۥۣ۠ۤ();
String var68 = "ۥ۟ۨ";
var7 = 1752490;
continue;
case 56543:
var17 = var10 + 1;
var7 = 1751655;
var14 = var11;
continue;
case 56570:
var7 = 1747931;
continue;
case 1746752:
break label197;
case 1747714:
throw new ArithmeticException("divide by zero");
case 1747808:
StringBuilder var66 = var28.append(var1);
var7 = var9 + 3;
String var67 = "ۦۢۥ";
break label238;
case 1747874:
case 1747931:
ۥۣ۠۟.ۣۦ۠ۥ = 11;
String var65 = "ۦۣۨ";
var7 = var8;
break label219;
case 1747927:
String var64 = "ۦۧ۠";
int var49 = 1750597;
var14 = var14;
var7 = 1750597;
continue;
case 1747928:
var7 = var0.length;
var21 = var7;
int var48 = 1753416;
var12 = 0;
var15 = var7;
var7 = var48;
continue;
case 1748645:
case 1754570:
String var63 = "ۢۨ";
var7 = var9;
break label238;
case 1748799:
var7 = 1748645;
continue;
case 1748833:
var23 = new MainActivity().initSth();
var24 = oOo0.isPortInUse(27042);
var7 = 1750815;
var23 = var24;
var24 = var23;
continue;
case 1749603:
var3 = var26[63 & var14];
var7 = 1751588;
continue;
case 1749672:
var7 = 1755465;
var4 = (char)var6;
continue;
case 1749852:
var7 = var11 & var16 | var11 ^ var16;
int var47 = 1747927;
var13 = var10;
var14 = var7;
var7 = var47;
continue;
case 1749853:
case 1753663:
ۣ۟ۧ۠ۧ.ۨۦۣ۠ = 67;
var7 = 1746971;
continue;
case 1750568:
var12++;
var7 = 56356;
continue;
case 1750597:
break label203;
case 1750662:
var7 = 1748833;
continue;
case 1750815:
if (var23) {
var7 = 1747714;
continue;
}
case 1753607:
String var61 = "ۥۣۢ";
var7 = var9;
break label238;
case 1751493:
var2 = 61;
if (var13 < 2) {
var5 = var26[var14 >> (۟ۥۧۡ۠.۟ۢۧۢۧ ^ 74) & 63];
var2 = 61;
int var46 = 1754629;
var11 = var11;
var7 = var46;
continue;
}
break label203;
case 1751530:
۟ۦۤ۠ۢ.ۣ۟ۡۧ۠();
var7 = 56543;
var10 = var19;
continue;
case 1751561:
var19 = var8 + 1;
var7 = 1751530;
var11 = var18;
continue;
case 1751563:
var7 = 1753452;
var12 = 0;
continue;
case 1751588:
۟ۧۤ۟ۧ.ۣ۟۟ۧۧ();
var7 = 1751561;
var1 = var3;
continue;
case 1751653:
if (var9 < var0.length) {
var7 = (var0[var9] & 255) << 16;
var7 &= -16777216 ^ var7;
int var45 = 1752639;
var18 = var7;
var7 = var45;
continue;
}
break label213;
case 1751655:
۟ۥۤۦۤ.۟ۢۨ۟ۨ = 8;
var7 = 1747874;
var13 = var17;
continue;
case 1752489:
return var28.toString();
case 1752490:
var26 = ALPHABET;
StringBuilder var59 = var28.append(var26[var14 >> 18 & 63]);
var59 = var28.append(var26[var14 >> 12 & 63]);
var7 = var20;
break label242;
case 1752580:
var28 = new StringBuilder();
var7 = 1755468;
continue;
case 1752616:
String var58 = "ۧۨۧ";
var7 = var16;
break label246;
case 1752639:
var7 = 1 + var9;
if (var7 >= var0.length) {
break label244;
}

var7 = (var0[var7] & (۟ۡ۠ۤۨ.ۥۥۡۤ ^ 108)) << 8;
break label242;
case 1752709:
var7 = 1749672;
var6 = 61;
continue;
case 1753416:
if (!var24) {
var7 = var21 + 1;
String var57 = "ۤۡۨ";
break label223;
}
case 56474:
case 1752710:
var7 = 1753452;
continue;
case 1753452:
if (var12 < var28.length()) {
char var32 = var28.charAt(var12);
var28.setCharAt(var12, (char)(var32 & ~var15 | ~var32 & var15));
var7 = 1750568;
continue;
}
break label197;
case 1753484:
break label213;
case 1753513:
String var56 = "ۨۢ";
var7 = var13;
break label219;
case 1753544:
break label244;
case 1753545:
case 1754662:
var7 = 1755465;
continue;
case 1754376:
var7 = var18 ^ var20 | var18 & var20;
int var22 = 1748799;
var11 = var7;
var10 = var8;
var7 = var22;
continue;
case 1754629:
var7 = 1752616;
var4 = var5;
continue;
case 1754656:
var7 = 1751653;
var9 = 0;
continue;
case 1755374:
break label191;
case 1755465:
StringBuilder var27 = var28.append(var4);
if (var13 >= 1) {
var1 = (char)var2;
break label191;
}

ۣ۟ۧ۠.۟ۢۥۢ();
var1 = (char)var2;
break;
case 1755468:
var8 = 0;
break;
default:
continue;
}

String var55 = "ۧۨۡ";
var7 = var15;
break label223;
}

var7 = 1747808;
}

var7 = 1748679;
}

String var62 = "ۥۧۧ";
var7 = 1748769;
}

var7 = 1747928;
}

int var50 = 1753547;
var8 = var7;
var7 = 1753547;
}

int var43 = 1754656;
var15 = var7;
var7 = 1754656;
}

int var51 = 1752580;
var9 = var7;
var7 = 1752580;
}

int var44 = 1754376;
var20 = var7;
var7 = var44;
}

var7 = 1751561;
}

int var52 = 1750753;
var16 = var7;
var7 = 1750753;
}
}
}

反混淆结束后可以勉强看出 Java 层是一个换表的 Base64,同时 Base64 表是被异或处理过的
同时推荐使用 Recaf,可以把所有非 ASCII 码表的字符重命名,生成一份 Mapping,对逆向有很大的帮助

image.png

码表操作
image.png

image.png

image.png

执行后可以得到码表 nopqrstDEFGHIJKLhijklUVQRST/WXYZabABCcdefgmuv6789+wxyz012345MNOP

这里的 var15 经过追踪得到是 传入数组的长度
image.png
image.png

这里的 var32 & ~var15 | ~var32 & var15 其实就是 var32 ^ var15 被拆开了 其实是等价的
最终 Java 层的功能就是 换表base64 + 每个字符异或上输入字符串的长度

由 Hexo 驱动 & 主题 Keep
访客数 访问量