NCTF 2024 WriteUp

6.7k words

经过了前几次的恶心vm之后,这次最后的go vm实在逆不动了…

Reverse方向:

ezDOS:

发现是8086架构的DOS程序,主函数中有几个花指令,抹掉后观察逻辑:

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
dseg:0000 dseg            segment para public 'DATA' use16
dseg:0000 assume cs:dseg
dseg:0000 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; S盒
dseg:0010 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
dseg:0020 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
dseg:0030 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
dseg:0040 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
dseg:0050 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
dseg:0060 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
dseg:0070 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
dseg:0080 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
dseg:0090 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
dseg:00A0 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
dseg:00B0 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
dseg:00C0 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
dseg:00D0 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
dseg:00E0 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
dseg:00F0 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
dseg:0100 aShowMeYourFlag db 0Dh,0Ah ; DATA XREF: seg002:0012↓o
dseg:0102 db 'Show me your flag: $'
dseg:0116 aCongradulatuon db 0Dh,0Ah ; DATA XREF: seg002:0136↓o
dseg:0118 db 'Congradulatuons! $'
dseg:012A aWrong db 0Dh,0Ah ; DATA XREF: seg002:0109↓o
dseg:012C db 'Wrong! $'
dseg:0134 aNctf2024nctf db 0Ch,'NCTf2024nctF' ; 密钥长度和密钥
dseg:0141 db 26h, 7Ch, 3Eh, 0Dh, 3Ch, 88h, 54h, 83h, 0Eh, 3Bh,0B8h, 99h, 1Bh ; 密文
dseg:014E db 9Bh,0E5h, 23h, 43h,0C5h, 80h, 45h, 5Bh, 9Ah, 29h, 24h, 38h,0A9h
dseg:015B db 5Ch,0CBh, 7Ah,0E5h, 93h, 73h, 0Eh, 70h, 6Dh, 7Ch, 31h, 2Bh, 8Ch
dseg:0168 input db 40h, 0, 24h, 24h, 24h, 24h, 24h, 24h ;存入读取
dseg:0168 ; DATA XREF: seg002:001A↓o
dseg:0168 ; seg002:0025↓o
dseg:0170 db 24h, 24h, 24h, 24h, 24h, 24h, 24h, 24h
dseg:0178 db 24h, 24h, 24h, 24h, 24h, 24h, 24h, 24h
dseg:0180 db 24h, 24h, 24h, 24h, 24h, 24h, 24h, 24h
dseg:0188 db 24h, 24h, 24h, 24h, 24h, 24h, 24h, 24h
dseg:0190 db 24h, 24h, 24h, 24h, 24h, 24h, 24h, 24h
dseg:0198 db 24h, 24h, 24h, 24h, 24h, 24h, 24h, 24h
dseg:01A0 db 24h, 24h, 24h, 24h, 24h, 24h, 24h, 24h
dseg:01A8 db 24h, 24h, 0, 0, 0, 0, 0, 0
dseg:01A8 dseg ends
dseg:01A8
seg001:0000 ; ===========================================================================
seg001:0000
seg001:0000 ; Segment type: Uninitialized
seg001:0000 seg001 segment byte stack 'STACK' use16
seg001:0000 assume cs:seg001
seg001:0000 assume es:nothing, ss:nothing, ds:dseg, fs:nothing, gs:nothing
seg001:0000 db 300h dup(0)
seg001:0000 seg001 ends
seg001:0000
seg002:0000 ; ---------------------------------------------------------------------------
seg002:0000 ; ===========================================================================
seg002:0000
seg002:0000 ; Segment type: Pure code
seg002:0000 seg002 segment byte public 'CODE' use16
seg002:0000 assume cs:seg002
seg002:0000 assume es:nothing, ss:seg001, ds:nothing, fs:nothing, gs:nothing
seg002:0000
seg002:0000 public start
seg002:0000 start:
seg002:0000 mov ax, seg dseg
seg002:0003 mov ds, ax
seg002:0005 assume ds:dseg
seg002:0005 mov es, ax
seg002:0007 assume es:dseg
seg002:0007 xor ax, ax
seg002:0009 test ax, ax
seg002:000B jnz short near ptr unk_104BF ; 花指令
seg002:000D jz short loc_104C0
seg002:000D ; ---------------------------------------------------------------------------
seg002:000F unk_104BF db 0E8h ; CODE XREF: seg002:000B↑j
seg002:0010 ; ---------------------------------------------------------------------------
seg002:0010
seg002:0010 loc_104C0: ; CODE XREF: seg002:000D↑j
seg002:0010 mov ah, 9
seg002:0012 lea dx, aShowMeYourFlag ; "\r\nShow me your flag: $"
seg002:0016 int 21h ; DOS - PRINT STRING
seg002:0016 ; DS:DX -> string terminated by "$"
seg002:0018 mov ah, 0Ah ; ax的高字节被设为0A
seg002:001A lea dx, input ; 输入值被存入dx
seg002:001E int 21h ; DOS - BUFFERED KEYBOARD INPUT
seg002:001E ; DS:DX -> buffer
seg002:0020 call addr1 ; 第一个函数,实际上未修改返回地址,也没有进行什么有效的操作
seg002:0025 lea bx, input ; 输入值存入bx
seg002:0029 inc bx ; bx自增
seg002:002A cmp byte ptr [bx], 26h ; '&' ; 根据DOS格式,字节1指向的是输入数据的长度(38)
seg002:002D jz short loc_104E2 ; 如果是,继续加密
seg002:002F jmp loc_105B7 ; 否则打印错误并退出
seg002:0032 ; ---------------------------------------------------------------------------
seg002:0032
seg002:0032 loc_104E2: ; CODE XREF: seg002:002D↑j
seg002:0032 xor si, si
seg002:0034 xor di, di
seg002:0036 xor cx, cx
seg002:0038 mov cx, 100h
seg002:003B
seg002:003B loc_104EB: ; CODE XREF: seg002:003D↓j
seg002:003B push di
seg002:003C inc di
seg002:003D loop loc_104EB ; 初始化S盒堆栈为0~255
seg002:003F
seg002:003F loc_104EF: ; CODE XREF: seg002:0052↓j
seg002:003F pop bx
seg002:0040 call addr3 ; 第三个函数,返回地址+2
seg002:0045 mov bx, si ; 无效指令
seg002:0047 mov [si+0], bl ; 从堆栈填入,S盒被初始化为255~0
seg002:004B inc si
seg002:004C cmp si, 100h
seg002:0050 jnb short loc_10504 ; 256次循环后跳出
seg002:0052 jmp short loc_104EF ; 继续循环
seg002:0054 ; ---------------------------------------------------------------------------
seg002:0054
seg002:0054 loc_10504: ; CODE XREF: seg002:0050↑j
seg002:0054 xor si, si
seg002:0056 xor di, di
seg002:0058 mov bx, 134h ; 指向dseg中密钥的长度0x0C
seg002:005B mov cl, [bx]
seg002:005D
seg002:005D loc_1050D: ; CODE XREF: seg002:00B9↓j
seg002:005D mov dl, [si+0]
seg002:0061 add di, dx
seg002:0063 mov ax, si
seg002:0065 div cl ; %key_length
seg002:0067 mov al, ah
seg002:0069 xor ah, ah
seg002:006B mov bx, 135h ; 指向dseg中密钥的字符串“NCTf2024nctF”
seg002:006E add bx, ax
seg002:0070 mov al, [bx]
seg002:0072 push ax
seg002:0073 call addr5 ; 第五个函数,返回地址+6
seg002:0078 shl ax, 1 ; 无效指令
seg002:007A shl ax, 1 ; 无效指令
seg002:007C shl ax, 1 ; 无效指令
seg002:007E shl ax, 1 ; 有效指令
seg002:0080 shl ax, 1 ; 有效指令
seg002:0082 shl ax, 1 ; 有效指令
seg002:0084 mov dx, ax
seg002:0086 pop ax
seg002:0087 push dx
seg002:0088 call addr4 ; 第四个函数,返回地址+2
seg002:008D shr ax, 1 ; 无效指令
seg002:008F shr ax, 1 ; 有效指令
seg002:0091 shr ax, 1 ; 有效指令
seg002:0093 shr ax, 1 ; 有效指令
seg002:0095 shr ax, 1 ; 有效指令
seg002:0097 shr ax, 1 ; 有效指令
seg002:0099 pop dx
seg002:009A or al, dl
seg002:009C add di, ax
seg002:009E and di, 0FFh ; 对于每个字节进行 ((key << 3) | (key >> 5)) & 0xFF
seg002:00A2 mov al, [si+0]
seg002:00A6 mov dl, [di+0]
seg002:00AA xchg al, dl ; swap
seg002:00AC mov [di+0], dl
seg002:00B0 mov [si+0], al
seg002:00B4 inc si
seg002:00B5 cmp si, 100h ; 循环256次
seg002:00B9 jb short loc_1050D ; 以上是KSA部分
seg002:00BB xor cx, cx
seg002:00BD mov bx, 169h ; 获取输入字符的长度
seg002:00C0 mov cl, [bx]
seg002:00C2 inc bx
seg002:00C3 mov si, bx
seg002:00C5 xor bx, bx
seg002:00C7 xor dx, dx
seg002:00C9 xor ax, ax
seg002:00CB test ax, ax
seg002:00CD jnz short near ptr unk_10581 ; 花指令
seg002:00CF jz short loc_10582
seg002:00CF ; ---------------------------------------------------------------------------
seg002:00D1 unk_10581 db 0E8h ; CODE XREF: seg002:00CD↑j
seg002:00D2 ; ---------------------------------------------------------------------------
seg002:00D2
seg002:00D2 loc_10582: ; CODE XREF: seg002:00CF↑j
seg002:00D2 ; seg002:00FE↓j
seg002:00D2 inc bl
seg002:00D4 mov al, [bx+0]
seg002:00D8 add dl, al
seg002:00DA push dx
seg002:00DB mov di, dx
seg002:00DD mov al, [bx+0]
seg002:00E1 xchg al, [di+0]
seg002:00E5 mov [bx+0], al
seg002:00E9 add al, [di+0]
seg002:00ED mov di, ax
seg002:00EF mov al, [di+0]
seg002:00F3 call addr2 ; 返回地址+1
seg002:00F8 inc ax ; 无效指令
seg002:00F9 inc ax ; 有效指令,额外的自增
seg002:00FA xor [si], al
seg002:00FC pop dx
seg002:00FD inc si
seg002:00FE loop loc_10582 ; 以上是PRGA部分
seg002:0100 jmp short loc_105C1 ; 进入比较
seg002:0102 ; ---------------------------------------------------------------------------
seg002:0102
seg002:0102 loc_105B2: ; CODE XREF: seg002:010F↓j
seg002:0102 ; seg002:013C↓j
seg002:0102 mov ax, 4C00h ; 退出
seg002:0105 int 21h ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT)
seg002:0105 ; AL = exit code
seg002:0107 ; ---------------------------------------------------------------------------
seg002:0107
seg002:0107 loc_105B7: ; CODE XREF: seg002:002F↑j
seg002:0107 ; seg002:0130↓j
seg002:0107 mov ah, 9
seg002:0109 lea dx, aWrong ; "\r\nWrong! $"
seg002:010D int 21h ; DOS - PRINT STRING
seg002:010D ; DS:DX -> string terminated by "$"
seg002:010F jmp short loc_105B2
seg002:0111 ; ---------------------------------------------------------------------------
seg002:0111
seg002:0111 loc_105C1: ; CODE XREF: seg002:0100↑j
seg002:0111 xor cx, cx
seg002:0113 xor di, di
seg002:0115 xor si, si
seg002:0117 mov di, 141h ; 密文的长度0x26
seg002:011A mov si, 168h ; 输入的缓冲区第一个字节0x40
seg002:011D test cl, cl
seg002:011F jnz short near ptr unk_105D3 ; 花指令
seg002:0121 jz short loc_105D4
seg002:0121 ; ---------------------------------------------------------------------------
seg002:0123 unk_105D3 db 0E8h ; CODE XREF: seg002:011F↑j
seg002:0124 ; ---------------------------------------------------------------------------
seg002:0124
seg002:0124 loc_105D4: ; CODE XREF: seg002:0121↑j
seg002:0124 add si, 2 ; 对齐偏移到加密的输入的第一个字节
seg002:0127 mov cl, [di]
seg002:0129 inc di ; 对齐偏移到密文的第一个字节
seg002:012A
seg002:012A loc_105DA: ; CODE XREF: seg002:0132↓j
seg002:012A mov al, [si]
seg002:012C mov dl, [di]
seg002:012E cmp al, dl ; 比较
seg002:0130 jnz short loc_105B7
seg002:0132 loop loc_105DA
seg002:0134 mov ah, 9
seg002:0136 lea dx, aCongradulatuon ; "\r\nCongradulatuons! $"
seg002:013A int 21h ; DOS - PRINT STRING
seg002:013A ; DS:DX -> string terminated by "$"
seg002:013C jmp short loc_105B2
seg002:013E ; ---------------------------------------------------------------------------
seg002:013E push cx
seg002:013F mov cl, 4
seg002:0141 shr al, cl
seg002:0143 cmp al, 0Ah
seg002:0145 jl short loc_105FB
seg002:0147 add al, 37h ; '7'
seg002:0149 jmp short loc_105FD
seg002:014B ; ---------------------------------------------------------------------------
seg002:014B
seg002:014B loc_105FB: ; CODE XREF: seg002:0145↑j
seg002:014B add al, 30h ; '0'
seg002:014D
seg002:014D loc_105FD: ; CODE XREF: seg002:0149↑j
seg002:014D mov [di], al
seg002:014F inc di
seg002:0150 mov al, ah
seg002:0152 and al, 0Fh
seg002:0154 cmp al, 0Ah
seg002:0156 jl short loc_1060C
seg002:0158 add al, 37h ; '7'
seg002:015A jmp short loc_1060E
seg002:015C ; ---------------------------------------------------------------------------
seg002:015C
seg002:015C loc_1060C: ; CODE XREF: seg002:0156↑j
seg002:015C add al, 30h ; '0'
seg002:015E
seg002:015E loc_1060E: ; CODE XREF: seg002:015A↑j
seg002:015E mov [di], al
seg002:0160 inc di
seg002:0161 pop cx
seg002:0162 retn
seg002:0163 ; ---------------------------------------------------------------------------
seg002:0163 mov ah, 2
seg002:0165
seg002:0165 loc_10615: ; CODE XREF: seg002:016E↓j
seg002:0165 lodsb
seg002:0166 cmp al, 24h ; '$'
seg002:0168 jz short locret_10620
seg002:016A mov dl, al
seg002:016C int 21h ; DOS - DISPLAY OUTPUT
seg002:016C ; DL = character to send to standard output
seg002:016E jmp short loc_10615
seg002:0170 ; ---------------------------------------------------------------------------
seg002:0170
seg002:0170 locret_10620: ; CODE XREF: seg002:0168↑j
seg002:0170 retn
seg002:0170 ; ---------------------------------------------------------------------------
seg002:0171 align 10h
seg002:0171 seg002 ends
seg002:0171
seg003:0000 ; ===========================================================================
seg003:0000
seg003:0000 ; Segment type: Pure code
seg003:0000 seg003 segment byte public 'CODE' use16
seg003:0000 assume cs:seg003
seg003:0000 assume es:nothing, ss:nothing, ds:dseg, fs:nothing, gs:nothing
seg003:0000
seg003:0000 ; =============== S U B R O U T I N E =======================================
seg003:0000
seg003:0000
seg003:0000 addr1 proc far ; CODE XREF: seg002:0020↑P ; 没有修改返回地址
seg003:0000 pop dx
seg003:0001 push ax
seg003:0002 xor ax, ax
seg003:0004 add dl, al
seg003:0006 pop ax
seg003:0007 push dx
seg003:0008 retf
seg003:0008 addr1 endp
seg003:0008
seg003:0008 ; ---------------------------------------------------------------------------
seg003:0009 align 8
seg003:0009 seg003 ends
seg003:0009
seg004:0000 ; ===========================================================================
seg004:0000
seg004:0000 ; Segment type: Pure code
seg004:0000 seg004 segment byte public 'CODE' use16
seg004:0000 assume cs:seg004
seg004:0000 assume es:nothing, ss:nothing, ds:dseg, fs:nothing, gs:nothing
seg004:0000
seg004:0000 ; =============== S U B R O U T I N E =======================================
seg004:0000
seg004:0000
seg004:0000 addr2 proc far ; CODE XREF: seg002:00F3↑P ; 返回地址+1
seg004:0000 pop dx
seg004:0001 push ax
seg004:0002 xor ax, ax
seg004:0004 mov ah, 1Dh
seg004:0006 not ah
seg004:0008 shr ah, 1
seg004:000A shr ah, 1
seg004:000C add ax, 0Fh
seg004:000F and al, 7
seg004:0011 xor al, 0Eh
seg004:0013 shl al, 1
seg004:0015 or al, ah
seg004:0017 dec ah
seg004:0019 sub al, ah
seg004:001B add al, 2
seg004:001D and al, 11h
seg004:001F add dl, al
seg004:0021 pop ax
seg004:0022 push dx
seg004:0023 retf
seg004:0023 addr2 endp
seg004:0023
seg004:0023 ; ---------------------------------------------------------------------------
seg004:0024 align 10h
seg004:0024 seg004 ends
seg004:0024
seg005:0000 ; ===========================================================================
seg005:0000
seg005:0000 ; Segment type: Pure code
seg005:0000 seg005 segment byte public 'CODE' use16
seg005:0000 assume cs:seg005
seg005:0000 assume es:nothing, ss:nothing, ds:dseg, fs:nothing, gs:nothing
seg005:0000
seg005:0000 ; =============== S U B R O U T I N E =======================================
seg005:0000
seg005:0000
seg005:0000 addr3 proc far ; CODE XREF: seg002:0040↑P ; 返回地址+2
seg005:0000 pop dx
seg005:0001 push ax
seg005:0002 xor ax, ax
seg005:0004 mov ax, 0Fh
seg005:0007 and ax, 7
seg005:000A shl ax, 1
seg005:000C shl ax, 1
seg005:000E mov ah, 1Ah
seg005:0010 not ah
seg005:0012 shr ah, 1
seg005:0014 shr ah, 1
seg005:0016 xor al, ah
seg005:0018 sub ah, 1Eh
seg005:001B and al, ah
seg005:001D inc al
seg005:001F add dl, al
seg005:0021 pop ax
seg005:0022 push dx
seg005:0023 retf
seg005:0023 addr3 endp
seg005:0023
seg005:0023 ; ---------------------------------------------------------------------------
seg005:0024 align 10h
seg005:0024 seg005 ends
seg005:0024
seg006:0000 ; ===========================================================================
seg006:0000
seg006:0000 ; Segment type: Pure code
seg006:0000 seg006 segment byte public 'CODE' use16
seg006:0000 assume cs:seg006
seg006:0000 assume es:nothing, ss:nothing, ds:dseg, fs:nothing, gs:nothing
seg006:0000
seg006:0000 ; =============== S U B R O U T I N E =======================================
seg006:0000
seg006:0000
seg006:0000 addr4 proc far ; CODE XREF: seg002:0088↑P ; 返回地址+2
seg006:0000 pop dx
seg006:0001 push ax
seg006:0002 xor ax, ax
seg006:0004 shl ax, 1
seg006:0006 add ax, 3
seg006:0009 and ax, 1Fh
seg006:000C mov ah, 1Ch
seg006:000E not ah
seg006:0010 shr ah, 1
seg006:0012 shr ah, 1
seg006:0014 or al, ah
seg006:0016 mov ah, 0Eh
seg006:0018 shl ah, 1
seg006:001A shl ah, 1
seg006:001C add ah, 1
seg006:001F xor al, ah
seg006:0021 add dl, al
seg006:0023 pop ax
seg006:0024 push dx
seg006:0025 retf
seg006:0025 addr4 endp
seg006:0025
seg006:0025 ; ---------------------------------------------------------------------------
seg006:0026 align 10h
seg006:0026 seg006 ends
seg006:0026
seg007:0000 ; ===========================================================================
seg007:0000
seg007:0000 ; Segment type: Pure code
seg007:0000 seg007 segment byte public 'CODE' use16
seg007:0000 assume cs:seg007
seg007:0000 assume es:nothing, ss:nothing, ds:dseg, fs:nothing, gs:nothing
seg007:0000
seg007:0000 ; =============== S U B R O U T I N E =======================================
seg007:0000
seg007:0000
seg007:0000 addr5 proc far ; CODE XREF: seg002:0073↑P ; 返回地址+6
seg007:0000 pop dx
seg007:0001 push ax
seg007:0002 xor ax, ax
seg007:0004 mov ax, 0Fh
seg007:0007 and ax, 7
seg007:000A xor ax, 0Eh
seg007:000D shl ax, 1
seg007:000F mov ah, 3
seg007:0011 or ah, 1
seg007:0014 and ah, 2
seg007:0017 sub al, ah
seg007:0019 shr al, 1
seg007:001B mov ah, 4
seg007:001D shr ah, 1
seg007:001F sub al, 5
seg007:0021 mul ah
seg007:0023 add dl, al
seg007:0025 pop ax
seg007:0026 push dx
seg007:0027 retf
seg007:0027 addr5 endp
seg007:0027
seg007:0027 seg007 ends
seg007:0027
seg007:0027
seg007:0027 end start

发现是魔改的RC4,编写脚本解密:

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
def rol3(b):
return ((b << 3) | (b >> 5)) & 0xFF

def KSA(key):
S = [i for i in range(255,-1,-1)]
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
return S

def PRGA(S, length):
i = j = 0
keystream = []
for _ in range(length):
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
k = S[(S[i] + S[j]) % 256]
k = (k + 1) % 256
keystream.append(k)
return bytes(keystream)

key = "NCTf2024nctF"
processed_key = bytes([rol3(ord(c)) for c in key])

S = KSA(processed_key)

ciphertext = bytes([
0x7C, 0x3E, 0x0D, 0x3C, 0x88, 0x54, 0x83, 0x0E, 0x3B, 0xB8, 0x99, 0x1B, 0x9B, 0xE5, 0x23, 0x43, 0xC5, 0x80, 0x45,
0x5B, 0x9A, 0x29, 0x24, 0x38, 0xA9, 0x5C, 0xCB, 0x7A, 0xE5, 0x93, 0x73, 0x0E, 0x70, 0x6D, 0x7C, 0x31, 0x2B, 0x8C])
keystream = PRGA(S, len(ciphertext))

plaintext = bytearray([c ^ k for c, k in zip(ciphertext, keystream)])
print("Flag:", plaintext)

输出flag:NCTF{Y0u_4r3_Assemb1y_M4st3r_5d0b497e}.

x1Login:

注:这题我都不知道我怎么hook出来的,程序压根没给x64的库,只给了arm的,而我只有x64的模拟器,不知道怎么居然能正常dump出来dex和执行后续的libnative.so

查看mainactivity:

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
package com.nctf.simplelogin;

import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.activity.ComponentActivity;
import dalvik.system.InMemoryDexClassLoader;
import java.nio.ByteBuffer;
import java.util.Arrays;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;

@Metadata(d1 = {"\u00006\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u000E\n\u0000\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0011\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\b\u0010\u0003\u001A\u00020\u0004H\u0002J\b\u0010\u0005\u001A\u00020\u0004H\u0002J\u0016\u0010\u0006\u001A\b\u0012\u0002\b\u0003\u0018\u00010\u00072\u0006\u0010\b\u001A\u00020\tH\u0002J-\u0010\n\u001A\u00020\u000B2\n\u0010\f\u001A\u0006\u0012\u0002\b\u00030\u00072\u0012\u0010\r\u001A\n\u0012\u0006\b\u0001\u0012\u00020\u000B0\u000E\"\u00020\u000BH\u0002¢\u0006\u0002\u0010\u000FJ\u0012\u0010\u0010\u001A\u00020\u00042\b\u0010\u0011\u001A\u0004\u0018\u00010\u0012H\u0014¨\u0006\u0013"}, d2 = {"Lcom/nctf/simplelogin/MainActivity;", "Landroidx/activity/ComponentActivity;", "()V", "checkSecutity", "", "exit", "getClass", "Ljava/lang/Class;", "classname", "", "getInstance", "", "clazz", "args", "", "(Ljava/lang/Class;[Ljava/lang/Object;)Ljava/lang/Object;", "onCreate", "savedInstanceState", "Landroid/os/Bundle;", "app_release"}, k = 1, mv = {1, 9, 0}, xi = 0x30)
public final class MainActivity extends ComponentActivity {
private final void checkSecutity() {
if(Secure.checkDebug()) {
Toast.makeText(((Context)this), ((CharSequence)"Debugger Detected!"), 0).show();
this.exit();
}

if(Secure.checkRoot()) {
Toast.makeText(((Context)this), ((CharSequence)"Root Detected!"), 0).show();
this.exit();
}
}

private final void exit() {
this.finish();
System.exit(-1);
throw new RuntimeException("System.exit returned normally, while it was supposed to halt JVM.");
}

private final Class getClass(String s) {
try {
return new InMemoryDexClassLoader(ByteBuffer.wrap(Secure.loadDex(this.getApplicationContext(), DecStr.get("ygvUF2vHFgbPiN9J"))), this.getClassLoader()).loadClass(s);
}
catch(Exception exception0) {
exception0.printStackTrace();
return null;
}
}

private final Object getInstance(Class class0, Object[] arr_object) {
Object object0 = class0.getConstructor(Context.class, String.class, String.class).newInstance(Arrays.copyOf(arr_object, arr_object.length));
Intrinsics.checkNotNullExpressionValue(object0, "newInstance(...)");
return object0;
}

@Override // androidx.activity.ComponentActivity
protected void onCreate(Bundle bundle0) {
super.onCreate(bundle0);
this.setContentView(layout.activity_main);
this.checkSecutity();
EditText editText0 = (EditText)this.findViewById(id.usernameEditText);
EditText editText1 = (EditText)this.findViewById(id.passwordEditText);
Button button0 = (Button)this.findViewById(id.loginButton);
String s = DecStr.get("Exv3nhr5BNW0axn3aNz/DNv9C3q0wxj/Exe=");
Intrinsics.checkNotNullExpressionValue(s, "get(...)");
Class class0 = this.getClass(s);
if(class0 != null) {
button0.setOnClickListener((View view0) -> {
Intrinsics.checkNotNullParameter(this, "this$0");
String s = editText0.getText().toString();
String s1 = editText1.getText().toString();
try {
Object object0 = this.getInstance(class0, new Object[]{this, s, s1});
class0.getMethod(DecStr.get("zM1GzM4=")).invoke(object0);
}
catch(Exception exception0) {
exception0.printStackTrace();
}
});
return;
}

Toast.makeText(((Context)this), ((CharSequence)"Error: Program load failure"), 0).show();
this.finish();
System.exit(-1);
throw new RuntimeException("System.exit returned normally, while it was supposed to halt JVM.");
}

// Detected as a lambda impl.
private static final void onCreate$lambda$0(EditText editText0, EditText editText1, MainActivity mainActivity0, Class class0, View view0) {
Intrinsics.checkNotNullParameter(mainActivity0, "this$0");
String s = editText0.getText().toString();
String s1 = editText1.getText().toString();
try {
Object object0 = mainActivity0.getInstance(class0, new Object[]{mainActivity0, s, s1});
class0.getMethod(DecStr.get("zM1GzM4=")).invoke(object0);
}
catch(Exception exception0) {
exception0.printStackTrace();
}
}
}

发现了调试器和root检测,有一个关键方法DecStr,在运行过程中动态加载了一个dex文件,之后创建了一个登录页面,先查看DecStr,发现是从so文件中加载的,查看:

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
__int64 __fastcall Java_com_nctf_simplelogin_DecStr_get(__int64 a1, __int64 a2, __int64 a3)
{
const char *v5; // x21
size_t v6; // x22
int8x16_t *v7; // x20
size_t v8; // x0
unsigned __int64 v9; // x8
int8x16_t *v10; // x9
unsigned __int64 v11; // x10
int8x16_t v12; // q0
int8x16_t v13; // q2
unsigned __int64 v14; // x10
int8x8_t v15; // d0
int8x8_t *v16; // x9
unsigned __int64 v17; // x10
size_t v18; // x9
_BYTE *v19; // x8
__int64 v20; // x19

v5 = (const char *)(*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 1352LL))(a1, a3, 0LL);
v6 = (*(int (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 1312LL))(a1, a3);
v7 = (int8x16_t *)malloc(v6);
memset(v7, 0, v6);
sub_7C0(v5, aAbcdefghijklmn, (__int64)v7);
v8 = strlen((const char *)v7);
if ( v8 )
{
if ( v8 < 8 )
{
v9 = 0LL;
goto LABEL_13;
}
if ( v8 >= 0x20 )
{
v9 = v8 & 0xFFFFFFFFFFFFFFE0LL;
v10 = v7 + 1;
v11 = v8 & 0xFFFFFFFFFFFFFFE0LL;
v12 = vdupq_n_s8(v8);
do
{
v11 -= 32LL;
v13 = veorq_s8(*v10, v12);
v10[-1] = veorq_s8(v10[-1], v12);
*v10 = v13;
v10 += 2;
}
while ( v11 );
if ( v8 == v9 )
goto LABEL_15;
if ( (v8 & 0x18) == 0 )
{
LABEL_13:
v18 = v8 - v9;
v19 = (char *)v7 + v9;
do
{
--v18;
*v19++ ^= v8;
}
while ( v18 );
goto LABEL_15;
}
}
else
{
v9 = 0LL;
}
v14 = v9;
v9 = v8 & 0xFFFFFFFFFFFFFFF8LL;
v15.n64_u64[0] = vdup_n_s8(v8).n64_u64[0];
v16 = (int8x8_t *)((char *)v7 + v14);
v17 = v14 - (v8 & 0xFFFFFFFFFFFFFFF8LL);
do
{
v17 += 8LL;
v16->n64_u64[0] = veor_s8((int8x8_t)v16->n64_u64[0], v15).n64_u64[0];
++v16;
}
while ( v17 );
if ( v8 != v9 )
goto LABEL_13;
}
LABEL_15:
v20 = (*(__int64 (__fastcall **)(__int64, int8x16_t *))(*(_QWORD *)a1 + 1336LL))(a1, v7);
free(v7);
return v20;
}

发现了一个使用自定义base64表AbcdefghijklmnopqrstuvwxyzaBCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/和异或字符串长度来进行解密的函数,据此解密上文出现的字符串,得到结果为:
ygvUF2vHFgbPiN9Jlibsimple.so

Exv3nhr5BNW0axn3aNz/DNv9C3q0wxj/Exe=com.nctf.simplelogin.Check

zM1GzM4=check

但是加载dex不能加载so文件,查看Secure:

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
package com.nctf.simplelogin;

import android.content.Context;
import android.os.Debug;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Secure {
static {
System.loadLibrary(DecStr.get("agDYB3bJ"));
}

public static boolean checkDebug() {
return Debug.isDebuggerConnected();
}

public static boolean checkRoot() {
return Secure.checkSuExists();
}

private static boolean checkSuExists() {
boolean z = false;
Process process0 = null;
try {
process0 = Runtime.getRuntime().exec(new String[]{"which", "su"});
String s = new BufferedReader(new InputStreamReader(process0.getInputStream())).readLine();
}
catch(Throwable unused_ex) {
if(process0 != null) {
process0.destroy();
}

return false;
}

if(s != null) {
z = true;
}

if(process0 != null) {
process0.destroy();
}

return z;
}

public static native boolean doCheck(String arg0, byte[] arg1) {
}

public static native byte[] loadDex(Context arg0, String arg1) {
}
}

agDYB3bJnative

发现是加载了libnative.so其中的两种方法,尝试用frida绕过检测直接dump出dex文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import frida
import sys
import argparse

def on_message(message, data):
if message.get('type') == 'send':
payload = message.get('payload')
if isinstance(payload, dict) and payload.get('type') == 'dex_data':
output_path = "E:/CTF/dump.dex"
with open(output_path, 'wb') as f:
f.write(data)
print(f"[*] Dex文件已保存到: {output_path}")
else:
print(f"[调试信息] {message}")
else:
print(f"[其他消息] {message}")

# JavaScript注入代码
jscode = """
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
Java.perform(function() {
// 绕过调试器检测
var Secure = Java.use('com.nctf.simplelogin.Secure');
Secure.checkDebug.implementation = function() {
console.log('绕过调试检测');
return false;
};

// 绕过Root检测
Secure.checkRoot.implementation = function() {
console.log('绕过Root检测');
return false;
};

// Hook loadDex方法以捕获Dex数据
Secure.loadDex.implementation = function(context, str) {
var result = this.loadDex(context, str);
console.log('捕获到Dex数据,长度: ' + result.length);

// 转换字节数组
var buffer = Java.array('byte', result);
var arrayBuffer = new ArrayBuffer(buffer.length);
var uint8Array = new Uint8Array(arrayBuffer);
for (var i = 0; i < buffer.length; i++) {
uint8Array[i] = buffer[i] & 0xff;
}
send({ type: 'dex_data' }, uint8Array.buffer);
return result;
};
});
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def main(target_process):
device = frida.get_usb_device()
try:
pid = device.spawn([target_process])
except frida.NotSupportedError:
print("请确保应用未运行,然后重试。")
sys.exit(1)

session = device.attach(pid)
script = session.create_script(jscode)
script.on('message', on_message)
print('[*] 附加到进程')
script.load()

device.resume(pid)
print('[*] 恢复进程执行')

try:
sys.stdin.read()
except KeyboardInterrupt:
print("[*] 分离进程")
session.detach()

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Frida脚本绕过检测并Dump Dex")
parser.add_argument('package', help="应用的包名")
args = parser.parse_args()

main(args.package)

得到dex文件,查看:

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
package com.nctf.simplelogin;
import android.content.Context;
import android.widget.Toast;
import java.security.MessageDigest;
/* loaded from: E:\CTF\dump.dex */
public class Check {
Context context;
String password;
String username;
public Check(Context context, String username, String password) {
this.username = username;
this.password = password;
this.context = context;
}
public void check() {
try {
if (check_username()) {
MessageDigest digest = MessageDigest.getInstance(DecStr.get("tMC2"));
digest.update(this.username.getBytes());
byte[] output = digest.digest();
boolean result = check_password(output);
if (result) {
Toast.makeText(this.context, "Login Successful! Now submit your flag!", 0).show();
return;
}
}
Toast.makeText(this.context, "Login Failed!", 0).show();
} catch (Exception e) {
e.printStackTrace();
}
}
private boolean check_username() {
return this.username.equals(DecStr.get("uZPOs29goMu6l38="));
}
private boolean check_password(byte[] key) {
return Secure.doCheck(this.password, key);
}
}

其中的tMC2解得是md5,用户名解得是X1c@dM1n1$t,但是密码是用libnative中的本地方法doCheck检测的,反编译查看函数:

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
__int64 __fastcall sub_1E30(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
int v7; // w20
int v8; // w0
unsigned __int8 *v9; // x19
const void *v10; // x22
unsigned __int64 *v11; // x23
unsigned __int8 *v12; // x21
unsigned int v13; // w22
unsigned __int64 v14; // x22
unsigned __int64 v15; // x23
__int64 v16; // x26
unsigned __int64 *v17; // x27
unsigned __int64 v18; // x0
unsigned __int64 v19; // x0
unsigned __int64 v20; // x0
unsigned __int8 *v21; // x9
__int64 v22; // x8
unsigned __int8 *v23; // x10
int v24; // w11
int v25; // t1
int v26; // t1

v7 = (*(__int64 (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 1312LL))(a1, a3);
v8 = (*(__int64 (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 1312LL))(a1, a3);
v9 = (unsigned __int8 *)malloc(v8 + 1);
memset(v9, 0, v7 + 1);
v10 = (const void *)(*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 1352LL))(a1, a3, 0LL);
v11 = (unsigned __int64 *)(*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 1472LL))(a1, a4, 0LL);
memcpy(v9, v10, v7);
v12 = (unsigned __int8 *)malloc(0x20uLL);
v13 = 0;
*(_OWORD *)v12 = *(_OWORD *)cipher;
*((_OWORD *)v12 + 1) = *(_OWORD *)&cipher[16]; //密文
if ( (v7 & 7) == 0 )
{
if ( v7 >= 8 )
{
v14 = *v11;
v15 = v11[1];
v16 = (unsigned int)v7 >> 3;
v17 = (unsigned __int64 *)v9;
do
{
TrippleDES_ENC(*v17, v14); //3DES
TrippleDES_DEC(v18, v15);
TrippleDES_ENC(v19, v14);
*v17++ = v20;
__android_log_print(3, "native", "%llx", v20); //至关重要的打印调试信息
--v16;
}
while ( v16 );
}
v21 = v12;
if ( v7 <= 0 )
v22 = 0LL;
else
v22 = (unsigned int)v7;
v23 = v9;
while ( v22 )
{
v25 = *v21++;
v24 = v25;
v26 = *v23++;
--v22;
if ( v24 != v26 ) //比较
{
v13 = 0;
goto LABEL_13;
}
}
v13 = 1;
}
LABEL_13:
free(v12);
free(v9);
return v13;
}

adb logcat查看调试输出,带入密钥12345678,发现加密方式是8字节小端序转换的d2d3436ad3ec537d9784cf3d63dde737和8字节小端序转换的明文进行加密,带入密文409eec86b884a58b7e8a64e21ad3b8bbdf4bfa1246453e52011b033b78000000解密得到密钥SafePWD~5y$x?YM+5U05Gm6=,合并得到flag:NCTF{X1c@dM1n1$t_SafePWD~5y$x?YM+5U05Gm6=}.

解密脚本:

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
from Crypto.Cipher import DES3
import binascii
import hashlib

def to_little_endian(data):
result = bytearray()
for i in range(0, len(data), 8):
block = data[i:i+8]
result.extend(block[::-1])
return bytes(result)
username = b"X1c@dM1n1$t"
md5 = hashlib.md5(username).digest()
k1 = md5[:8]
k2 = md5[8:16]
des3_key = k1 + k2 + k1
des3_key = to_little_endian(des3_key)
ciphertext_hex = "409eec86b884a58b7e8a64e21ad3b8bbdf4bfa1246453e52011b033b78000000"
ciphertext = binascii.unhexlify(ciphertext_hex)
ciphertext = to_little_endian(ciphertext)
cipher = DES3.new(des3_key, DES3.MODE_ECB)
plaintext = cipher.decrypt(ciphertext)
pad_length = plaintext[-1]
plaintext = plaintext[:-pad_length] if pad_length <= 8 else plaintext
password = to_little_endian(plaintext)
print("密码:", password)

SafeProgram:

查看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
32
33
34
35
36
37
38
int __fastcall main(int argc, const char **argv, const char **envp)
{
char v4[10]; // [rsp+38h] [rbp-70h] BYREF
char v5[14]; // [rsp+42h] [rbp-66h] BYREF
char Buf1[16]; // [rsp+50h] [rbp-58h] BYREF
char v7[48]; // [rsp+60h] [rbp-48h] BYREF

sub_1400015F0(argc, argv, envp);
sub_14001E3D0((__m128i *)Str1, 0, 0x40ui64);
sub_14001E3D0((__m128i *)byte_14002C380, 0, 0x40ui64);
sub_14001E3D0((__m128i *)v4, 0, 0x14ui64);
printf("Welcome to NCTF\n");
Sleep(0);
printf("Enter your flag ");
Sleep(0);
printf("And have a good time: ");
scanf("%64s", Str1);
if ( strlen(Str1) != 38 ) //flag长度为38
{
printf("Length Error!");
ExitProcess(1u);
}
if ( !strncmp(Str1, Str2, 5ui64) && Str1[37] == 125 ) //检测flag格式
{
extract(Str1, "NCTF{%32s}", input); //提取中间的32字节
keygen(v4, &key, 10i64);
keygen(v5, &key, 6i64);
encrypt(input, v4, Buf1); //16字节加密一次
encrypt(&input[16], v4, v7);
}
if ( memcmp(Buf1, cipher, 0x20ui64) )
{
printf("Wrong Flag!");
ExitProcess(1u);
}
printf("Correct!");
return 0;
}

得到flag长度为38,中间的32字节会被用来加密比较,密文的十六进制为FB973C3BF19912DF1330F7D87FEBA06C145BA62AA805A5F376BEC901F9367B46,查看加密函数:

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
unsigned __int64 __fastcall encrypt(__int64 a1, __int64 a2, __int64 a3)
{
unsigned __int64 result; // rax
int i; // [rsp+0h] [rbp-178h]
int j; // [rsp+4h] [rbp-174h]
int m; // [rsp+8h] [rbp-170h]
int ii; // [rsp+Ch] [rbp-16Ch]
int k; // [rsp+10h] [rbp-168h]
int n; // [rsp+14h] [rbp-164h]
int v10; // [rsp+18h] [rbp-160h]
int v11; // [rsp+1Ch] [rbp-15Ch]
int v12[4]; // [rsp+30h] [rbp-148h] BYREF
int v13[36]; // [rsp+40h] [rbp-138h] BYREF
int v14[36]; // [rsp+D0h] [rbp-A8h] BYREF

memset(v14, 0, sizeof(v14));
memset(v13, 0, sizeof(v13));
result = (unsigned __int64)v12;
for ( i = 0; i < 4; ++i )
{
v10 = *(_DWORD *)(a1 + 4i64 * i);
v13[i] = (BYTE1(v10) << 16) | ((unsigned __int8)v10 << 24);
v13[i] |= HIBYTE(v10) | (BYTE2(v10) << 8);
v14[i] = dwordarr[i] ^ ((unsigned __int8)HIBYTE(*(_DWORD *)(a2 + 4i64 * i)) | ((unsigned __int8)BYTE2(*(_DWORD *)(a2 + 4i64 * i)) << 8) | ((unsigned __int8)BYTE1(*(_DWORD *)(a2 + 4i64 * i)) << 16) | ((unsigned __int8)*(_DWORD *)(a2 + 4i64 * i) << 24));
result = (unsigned int)(i + 1);
}
for ( j = 0; j < 32; ++j )
{
v12[0] = dwordbox[j] ^ v14[j + 3] ^ v14[j + 2] ^ v14[j + 1];
for ( k = 0; k < 4; ++k )
*((_BYTE *)v12 + k) = sbox[*((unsigned __int8 *)v12 + k)];
v14[j + 4] = (((unsigned int)v12[0] >> 9) | (v12[0] << 23)) ^ (((unsigned int)v12[0] >> 19) | (v12[0] << 13)) ^ v12[0] ^ v14[j];
result = (unsigned int)(j + 1);
}
for ( m = 0; m < 32; ++m )
{
v12[0] = v14[m + 4] ^ v13[m + 3] ^ v13[m + 2] ^ v13[m + 1];
for ( n = 0; n < 4; ++n )
*((_BYTE *)v12 + n) = sbox[*((unsigned __int8 *)v12 + n)];
v13[m + 4] = (((unsigned int)v12[0] >> 8) | (v12[0] << 24)) ^ (((unsigned int)v12[0] >> 14) | (v12[0] << 18)) ^ (((unsigned int)v12[0] >> 22) | (v12[0] << 10)) ^ (((unsigned int)v12[0] >> 30) | (4 * v12[0])) ^ v12[0] ^ v13[m];
result = (unsigned int)(m + 1);
}
for ( ii = 0; ii < 4; ++ii )
{
v11 = v13[35 - ii];
*(_DWORD *)(a3 + 4i64 * ii) = (BYTE1(v11) << 16) | ((unsigned __int8)v11 << 24);
*(_DWORD *)(a3 + 4i64 * ii) |= HIBYTE(v11) | (BYTE2(v11) << 8);
result = (unsigned int)(ii + 1);
}
return result;
}

发现是一堆复杂的加密,提取各种密钥,仔细观察后发现key和sbox有额外的xref:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 sub_7FF6880B1480()
{
__int64 result; // rax
int i; // [rsp+20h] [rbp-18h]
int j; // [rsp+24h] [rbp-14h]

for ( i = 0; i < 10; ++i )
{
key[i] ^= 0x91u;
result = (unsigned int)(i + 1);
}
for ( j = 0; j < 10; ++j )
{
swap(sbox, &sbox[(unsigned __int8)key[j]]);
result = (unsigned int)(j + 1);
}
return result;
}

发现密钥被额外异或了0x91,sbox则做过替换,开始正向分析加密函数,发现是SM4加密,结合前面更换密钥和sbox,编写脚本:

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
dwordarr = [0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC]
dwordbox = [0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269, 0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249, 0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229, 0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209, 0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279]
sbox = [0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c,0x05,
0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86,0x06,0x99,
0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed,0xcf,0xac,0x62,
0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa,0x75,0x8f,0x3f,0xa6,
0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c,0x19,0xe6,0x85,0x4f,0xa8,
0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb,0x0f,0x4b,0x70,0x56,0x9d,0x35,
0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25,0x22,0x7c,0x3b,0x01,0x21,0x78,0x87,
0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52,0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e,
0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38,0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1,
0xe0,0xae,0x5d,0xa4,0x9b,0x34,0x1a,0x55,0xad,0x93,0x32,0x30,0xf5,0x8c,0xb1,0xe3,
0x1d,0xf6,0xe2,0x2e,0x82,0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f,
0xd5,0xdb,0x37,0x45,0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51,
0x8d,0x1b,0xaf,0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8,
0x0a,0xc1,0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0,
0x89,0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84,
0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39,0x48]
key = [0xdf, 0xd2, 0xc5, 0xd7, 0xa3, 0xa5, 0xff, 0xf2,
0xe5, 0xf7, 0xdf, 0xd2, 0xc5, 0xd7, 0xa3, 0xa5]
for i in range(16):
key[i] ^= 0x91
for j in range(10):
sbox[0], sbox[key[j]] = sbox[key[j]], sbox[0]

def endian_swap(num):
return (((num >> 24) & 0xFF) | (((num >> 16) & 0xFF) << 8) | (((num >> 8) & 0xFF) << 16) | ((num & 0xFF) << 24)) & 0xFFFFFFFF

def b2dle(byte_array):
return [int.from_bytes(byte_array[i:i+4], byteorder='little', signed=False) for i in range(0, len(byte_array), 4)]

def b2dbe(byte_array):
return [int.from_bytes(byte_array[i:i+4], byteorder='big', signed=False) for i in range(0, len(byte_array), 4)]

def d2ble(dword_array):
if isinstance(dword_array, int):
return list(dword_array.to_bytes(4, byteorder='little'))
elif hasattr(dword_array, '__iter__'):
byte_list = []
for dword in dword_array:
byte_list.extend(dword.to_bytes(4, byteorder='little'))
return byte_list
else:
raise TypeError("Input must be an integer or an iterable of integers.")

def d2bbe(dword_array):
if isinstance(dword_array, int):
return list(dword_array.to_bytes(4, byteorder='big'))
elif hasattr(dword_array, '__iter__'):
byte_list = []
for dword in dword_array:
byte_list.extend(dword.to_bytes(4, byteorder='big'))
return byte_list
else:
raise TypeError("Input must be an integer or an iterable of integers.")

def encrypt(a1, a2, a3, dwordarr, dwordbox, sbox):
v12 = 0
b12 = [0] * 4
v13 = [0] * 36
v14 = [0] * 36
for i in range(4):
v13[i] = endian_swap(a1[i])
v14[i] = dwordarr[i] ^ endian_swap(a2[i])
for j in range(32):
v12 = (dwordbox[j] ^ v14[j + 3] ^ v14[j + 2] ^ v14[j + 1]) & 0xFFFFFFFF
b12 = d2ble(v12)
for k in range(4):
b12[k] = sbox[b12[k]]
v12 = b2dle(b12)[0]
v14[j + 4] = ((v12 >> 9) | (v12 << 23)) ^ ((v12 >> 19) | (v12 << 13)) ^ v12 ^ v14[j]
for m in range(32):
v12 = (v14[m + 4] ^ v13[m + 3] ^ v13[m + 2] ^ v13[m + 1]) &0xFFFFFFFF
b12 = d2ble(v12)
for n in range(4):
b12[n] = sbox[b12[n]]
v12 = b2dle(b12)[0]
v13[m + 4] = ((v12 >> 8) | (v12 << 24)) ^ ((v12 >> 14) | (v12 << 18)) ^ ((v12 >> 22) | (v12 << 10)) ^ ((v12 >> 30) | (4 * v12)) ^ v12 ^ v13[m]
for ii in range(4):
v11 = v13[35 - ii]
a3[ii] = endian_swap(v11)
return a3

def decrypt(a1, a2, a3, dwordarr, dwordbox, sbox):
v12 = 0
b12 = [0] * 4
v13 = [0] * 36
v14 = [0] * 36
for i in range(4):
v13[i] = endian_swap(a1[i])
v14[i] = dwordarr[i] ^ endian_swap(a2[i])
for j in range(32):
v12 = (dwordbox[j] ^ v14[j+3] ^ v14[j+2] ^ v14[j+1]) & 0xFFFFFFFF
b12 = d2ble(v12)
for k in range(4):
b12[k] = sbox[b12[k]]
v12 = b2dle(b12)[0]
v14[j+4] = ((v12 >> 9) | (v12 << 23)) ^ ((v12 >> 19) | (v12 << 13)) ^ v12 ^ v14[j]
for m in range(32):
v12 = (v14[35 - m] ^ v13[m+3] ^ v13[m+2] ^ v13[m+1]) & 0xFFFFFFFF
b12 = d2ble(v12)
for n in range(4):
b12[n] = sbox[b12[n]]
v12 = b2dle(b12)[0]
v13[m+4] = ((v12 >> 8) | (v12 << 24)) ^ ((v12 >> 14) | (v12 << 18)) ^ ((v12 >> 22) | (v12 << 10)) ^ ((v12 >> 30) | (4 * v12)) ^ v12 ^ v13[m]
for ii in range(4):
v11 = v13[35 - ii]
a3[ii] = endian_swap(v11)
return a3

# 输入数据
# a1 = [0x12345678, 0x12345679, 0x12345677, 0x12345670]
a2 = b2dle(key)
a3 = [0] * 4
# result = encrypt(a1, a2, a3, dwordarr, dwordbox, sbox.copy())
# print([hex(x) for x in result])
# m = decrypt(result, a2, a3, dwordarr, dwordbox, sbox.copy())
# print([hex(x) for x in m])
m1 = [0xfb,0x97,0x3c,0x3b,0xf1,0x99,0x12,0xdf,
0x13,0x30,0xf7,0xd8,0x7f,0xeb,0xa0,0x6c]
m2 = [0x14,0x5b,0xa6,0x2a,0xa8,0x05,0xa5,0xf3,
0x76,0xbe,0xc9,0x01,0xf9,0x36,0x7b,0x46]
dm1 = b2dle(m1)
dm2 = b2dle(m2)
# 解密
r1 = decrypt(dm1, a2, a3.copy(), dwordarr, dwordbox, sbox.copy())
r2 = decrypt(dm2, a2, a3.copy(), dwordarr, dwordbox, sbox.copy())
br1 = d2ble(r1)
br2 = d2ble(r2)
for i in range(16):
print(hex(br1[i]),end = ", ")
print()
for i in range(16):
print(hex(br2[i]),end = ", ")
print()
print()
# 验证加密
c1 = encrypt(r1, a2, a3.copy(), dwordarr, dwordbox, sbox.copy())
c2 = encrypt(r2, a2, a3.copy(), dwordarr, dwordbox, sbox.copy())
bc1 = d2ble(c1)
bc2 = d2ble(c2)
for i in range(16):
print(hex(bc1[i]),end = ", ")
print()
for i in range(16):
print(hex(bc2[i]),end = ", ")
print()

输出0x35, 0x38, 0x63, 0x62, 0x39, 0x32, 0x35, 0x65, 0x30, 0x63, 0x64, 0x38, 0x32, 0x33, 0x63, 0x30, 0x64, 0x30, 0x62, 0x35, 0x34, 0x66, 0x64, 0x30, 0x36, 0x62, 0x38, 0x32, 0x30, 0x62, 0x37, 0x65,即58cb925e0cd823c0d0b54fd06b820b7e,则flag为NCTF{58cb925e0cd823c0d0b54fd06b820b7e}.

Misc方向:

QRcode Reconstruction:

发现给出的png图片有一些空白像素,但是这些像素只是透明度被设置为了0,用脚本修改为255后得到二维码:

扫描得到ªª¥UUWeLc0mE\_t0\_Nctf\_2024!!!},则flag为NCTF{WeLc0mE_t0_Nctf_2024!!!}.