apk逆向部分总结持续更新

apk逆向部分总结持续更新

strangeapp.apk(frida的使用)

先用jadx打开

一般主要逻辑就在源代码的com的MainActivity里面。

本题:

shell:

JniBridge主要用于 Java 与原生代码(如 C/C++)之间的交互。

MainActivity在布局中的sampleText控件上显示文本 “hello”。

主要实现了应用启动时的初始化、环境检测、动态加载 Dex 文件等功能,是一个应用壳程序(Shell)。

strangeapp:

MainActivity用户在输入框中输入一段文本,点击按钮后,程序将其处理成字节数组(aa()),然后与 TARGET 比较(compareBytes),如果一致就弹出成功提示。

MainActivity$$ExternalSyntheticLambda0用来实现一个按钮的点击事件。

总的来说

它主要是一个”壳”(Shell),负责解密和加载真正的应用逻辑隐藏在assets的extract.dat。

壳的主要逻辑在native层,native层在apk(apk是zip文件可以解压)的lib里的so文件里。

1

so文件可以用IDA打开

分析so文件知道JNI_OnLoad`函数处理APK中的隐藏数据(偏移298440处),映射为ELF文件并执行。flag可能由这个ELF文件生成或解密得出。

此时我们可以用frida进行hook。

先写一下frida的安装,我这里用的是夜神模拟器。

先用pip安转frida和frida-tools,我这里用的是conda安转的直接pip install xxx就行了。

然后

1
2
3
4
conda activate xxxx 
pip install frida
pip install frida-tools
frida --version #查看版本

我这里是16.5.9,然后去官网下载

然后到 夜神模拟器\Nox\bin 打开终端

1
2
3
nox_adb.exe devices
adb -s <ip:port> shell
getprop ro.product.cpu.abi

1

64位 所以我们下载frida-server-16.5.9-android-x86_64.xz

解压

再到 夜神模拟器\Nox\bin 打开终端

1
2
3
4
5
6
7
8
adb push frida-server-16.5.9-android-x86_64 /data/local/tmp
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
adb shell
su
cd /data/local/tmp/
chmod 755 frida-server-16.5.9-android-x86_64
./frida-server-16.5.9-android-x86_64
1
2
3
frida-ps -U #查看所有进程
frida-ps -Ua #查看所有安装的应用
frida -U -l hook.js -n "com.example.myapp" #运行js脚本

有时候会遇见闪退,就点击的时候立即运行

1
adb shell pidof com.swdd.strangeapp  #获取PID

然后再用获取的PID运行hook.js注意此时不能点击模拟器。

1
frida -U -p PID -l hook.js

用frida直接hook密文,秘钥,和iv直接猜测AES解密,这个感觉不是正解,正解应该是要脱壳。

先分析JNI_OnLoad的sub_1FDE8,可以知道高度混淆的原生库加载器。

用脚本提取出elf

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
#!/usr/bin/env python3

import sys
import os

# 定义Payload的起始偏移量
PAYLOAD_OFFSET = 0x48DC8 # 十进制 298440


def extract_payload(input_file, output_file):
"""
从输入的SO文件中提取Payload并保存到输出文件
"""
try:
# 检查输入文件是否存在
if not os.path.exists(input_file):
print(f"错误: 输入文件 '{input_file}' 不存在。")
return False

with open(input_file, 'rb') as f_in:
# 跳转到Payload的起始偏移量
f_in.seek(PAYLOAD_OFFSET)
# 读取从偏移量开始到文件末尾的所有数据
payload_data = f_in.read()

# 检查是否读取到了数据
if not payload_data:
print(f"错误: 在偏移量 {PAYLOAD_OFFSET} 之后没有数据。")
return False

# 将数据写入输出文件
with open(output_file, 'wb') as f_out:
f_out.write(payload_data)

print(f"成功! Payload 已从 '{input_file}' 提取到 '{output_file}'")
print(f"提取大小: {len(payload_data)} 字节")
return True

except Exception as e:
print(f"提取过程中发生错误: {e}")
return False


if __name__ == '__main__':
# 如果没有提供参数,使用默认路径
if len(sys.argv) < 3:
input_file = r"D:\桌面\libshell.so"
output_file = r"D:\桌面\payload.elf"
print(f"使用默认路径: 输入文件={input_file}, 输出文件={output_file}")
else:
# 使用提供的参数
input_file = sys.argv[1]
output_file = sys.argv[2]

extract_payload(input_file, output_file)

把dat文件(在解压后的assets文件夹里)用010打开,分析。

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
数据项1
键: 0x003BEF18
原始大小: 8 (0x08)
数据: 70 10 27 AF 00 00 5B 01 22 73 5B 02 23 73 0E 00 (16字节)

数据项2
键: 0x003BEF38
原始大小: 8 (0x08)
数据: 54 20 22 73 54 21 23 73 6E 30 63 AD 10 03 0E 00 (16字节)

数据项3
键: 0x003BF224
原始大小: 4 (0x04)
数据: 70 10 27 AF 00 00 0E 00 (8字节)

数据项4
键: 0x003BF23C
原始大小: 4 (0x04)
数据: 70 10 27 AF 00 00 0E 00 (8字节)

数据项5
键: 0x003BF254
原始大小: 4 (0x04)
数据: 70 10 27 AF 00 00 0E 00 (8字节)

数据项6
键: 0x003BF26C
原始大小: 4 (0x04)
数据: 70 10 27 AF 00 00 0E 00 (8字节)

数据项7
键: 0x003BF284
原始大小: 4 (0x04)
数据: 70 10 27 AF 00 00 0E 00 (8字节)

数据项8
键: 0x003BF29C
原始大小: 4 (0x04)
数据: 70 10 27 AF 00 00 0E 00 (8字节)

数据项9
键: 0x003BF2B4
原始大小: 4 (0x04)
数据: 70 10 27 AF 00 00 0E 00 (8字节)

数据项10
键: 0x003BF2CC
原始大小: 4 (0x04)
数据: 70 10 27 AF 00 00 0E 00 (8字节)

数据项11
键: 0x003BF2E4
原始大小: 4 (0x04)
数据: 70 10 27 AF 00 00 0E 00 (8字节)

数据项12
键: 0x003BF088
原始大小: 38 (0x26)
数据: 13 00 30 00 23 00 E1 1D 26 00 06 00 00 00 69 00 24 73 0E 00 00 03 01 00 30 00 00 00 76 11 07 7C 9D 33 17 85 B2 17 CB 01 2A 6D B3 05 A9 0A B3 6A 4E 64 7B 8A D1 1F 13 38 73 97 F5 DA EE B8 0C 2A 11 37 87 D4 77 D7 57 76 5F B4 AC 45 (76字节)

数据项13
键: 0x003BF0E4
原始大小: 4 (0x04)
数据: 70 10 E5 16 00 00 0E 00 (8字节)

数据项14
键: 0x003BF024
原始大小: 41 (0x29)
数据: 38 04 28 00 6E 10 68 AF 04 00 0A 00 38 00 03 00 28 20 12 00 6E 20 51 AF 04 00 0A 00 DF 01 00 05 8E 11 22 02 C8 15 70 10 89 AF 02 00 6E 20 8D AF 12 00 0C 02 12 13 6E 20 79 AF 34 00 0C 03 6E 20 95 AF 32 00 0C 02 6E 10 A5 AF 02 00 0C 02 11 02 11 04 (82字节)

数据项15
键: 0x003BEFA0
原始大小: 57 (0x39)
数据: 1A 00 09 27 1A 01 09 27 22 02 A4 16 62 03 59 73 6E 20 60 AF 30 00 0C 03 1A 04 AC 36 71 10 5F AD 04 00 0C 04 70 30 50 B3 32 04 22 03 A3 16 62 04 59 73 6E 20 60 AF 41 00 0C 04 70 20 4F B3 43 00 1A 04 AD 36 71 10 5F AD 04 00 0C 04 71 10 4D B3 04 00 0C 04 12 15 6E 40 4E B3 54 32 62 05 59 73 6E 20 60 AF 57 00 0C 05 6E 20 4C B3 54 00 0C 05 11 05 (114字节)

数据项16
键: 0x003BEF58
原始大小: 27 (0x1B)
数据: 12 00 38 05 19 00 38 06 17 00 21 51 21 62 32 21 03 00 28 11 12 01 21 52 35 21 0C 00 48 02 05 01 48 03 06 01 32 32 03 00 0F 00 D8 01 01 01 28 F4 12 10 0F 00 0F 00 (54字节)

数据项17
键: 0x003BF1EC
原始大小: 20 (0x14)
数据: 22 00 C2 03 70 20 97 16 30 00 6E 20 A6 16 40 00 0C 00 1A 01 8D 6B 12 02 6E 30 B5 16 10 02 0C 00 6E 10 C1 16 00 00 0E 00 (40字节)

数据项18
键: 0x003BF0FC
原始大小: 61 (0x3D)
数据: 6E 10 EE 0F 05 00 0C 00 6E 10 2D AF 00 00 0C 00 70 20 60 AD 04 00 0C 01 62 02 24 73 70 30 61 AD 14 02 0A 02 38 02 08 00 1A 02 48 3D 70 20 66 AD 24 00 28 06 1A 02 58 6A 70 20 66 AD 24 00 28 1D 0D 01 22 02 C8 15 70 10 89 AF 02 00 1B 03 43 26 01 00 6E 20 95 AF 32 00 0C 02 6E 10 94 AE 01 00 0C 03 6E 20 95 AF 32 00 0C 02 6E 10 A5 AF 02 00 0C 02 70 20 66 AD 24 00 0E 00 (122字节)

数据项19
键: 0x003BF198
原始大小: 33 (0x21)
数据: 6F 20 FB 16 43 00 60 00 2B 73 6E 20 65 AD 03 00 60 00 2A 73 6E 20 62 AD 03 00 0C 00 1F 00 92 02 60 01 29 73 6E 20 62 AD 13 00 0C 01 1F 01 8A 02 22 02 56 15 70 30 5B AD 32 00 6E 20 73 0F 21 00 0E 00

用dalvik源码打表,把extract.dat文件完全转为smali

原码地址Indroid/libdex/DexOpcodes.h at master · UchihaL/Indroid · GitHub

由于我们仅对 extract.dat 这一个文件进行指令还原,还需要从原始的 classes.dex 文件中提取数据,将其中的字符串索引一并还原。

classes.dex解压apk就可以看到。

exp1

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
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import struct
from typing import Dict, Tuple, List, Optional


def s1_to_s8(x: int, bits: int) -> int:
sign = 1 << (bits - 1)
mask = (1 << bits) - 1
x &= mask
return (x ^ sign) - sign


def s16(x: int) -> int:
return struct.unpack('<h', struct.pack('<H', x & 0xFFFF))[0]


def s32_from_u2(lo: int, hi: int) -> int:
v = (hi << 16) | lo
return struct.unpack('<i', struct.pack('<I', v))[0]


def s64_from_u2(u1: int, u2_: int, u3: int, u4: int) -> int:
lo = (u2_ << 16) | u1
hi = (u4 << 16) | u3
v = (hi << 32) | lo
return struct.unpack('<q', struct.pack('<Q', v & 0xFFFFFFFFFFFFFFFF))[0]


def hex32(n: int) -> str:
return f"0x{(n & 0xFFFFFFFF):08x}"


def hex16(n: int) -> str:
return f"0x{(n & 0xFFFF):04x}"


def hex8(n: int) -> str:
return f"0x{(n & 0xFF):02x}"


OPCODES: Dict[int, Tuple[str, str, Optional[int], Optional[int]]] = {
0x00: ("nop", "10x", None, None),
0x01: ("move", "12x", None, None),
0x02: ("move/from16", "22x", None, None),
0x03: ("move/16", "32x", None, None),
0x04: ("move-wide", "12x", None, None),
0x05: ("move-wide/from16", "22x", None, None),
0x06: ("move-wide/16", "32x", None, None),
0x07: ("move-object", "12x", None, None),
0x08: ("move-object/from16", "22x", None, None),
0x09: ("move-object/16", "32x", None, None),
0x0a: ("move-result", "11x", None, None),
0x0b: ("move-result-wide", "11x", None, None),
0x0c: ("move-result-object", "11x", None, None),
0x0d: ("move-exception", "11x", None, None),
0x0e: ("return-void", "10x", None, None),
0x0f: ("return", "11x", None, None),
0x10: ("return-wide", "11x", None, None),
0x11: ("return-object", "11x", None, None),
0x12: ("const/4", "11n", None, None),
0x13: ("const/16", "21s", None, None),
0x14: ("const", "31i", None, None),
0x15: ("const/high16", "21ih", None, None),
0x16: ("const-wide/16", "21s", None, None),
0x17: ("const-wide/32", "31i", None, None),
0x18: ("const-wide", "51l", None, None),
0x19: ("const-wide/high16", "21lh", None, None),
0x1a: ("const-string", "21c", 0, None),
0x1b: ("const-string/jumbo", "31c", 0, None),
0x1c: ("const-class", "21c", 1, None),
0x1d: ("monitor-enter", "11x", None, None),
0x1e: ("monitor-exit", "11x", None, None),
0x1f: ("check-cast", "21c", 1, None),
0x20: ("instance-of", "22c", 1, None),
0x21: ("array-length", "12x", None, None),
0x22: ("new-instance", "21c", 1, None),
0x23: ("new-array", "22c", 1, None),
0x24: ("filled-new-array", "35c", 1, None),
0x25: ("filled-new-array/range", "3rc", 1, None),
0x26: ("fill-array-data", "31t", None, None),
0x27: ("throw", "11x", None, None),
0x28: ("goto", "10t", None, None),
0x29: ("goto/16", "20t", None, None),
0x2a: ("goto/32", "30t", None, None),
0x2b: ("packed-switch", "31t", None, None),
0x2c: ("sparse-switch", "31t", None, None),
0x2d: ("cmpl-float", "23x", None, None),
0x2e: ("cmpg-float", "23x", None, None),
0x2f: ("cmpl-double", "23x", None, None),
0x30: ("cmpg-double", "23x", None, None),
0x31: ("cmp-long", "23x", None, None),
0x32: ("if-eq", "22t", None, None),
0x33: ("if-ne", "22t", None, None),
0x34: ("if-lt", "22t", None, None),
0x35: ("if-ge", "22t", None, None),
0x36: ("if-gt", "22t", None, None),
0x37: ("if-le", "22t", None, None),
0x38: ("if-eqz", "21t", None, None),
0x39: ("if-nez", "21t", None, None),
0x3a: ("if-ltz", "21t", None, None),
0x3b: ("if-gez", "21t", None, None),
0x3c: ("if-gtz", "21t", None, None),
0x3d: ("if-lez", "21t", None, None),

0x44: ("aget", "23x", None, None),
0x45: ("aget-wide", "23x", None, None),
0x46: ("aget-object", "23x", None, None),
0x47: ("aget-boolean", "23x", None, None),
0x48: ("aget-byte", "23x", None, None),
0x49: ("aget-char", "23x", None, None),
0x4a: ("aget-short", "23x", None, None),
0x4b: ("aput", "23x", None, None),
0x4c: ("aput-wide", "23x", None, None),
0x4d: ("aput-object", "23x", None, None),
0x4e: ("aput-boolean", "23x", None, None),
0x4f: ("aput-byte", "23x", None, None),
0x50: ("aput-char", "23x", None, None),
0x51: ("aput-short", "23x", None, None),

0x52: ("iget", "22c", 2, None),
0x53: ("iget-wide", "22c", 2, None),
0x54: ("iget-object", "22c", 2, None),
0x55: ("iget-boolean", "22c", 2, None),
0x56: ("iget-byte", "22c", 2, None),
0x57: ("iget-char", "22c", 2, None),
0x58: ("iget-short", "22c", 2, None),

0x59: ("iput", "22c", 2, None),
0x5a: ("iput-wide", "22c", 2, None),
0x5b: ("iput-object", "22c", 2, None),
0x5c: ("iput-boolean", "22c", 2, None),
0x5d: ("iput-byte", "22c", 2, None),
0x5e: ("iput-char", "22c", 2, None),
0x5f: ("iput-short", "22c", 2, None),

0x60: ("sget", "21c", 2, None),
0x61: ("sget-wide", "21c", 2, None),
0x62: ("sget-object", "21c", 2, None),
0x63: ("sget-boolean", "21c", 2, None),
0x64: ("sget-byte", "21c", 2, None),
0x65: ("sget-char", "21c", 2, None),
0x66: ("sget-short", "21c", 2, None),

0x67: ("sput", "21c", 2, None),
0x68: ("sput-wide", "21c", 2, None),
0x69: ("sput-object", "21c", 2, None),
0x6a: ("sput-boolean", "21c", 2, None),
0x6b: ("sput-byte", "21c", 2, None),
0x6c: ("sput-char", "21c", 2, None),
0x6d: ("sput-short", "21c", 2, None),

0x6e: ("invoke-virtual", "35c", 3, None),
0x6f: ("invoke-super", "35c", 3, None),
0x70: ("invoke-direct", "35c", 3, None),
0x71: ("invoke-static", "35c", 3, None),
0x72: ("invoke-interface", "35c", 3, None),

0x74: ("invoke-virtual/range", "3rc", 3, None),
0x75: ("invoke-super/range", "3rc", 3, None),
0x76: ("invoke-direct/range", "3rc", 3, None),
0x77: ("invoke-static/range", "3rc", 3, None),
0x78: ("invoke-interface/range", "3rc", 3, None),

0x7b: ("neg-int", "12x", None, None),
0x7c: ("not-int", "12x", None, None),
0x7d: ("neg-long", "12x", None, None),
0x7e: ("not-long", "12x", None, None),
0x7f: ("neg-float", "12x", None, None),
0x80: ("neg-double", "12x", None, None),

0x81: ("int-to-long", "12x", None, None),
0x82: ("int-to-float", "12x", None, None),
0x83: ("int-to-double", "12x", None, None),
0x84: ("long-to-int", "12x", None, None),
0x85: ("long-to-float", "12x", None, None),
0x86: ("long-to-double", "12x", None, None),
0x87: ("float-to-int", "12x", None, None),
0x88: ("float-to-long", "12x", None, None),
0x89: ("float-to-double", "12x", None, None),
0x8a: ("double-to-int", "12x", None, None),
0x8b: ("double-to-long", "12x", None, None),
0x8c: ("double-to-float", "12x", None, None),
0x8d: ("int-to-byte", "12x", None, None),
0x8e: ("int-to-char", "12x", None, None),
0x8f: ("int-to-short", "12x", None, None),

0x90: ("add-int", "23x", None, None),
0x91: ("sub-int", "23x", None, None),
0x92: ("mul-int", "23x", None, None),
0x93: ("div-int", "23x", None, None),
0x94: ("rem-int", "23x", None, None),
0x95: ("and-int", "23x", None, None),
0x96: ("or-int", "23x", None, None),
0x97: ("xor-int", "23x", None, None),
0x98: ("shl-int", "23x", None, None),
0x99: ("shr-int", "23x", None, None),
0x9a: ("ushr-int", "23x", None, None),

0x9b: ("add-long", "23x", None, None),
0x9c: ("sub-long", "23x", None, None),
0x9d: ("mul-long", "23x", None, None),
0x9e: ("div-long", "23x", None, None),
0x9f: ("rem-long", "23x", None, None),
0xa0: ("and-long", "23x", None, None),
0xa1: ("or-long", "23x", None, None),
0xa2: ("xor-long", "23x", None, None),
0xa3: ("shl-long", "23x", None, None),
0xa4: ("shr-long", "23x", None, None),
0xa5: ("ushr-long", "23x", None, None),

0xa6: ("add-float", "23x", None, None),
0xa7: ("sub-float", "23x", None, None),
0xa8: ("mul-float", "23x", None, None),
0xa9: ("div-float", "23x", None, None),
0xaa: ("rem-float", "23x", None, None),

0xab: ("add-double", "23x", None, None),
0xac: ("sub-double", "23x", None, None),
0xad: ("mul-double", "23x", None, None),
0xae: ("div-double", "23x", None, None),
0xaf: ("rem-double", "23x", None, None),

0xb0: ("add-int/2addr", "12x", None, None),
0xb1: ("sub-int/2addr", "12x", None, None),
0xb2: ("mul-int/2addr", "12x", None, None),
0xb3: ("div-int/2addr", "12x", None, None),
0xb4: ("rem-int/2addr", "12x", None, None),
0xb5: ("and-int/2addr", "12x", None, None),
0xb6: ("or-int/2addr", "12x", None, None),
0xb7: ("xor-int/2addr", "12x", None, None),
0xb8: ("shl-int/2addr", "12x", None, None),
0xb9: ("shr-int/2addr", "12x", None, None),
0xba: ("ushr-int/2addr", "12x", None, None),

0xbb: ("add-long/2addr", "12x", None, None),
0xbc: ("sub-long/2addr", "12x", None, None),
0xbd: ("mul-long/2addr", "12x", None, None),
0xbe: ("div-long/2addr", "12x", None, None),
0xbf: ("rem-long/2addr", "12x", None, None),
0xc0: ("and-long/2addr", "12x", None, None),
0xc1: ("or-long/2addr", "12x", None, None),
0xc2: ("xor-long/2addr", "12x", None, None),
0xc3: ("shl-long/2addr", "12x", None, None),
0xc4: ("shr-long/2addr", "12x", None, None),
0xc5: ("ushr-long/2addr", "12x", None, None),

0xc6: ("add-float/2addr", "12x", None, None),
0xc7: ("sub-float/2addr", "12x", None, None),
0xc8: ("mul-float/2addr", "12x", None, None),
0xc9: ("div-float/2addr", "12x", None, None),
0xca: ("rem-float/2addr", "12x", None, None),

0xcb: ("add-double/2addr", "12x", None, None),
0xcc: ("sub-double/2addr", "12x", None, None),
0xcd: ("mul-double/2addr", "12x", None, None),
0xce: ("div-double/2addr", "12x", None, None),
0xcf: ("rem-double/2addr", "12x", None, None),

0xd0: ("add-int/lit16", "22s", None, None),
0xd1: ("rsub-int", "22s", None, None),
0xd2: ("mul-int/lit16", "22s", None, None),
0xd3: ("div-int/lit16", "22s", None, None),
0xd4: ("rem-int/lit16", "22s", None, None),
0xd5: ("and-int/lit16", "22s", None, None),
0xd6: ("or-int/lit16", "22s", None, None),
0xd7: ("xor-int/lit16", "22s", None, None),

0xd8: ("add-int/lit8", "22b", None, None),
0xd9: ("rsub-int/lit8", "22b", None, None),
0xda: ("mul-int/lit8", "22b", None, None),
0xdb: ("div-int/lit8", "22b", None, None),
0xdc: ("rem-int/lit8", "22b", None, None),
0xdd: ("and-int/lit8", "22b", None, None),
0xde: ("or-int/lit8", "22b", None, None),
0xdf: ("xor-int/lit8", "22b", None, None),
0xe0: ("shl-int/lit8", "22b", None, None),
0xe1: ("shr-int/lit8", "22b", None, None),
0xe2: ("ushr-int/lit8", "22b", None, None),

0x0100: ("packed-switch-payload", "pswitch-payload", None, None),
0x0200: ("sparse-switch-payload", "sswitch-payload", None, None),
0x0300: ("array-payload", "array-payload", None, None),

0xfa: ("invoke-polymorphic", "45cc", 3, 4),
0xfb: ("invoke-polymorphic/range", "4rcc", 3, 4),
0xfc: ("invoke-custom", "35c", 5, None),
0xfd: ("invoke-custom/range", "3rc", 5, None),
0xfe: ("const-method-handle", "21c", 6, None),
0xff: ("const-method-type", "21c", 4, None),
}


def reg_name(r: int, regs_size: int, ins_size: int) -> str:
locals_size = max(0, regs_size - ins_size)
if r >= locals_size:
return f"p{r - locals_size}"
return f"v{r}"


def ref_kind_name(kind: Optional[int]) -> str:
if kind is None:
return "ref"
return {
0: "string",
1: "type",
2: "field",
3: "method",
4: "proto",
5: "call_site",
6: "method_handle",
}.get(kind, "ref")


class DexFile:
def __init__(self, path: str):
with open(path, 'rb') as f:
self.data: bytes = f.read()
self.size = len(self.data)
self._parse_header()
self._parse_primary_tables()
self._parse_map_list()
self._mh_count = 0
self._mh_off = 0
self._cs_count = 0
self._cs_off = 0
if self.map_items.get(0x0008): # TYPE_METHOD_HANDLE_ITEM
self._mh_count, self._mh_off = self.map_items[0x0008]
if self.map_items.get(0x0007): # TYPE_CALL_SITE_ID_ITEM
self._cs_count, self._cs_off = self.map_items[0x0007]

def _u1(self, off: int) -> int:
return self.data[off]

def _u2(self, off: int) -> int:
return struct.unpack_from('<H', self.data, off)[0]

def _u4(self, off: int) -> int:
return struct.unpack_from('<I', self.data, off)[0]

def _bytes(self, off: int, n: int) -> bytes:
return self.data[off:off + n]

def _parse_header(self) -> None:
if self.size < 0x70:
raise ValueError("DEX 文件过小")
self.magic = self._bytes(0, 8)
self.file_size = self._u4(0x20)
self.header_size = self._u4(0x24)
self.endian_tag = self._u4(0x28)
self.map_off = self._u4(0x34)
self.string_ids_size = self._u4(0x38)
self.string_ids_off = self._u4(0x3C)
self.type_ids_size = self._u4(0x40)
self.type_ids_off = self._u4(0x44)
self.proto_ids_size = self._u4(0x48)
self.proto_ids_off = self._u4(0x4C)
self.field_ids_size = self._u4(0x50)
self.field_ids_off = self._u4(0x54)
self.method_ids_size = self._u4(0x58)
self.method_ids_off = self._u4(0x5C)

def _parse_primary_tables(self) -> None:
self._string_offs: List[int] = []
for i in range(self.string_ids_size):
self._string_offs.append(self._u4(self.string_ids_off + i * 4))
self._string_cache: Dict[int, str] = {}

self._type_str_idx: List[int] = []
for i in range(self.type_ids_size):
self._type_str_idx.append(self._u4(self.type_ids_off + i * 4))

self._proto_list: List[Tuple[int, int, int]] = []
for i in range(self.proto_ids_size):
off = self.proto_ids_off + i * 12
shorty_idx = self._u4(off + 0)
return_type_idx = self._u4(off + 4)
parameters_off = self._u4(off + 8)
self._proto_list.append((shorty_idx, return_type_idx, parameters_off))

self._field_list: List[Tuple[int, int, int]] = []
for i in range(self.field_ids_size):
off = self.field_ids_off + i * 8
class_idx = self._u2(off + 0)
type_idx = self._u2(off + 2)
name_idx = self._u4(off + 4)
self._field_list.append((class_idx, type_idx, name_idx))

self._method_list: List[Tuple[int, int, int]] = []
for i in range(self.method_ids_size):
off = self.method_ids_off + i * 8
class_idx = self._u2(off + 0)
proto_idx = self._u2(off + 2)
name_idx = self._u4(off + 4)
self._method_list.append((class_idx, proto_idx, name_idx))

def _parse_map_list(self) -> None:
self.map_items: Dict[int, Tuple[int, int]] = {}
if self.map_off == 0:
return
size = self._u4(self.map_off)
off = self.map_off + 4
for _ in range(size):
if off + 12 > self.size:
break
typ = self._u2(off + 0)
_unused = self._u2(off + 2)
count = self._u4(off + 4)
item_off = self._u4(off + 8)
self.map_items[typ] = (count, item_off)
off += 12

def _uleb128(self, off: int) -> Tuple[int, int]:
result = 0
shift = 0
pos = off
while True:
b = self._u1(pos)
pos += 1
result |= (b & 0x7F) << shift
if (b & 0x80) == 0:
break
shift += 7
return result, pos

def _read_mutf8_at(self, off: int) -> Tuple[str, int]:
_, pos = self._uleb128(off)
chars: List[str] = []
while True:
b0 = self._u1(pos);
pos += 1
if b0 == 0x00:
break
if b0 < 0x80:
chars.append(chr(b0))
elif (b0 & 0xE0) == 0xC0:
b1 = self._u1(pos);
pos += 1
code = ((b0 & 0x1F) << 6) | (b1 & 0x3F)
if code == 0:
chars.append('\x00')
else:
chars.append(chr(code))
else:
b1 = self._u1(pos);
b2 = self._u1(pos + 1);
pos += 2
code = ((b0 & 0x0F) << 12) | ((b1 & 0x3F) << 6) | (b2 & 0x3F)
if 0xD800 <= code <= 0xDBFF:
b0n = self._u1(pos);
b1n = self._u1(pos + 1);
b2n = self._u1(pos + 2);
pos += 3
code2 = ((b0n & 0x0F) << 12) | ((b1n & 0x3F) << 6) | (b2n & 0x3F)
if 0xDC00 <= code2 <= 0xDFFF:
cp = 0x10000 + (((code - 0xD800) << 10) | (code2 - 0xDC00))
chars.append(chr(cp))
else:
chars.append(chr(code))
chars.append(chr(code2))
else:
chars.append(chr(code))
return ''.join(chars), pos

def get_string(self, idx: int) -> Optional[str]:
if idx < 0 or idx >= self.string_ids_size:
return None
if idx in self._string_cache:
return self._string_cache[idx]
off = self._string_offs[idx]
if off <= 0 or off >= self.size:
return None
s, _ = self._read_mutf8_at(off)
self._string_cache[idx] = s
return s

def get_type_desc(self, idx: int) -> Optional[str]:
if idx < 0 or idx >= self.type_ids_size:
return None
sidx = self._type_str_idx[idx]
return self.get_string(sidx)

def _get_type_list(self, off: int) -> List[str]:
if off == 0 or off >= self.size:
return []
size = self._u4(off)
types: List[str] = []
p = off + 4
for _ in range(size):
t_idx = self._u2(p)
p += 2
desc = self.get_type_desc(t_idx)
types.append(desc if desc is not None else f"type@{hex16(t_idx)}")
return types

def get_proto_sig(self, idx: int) -> Optional[str]:
if idx < 0 or idx >= self.proto_ids_size:
return None
shorty_idx, ret_type_idx, params_off = self._proto_list[idx]
ret = self.get_type_desc(ret_type_idx) or "V"
params = self._get_type_list(params_off)
return f"({''.join(params)}){ret}"

def get_field_str(self, idx: int) -> Optional[str]:
if idx < 0 or idx >= self.field_ids_size:
return None
class_idx, type_idx, name_idx = self._field_list[idx]
owner = self.get_type_desc(class_idx) or f"type@{hex16(class_idx)}"
ftype = self.get_type_desc(type_idx) or f"type@{hex16(type_idx)}"
name = self.get_string(name_idx) or f"string@{hex32(name_idx)}"
return f"{owner}->{name}:{ftype}"

def get_method_str(self, idx: int) -> Optional[str]:
if idx < 0 or idx >= self.method_ids_size:
return None
class_idx, proto_idx, name_idx = self._method_list[idx]
owner = self.get_type_desc(class_idx) or f"type@{hex16(class_idx)}"
name = self.get_string(name_idx) or f"string@{hex32(name_idx)}"
sig = self.get_proto_sig(proto_idx) or f"proto@{hex16(proto_idx)}"
return f"{owner}->{name}{sig}"

def get_call_site_str(self, idx: int) -> Optional[str]:
if self._cs_off == 0 or idx < 0 or idx >= self._cs_count:
return None
entry_off = self._u4(self._cs_off + idx * 4)
return f"call_site@{hex32(entry_off)}"

def get_method_handle_str(self, idx: int) -> Optional[str]:
if self._mh_off == 0 or idx < 0 or idx >= self._mh_count:
return None
off = self._mh_off + idx * 8
if off + 8 > self.size:
return None
mh_type = self._u2(off + 0)
_unused = self._u2(off + 2)
target_id = self._u2(off + 4)
_unused2 = self._u2(off + 6)
target_field = self.get_field_str(target_id)
target_method = self.get_method_str(target_id)
target = target_method if target_method is not None else target_field
if target is None:
target = f"id@{hex16(target_id)}"
return f"method_handle[kind={mh_type}] {target}"

def format_ref(self, kind: Optional[int], idx: int) -> str:
if kind is None:
return f"ref@{hex16(idx) if idx <= 0xFFFF else hex32(idx)}"
if kind == 0:
s = self.get_string(idx)
return f"\"{s}\"" if s is not None else f"string@{hex16(idx) if idx <= 0xFFFF else hex32(idx)}"
if kind == 1:
s = self.get_type_desc(idx)
return s if s is not None else f"type@{hex16(idx) if idx <= 0xFFFF else hex32(idx)}"
if kind == 2:
s = self.get_field_str(idx)
return s if s is not None else f"field@{hex16(idx) if idx <= 0xFFFF else hex32(idx)}"
if kind == 3:
s = self.get_method_str(idx)
return s if s is not None else f"method@{hex16(idx) if idx <= 0xFFFF else hex32(idx)}"
if kind == 4:
s = self.get_proto_sig(idx)
return s if s is not None else f"proto@{hex16(idx) if idx <= 0xFFFF else hex32(idx)}"
if kind == 5:
s = self.get_call_site_str(idx)
return s if s is not None else f"call_site@{hex16(idx) if idx <= 0xFFFF else hex32(idx)}"
if kind == 6:
s = this.get_method_handle_str(idx)
return s if s is not None else f"method_handle@{hex16(idx) if idx <= 0xFFFF else hex32(idx)}"
return f"{ref_kind_name(kind)}@{hex16(idx) if idx <= 0xFFFF else hex32(idx)}"


def decode_block(insns_bytes: bytes, regs_size: int, ins_size: int, dex: Optional[DexFile] = None) -> str:
insns_size = len(insns_bytes) // 2
units = list(struct.unpack('<' + 'H' * insns_size, insns_bytes))
labels_needed: Dict[int, str] = {}
payload_labels: Dict[int, str] = {}
switch_bases: Dict[int, int] = {}
decoded: List[Tuple[int, int, List[str]]] = []

def rn(x: int) -> str:
return reg_name(x, regs_size, ins_size)

def fmt_ref(kind: Optional[int], idx: int, is_32: bool) -> str:
if dex is None:
base = hex32(idx) if is_32 else hex16(idx)
return f"{ref_kind_name(kind)}@{base}"
return dex.format_ref(kind, idx)

def decode_one(pc: int) -> Tuple[int, List[str]]:
u0 = units[pc]
op_lo = u0 & 0xFF
op_hi = (u0 >> 8) & 0xFF

if u0 in (0x0100, 0x0200, 0x0300):
if u0 == 0x0100:
if pc + 4 > insns_size: return (1, ["# truncated packed-switch-payload"])
size = units[pc + 1]
lines = [".packed-switch 0x%08x" % ((units[pc + 3] << 16) | units[pc + 2])]
for _ in range(size):
lines.append(" <tbd>")
lines.append(".end packed-switch")
return (4 + size * 2, lines)
if u0 == 0x0200:
if pc + 2 > insns_size: return (1, ["# truncated sparse-switch-payload"])
size = units[pc + 1]
lines = [".sparse-switch"]
for _ in range(size):
lines.append(" <tbd> -> <tbd>")
lines.append(".end sparse-switch")
return (2 + size * 4, lines)
if u0 == 0x0300:
if pc + 4 > insns_size: return (1, ["# truncated array-payload"])
elem_width = units[pc + 1]
size = (units[pc + 3] << 16) | units[pc + 2]
total_bytes = elem_width * size
cu = (total_bytes + 1) // 2
pad = cu & 1
lines = [f".array-data {elem_width}", ".end array-data"]
return (4 + cu + pad, lines)

info = OPCODES.get(op_lo)
if info is None:
return (1, [f"# unknown-op {hex8(op_lo)}"])

name, fmt, ref1, ref2 = info
a8 = op_hi
out: List[str] = []
size = 1

if fmt == "10x":
out.append(name)
elif fmt == "11x":
out.append(f"{name} {rn(a8)}")
elif fmt == "11n":
A = (a8 >> 4) & 0xF;
B = s1_to_s8(a8 & 0xF, 4)
out.append(f"{name} {rn(A)}, {B}")
elif fmt == "12x":
A = (a8 >> 4) & 0xF;
B = a8 & 0xF
out.append(f"{name} {rn(A)}, {rn(B)}")
elif fmt == "10t":
off8 = s1_to_s8(a8, 8);
tgt = pc + off8
labels_needed.setdefault(tgt, f":L{tgt:04x}")
out.append(f"{name} {labels_needed[tgt]}")
elif fmt == "20t":
size = 2;
off16 = s16(units[pc + 1]);
tgt = pc + off16
labels_needed.setdefault(tgt, f":L{tgt:04x}")
out.append(f"{name} {labels_needed[tgt]}")
elif fmt == "30t":
size = 3;
off32 = s32_from_u2(units[pc + 1], units[pc + 2]);
tgt = pc + off32
labels_needed.setdefault(tgt, f":L{tgt:04x}")
out.append(f"{name} {labels_needed[tgt]}")
elif fmt == "21t":
size = 2;
A = a8;
off16 = s16(units[pc + 1]);
tgt = pc + off16
labels_needed.setdefault(tgt, f":L{tgt:04x}")
out.append(f"{name} {rn(A)}, {labels_needed[tgt]}")
elif fmt == "22t":
size = 2;
A = (a8 >> 4) & 0xF;
B = a8 & 0xF;
off16 = s16(units[pc + 1]);
tgt = pc + off16
labels_needed.setdefault(tgt, f":L{tgt:04x}")
out.append(f"{name} {rn(A)}, {rn(B)}, {labels_needed[tgt]}")
elif fmt == "21s":
size = 2;
A = a8;
lit = s16(units[pc + 1])
out.append(f"{name} {rn(A)}, {lit}")
elif fmt == "21ih":
size = 2;
A = a8;
val = units[pc + 1] << 16
out.append(f"{name} {rn(A)}, {hex(val)}")
elif fmt == "21lh":
size = 2;
A = a8;
val = units[pc + 1] << 48
out.append(f"{name} {rn(A)}, {hex(val)}")
elif fmt == "31i":
size = 3;
A = a8;
lit = s32_from_u2(units[pc + 1], units[pc + 2])
out.append(f"{name} {rn(A)}, {hex32(lit)}")
elif fmt == "51l":
size = 5;
A = a8;
lit = s64_from_u2(units[pc + 1], units[pc + 2], units[pc + 3], units[pc + 4])
out.append(f"{name} {rn(A)}, {hex(lit & 0xFFFFFFFFFFFFFFFF)}")
elif fmt == "21c":
size = 2;
A = a8;
idx = units[pc + 1]
out.append(f"{name} {rn(A)}, {fmt_ref(ref1, idx, False)}")
elif fmt == "31c":
size = 3;
A = a8;
idx = (units[pc + 2] << 16) | units[pc + 1]
out.append(f"{name} {rn(A)}, {fmt_ref(ref1, idx, True)}")
elif fmt == "22c":
size = 2;
A = (a8 >> 4) & 0xF;
B = a8 & 0xF;
idx = units[pc + 1]
out.append(f"{name} {rn(A)}, {rn(B)}, {fmt_ref(ref1, idx, False)}")
elif fmt == "22x":
size = 2;
A = a8;
BBBB = units[pc + 1]
out.append(f"{name} {rn(A)}, {rn(BBBB)}")
elif fmt == "23x":
size = 2;
A = a8;
B = units[pc + 1] & 0xFF;
C = (units[pc + 1] >> 8) & 0xFF
out.append(f"{name} {rn(A)}, {rn(B)}, {rn(C)}")
elif fmt == "22s":
size = 2;
A = (a8 >> 4) & 0xF;
B = a8 & 0xF;
lit = s16(units[pc + 1])
out.append(f"{name} {rn(A)}, {rn(B)}, {lit}")
elif fmt == "22b":
size = 2
A = a8
BC = units[pc + 1]
B = BC & 0xFF
C = s1_to_s8((BC >> 8) & 0xFF, 8)
out.append(f"{name} {rn(A)}, {rn(B)}, {C}")
elif fmt == "32x":
size = 3;
AAAA = units[pc + 1];
BBBB = units[pc + 2]
out.append(f"{name} {rn(AAAA)}, {rn(BBBB)}")
elif fmt == "31t":
size = 3;
A = a8;
off32 = s32_from_u2(units[pc + 1], units[pc + 2]);
tgt = pc + off32
payload_labels.setdefault(tgt, f":payload_{tgt:04x}")
switch_bases[tgt] = pc
out.append(f"{name} {rn(A)}, {payload_labels[tgt]}")
elif fmt == "35c":
size = 3;
A = (a8 >> 4) & 0xF;
G = a8 & 0xF;
bbbb = units[pc + 1];
cdef = units[pc + 2]
C = (cdef) & 0xF;
D = (cdef >> 4) & 0xF;
E = (cdef >> 8) & 0xF;
F = (cdef >> 12) & 0xF
regs = [C, D, E, F, G][:A]
out.append(f"{name} {{{', '.join(rn(r) for r in regs)}}}, {fmt_ref(ref1, bbbb, False)}")
elif fmt == "3rc":
size = 3;
A = a8;
bbbb = units[pc + 1];
cccc = units[pc + 2]
regs = [rn(cccc + i) for i in range(A)]
out.append(f"{name} {{{', '.join(regs)}}}, {fmt_ref(ref1, bbbb, False)}")
elif fmt == "45cc":
size = 4;
A = (a8 >> 4) & 0xF;
G = a8 & 0xF;
bbbb = units[pc + 1];
cdef = units[pc + 2];
hhhh = units[pc + 3]
C = (cdef) & 0xF;
D = (cdef >> 4) & 0xF;
E = (cdef >> 8) & 0xF;
F = (cdef >> 12) & 0xF
regs = [C, D, E, F, G][:A]
out.append(
f"{name} {{{', '.join(rn(r) for r in regs)}}}, {fmt_ref(ref1, bbbb, False)}, {fmt_ref(ref2, hhhh, False)}")
elif fmt == "4rcc":
size = 4;
A = a8;
bbbb = units[pc + 1];
cccc = units[pc + 2];
hhhh = units[pc + 3]
regs = [rn(cccc + i) for i in range(A)]
out.append(f"{name} {{{', '.join(regs)}}}, {fmt_ref(ref1, bbbb, False)}, {fmt_ref(ref2, hhhh, False)}")
else:
out.append(f"# unhandled-format {fmt} ({name})")

return (size, out)

pc = 0
while pc < insns_size:
sz, lines = decode_one(pc)
decoded.append((pc, max(1, sz), lines))
pc += max(1, sz)

for pco in list(payload_labels.keys()):
if 0 <= pco < insns_size:
u0 = units[pco]
if u0 == 0x0100:
payload_labels[pco] = f":pswitch_{pco:04x}"
elif u0 == 0x0200:
payload_labels[pco] = f":sswitch_{pco:04x}"
elif u0 == 0x0300:
payload_labels[pco] = f":array_{pco:04x}"
else:
payload_labels[pco] = f":payload_{pco:04x}"

out: List[str] = []
out.append(f".registers {regs_size}")
out.append("")

label_for_pc = {pc: name for pc, name in labels_needed.items()}
label_for_pc.update({pc: name for pc, name in payload_labels.items()})

def render_payload(pc0: int, content_lines: List[str]) -> List[str]:
u0 = units[pc0]
lines: List[str] = []
if u0 == 0x0100:
size = units[pc0 + 1] if pc0 + 1 < insns_size else 0
first_key = s32_from_u2(units[pc0 + 2], units[pc0 + 3]) if pc0 + 3 < insns_size else 0
base_pc = switch_bases.get(pc0, pc0)
lines.append(f".packed-switch {hex32(first_key)}")
for i in range(size):
idx = pc0 + 4 + i * 2
if idx + 1 >= insns_size:
lines.append("# truncated targets");
break
tgt_rel = s32_from_u2(units[idx], units[idx + 1])
tgt_abs = base_pc + tgt_rel
lbl = label_for_pc.get(tgt_abs, f":L{tgt_abs:04x}")
lines.append(f" {lbl}")
lines.append(".end packed-switch")
return lines
if u0 == 0x0200:
size = units[pc0 + 1] if pc0 + 1 < insns_size else 0
base_pc = switch_bases.get(pc0, pc0)
lines.append(".sparse-switch")
for i in range(size):
kp = pc0 + 2 + i * 4
if kp + 3 >= insns_size:
lines.append("# truncated pairs");
break
key = s32_from_u2(units[kp], units[kp + 1])
tgt_rel = s32_from_u2(units[kp + 2], units[kp + 3])
tgt_abs = base_pc + tgt_rel
lbl = label_for_pc.get(tgt_abs, f":L{tgt_abs:04x}")
lines.append(f" {hex32(key)} -> {lbl}")
lines.append(".end sparse-switch")
return lines
if u0 == 0x0300:
elem_width = units[pc0 + 1] if pc0 + 1 < insns_size else 1
size = (units[pc0 + 3] << 16 | units[pc0 + 2]) if pc0 + 3 < insns_size else 0
lines.append(f".array-data {elem_width}")
byte_off = pc0 * 2 + 8
for i in range(size):
start = byte_off + i * elem_width
end = start + elem_width
if end > len(insns_bytes): break
val = int.from_bytes(insns_bytes[start:end], 'little', signed=False)
lines.append(f" {hex(val)}")
lines.append(".end array-data")
return lines
return content_lines

for pc0, sz, content in decoded:
if pc0 in label_for_pc:
out.append(label_for_pc[pc0])
u0 = units[pc0]
if u0 in (0x0100, 0x0200, 0x0300):
out += render_payload(pc0, content)
else:
fixed = []
for ln in content:
if "packed-switch" in ln or "sparse-switch" in ln or "fill-array-data" in ln:
for tgt, lbl in payload_labels.items():
ln = ln.replace(f":payload_{tgt:04x}", lbl)
fixed.append(ln)
out += fixed

return "\n".join(out)


def disasm_pack(path: str, regs_size: int, ins_size: int, dex: Optional[DexFile]) -> str:
with open(path, 'rb') as f:
data = f.read()

pos = 0
idx = 0
out_all: List[str] = []
total = len(data)
while pos + 8 <= total:
off_u32, units = struct.unpack_from('<II', data, pos)
pos += 8
need = units * 2
if units == 0 or pos + need > total:
break
insns = data[pos:pos + need]
pos += need
smali = decode_block(insns, regs_size, ins_size, dex)
out_all.append(f"# record {idx}: file_off={hex(off_u32)} units={units} bytes={need}\n{smali}\n")
idx += 1
if idx == 0:
return "# no records parsed (check file format)"
return "\n".join(out_all)


def main():
if len(sys.argv) != 3:
print("Usage: python disassemble.py <input_file> <dex_file>")
sys.exit(1)

path = sys.argv[1]
dex_path = sys.argv[2]

# 寄存器数量和输入参数数量,根据实际情况调整
regs = 64
ins = 0

dex = DexFile(dex_path)
smali = disasm_pack(path, regs, ins, dex)

# 输出到文件
with open("out.smali", "w", encoding="utf-8") as f:
f.write(smali)

print("Disassembly completed. Output saved to out.smali")


if __name__ == "__main__":
main()
1
python xxx.py extract.dat classes.dex

得到out.smali smali –> java

exp2

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
#!/usr/bin/env python3
"""
smali2java.py

Lightweight heuristic smali -> java-like translator for small smali fragments
(works on the '# record N: ...' style blocks like in the user's sample).

Usage:
python3 smali2java.py input.smali.txt > output.java

Notes:
- This is NOT a full decompiler. It maps common smali instructions to readable Java-like lines.
- Useful for quickly reading what smali is doing; output needs manual cleanup.
"""

import sys
import re
from typing import List

# --- Helpers ---------------------------------------------------------------
def read_records(lines: List[str]) -> List[dict]:
records = []
cur = None
for line in lines:
m = re.match(r"^# record\s+(\d+):\s*(.*)$", line)
if m:
if cur:
records.append(cur)
cur = {"id": int(m.group(1)), "header": m.group(2), "body": []}
continue
if cur is None:
continue
# collect lines until next record
cur["body"].append(line.rstrip("\n"))
if cur:
records.append(cur)
return records

# map registers v0..vN to readable names: var0, var1, this if v0? (heuristic)
def vname(v: str) -> str:
# v may be like "v0" or "p0"
return v.replace("v", "var").replace("p", "arg")

def decode_array_data(block_lines: List[str]) -> str:
# find .array-data and bytes then create Java array literal
arr = []
in_array = False
for L in block_lines:
if ".array-data" in L:
in_array = True
continue
if in_array:
if L.strip().startswith(".end array-data"):
break
# lines like 0x76
tokens = L.strip().split()
for t in tokens:
try:
val = int(t, 16)
arr.append(str(val))
except:
pass
return "new byte[]{%s}" % (", ".join(arr))

# translate a single record body to a Java-like snippet
def translate_body(body: List[str]) -> List[str]:
out = []
i = 0
# accumulate array-data blocks
if any(".array-data" in l for l in body):
# try to detect the sput-object with TARGET and attach the array literal
# naive: find sput-object line target field name
sput = next((l for l in body if "sput-object" in l), None)
if sput:
# sput-object v0, Lcom/swdd/strangeapp/MainActivity;->TARGET:[B
m = re.search(r"sput-object\s+\S+,\s+L([^;]+);->(\w+):(\[B)", sput)
if m:
class_path = m.group(1).replace("/", ".")
field = m.group(2)
arr_lit = decode_array_data(body)
out.append("/* static byte[] %s.%s initialized */" % (class_path, field))
out.append("private static byte[] %s = %s;" % (field, arr_lit))
return out

# generic instruction translations
for L in body:
if not L.strip() or L.strip().startswith("#"):
continue

# .registers N -> comment
if L.strip().startswith(".registers"):
out.append("// " + L.strip())
continue
# const-string v0, "..."
m = re.match(r"\s*const-string\s+(\S+),\s+\"(.*)\"", L)
if m:
out.append("%s = \"%s\";" % (vname(m.group(1)), m.group(2)))
continue
# const/4 v0, 0 or const/16
m = re.match(r"\s*const(?:/\w+)?\s+(\S+),\s+(-?\d+|0x[0-9a-fA-F]+)", L)
if m:
out.append("%s = %s;" % (vname(m.group(1)), m.group(2)))
continue
# new-instance v2, Lcom/xxx/MyClass;
m = re.match(r"\s*new-instance\s+(\S+),\s+L([^;]+);", L)
if m:
out.append("%s = new %s();" % (vname(m.group(1)), m.group(2).replace("/", ".")))
continue
# new-array v0, vN, [B => byte[] arr = new byte[len];
m = re.match(r"\s*new-array\s+(\S+),\s+(\S+),\s+\[B", L)
if m:
out.append("%s = new byte[%s];" % (vname(m.group(1)), vname(m.group(2))))
continue
# fill-array-data v0, :array_000a -> handled by array-data block
if "fill-array-data" in L:
out.append("// " + L.strip())
continue
# iput-object v0, v1, Lcom/...;->f$0:Type
m = re.match(r"\s*iput-object\s+(\S+),\s+(\S+),\s+L([^;]+);->([^:]+):(.+)", L)
if m:
out.append("%s.%s = %s;" % (vname(m.group(2)), m.group(4), vname(m.group(1))))
continue
# sput-object v0, Lcom/...;->TARGET:[B (static put)
m = re.match(r"\s*sput-object\s+(\S+),\s+L([^;]+);->([^:]+):(.+)", L)
if m:
out.append("%s.%s = %s;" % (m.group(2).replace("/", "."), m.group(3), vname(m.group(1))))
continue
# sget-object v2, Lcom/...;->TARGET:[B
m = re.match(r"\s*sget-object\s+(\S+),\s+L([^;]+);->([^:]+):(.+)", L)
if m:
out.append("%s = %s.%s;" % (vname(m.group(1)), m.group(2).replace("/", "."), m.group(3)))
continue
# invoke-direct {v0}, Ljava/lang/Object;-><init>()V
m = re.match(r"\s*invoke-direct\s+\{([^}]+)\},\s+L([^;]+);->(\S+)\((.*?)\)(.+)", L)
if m:
regs = [r.strip() for r in m.group(1).split(",")]
method_owner = m.group(2).replace("/", ".")
method_name = m.group(3)
params = m.group(4)
out.append("// constructor call: %s.%s(%s) // from regs %s" % (method_owner, method_name, params, ", ".join(map(vname, regs))))
if method_name == "<init>":
# make a new-expression if single reg used as receiver assignment
if len(regs) >= 1:
out.append("%s = new %s();" % (vname(regs[0]), method_owner))
continue
# invoke-virtual {v3, v0}, Lcom/swdd/...;->setContentView(I)V
m = re.match(r"\s*invoke-virtual\s+\{([^}]+)\},\s+L([^;]+);->([^()]+)\((.*?)\)(.+)", L)
if m:
regs = [r.strip() for r in m.group(1).split(",")]
owner = m.group(2).replace("/", ".")
mname = m.group(3)
sig = m.group(4)
args = ", ".join(vname(r) for r in regs[1:]) if len(regs) > 1 else ", ".join(vname(r) for r in regs)
# heuristic: if first register is 'v3' likely it's "this"
out.append("%s.%s(%s);" % (vname(regs[0]), mname, args))
continue
# invoke-static (call static method)
m = re.match(r"\s*invoke-static\s+\{([^}]+)\},\s+L([^;]+);->([^()]+)\((.*?)\)(.+)", L)
if m:
regs = [r.strip() for r in m.group(1).split(",")]
owner = m.group(2).replace("/", ".")
mname = m.group(3)
args = ", ".join(vname(r) for r in regs)
out.append("%s.%s(%s);" % (owner, mname, args))
continue
# invoke-super {v3, v4}, Landroidx/...;->onCreate(Landroid/os/Bundle;)V
m = re.match(r"\s*invoke-super\s+\{([^}]+)\},\s+L([^;]+);->([^()]+)\((.*?)\)(.+)", L)
if m:
regs = [r.strip() for r in m.group(1).split(",")]
owner = m.group(2).replace("/", ".")
mname = m.group(3)
out.append("super.%s(%s);" % (mname, ", ".join(vname(r) for r in regs[1:])))
continue
# return-void / return-object v2
if re.search(r"\breturn-void\b", L):
out.append("return;")
continue
m = re.match(r"\s*return-object\s+(\S+)", L)
if m:
out.append("return %s;" % vname(m.group(1)))
continue
m = re.match(r"\s*return-(?:value|v32)?\s+(\S+)", L)
if m:
out.append("return %s;" % vname(m.group(1)))
continue
# check-cast / check-cast v0, Landroid/widget/EditText;
m = re.match(r"\s*check-cast\s+(\S+),\s+L([^;]+);", L)
if m:
out.append("%s = (%s) %s;" % (vname(m.group(1)), m.group(2).replace("/", "."), vname(m.group(1))))
continue
# find view: invoke-virtual {this, id}, L...;->findViewById(I)Landroid/view/View;
if "findViewById" in L:
out.append("// " + L.strip())
# try to convert to: varX = findViewById(id);
m = re.search(r"findViewById\((I)?\)", L)
# fallback simple comment
continue
# substring/charAt/xor/text constructs (simple heuristics)
if "charAt" in L or "substring" in L or "StringBuilder" in L:
out.append("// string manipulation: " + L.strip())
continue

# fallback: keep the original line as a comment for inspection
out.append("// " + L.strip())

return out

# --- Main ------------------------------------------------------------------
def main():
if len(sys.argv) < 2:
print("Usage: python3 smali2java.py input.smali.txt", file=sys.stderr)
print("Output is printed to stdout. Redirect to a file if you want.", file=sys.stderr)
# read stdin as fallback
data = sys.stdin.read()
else:
with open(sys.argv[1], "r", encoding="utf-8") as f:
data = f.read()

lines = data.splitlines()
records = read_records(lines)

header = []
body_lines = []
header.append("// Generated by smali2java.py (heuristic).")
header.append("// Input records: %d" % len(records))
header.append("public class Decompiled {")
header.append("")
footer = ["}", ""]

# collect static fields from records (like TARGET array)
static_fields = []
methods = []

for rec in records:
tid = rec["id"]
# quick detect: array-data + sput-object -> static array field
if any(".array-data" in l for l in rec["body"]) and any("sput-object" in l for l in rec["body"]):
translated = translate_body(rec["body"])
static_fields.extend(translated)
continue

# detect onCreate-like (invoke-super onCreate, setContentView, findViewById etc)
if any("onCreate" in l or "setContentView" in l for l in rec["body"]):
body = translate_body(rec["body"])
methods.append(("void onCreate(Bundle savedInstanceState /* inferred from smali */)", body))
continue

# else generic method block
body = translate_body(rec["body"])
methods.append((f"/* record_{tid}_method */ void method_{tid}()", body))

# print Java-like file
out = []
out.extend(header)
if static_fields:
out.append(" // static fields inferred")
for sf in static_fields:
out.append(" " + sf)
out.append("")

for sig, body in methods:
out.append(" public " + sig + " {")
if not body:
out.append(" // (empty / unrecognized body)")
else:
for ln in body:
# indent translated lines
for sub in ln.splitlines():
out.append(" " + sub)
out.append(" }")
out.append("")

out.extend(footer)
print("\n".join(out))


if __name__ == "__main__":
main()

得到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
# decrypt_try.py
# 运行前: pip install pycryptodome
from Crypto.Cipher import DES, DES3, AES
from Crypto.Util.Padding import unpad
import binascii

TARGET = bytes([118, 17, 7, 124, 157, 51, 23, 133, 178, 23, 203, 1, 42, 109, 179, 5, 169, 10, 179, 106, 78, 100, 123, 138, 209, 31, 19, 56, 115, 151, 245, 218, 238, 184, 12, 42, 17, 55, 135, 212, 119, 215, 87, 118, 95, 180, 172, 69])

# 从反编译看到的 key/iv 字符串
key_str = b"1234567891123456" # 16 bytes
iv_str = b"1234567891123456" # 16 bytes

print("TARGET length:", len(TARGET))
print("TARGET hex:", binascii.hexlify(TARGET).decode())

def try_aes_128_cbc():
try:
cipher = AES.new(key_str, AES.MODE_CBC, iv=iv_str[:16])
pt = unpad(cipher.decrypt(TARGET), AES.block_size)
print("\nAES-128-CBC -> success")
print("plaintext bytes:", pt)
try:
print("utf-8:", pt.decode('utf-8'))
except Exception as e:
print("utf-8 decode error:", e)
except Exception as e:
print("\nAES-128-CBC -> failed:", e)

def try_des_cbc_with_truncated_key():
try:
k = key_str[:8]
iv = iv_str[:8]
cipher = DES.new(k, DES.MODE_CBC, iv=iv)
pt = unpad(cipher.decrypt(TARGET), DES.block_size)
print("\nDES-CBC (key=first8, iv=first8) -> success")
print("key (hex):", binascii.hexlify(k).decode())
print("plaintext bytes:", pt)
try:
print("utf-8:", pt.decode('utf-8'))
except Exception as e:
print("utf-8 decode error:", e)
except Exception as e:
print("\nDES-CBC -> failed:", e)

def try_des_ecb_truncated_key():
try:
k = key_str[:8]
cipher = DES.new(k, DES.MODE_ECB)
pt = unpad(cipher.decrypt(TARGET), DES.block_size)
print("\nDES-ECB (key=first8) -> success")
print("plaintext bytes:", pt)
try:
print("utf-8:", pt.decode('utf-8'))
except Exception as e:
print("utf-8 decode error:", e)
except Exception as e:
print("\nDES-ECB -> failed:", e)

def try_3des_cbc_with_16byte_key_padded():
try:
k16 = key_str
k24 = k16 + k16[:8] # 常用的 from-16-to-24 扩展
iv = iv_str[:8]
cipher = DES3.new(k24, DES3.MODE_CBC, iv=iv)
pt = unpad(cipher.decrypt(TARGET), DES3.block_size)
print("\n3DES-CBC (16->24 padded) -> success")
print("key24 hex:", binascii.hexlify(k24).decode())
print("plaintext bytes:", pt)
try:
print("utf-8:", pt.decode('utf-8'))
except Exception as e:
print("utf-8 decode error:", e)
except Exception as e:
print("\n3DES-CBC -> failed:", e)

def try_3des_cbc_with_24byte_key():
try:
k24 = (key_str * 2)[:24]
iv = iv_str[:8]
cipher = DES3.new(k24, DES3.MODE_CBC, iv=iv)
pt = unpad(cipher.decrypt(TARGET), DES3.block_size)
print("\n3DES-CBC (constructed 24) -> success")
print("key24 hex:", binascii.hexlify(k24).decode())
print("plaintext bytes:", pt)
try:
print("utf-8:", pt.decode('utf-8'))
except Exception as e:
print("utf-8 decode error:", e)
except Exception as e:
print("\n3DES-CBC (constructed 24) -> failed:", e)

# 执行尝试
try_aes_128_cbc()
try_des_cbc_with_truncated_key()
try_des_ecb_truncated_key()
try_3des_cbc_with_16byte_key_padded()
try_3des_cbc_with_24byte_key()

print("\nDone.")
1
flag{just_easy_strange_app_right?}

[SCTF2019]Strange apk(Xposed安装以及反射大师安装)

1

是个输入flag验证的题目。

1

用jeb打开可以发现真的入口点是sctf.demo.myapplication.t,但是我们只看到了sctf.demo.myapplication.s所以我们知道app动态释放文件

我们要下载Xposed和反射大师这个下载太难搞了~~~~

记录一下步骤

1

点击激活在点击上方三条杠选择模版,首要是你已经安装了反射大师。

1

打开反射大师,按照一下步骤

1

1

1

1

1

1

然后打开windows的共享文件夹就可以得到dex文件。

在用jeb打开

1

1

第一部分解base64,第二部分去8

1
flag{W3lc0me~t0_An4r0id-w0rld}

这题也可以直接用脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def decrypt_data(input_file, output_file):
key = "syclover"
with open(input_file, "rb") as f:
encrypted_data = f.read()

decrypted_data = bytearray()
for i in range(len(encrypted_data)):
decrypted_data.append(encrypted_data[i] ^ ord(key[i % len(key)]))

with open(output_file, "wb") as f:
f.write(decrypted_data)


if __name__ == "__main__":
input_file = "data" # 指定输入的加密文件路径
output_file = "sctf.apk" # 输出解密后的APK文件
decrypt_data(input_file, output_file)
print(f"解密完成,APK已保存为 {output_file}")

1

这个就记录一下查壳把,答案base64转图片就成了。