首页
社区
课程
招聘
[原创]看雪 2022·KCTF 春季赛 > 第三题 石像病毒 by 心学
2022-5-16 23:22 11507

[原创]看雪 2022·KCTF 春季赛 > 第三题 石像病毒 by 心学

htg 活跃值
4
2022-5-16 23:22
11507

日期:2022-05-14
CTF:htg
题目:看雪 2022·KCTF 春季赛 > 第三题 石像病毒

 

工具:IDA
要点:算法 MD5、AES

一、预习算法知识

1.1、MD5

1.1.1 定义:

MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。
将任意长度的明文,转化为128位的哈希值。

1.1.2 算法流程

1.1.2.1 明文处理

1.1.2.1.1 转储字节

明文字符串转储为字节序列,默认编码格式为utf-8

1.1.2.1.2 补充至448位

字节序列长度整除512,如余数不等于448,则在后面补充1个1和多个0,直至整除512的余数为448

1.1.2.1.3 添加长度

接着在后面添加字节序列的长度(二进制位数)

1.1.2.1.4 补充至512位

接着在后面补0,直至长度被512整除

1.1.2.2 设置初值

设置后续循环计算的初始值

1
2
3
4
A = 0x67452301
B = 0xEFCDAB89
C = 0x98BADCFE
D = 0x10325476

1.1.2.3 循环运算

每次处理512个字节,直至处理完成,即循环次数为:预处理后的字节序列长度 / 512 的结果

基本概念

四种函数

1
2
3
4
F(X, Y, Z) = (X&Y) | ((~X) & Z)
G(X, Y, Z) = (X&Z) | (Y & (~Z))
H(X, Y, Z) = X^Y^Z
I(X, Y, Z) = Y^(X|(~Z))

针对四种函数,定义与其对应的四个

1
2
3
4
5
6
7
8
9
FF(a,b,c,d,Mi,s,tj)  表示 a=b+((a+(F(b,c,d)+Mi+tj)<<< s)
GG(a,b,c,d,Mi,s,tj)  表示 a=b+((a+(G(b,c,d)+Mi+tj)<<< s)
HH(a,b,c,d,Mi,s,tj)  表示 a=b+((a+(H(b,c,d)+Mi+tj)<<< s)
II(a,b,c,d,Mi,s,tj)  表示 a=b+((a+(I(b,c,d)+Mi+tj)<<< s)
1)其中Mi表示消息的第i个子分组(从015,共16个)
2)<<< s表示循环左移s位
3)常数tj为:在第j步中,tj是4294967296*abs(sin(j))的整数部分,i的单位是弧度。(4294967296232次方)亦可用 0x100000000UL * abs(sin((double)j)) 计算
4)i和j 都是针对一次主循环而言的,下一个主循环开始后,i和j将重置。
5)a,b,c,d 就是每一步中的 A,B,C,D

以一个512字节序列的过程如下:

对预处理后的字节序列进行分组

分组后的数组长度为 16 ,每个单元长度为32,即4个字节
分别进行16次FF、GG、HH、II运算,每次运行时,ABCD的参数进行右移替换
以16次FF举例,分别是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a=FF(a,b,c,d,M0,7,0xd76aa478)
b=FF(d,a,b,c,M1,12,0xe8c7b756)
c=FF(c,d,a,b,M2,17,0x242070db)
d=FF(b,c,d,a,M3,22,0xc1bdceee)
a=FF(a,b,c,d,M4,7,0xf57c0faf)
b=FF(d,a,b,c,M5,12,0x4787c62a)
c=FF(c,d,a,b,M6,17,0xa8304613)
d=FF(b,c,d,a,M7,22,0xfd469501)
a=FF(a,b,c,d,M8,7,0x698098d8)
b=FF(d,a,b,c,M9,12,0x8b44f7af)
c=FF(c,d,a,b,M10,17,0xffff5bb1)
d=FF(b,c,d,a,M11,22,0x895cd7be)
a=FF(a,b,c,d,M12,7,0x6b901122)
b=FF(d,a,b,c,M13,12,0xfd987193)
c=FF(c,d,a,b,M14,17,0xa679438e)
d=FF(b,c,d,a,M15,22,0x49b40821)

1.1.2.4 反转合并

实际上是将int类型的值在内存中字节序列原位输出。
比如:0x01234567,其在内存中存储序列为 67452301,那么反转之后为'67452301',将四个字符串合并输出,即为 md5 hash

1.1.3 算法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#来源:https://github.com/timvandermeij/md5.py/blob/master/md5.py
"""
The implementation of the MD5 algorithm is based on the original RFC at
https://www.ietf.org/rfc/rfc1321.txt and contains optimizations from
https://en.wikipedia.org/wiki/MD5.
"""
 
import struct
from enum import Enum
from math import (floor,sin,)
import os
import binascii
 
#pip install bitarray
from bitarray import bitarray
 
class MD5Buffer(Enum):
    A = 0x67452301
    B = 0xEFCDAB89
    C = 0x98BADCFE
    D = 0x10325476
 
class MD5(object):
    _string = None
    _buffers = {
        MD5Buffer.A: None,
        MD5Buffer.B: None,
        MD5Buffer.C: None,
        MD5Buffer.D: None,
    }
 
    @classmethod
    def hash(cls, string):
        cls._string = string
 
        preprocessed_bit_array = cls._step_2(cls._step_1())
        cls._step_3()
        cls._step_4(preprocessed_bit_array)
        return cls._step_5()
 
    @classmethod
    def _step_1(cls):
        # Convert the string to a bit array.
        # 将 string 转换为 bit_array (比特序列)
        bit_array = bitarray(endian="big")
        # big 大端存储就是将字符串原为转为字节序列,比如字符串'01234567'转到内存中存储(地址由低到高)就是 30 31 32 33 34 35 36 37,进一步将该字节序列转为一个int型,其值就是 0x
        bit_array.frombytes(cls._string.encode("utf-8"))
        #print("bit_array:{}".format(bit_array))
        #os.system("pause")
        # Pad the string with a 1 bit and as many 0 bits required such that
        # the length of the bit array becomes congruent to 448 modulo 512.
        # Note that padding is always performed, even if the string's bit
        # length is already conguent to 448 modulo 512, which leads to a
        # new 512-bit message block.
        # 在 bit_array 比特序列后面添加1个1和多个0,直至能够被 512 整数之后余数为 448
        bit_array.append(1)
        while len(bit_array) % 512 != 448:
            bit_array.append(0)
 
        # For the remainder of the MD5 algorithm, all values are in
        # little endian, so transform the bit array to little endian.
        # 转换 bit_array 之后的存储方式
        bit_array = bitarray(bit_array, endian="little")
        #print("bit_array:{}".format(bit_array))
        #os.system("pause")
        return bit_array
 
    @classmethod
    def _step_2(cls, step_1_result):
        # Extend the result from step 1 with a 64-bit little endian
        # representation of the original message length (modulo 2^64).
        # 在 bit_array 之后添加 原始字符串的长度(比特位数)整除 2^64 的 余数
        # MD5 处理的长度最高限定为 2^61-1 个字节,64个比特位
        length = (len(cls._string) * 8) % pow(2, 64)
        #print("length:{}={}={}".format(length,hex(length),bin(length)))
        #os.system("pause")
 
        #下面两句,直接按【比特位】进行小端存放,    
        #length_bit_array = bitarray(endian="little")
        #length_bit_array.frombytes(struct.pack("<Q", length))#按照QWORD类型存储,即2^64
 
        #替换代码:实际应该是按照【字节】小端存放(CTF题目是字节小端存放)
        length_byte_array = struct.pack("<Q", length)
        #print("length_byte_array:{}".format(length_byte_array))
        #os.system("pause")       
        length_bit_array = bitarray(endian="big")
        length_bit_array.frombytes(length_byte_array)       
        #print("length_bit_array:{}".format(length_bit_array))
        #os.system("pause")
 
        result = step_1_result.copy()
        result.extend(length_bit_array)
        print("result:{}".format(result))
        os.system("pause")
 
        return result
 
    @classmethod
    def _step_3(cls):
        # Initialize the buffers to their default values.
        for buffer_type in cls._buffers.keys():
            cls._buffers[buffer_type] = buffer_type.value
 
    @classmethod
    def _step_4(cls, step_2_result):
        # Define the four auxiliary functions that produce one 32-bit word.
        # 定义四种处理函数
        F = lambda x, y, z: (x & y) | (~x & z)
        G = lambda x, y, z: (x & z) | (y & ~z)
        H = lambda x, y, z: x ^ y ^ z
        I = lambda x, y, z: y ^ (x | ~z)
 
        # Define the left rotation function, which rotates `x` left `n` bits.
        # 定义 循环左移操作操作
        rotate_left = lambda x, n: (x << n) | (x >> (32 - n))
 
        # Define a function for modular addition.
        # 定义 基于 int32(2^32)的模加法
        modular_add = lambda a, b: (a + b) % pow(2, 32)
 
        # Compute the T table from the sine function. Note that the
        # RFC starts at index 1, but we start at index 0.
        # 建议长度为64的 Sin表,即 0x100000000UL*abs(sin(j))的整数部分,其中j从1开始。
        T = [floor(pow(2, 32) * abs(sin(i + 1))) for i in range(64)]
 
        #更改T[0x2A]:这个就是 CTF题目中更改的地方。表 T 与 表 MD5_K_order 相同
        T[0x2A]=0xD4AF3085
 
        # The total number of 32-bit words to process, N, is always a
        # multiple of 16.
        #
        N = len(step_2_result) // 32
 
        # Process chunks of 512 bits.
        # 每次处理 512 位二进制序列
        for chunk_index in range(N // 16):
            # Break the chunk into 16 words of 32 bits in list X.
            # 分成16个在分组,每个单元是 32 位,然后转换成 int
 
            # chunk_index 是一个大循环的索引号,每次主循环处理512个字节
            start = chunk_index * 512
            X = [step_2_result[start + (x * 32) : start + (x * 32) + 32] for x in range(16)]
            #print("X[0]:{}".format(X[0]))
            # Convert the `bitarray` objects to integers.
            '''
            #原始代码:按【比特位】小端进行处理   
            X = [int.from_bytes(word.tobytes(), byteorder="little") for word in X]
            '''
 
            #按【字节】小端进行处理
            #按大端存放,也就是解析时位置保存不变
            X = [bitarray(word, endian="big") for word in X]
            print("X[0]:{}".format(X[0]))
            #转为字节序列:这一步就是要保持比特位顺序原位不变,直接转换为 字节序列
            #X[0]:bitarray('01000101011011100110101000110000')
            #X[0]:b'Enj0'      4   5   6   E   6   A   3   0     
            #X[0]:306A6E45
            X = [word.tobytes() for word in X]           
            print("X[0]:{}".format(X[0]))
            #按【字节】小端存放,也就是\x45\x6E\x6A\x30--->0x306A6E45
            X = [int.from_bytes(word, byteorder="little") for word in X]
            print("X[0]:{:02X}".format(X[0]))
            #os.system("pause")
 
            # Make shorthands for the buffers A, B, C and D.
            # 定义A B C D的初值:每次处理512位数据前,均要将ABCD初始化
            A = cls._buffers[MD5Buffer.A]
            B = cls._buffers[MD5Buffer.B]
            C = cls._buffers[MD5Buffer.C]
            D = cls._buffers[MD5Buffer.D]
 
            # Execute the four rounds with 16 operations each.
            # 执行 64 次循环,每16次为一组,依次是 F、G、H、I各执行16次
            for i in range(4 * 16):
                if 0 <= i <= 15:
                    k = i
                    s = [7, 12, 17, 22]
                    temp = F(B, C, D)
                elif 16 <= i <= 31:
                    k = ((5 * i) + 1) % 16
                    s = [5, 9, 14, 20]
                    temp = G(B, C, D)
                elif 32 <= i <= 47:
                    k = ((3 * i) + 5) % 16
                    s = [4, 11, 16, 23]
                    temp = H(B, C, D)
                elif 48 <= i <= 63:
                    k = (7 * i) % 16
                    s = [6, 10, 15, 21]
                    temp = I(B, C, D)
 
                # The MD5 algorithm uses modular addition. Note that we need a
                # temporary variable here. If we would put the result in `A`, then
                # the expression `A = D` below would overwrite it. We also cannot
                # move `A = D` lower because the original `D` would already have
                # been overwritten by the `D = C` expression.
                # 依次执行模加、模加、模加、循环左移、模加运算
                # 对应的是:b+((a+(F(b,c,d)+Mi+tj)<<< s)
                temp = modular_add(temp, X[k])      #F(b,c,d)+Mi
                temp = modular_add(temp, T[i])      #F(b,c,d)+Mi+tj
                temp = modular_add(temp, A)         #a+(F(b,c,d)+Mi+tj
                temp = rotate_left(temp, s[i % 4])  #【上一步结果】<<<s
                temp = modular_add(temp, B)         #b+【上一步结果】
 
                # Swap the registers for the next operation.
                # 循环交换 A、B、C、D的值。对照下面的例子
                # a=FF(a,b,c,d,M0,7,0xd76aa478)
                # b=FF(d,a,b,c,M1,12,0xe8c7b756)
                A = D
                D = C
                C = B
                B = temp
 
            #循环已经结果
 
            # Update the buffers with the results from this chunk.
            # 更新 A、B、C、D的值,作为下一个512位数据处理的初始值。
            cls._buffers[MD5Buffer.A] = modular_add(cls._buffers[MD5Buffer.A], A)
            cls._buffers[MD5Buffer.B] = modular_add(cls._buffers[MD5Buffer.B], B)
            cls._buffers[MD5Buffer.C] = modular_add(cls._buffers[MD5Buffer.C], C)
            cls._buffers[MD5Buffer.D] = modular_add(cls._buffers[MD5Buffer.D], D)
 
    @classmethod
    def _step_5(cls):
        # Convert the buffers to little-endian.
        # 将 A、B、C、D 值转换成小端存放
        A = struct.unpack("<I", struct.pack(">I", cls._buffers[MD5Buffer.A]))[0]
        B = struct.unpack("<I", struct.pack(">I", cls._buffers[MD5Buffer.B]))[0]
        C = struct.unpack("<I", struct.pack(">I", cls._buffers[MD5Buffer.C]))[0]
        D = struct.unpack("<I", struct.pack(">I", cls._buffers[MD5Buffer.D]))[0]
 
        # Output the buffers in lower-case hexadecimal format.
        # 输出结果
        return f"{format(A, '08x')}{format(B, '08x')}{format(C, '08x')}{format(D, '08x')}"
 
#测试
strInter = "Enj0y_1t_4_fuuuN"
md5Value = MD5.hash(strInter).upper()
print("md5Value:{}".format(md5Value))

1.1.4 算法要点回顾

初始值是识别算法的特征
两处扩充(448、512)
有4种基本操作的函数
MD5_K_Order有0x64,存储sin值
主循环内有64次子循环

1.2、AES

算法详解:https://blog.csdn.net/u010037269/article/details/123863979
主要概念:

1.2.1 AES基本概念

AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)。
密钥的长度可以使用128位(10轮)、192位(12轮)或256位(14轮)。密钥的长度不同,推荐加密轮数也不同。
本题是AES-128,有11轮操作
AES的处理单位是字节
图片引自https://blog.csdn.net/u010037269/article/details/123863979

1.2.2 AES操作

1.2.2.1、分组:

128位的输入明文分组P被分成16个字节

1.2.2.2、密钥扩充

128位密钥也是用字节为单位的矩阵表示,矩阵的每一列被称为1个32位比特字。
通过密钥编排函数该密钥矩阵被扩展成一个44个字组成的序列W[0],W[1], … ,W[43],该序列的前4个元素W[0],W[1],W[2],W[3]是原始密钥,用于加密运算中的初始密钥加(下面介绍);后面40个字分为10组,每组4个字(128比特)分别用于10轮加密运算中的轮密钥加。

1.2.2.3、加密过程

加密的第1轮到第9轮的轮函数一样,包括4个操作:字节代换、行位移、列混合和轮密钥加。最后一轮迭代不执行列混合。另外,在第一轮迭代之前,先将明文和原始密钥进行一次异或加密操作。

1.2.2.3.1、字节代换

AES的字节代换其实就是一个简单的查表操作。AES定义了一个S盒和一个逆S盒。

1.2.2.3.2、行位移

行移位是一个简单的左循环移位操作。当密钥长度为128比特时,状态矩阵的第0行左移0字节,第1行左移1字节,第2行左移2字节,第3行左移3字节

1.2.2.3.3、列混合

列混合变换是通过矩阵相乘来实现的,经行移位后的状态矩阵与固定的矩阵相乘,得到混淆后的状态矩阵

1.2.2.3.4、轮密钥加

轮密钥加是将128位轮密钥Ki同状态矩阵中的数据进行逐位异或操作

1.2.2.4 分组方式:本题是ECB模式

分组密码有五种工作体制:1.电码本模式(Electronic Codebook Book (ECB));2.密码分组链接模式(Cipher Block Chaining (CBC));3.计算器模式(Counter (CTR));4.密码反馈模式(Cipher FeedBack (CFB));5.输出反馈模式(Output FeedBack (OFB))。

1.2.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
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
#来源:https://github.com/heartingrass/AES-Python/blob/master/aes.py
#!/usr/bin/env python
#根据题目的加密方式做了修改
import os
"""
    Copyright (C) 2012 Bo Zhu http://about.bozhu.me
    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:
    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.
"""
 
#定义:是否执行题目修改了原AES的算法
flag = True
#flag = False
 
Sbox = [
    0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
    0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
    0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
    0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
    0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
    0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
    0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
    0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
    0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
    0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
    0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
    0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
    0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
    0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
    0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
    0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
]
 
InvSbox = [
    0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
    0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
    0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
    0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
    0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
    0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
    0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
    0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
    0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
    0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
    0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
    0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
    0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
    0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
    0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
    0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
]
 
#题目更改了S盒子,那么就更改对应的逆盒子
#Python错误集锦:TypeError: ‘tuple’ object does not support item assignment
#解决方法:
#1、tuple不能修改元素,无解。如果要修改元素,改用list类型。
if flag :
    valueA = Sbox[0x71]
    valueB = Sbox[0xA3]
    #更改盒子
    Sbox[0x71] ^= Sbox[0xA3]
    Sbox[0xA3] ^= Sbox[0x71]
    Sbox[0x71] ^= Sbox[0xA3]
    #更改逆盒子
    InvSbox[valueA] ^= InvSbox[valueB]
    InvSbox[valueB] ^= InvSbox[valueA]
    InvSbox[valueA] ^= InvSbox[valueB]
    print("Sbox[0x71]:{:02X}".format(Sbox[0x71]))
    print("Sbox[0xA3]:{:02X}".format(Sbox[0xA3]))#结果错了。。。
 
 
# learnt from http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c
xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)
 
#按照题目的方法来
#/* Galois Field (256) Multiplication of two Bytes */
def GMul(u,v):
    tmpFlag = False
    if u == 0x3 and v == 0x72: tmpFlag=False
    p = 0
    tmpV = 0
    for i in range(8):
        if u & 0x01:
            p ^=v
        if tmpFlag: print('v:{:02X}-->'.format(v))
        tmpV = v
        v <<= 1#逻辑左移,要考虑超标问题
        if tmpFlag: print('v:{:02X}'.format(v))
        v = v & 0xFF#保证为字节
        if tmpFlag: print('v:{:02X}'.format(v))
        if tmpV & 0x80:#看看这个与,对什么造成了影响。这个是另外一个变量。判断的是循环右移之前的值
            v ^= 0x1B
            if tmpFlag: print('v:{:02X} #v & 0x80'.format(v))
        if tmpFlag: print('u:{:02X}<--'.format(u))
        u >>= 1#逻辑右移,要考虑高位是否存在问题
        if tmpFlag: print('u:{:02X}'.format(u))
        u = u & 0x7F  #去除高位           
    return p
 
#与题目保持一致
Rcon = (
    0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
    0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A,
    0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A,
    0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39,
)
#原始数据,与CTF不一致
'''
static const uint32_t rcon[10] = {
        0x01000000UL, 0x02000000UL, 0x04000000UL, 0x08000000UL, 0x10000000UL,
        0x20000000UL, 0x40000000UL, 0x80000000UL, 0x1B000000UL, 0x36000000UL
};
'''
 
def PrintList(twoDlist):
    returnList = []
    for i in twoDlist:
        returnList.append(["{:02X}".format(j) for j in i])
    return returnList
 
def text2matrix(text):
    matrix = []
    #按行存放:   
    for i in range(16):
        byte = (text >> (8 * (15 - i))) & 0xFF
        if i % 4 == 0:
            matrix.append([byte])
        else:
            #matrix[i / 4].append(byte) #TypeError: list indices must be integers or slices, not float
            tmpI = i // 4
            matrix[tmpI].append(byte)
            #逆序存放
            #补充内容
            #print(matrix)
            if len(matrix[tmpI]) == 4:
                matrix[tmpI] = (matrix[tmpI])[::-1]
 
    '''
    #按列存放
    for i in range(16):
        byte = (text >> (8 * (15 - i))) & 0xFF
        if i < 4 :
            matrix.append([byte])
        else:
            #matrix[i / 4].append(byte) #TypeError: list indices must be integers or slices, not float
            matrix[i % 4].append(byte)
    '''
    return matrix
 
 
def matrix2text(matrix):
    text = 0
    for i in range(4):
        for j in range(4):
            text |= (matrix[i][j] << (120 - 8 * (4 * j + i)))
    return text
 
def plaintext2matrix(plaintext):
    matrix = []
    #按列存放
    for i in range(16):
        byte = (plaintext >> (8 * (15 - i))) & 0xFF
        if i < 4 :
            matrix.append([byte])           
        else:
            #matrix[i / 4].append(byte) #TypeError: list indices must be integers or slices, not float
            matrix[i % 4].append(byte) # 按列存放
        #print("i={:02X}\tbyte={:02X}".format(i,byte))
        #os.system("pause")
    return  matrix
 
def matrix2plaintext(matrix):
    text = ""
    #按列读取
    matrixTrans = list(map(list,zip(*matrix)))
    for i in matrixTrans:
        text += "".join(["{:02X}".format(j) for j in i])
    return text
 
class AES:
    def __init__(self, master_key):
        self.change_key(master_key)
 
    def change_key(self, master_key):
        self.round_keys = text2matrix(master_key)
        print("self.round_keys")
        print(PrintList(self.round_keys))
        print("#"*50)
 
        for i in range(4, 4 * 11):
            self.round_keys.append([])
            '''
            if i % 4 == 0:
                byte = self.round_keys[i - 4][0]        \
                     ^ Sbox[self.round_keys[i - 1][1]]  \#错误,此处应该是[i-1][3],它与CTF题目不一样
                     ^ Rcon[i // 4]
                     #^ Rcon[i / 4]
                self.round_keys[i].append(byte)
 
                for j in range(1, 4):
                    byte = self.round_keys[i - 4][j]    \
                         ^ Sbox[self.round_keys[i - 1][(j + 1) % 4]]##错误,此处应该是[i-1][j-1],它与CTF题目不一样
                    self.round_keys[i].append(byte)
            else:
                for j in range(4):
                    byte = self.round_keys[i - 4][j]    \
                         ^ self.round_keys[i - 1][j]
                    self.round_keys[i].append(byte)
            '''
            if i % 4 == 0:
                byte = self.round_keys[i - 4][0]        \
                     ^ Sbox[self.round_keys[i - 1][3]]  \
 
                     #^ Rcon[i // 4] #错误,应该挪一个位置 3
                     #^ Rcon[i / 4]
                self.round_keys[i].append(byte)
                '''
                print("i:{:02X}".format(i))
                print("byte:{:02X}".format(byte))
                print("self.round_keys[i - 4][0]:{:02X}".format(self.round_keys[i - 4][0]))
                print("self.round_keys[i - 1][3]:{:02X}".format(self.round_keys[i - 1][3]))
                print("Sbox[self.round_keys[i - 1][3]]:{:02X}".format(Sbox[self.round_keys[i - 1][3]]))
                print("Rcon[i // 4]:{:02X}".format(Rcon[i // 4]))
                os.system("pause")
                '''
                for j in range(1, 4):
                    byte = self.round_keys[i - 4][j]    \
                         ^ Sbox[self.round_keys[i - 1][(j - 1) % 4]]
                    if j == 3:
                        byte ^=Rcon[i // 4]
                    self.round_keys[i].append(byte)
            else:
                for j in range(4):
                    byte = self.round_keys[i - 4][j]    \
                         ^ self.round_keys[i - 1][j]
                    self.round_keys[i].append(byte)
        print("self.round_keys")
        print(PrintList(self.round_keys))
        #2022-05-21 2231 密钥扩展已处理完成,和题目一致
        print("#"*50)
 
    #修改:调整输入参数为是字节序列,而非一个超大整数
    def encrypt(self, plaintext):
        #按照0x10进行截断,填充模式采用 00 填充
        encryptText = ""
        last = len(plaintext) % 0x10
        cnt = len(plaintext) // 0x10
        for i in range(cnt) :
 
            byteTtext_0x10 = plaintext[i*0x10:(i+1)*0x10]
            plaintext_0x10 = int.from_bytes(byteTtext_0x10, byteorder='big')
            encryptText+=self.encrypt_0x10(plaintext_0x10)
 
        #处理余数。
        if last!= 0:
            byteTtext_0x10 = plaintext[cnt*0x10:]
            plaintext_0x10 = int.from_bytes(byteTtext_0x10, byteorder='big')
            plaintext_0x10 << (last*8)
            encryptText+=self.encrypt_0x10(plaintext_0x10)
 
        return encryptText
 
    #维持原有代码的效果,输入是一个 0x10字节长的整数
    def encrypt_0x10(self, plaintext):
        '''
        输入的字符串:'flag{db5c6a8dfec4d0ec5569899640}',0
        66 6C 61 67 7B 64 62 35  63 36 61 38 64 66 65 63 34 64 30 65 63 35 35 36 39 38 39 39 36 34 30 7D 00
        第一次为:Stack[00004680]:00EFE908 66 7B 63 64 6C 64 36 66+aFCdld6fabaeg58 db 'f{cdld6fabaeg58c'
        'f{cdld6fabaeg58c'
        '''
        tmpFlag = False
        #更换text转matrix方法
        #self.plain_state = text2matrix(plaintext)
        #换一个新方法
        if tmpFlag: print('plaintext:{:0X}'.format(plaintext))
        self.plain_state = plaintext2matrix(plaintext)
        if tmpFlag: print("*"*50+"\n"+"*"*50)
        if tmpFlag: print("第一轮:轮密钥加")
        if tmpFlag: print("self.plain_state:{}".format(PrintList(self.plain_state)))
        if tmpFlag: print("self.round_keys:{}".format(PrintList(self.round_keys[:4])))
        self.__add_round_key(self.plain_state, self.round_keys[:4])
        if tmpFlag: print("self.plain_state:{}".format(PrintList(self.plain_state)))
        #os.system("pause")#2022-05-21 2313 已与题目相符
 
        for i in range(1, 10):
            if tmpFlag:print("@"*70+"\n"+"第{}轮:".format(i))
            self.__round_encrypt(self.plain_state, self.round_keys[4 * i : 4 * (i + 1)])
 
        self.__sub_bytes(self.plain_state)
        self.__shift_rows(self.plain_state)
        self.__add_round_key(self.plain_state, self.round_keys[40:])
 
        if tmpFlag: print("*"*55 +"\n"+"跟踪:"+"matrix2text(self.plain_state)")#结果正确 
        if tmpFlag: print("self.plain_state:{}".format(PrintList(self.plain_state)))
        if tmpFlag: print("matrix2plaintext(self.plain_state):{}".format(matrix2plaintext(self.plain_state)))
        return matrix2plaintext(self.plain_state)
 
    #输入的是字节序列,而非一个超大整数
    def decrypt(self, ciphertext):
        decryptText=""
        #先判断长度是否满足要求,如果不是 0x10 整数倍,则退出
        lenCiphertext = len(ciphertext)
        if lenCiphertext % 0x10 != 0:
            return "输入错误,必须为0x10整数倍"
 
        cnt = lenCiphertext // 0x10
        print("lenCiphertext:{:02X}".format(lenCiphertext))
        print("cnt:{}".format(cnt))
        for i in range(cnt) :
            print("i:{}".format(i))
            byteCiphertext_0x10 = ciphertext[i*0x10:(i+1)*0x10]
            ciphertext_0x10 = int.from_bytes(byteCiphertext_0x10, byteorder='big')
            decryptText+=self.decrypt_x10(ciphertext_0x10)
            print("decryptText:{}".format(decryptText))
 
        return decryptText
 
    #与源代码保持一致,输入是整数
    def decrypt_x10(self, ciphertext):
        #self.cipher_state = text2matrix(ciphertext)
        tmpFlag = False
        if tmpFlag: print('ciphertext:{:0X}'.format(ciphertext))
        self.cipher_state = plaintext2matrix(ciphertext)
        if tmpFlag: print('ciphertext:{:0X}'.format(ciphertext))
 
        if tmpFlag: print("*"*50+"\n"+"*"*50)
 
        if tmpFlag: print("*"*55 +"\n"+ "跟踪:"+"self.__add_round_key(self.cipher_state, self.round_keys[40:])")
        if tmpFlag: print("self.cipher_state:{}".format(PrintList(self.cipher_state)))
        if tmpFlag: print("self.round_keys:{}".format(PrintList(self.round_keys[40:])))       
        self.__add_round_key(self.cipher_state, self.round_keys[40:])
        if tmpFlag: print("self.cipher_state:{}".format(PrintList(self.cipher_state)))
 
        if tmpFlag: print("*"*55 +"\n"+ "跟踪:"+"self.__inv_shift_rows(self.cipher_state)")
        self.__inv_shift_rows(self.cipher_state)
        if tmpFlag: print("self.cipher_state:{}".format(PrintList(self.cipher_state)))
 
        if tmpFlag: print("*"*55 +"\n"+ "跟踪:"+"self.__inv_sub_bytes(self.cipher_state)")
        self.__inv_sub_bytes(self.cipher_state)
        if tmpFlag: print("self.cipher_state:{}".format(PrintList(self.cipher_state)))
 
        for i in range(9, 0, -1):
            if tmpFlag:print("@"*70+"\n"+"第{}轮:".format(i))
            self.__round_decrypt(self.cipher_state, self.round_keys[4 * i : 4 * (i + 1)])
 
        if tmpFlag: print("最后一轮:轮密钥加")
        if tmpFlag: print("self.cipher_state:{}".format(PrintList(self.cipher_state)))
        if tmpFlag: print("self.round_keys:{}".format(PrintList(self.round_keys[:4])))
        self.__add_round_key(self.cipher_state, self.round_keys[:4])
        if tmpFlag: print("self.cipher_state:{}".format(PrintList(self.cipher_state)))
 
        if tmpFlag: print("*"*55 +"\n"+"跟踪:"+"matrix2text(self.cipher_state)")#结果正确 
        if tmpFlag: print("self.cipher_state:{}".format(PrintList(self.cipher_state)))
        if tmpFlag: print("matrix2plaintext(self.cipher_state):{}".format(matrix2plaintext(self.cipher_state)))
        #return matrix2text(self.cipher_state)
        return matrix2plaintext(self.cipher_state)
 
    #源代码,其与与题目不符合
    '''
    def __add_round_key(self, s, k):
        for i in range(4):
            for j in range(4):
                s[i][j] ^= k[i][j]
    '''
    #与题目保持一致,修改方法
    def __add_round_key(self, s, k):
        for i in range(4):
            for j in range(4):
                s[i][j] ^= k[j][3-i]
 
    def __round_encrypt(self, state_matrix, key_matrix):
        tmpFlag = False
        if tmpFlag: print("*"*55 +"\n"+ "跟踪:"+"self.__sub_bytes(state_matrix)")#结果正确      
        self.__sub_bytes(state_matrix)
        if tmpFlag: print("self.plain_state:{}".format(PrintList(self.plain_state)))
 
        if tmpFlag: print("*"*55 +"\n"+"跟踪:"+"self.__shift_rows(state_matrix)")#结果正确 
        self.__shift_rows(state_matrix)
        if tmpFlag: print("self.plain_state:{}".format(PrintList(self.plain_state)))#结果正确,排查完毕
 
        if tmpFlag: print("*"*55 +"\n""跟踪:"+"self.__mix_columns(state_matrix)")
        self.__mix_columns(state_matrix)
        #DB FE 10 D5 E9 25 E7 2F 34 D9 08 5C F2 9F 0A CD
        #错误
        #DB FE 10 CE E9 25 E7 2F 34 C2 08 5C F2 84 0A D6
        #         03             08 09          0D    0F          
        if tmpFlag: print("self.plain_state:{}".format(PrintList(self.plain_state)))
 
        if tmpFlag: print("*"*55 +"\n""跟踪:"+"self.__add_round_key(state_matrix)")
        self.__add_round_key(state_matrix, key_matrix)
        if tmpFlag: print("self.plain_state:{}".format(PrintList(self.plain_state)))
        #os.system("pause")
 
 
    def __round_decrypt(self, state_matrix, key_matrix):
        self.__add_round_key(state_matrix, key_matrix)
        self.__inv_mix_columns(state_matrix)
        self.__inv_shift_rows(state_matrix)
        self.__inv_sub_bytes(state_matrix)
 
    def __sub_bytes(self, s):
        for i in range(4):
            for j in range(4):
                s[i][j] = Sbox[s[i][j]]
 
 
    def __inv_sub_bytes(self, s):
        for i in range(4):
            for j in range(4):
                s[i][j] = InvSbox[s[i][j]]
 
    #题目对循环操作相关的方向进行调整,其实就是正向逆向操作互换
    def __shift_rows(self, s):
        #原始AES:循环左移;题目是循环右移
        if not flag:
            s[1][0], s[1][1], s[1][2], s[1][3] = s[1][1], s[1][2], s[1][3], s[1][0]
            s[2][0], s[2][1], s[2][2], s[2][3] = s[2][2], s[2][3], s[2][0], s[2][1]
            s[3][0], s[3][1], s[3][2], s[3][3] = s[3][3], s[3][0], s[3][1], s[3][2]
        else:
            s[1][0], s[1][1], s[1][2], s[1][3] = s[1][3], s[1][0], s[1][1], s[1][2]
            s[2][0], s[2][1], s[2][2], s[2][3] = s[2][2], s[2][3], s[2][0], s[2][1]
            s[3][0], s[3][1], s[3][2], s[3][3] = s[3][1], s[3][2], s[3][3], s[3][0]
 
    def __inv_shift_rows(self, s):
        #原始AES:循环左移;题目是循环右移
        if not flag:
            s[1][0], s[1][1], s[1][2], s[1][3] = s[1][3], s[1][0], s[1][1], s[1][2]
            s[2][0], s[2][1], s[2][2], s[2][3] = s[2][2], s[2][3], s[2][0], s[2][1]
            s[3][0], s[3][1], s[3][2], s[3][3] = s[3][1], s[3][2], s[3][3], s[3][0]
        else:
            s[1][0], s[1][1], s[1][2], s[1][3] = s[1][1], s[1][2], s[1][3], s[1][0]
            s[2][0], s[2][1], s[2][2], s[2][3] = s[2][2], s[2][3], s[2][0], s[2][1]
            s[3][0], s[3][1], s[3][2], s[3][3] = s[3][3], s[3][0], s[3][1], s[3][2]
 
    #源代码,其与题目不符合
    '''
    def __mix_single_column(self, a):
        # please see Sec 4.1.2 in The Design of Rijndael
        t = a[0] ^ a[1] ^ a[2] ^ a[3]
        u = a[0]
        a[0] ^= t ^ xtime(a[0] ^ a[1])
        a[1] ^= t ^ xtime(a[1] ^ a[2])
        a[2] ^= t ^ xtime(a[2] ^ a[3])
        a[3] ^= t ^ xtime(a[3] ^ u)
 
    def __mix_columns(self, s):
        for i in range(4):
            self.__mix_single_column(s[i])
    '''
    #调整新的列混淆方法,与题目保持一致
    def __mix_columns(self, a):
        tmp=[]
        M= [[0x02, 0x03, 0x01, 0x01],[0x01, 0x02, 0x03, 0x01],[0x01, 0x01, 0x02, 0x03],[0x03, 0x01, 0x01, 0x02]]
        #调试:跟踪第轮:
        tmpMixFlag = False
        '''
        if a[0][1]==0x35 and a[0][1]==0x5E and a[0][1]==0xFD and a[0][1]==0x2E and a[0][1]==0x03 and a[0][1]==0xD0 and a[0][1]==0xCF and a[0][1]==0xD2 : tmpMixFlag = True
        '''
 
        #Copy
        for i in range(4):
            tmpInter=[]
            for j in range(4):
                tmpInter.append(a[i][j])
            tmp.append(tmpInter)
        #混淆
        for i in range(4):
            for j in range(4):
                a[i][j] = GMul(M[i][0], tmp[0][j]) ^ GMul(M[i][1], tmp[1][j])^ GMul(M[i][2], tmp[2][j]) ^ GMul(M[i][3], tmp[3][j])
                if tmpMixFlag:
                    print("--"*20)
                    print("a[i][j]=a[{}][{}]={:02X}".format(i,j,a[i][j]))
                    print("GMul(M[{}][0], tmp[0][{}])=GMul({},{:02X})={:02X},累计:{:02X}".format(i,j,M[i][0], tmp[0][j],GMul(M[i][0], tmp[0][j]),GMul(M[i][0], tmp[0][j])))
                    print("GMul(M[{}][1], tmp[1][{}])=GMul({},{:02X})={:02X},累计:{:02X}".format(i,j,M[i][1], tmp[1][j],GMul(M[i][1], tmp[1][j]),GMul(M[i][0], tmp[0][j]) ^ GMul(M[i][1], tmp[1][j])))
                    print("GMul(M[{}][2], tmp[2][{}])=GMul({},{:02X})={:02X},累计:{:02X}".format(i,j,M[i][2], tmp[2][j],GMul(M[i][2], tmp[2][j]),GMul(M[i][0], tmp[0][j]) ^ GMul(M[i][1], tmp[1][j])^ GMul(M[i][2], tmp[2][j])))
                    print("GMul(M[{}][3], tmp[3][{}])=GMul({},{:02X})={:02X},累计:{:02X}".format(i,j,M[i][3], tmp[3][j],GMul(M[i][3], tmp[3][j]),GMul(M[i][0], tmp[0][j]) ^ GMul(M[i][1], tmp[1][j])^ GMul(M[i][2], tmp[2][j]) ^ GMul(M[i][3], tmp[3][j])))
 
                    os.system("pause")
 
    #源代码,其与题目不符合
    '''
    def __inv_mix_columns(self, s):
        # see Sec 4.1.3 in The Design of Rijndael
        for i in range(4):
            u = xtime(xtime(s[i][0] ^ s[i][2]))
            v = xtime(xtime(s[i][1] ^ s[i][3]))
            s[i][0] ^= u
            s[i][1] ^= v
            s[i][2] ^= u
            s[i][3] ^= v
 
        self.__mix_columns(s)
    '''
 
    #调整新的列混淆方法,与题目保持一致
    #为什么会有多种AES的处理方式,那输入来说,字节逆序、比特逆序、行存放或列存放、列混淆方法有多种?
    def __inv_mix_columns(self, a):
        tmp=[]
        M= [[0x0E, 0x0B, 0x0D, 0x09],[0x09, 0x0E, 0x0B, 0x0D],[0x0D, 0x09, 0x0E, 0x0B],[0x0B, 0x0D, 0x09, 0x0E]]
        #Copy
        for i in range(4):
            tmpInter=[]
            for j in range(4):
                tmpInter.append(a[i][j])
            tmp.append(tmpInter)
        #混淆
        for i in range(4):
            for j in range(4):
                a[i][j] = GMul(M[i][0], tmp[0][j]) ^ GMul(M[i][1], tmp[1][j])^ GMul(M[i][2], tmp[2][j]) ^ GMul(M[i][3], tmp[3][j])
 
##################################################################
##测试题目的密钥
print("#"*60+'\n'+"#"*60+'\n'+"测试解密")
##按字节顺序存储的。
##假定MD5的key已经获取到。可从内存中直接获取,也可以按附件md5-2.py来获取
key = '2F65B1FF31ED86D09A285C0F4048059D'
 
bKey = bytes.fromhex(key)
intKey = int.from_bytes(bKey, byteorder='big')
print("intKey:\t\t{:0X}".format(intKey))
testAES = AES(intKey)
 
SN = "flag{db5c6a8dfec4d0ec5569899640}"
#SN = "flag{db5c6a8dfec"
#SN = "4d0ec5569899640}"
bSN = bytes(SN,"utf-8")
 
#####修改传入的参数是 字节bytes类型
encrypted = testAES.encrypt(bSN)
print("encrypted:\t{}\n".format(encrypted))
 
print("#"*60+'\n'+"#"*60+"测试解密")
#解密方法还需要修正。
ciphertext = encrypted
ciphertextByte = bytes.fromhex(ciphertext)
print("ciphertextByte:\t{}\n".format(ciphertextByte))
#####修改传入的参数是 字节bytes类型
decrypted  = testAES.decrypt(ciphertextByte)
print("decrypted:{}".format(decrypted))
strBytes = bytes.fromhex(decrypted)
 
#以字符串形式输出
strCipher = strBytes.decode("utf-8")
print("strCipher:{}".format(strCipher))

二、代码结构分析

2.0、观察程序运行

双击运行程序,出现一个cmd窗口,随便输入信息,程序自动关闭
打开cmd,将程序拖入进去,回车,随便输入信息,显示 NO

2.1、找主程序

用IDA打开,分析之后,找到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
39
40
41
42
43
44
45
46
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  void *v3; // esp
  char v7; // [esp-10h] [ebp-1048h]
  int v8[1032]; // [esp+0h] [ebp-1038h] BYREF
  CPPEH_RECORD ms_exc; // [esp+1020h] [ebp-18h]
 
  v3 = alloca(0x1020);
  memset(v8, 0xCCu, sizeof(v8));
  ms_exc.registration.ScopeTable = (PSCOPETABLE_ENTRY)((int)ms_exc.registration.ScopeTable ^ __security_cookie);
  memset(&v8[0x1E], 0, 0xFA0u);
  memset(&v8[0x12], 0, 0x28);
  gets_s((char *)&v8[30], 0xFA0u);              // 输入序列号
  if ( strlen((const char *)&v8[30]) == 0x20 // 限定长度:0x20
  {
    for ( v8[0x10] = 0; v8[0x10] < 0x200; ++v8[0x10] )
    {
      v8[15] = 0;
      MEMORY[0] = v8[0x10];                     // 反调试:程序触发异常
      ms_exc.registration.TryLevel = 0xFFFFFFFE;
    }
    strcpy((char *)&v8[9], "Enj0y_1t_4_fuuuN");
    _DX = 0x5F00;
    *(_WORD *)((char *)&v8[13] + 1) = 0;
    HIBYTE(v8[0xD]) = 0;
    memset(&v8[2], 0, 0x14);
    for ( v8[0] = 0; v8[0] < 0x200; ++v8[0] )
    {
      __asm { insb; Input Byte(s) from Port to String }// 反调试:程序触发异常
      ms_exc.registration.TryLevel = -2;
      _DX = LOWORD(v8[0]) + 1;
    }
    sub_61109(&v8[9], 0x10u, (int)&v8[2]);      // MD5算法(已改)
    sub_61104(&v8[2], 0x10u, &v8[30], (int)&v8[18], 0x20u);// AES算法(已改)
    if ( !memcmp(&v8[0x12], Buf2, 0x20u) )      // Buf2:全局变量,但有一处对其进行了修改
      sub_61037("OK\n", v7);
    else
      sub_61037("NO\n", v7);
    return 0;
  }
  else
  {
    sub_61037("NO\n", v7);
    return 0;
  }
}

主程序架构不算复杂,主要结构如下:

2.1.1、获取用户输入,长度限定0x20字节

1
2
gets_s((char *)&v8[30], 0xFA0u);              // 输入序列号
if ( strlen((const char *)&v8[30]) == 0x20 // 限定长度:0x20

2.1.2、触发反调试:程序主动抛出异常

1
2
3
4
5
6
for ( v8[0x10] = 0; v8[0x10] < 0x200; ++v8[0x10] )
    {
      v8[15] = 0;
      MEMORY[0] = v8[0x10];                     // 反调试:程序触发异常
      ms_exc.registration.TryLevel = 0xFFFFFFFE;
    }

此时,要观察汇编代码,才能看出程序修改了哪些内容。
我们可以找到上述代码位置,异常处理有三块:
.text:000628D3 try
.text:00062901 filter【如果打开graphic,它在右上角】
.text:000628FB except

代码的编排顺序是:try->except->filter
代码的执行顺序却是: try->filter->except
可以进行动态调试跟踪确认,另外在IDA动态调试过程中需要注意:
弹出:EB28F0:The instruction at 0xEB28F0 referenced memory at 0x0. The memory could not be written -> 00000000(exc.code c0000005,tid 11380)
这是触发了内存不可访问异常
应对:去 IDA Debugger ---> Debugger Options ,打开 Debugger Setup 窗口
点击左下角 Edit exceptions
找到 第一项 code C0000005,去掉 Suspend program ,选中 Pass to applicaiton;勾选 log 就行(在控制台里可以看到信息)
也就是说,不让IDA接管这个异常,让应用程序自身的机制去处理。
后续出现的反调试,都可以按照这个来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.text:000628D3     ;   _ _try { // __except at loc_62901
.text:000628D3 104C mov     [ebp+ms_exc.registration.TryLevel], 0
.text:000628DA 104C mov     [ebp+var_FFC], 0
.text:000628E4 104C mov     edx, [ebp+var_FFC]
.text:000628EA 104C mov     al, byte ptr [ebp+var_FF8]
.text:000628F0 104C mov     [edx], al      ; 抛出异常
.text:000628F0     ;   } // starts at 628D3
.text:000628F2 104C mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:000628F9 104C jmp     short loc_6290B
.text:000628F9
.text:00062901     ; ---------------------------------------------------------------------------
.text:00062901
.text:00062901     loc_62901:
.text:00062901     ;   _ _except(loc_628FB) // owned by 628D3
.text:00062901 104C mov     esp, [ebp+ms_exc.old_esp]
.text:00062904 104C mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:00062904
.text:000628FB     ; ---------------------------------------------------------------------------
.text:000628FB
.text:000628FB     loc_628FB:
.text:000628FB     ;   _ _except filter // owned by 628D3
.text:000628FB 104C mov     eax, 1
.text:00062900 104C retn
.text:00062900

2.1.3、内置字符串A赋值给某个堆的位置

strcpy((char *)&v8[9], "Enj0y_1t_4_fuuuN");

2.1.4、初始化某个变量(其实是MD5(A))

1
memset(&v8[2], 0, 0x14);

2.1.5、触发反调试:程序主动抛出异常:同上面的处理

1
2
3
4
5
6
for ( v8[0] = 0; v8[0] < 0x200; ++v8[0] )
{
  __asm { insb; Input Byte(s) from Port to String }// 反调试:程序触发异常
  ms_exc.registration.TryLevel = -2;
  _DX = LOWORD(v8[0]) + 1;
}

2.1.6、将内置字符生成一个0x10长度的字节序列,其实是伪MD5(作者修改了其内容)

1
sub_61109(&v8[9], 0x10u, (int)&v8[2]);      // MD5算法(已改)

2.1.7、使用刚才的0x10字节的序列以及用户序列号,生成一个0x20字节的序列,实际是伪AES算法(已改)

1
sub_61104(&v8[2], 0x10u, &v8[30], (int)&v8[18], 0x20u);// AES算法(已改)

2.1.8、其结果与内置的Buf2进行对比,相等则成功

1
if ( !memcmp(&v8[0x12], Buf2, 0x20u) )      // Buf2:全局变量,但有一处对其进行了修改

自此,主程序代码分析完成,基本框架清楚,主要集中在两个函数sub_61109和sub_61104

2.2、分析sub_61109

主程序代码如下

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
void *__cdecl sub_62DB0(void *Src, size_t Size, _DWORD *a3)
{
  void *result; // eax
  int v4; // [esp+14h] [ebp-9Ch]
  unsigned int v5; // [esp+18h] [ebp-98h]
  int v6; // [esp+1Ch] [ebp-94h]
  unsigned int k; // [esp+20h] [ebp-90h]
  unsigned int m; // [esp+20h] [ebp-90h]
  int v9; // [esp+24h] [ebp-8Ch]
  int v10; // [esp+28h] [ebp-88h]
  int v11; // [esp+2Ch] [ebp-84h]
  int v12; // [esp+30h] [ebp-80h]
  int v13[17]; // [esp+38h] [ebp-78h]
  unsigned int j; // [esp+7Ch] [ebp-34h]
  unsigned int i; // [esp+80h] [ebp-30h]
  void *Block; // [esp+84h] [ebp-2Ch]
  int v17; // [esp+88h] [ebp-28h]
  int v18; // [esp+8Ch] [ebp-24h]
  int v19; // [esp+90h] [ebp-20h]
  int v20; // [esp+94h] [ebp-1Ch]
  CPPEH_RECORD ms_exc; // [esp+98h] [ebp-18h]
 
  Block = 0;
  MEMORY[0] = 1;                                // 有反调试:出发主动异常
  ms_exc.registration.TryLevel = -2;
  v20 = 0x67452301;
  v19 = 0xEFCDAB89;
  v18 = 0x98BADCFE;
  v17 = 0x10325476;
  for ( i = Size + 1; i % 0x40 != 0x38; ++i )
    ;
  result = malloc(__CFADD__(i, 8) ? -1 : i + 8);// 分配0x40空间
  Block = result;
  if ( result )
  {
    memcpy(Block, Src, Size);
    *((_BYTE *)Block + Size) = 0x80;            // MD5算法的前期处理:补充位数至448位。
                                                // 1)填充:首先将输入信息的长度(bit)进行填充,使得对512求余的结果等于448。填充的方法是填充一个1和n个0
    for ( j = Size + 1; j < i; ++j )
      *((_BYTE *)Block + j) = 0;
    sub_610AF(8 * Size, (char *)Block + i);     //  2)记录信息长度:用64位来存储填充前信息长度。这64位加在第一步结果的后面,这样信息长度就变为N*512+448+64=(N+1)*512位。
                                                //  存储信息长度:按bit计算
    sub_610AF(Size >> 0x1D, (char *)Block + i + 4);// 如果Size长度过大,将高字节部分存入。最后的8字节就是明文信息长度
    for ( j = 0; j < i; j += 0x40 )
    {
      for ( k = 0; k < 0x10; ++k )
        v13[k] = sub_611CC((unsigned __int16 *)((char *)Block + 4 * k + j));// 存在反调试:主动触发异常,并修改了MD5内的一个DWORD
      v12 = v20;//A
      v11 = v19;//B
      v10 = v18;//C
      v9 = v17;//D
      for ( m = 0; m < 0x40; ++m )
      {
        if ( m >= 0x10 )
        {
          if ( m >= 0x20 )
          {
            if ( m >= 0x30 )
            {
              v6 = v10 ^ (v11 | ~v9);
              v5 = 7 * m % 0x10;
            }
            else
            {
              v6 = v9 ^ v10 ^ v11;
              v5 = (3 * m + 5) % 0x10;
            }
          }
          else
          {
            v6 = v10 & ~v9 | v11 & v9;
            v5 = (5 * m + 1) % 0x10;
          }
        }
        else
        {
          v6 = v9 & ~v11 | v10 & v11;
          v5 = m;
        }
        v4 = v9;//D
        v9 = v10;//D=C
        v10 = v11;//C=B
        v11 += __ROL4__(v13[v5] + MD5_K_order[m] + v6 + v12, dword_68B88[m]);//B=temp
        v12 = v4;//A=D
      }
      v20 += v12;//更新 A、B、C、D的值,作为下一个512位数据处理的初始值。
      v19 += v11;
      v18 += v10;
      v17 += v9;
    }
    free(Block);
    sub_610AF(v20, a3);
    sub_610AF(v19, a3 + 1);
    sub_610AF(v18, a3 + 2);
    return (void *)sub_610AF(v17, a3 + 3);
  }
  return result;
}

2.2.1、关键位置

1
v13[k] = sub_611CC((unsigned __int16 *)((char *)Block + 4 * k + j));// 存在反调试:主动触发异常,并修改了MD5内的一个DWORD

在这里有一处主动触发异常,修改MD5算法,直接查看伪代码是看不来来的

1
2
3
4
5
6
7
8
9
// attributes: thunk
int __cdecl sub_611CC(unsigned __int16 *a1)
{
  return sub_62CE0(a1);
}
int __cdecl sub_62CE0(unsigned __int16 *a1)
{
  return (*((unsigned __int8 *)a1 + 3) << 24) | (*((unsigned __int8 *)a1 + 2) << 16) | *a1;// 存在反调试:主动异常,同时修改MD5
}

而查看 sub_62CE0 的汇编代码 graphic结构,会发现右侧有一个异常filter代码

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
.text:00062D13     ;   __try { // __except at loc_62D34
.text:00062D13 02C mov     [ebp+ms_exc.registration.TryLevel], 0
.text:00062D1A 02C xor     ebx, ebx
.text:00062D1C 02C div     ebx             ; 反调试
.text:00062D1C     ;   } // starts at 62D13
.text:00062D1E 02C mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:00062D25 02C jmp     short loc_62D3E
.text:00062D25
.text:00062D34     ; ---------------------------------------------------------------------------
.text:00062D34
.text:00062D34     loc_62D34:
.text:00062D34     ;   __except(loc_62D27) // owned by 62D13
.text:00062D34 02C mov     esp, [ebp+ms_exc.old_esp]
.text:00062D37 02C mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:00062D37
.text:00062D27     ; ---------------------------------------------------------------------------
.text:00062D27
.text:00062D27     loc_62D27:
.text:00062D27     ;   __except filter // owned by 62D13
.text:00062D27 02C mov     eax, [ebp+ms_exc.exc_ptr]
.text:00062D2A 02C push    eax
.text:00062D2B 030 call    sub_610FA
.text:00062D2B
.text:00062D30 030 add     esp, 4
.text:00062D33 02C retn
.text:00062D33

在 __except filter // owned by 62D13 内有一个方法call sub_610FA,他会对 对 MD5_K_order[ecx], 0D4AF3085h 进行修改

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
.text:000610FA
.text:000610FA
.text:000610FA     ; Attributes: thunk
.text:000610FA
.text:000610FA     sub_610FA proc near
.text:000610FA 000 jmp     sub_62C60
.text:000610FA
.text:000610FA     sub_610FA endp
.text:000610FA
.text:00062C60
.text:00062C60
.text:00062C60     ; Attributes: bp-based frame
.text:00062C60
.text:00062C60     ; int sub_62C60()
.text:00062C60     sub_62C60 proc near
.text:00062C60 000 push    ebp
.text:00062C61 004 mov     ebp, esp
.text:00062C63 004 mov     eax, 4
.text:00062C68 004 imul    ecx, eax, 2Ah ; '*'
.text:00062C6B 004 mov     MD5_K_order[ecx], 0D4AF3085h
.text:00062C75 004 mov     eax, 1
.text:00062C7A 004 pop     ebp
.text:00062C7B 000 retn
.text:00062C7B
.text:00062C7B     sub_62C60 endp
.text:00062C7B
.text:00062C7B     ; ------

这里面出现了 MD5_K_order ,建议加载 FindCrypt3 对代码进行算法分析
[FindCrypt3] - 0xEB2E35: found sparse constants MD5_initState for MD4/MD5/RMD128
[FindCrypt3] - 0xEB8B30: found const array AES_Rijndael_rcon (used in AES/Rijndael), size = 10, elsize = 4
[FindCrypt3] - 0xEBB000: found const array Rijndael_sbox (used in Rijndael), size = 256, elsize = 1
[FindCrypt3] - 0xEBB100: found const array Rijndael_inv_sbox (used in Rijndael), size = 256, elsize = 1
[FindCrypt3] - 0xEBB298: found const array MD5_K_order (used in MD5), size = 64, elsize = 4
[FindCrypt3] - 0xEBB298: found sparse constants MD5_Transform for MD5
[FindCrypt3] - Found 6 known constant arrays in total.
解决办法就是:找一段MD5的算法代码,在对应位置进行修改,以获取正确的值。
也可以直接在IDA调试的时候,拷贝出来
2F65B1FF31ED86D09A285C0F4048059D

2.3、分析sub_61104

2.3.1、主程序代码

1
2
3
4
5
// attributes: thunk
int __cdecl sub_61104(void *interTansInfo, size_t interLen, _BYTE *inputSN, int outputBuffer, unsigned int outputLen)
{
  return sub_62070(interTansInfo, interLen, inputSN, outputBuffer, outputLen);
}
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
int __cdecl sub_62070(
        void *interMD5_TansInfo,
        size_t interLen,
        _BYTE *inputSN,
        int outputBuffer,
        unsigned int outputLen)
{
  int j; // [esp+4h] [ebp-1C8h]
  unsigned int i; // [esp+8h] [ebp-1C4h]
  int v8[6]; // [esp+10h] [ebp-1BCh] BYREF
  int v9[11]; // [esp+28h] [ebp-1A4h] BYREF
  char *v10; // [esp+54h] [ebp-178h]
  int outputBuffer2; // [esp+58h] [ebp-174h]
  char v12[360]; // [esp+60h] [ebp-16Ch] BYREF
 
  outputBuffer2 = outputBuffer;
  v10 = v12;
  memset(&v9[6], 0, 0x10);
  memset(v9, 0, 0x10);
  memset(v8, 0, 0x10);
  if ( !interMD5_TansInfo || !inputSN || !outputBuffer )// 都不能为空
    return -1;
  if ( interLen > 0x10 )                        // 必须小于等于0x10,这是内置的,不太可能不成功
    return -1;
  if ( outputLen % 0x10 )                       // 如果输出的长度不满足要求,则退出,不过这里不可能,传入的就是0x20
    return -1;
  memcpy(v9, interMD5_TansInfo, interLen);
  sub_610BE((int)v9, 0x10, (int)v12);           // 对内部信息进行转换,初始化AES盒子,但是内部存在一个主动触发异常,修改了盒子内容
  for ( i = 0; i < outputLen; i += 0x10 )       // 一次处理0x10,处理2
  {
    j_snTo2Darray((int)v8, inputSN);
    sub_61186((int)v8, (int)v10);
    for ( j = 1; j < 0xA; ++j )
    {
      v10 += 0x10;
      sub_61172((int)v8);                       // 字节代替
      sub_6122B((int)v8);                       // 行位移
      sub_6100F((int)v8);                       // 列混淆
      sub_61186((int)v8, (int)v10);             // 轮密钥相加
    }
    sub_61172((int)v8);                         // 字节代替
    sub_6122B((int)v8);                         // 行位移
    sub_61186((int)v8, (int)(v10 + 0x10));      // 轮密钥相加
    sub_6125D((int)v8, outputBuffer2);          // 将分组密文保存到输出缓冲区
    outputBuffer2 += 0x10;
    inputSN += 0x10;
    v10 = v12;
  }
  return 0;
}

2.3.2、分析初始化AES盒子代码 sub_610BE

在IDA里,采用graphics模式,我们观察到 右侧有异常filter代码,证明里面存在try代码,猜测可能存在主动出发异常,在阅读代码的时候,发现有一段代码不连续

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
.text:0006167A     ;   __try { // __except at loc_61698
.text:0006167A 044                 mov     [ebp+ms_exc.registration.TryLevel], 0
.text:00061681 044                 inc     edi
.text:00061683 044                 inc     ebp
.text:00061684 044                 cld
.text:00061684     ;   } // starts at 6167A
.text:00061684     ; } // starts at 615E0
.text:00061684     sub_615E0       endp
.text:00061684
.text:00061684     ; ---------------------------------------------------------------------------
.text:00061685                     dd 0FFFFFFFEh
.text:00061689     ; ---------------------------------------------------------------------------
.text:00061689                     jmp     short loc_616A2
.text:0006168B     ; ---------------------------------------------------------------------------
.text:0006168B     ; START OF FUNCTION CHUNK FOR sub_615E0
.text:0006168B
.text:0006168B     loc_6168B:                              ; DATA XREF: .rdata:stru_69FF0↓o
.text:0006168B     ; __unwind { // __except_handler4
.text:0006168B     ;   __except filter // owned by 6167A
.text:0006168B 000                 mov     eax, [ebp+ms_exc.exc_ptr]
.text:0006168E 000                 push    eax
.text:0006168F 004                 call    sub_6108C
.text:00061694 004                 add     esp, 4
.text:00061697 000                 retn
.text:00061698     ; ---------------------------------------------------------------------------

.text:00061685 dd 0FFFFFFFEh
触发了 An attempt was made to execute an illegal instruction (exc.code c000001d,tid 11028)
进而会将代码跳转到 __except filter // owned by 6167A

1
2
3
4
5
6
7
8
9
10
11
12
13
.text:0006168B                             ; ---------------------------------------------------------------------------
.text:0006168B                             ; START OF FUNCTION CHUNK FOR sub_615E0
.text:0006168B
.text:0006168B                             loc_6168B:
.text:0006168B                             ; __unwind { // __except_handler4
.text:0006168B                             ;   __except filter // owned by 6167A
.text:0006168B 000 8B 45 EC                mov     eax, [ebp+ms_exc.exc_ptr]
.text:0006168E 000 50                      push    eax
.text:0006168F 004 E8 F8 F9 FF FF          call    sub_6108C
.text:0006168F
.text:00061694 004 83 C4 04                add     esp, 4
.text:00061697 000 C3                      retn
.text:00061697

我们发现右侧还有两个,证明还存在2处触发异常的代码

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
.text:0006174D                             ;   __try { // __except at loc_6176B
.text:0006174D 044 C7 45 FC 01 00 00 00    mov     [ebp+ms_exc.registration.TryLevel], 1
.text:00061754 044 EF                      out     dx, eax
.text:00061754                             ;   } // starts at 6174D
.text:00061755 044 C7 45 FC FE FF FF FF    mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:0006175C 044 EB 17                   jmp     short loc_61775
.text:0006175C
.text:0006176B                             ; ---------------------------------------------------------------------------
.text:0006176B
.text:0006176B                             ; int __usercall loc_6176B@<eax>(int@<ebp>)
.text:0006176B                             loc_6176B:
.text:0006176B                             ;   __except(loc_6175E) // owned by 6174D
.text:0006176B 044 8B 65 E8                mov     esp, [ebp+ms_exc.old_esp]
.text:0006176E 044 C7 45 FC FE FF FF FF    mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:0006176E
.text:0006175E                             ; ---------------------------------------------------------------------------
.text:0006175E
.text:0006175E                             ; int loc_6175E()
.text:0006175E                             loc_6175E:
.text:0006175E                             ;   __except filter // owned by 6174D
.text:0006175E 000 8B 4D EC                mov     ecx, [ebp+ms_exc.exc_ptr]
.text:00061761 000 51                      push    ecx
.text:00061762 004 E8 25 F9 FF FF          call    sub_6108C
.text:00061762
.text:00061767 004 83 C4 04                add     esp, 4
.text:0006176A 000 C3                      retn
.text:0006176A

这是最后一个

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
.text:000618E8                             ;   __try { // __except at loc_61906
.text:000618E8 044 C7 45 FC 02 00 00 00    mov     [ebp+ms_exc.registration.TryLevel], 2
.text:000618EF 044 6C                      insb
.text:000618EF                             ;   } // starts at 618E8
.text:000618F0 044 C7 45 FC FE FF FF FF    mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:000618F7 044 EB 17                   jmp     short loc_61910
.text:000618F7
.text:00061906                             ; ---------------------------------------------------------------------------
.text:00061906
.text:00061906                             ; void __usercall loc_61906(int@<ebp>)
.text:00061906                             loc_61906:
.text:00061906                             ;   __except(loc_618F9) // owned by 618E8
.text:00061906 044 8B 65 E8                mov     esp, [ebp+ms_exc.old_esp]
.text:00061909 044 C7 45 FC FE FF FF FF    mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:00061909
.text:000618F9                             ; ---------------------------------------------------------------------------
.text:000618F9
.text:000618F9                             ; int loc_618F9()
.text:000618F9                             loc_618F9:
.text:000618F9                             ;   __except filter // owned by 618E8
.text:000618F9 000 8B 55 EC                mov     edx, [ebp+ms_exc.exc_ptr]
.text:000618FC 000 52                      push    edx
.text:000618FD 004 E8 8A F7 FF FF          call    sub_6108C
.text:000618FD
.text:00061902 004 83 C4 04                add     esp, 4
.text:00061905 000 C3                      retn
.text:00061905

以上三处触发异常的filter,都是执行了一个方法 call sub_6108C
这个方法具体做了啥?对AES合资进行了交换,71 与 A3 进行了互换
我们查AES原始的盒子【71】=A3;【A3】=0A
那么我们构造逆盒子的时候,就应该将 0A 和 A3 进行替换

1
2
3
4
5
6
7
8
9
10
11
12
// attributes: thunk
int sub_6108C()
{
  return sub_62330();
}
int sub_62330()
{
  Rijndael_sbox[0x71] ^= Rijndael_sbox[0xA3];
  Rijndael_sbox[0xA3] ^= Rijndael_sbox[0x71];
  Rijndael_sbox[0x71] ^= Rijndael_sbox[0xA3];
  return 1;
}

2.3.3、分析行位移 sub_6122B

1
2
3
4
5
// attributes: thunk
int __cdecl sub_6122B(int a1)
{
  return sub_61AE0(a1);
}

sub_61AE0 无法F5,直接在graphics里观看,发现一处异常处理代码

1
2
3
4
5
6
7
.text:00061C26                             ; ---------------------------------------------------------------------------
.text:00061C26
.text:00061C26                             loc_61C26:
.text:00061C26                             ;   __except filter // owned by 61BE5
.text:00061C26 000 B8 01 00 00 00          mov     eax, 1
.text:00061C2B 000 C3                      retn
.text:00061C2B

其关联如下:这是 try 代码,触发异常之后,这段代码会回滚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.text:00061BE5                             ;   __try { // __except at loc_61C2C
.text:00061BE5 04C C7 45 FC 00 00 00 00    mov     [ebp+ms_exc.registration.TryLevel], 0
.text:00061BEC 04C CD 8B                   int     8Bh             ; used by BASIC while in interpreter
.text:00061BEC
.text:00061BEE 04C 4D                      dec     ebp
.text:00061BEF 04C C8 C1 E1 03             enter   0FFFFE1C1h, 3
.text:00061BF3 -1DE4 8B 55 C8              mov     edx, [ebp+var_38]
.text:00061BF6 -1DE4 8B 44 95 D0           mov     eax, [ebp+edx*4+var_30]
.text:00061BFA -1DE4 D3 E0                 shl     eax, cl
.text:00061BFC -1DE4 8B 4D C8              mov     ecx, [ebp+var_38]
.text:00061BFF -1DE4 C1 E1 03              shl     ecx, 3
.text:00061C02 -1DE4 BA 20 00 00 00        mov     edx, 20h ; ' '
.text:00061C07 -1DE4 2B D1                 sub     edx, ecx
.text:00061C09 -1DE4 8B 4D C8              mov     ecx, [ebp+var_38]
.text:00061C0C -1DE4 8B 74 8D D0           mov     esi, [ebp+ecx*4+var_30]
.text:00061C10 -1DE4 8B CA                 mov     ecx, edx
.text:00061C12 -1DE4 D3 EE                 shr     esi, cl
.text:00061C14 -1DE4 0B C6                 or      eax, esi
.text:00061C16 -1DE4 8B 55 C8              mov     edx, [ebp+var_38]
.text:00061C19 -1DE4 89 44 95 D0           mov     [ebp+edx*4+var_30], eax
.text:00061C19                             ;   } // starts at 61BE5
.text:00061C1D -1DE4 C7 45 FC FE FF FF FF  mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:00061C24 -1DE4 EB 3E                 jmp     short loc_61C64
.text:00061C24

异常filter执行之后的代码exception

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.text:00061C2C                             ; ---------------------------------------------------------------------------
.text:00061C2C
.text:00061C2C                             loc_61C2C:
.text:00061C2C                             ;   __except(loc_61C26) // owned by 61BE5
.text:00061C2C -1DE4 8B 65 E8              mov     esp, [ebp+ms_exc.old_esp]
.text:00061C2F 004 8B 4D C8                mov     ecx, [ebp+var_38]
.text:00061C32 004 C1 E1 03                shl     ecx, 3
.text:00061C35 004 8B 45 C8                mov     eax, [ebp+var_38]
.text:00061C38 004 8B 54 85 D0             mov     edx, [ebp+eax*4+var_30]
.text:00061C3C 004 D3 EA                   shr     edx, cl
.text:00061C3E 004 8B 45 C8                mov     eax, [ebp+var_38]
.text:00061C41 004 C1 E0 03                shl     eax, 3
.text:00061C44 004 B9 20 00 00 00          mov     ecx, 20h ; ' '
.text:00061C49 004 2B C8                   sub     ecx, eax
.text:00061C4B 004 8B 45 C8                mov     eax, [ebp+var_38]
.text:00061C4E 004 8B 44 85 D0             mov     eax, [ebp+eax*4+var_30]
.text:00061C52 004 D3 E0                   shl     eax, cl
.text:00061C54 004 0B D0                   or      edx, eax
.text:00061C56 004 8B 4D C8                mov     ecx, [ebp+var_38]
.text:00061C59 004 89 54 8D D0             mov     [ebp+ecx*4+var_30], edx
.text:00061C5D 004 C7 45 FC FE FF FF FF    mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:00061C5D

我们对比分析只有,发现两端代码有很多相似之处,但是有一个循环左移与循环右移的区别
那就是说,这段代码对 行位移 的关键操作进行了变换,循环左移 改为了循环右移

2.4.4、列混淆 sub_6100F

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
// attributes: thunk
int __cdecl sub_6100F(int a1)
{
  return sub_61E50(a1);
}
int __cdecl sub_61E50(int a1)
{
  char v1; // bl
  char v2; // bl
  char v3; // bl
  int m; // [esp+Ch] [ebp-40h]
  int k; // [esp+10h] [ebp-3Ch]
  int j; // [esp+14h] [ebp-38h]
  int i; // [esp+18h] [ebp-34h]
  char v9[44]; // [esp+20h] [ebp-2Ch] BYREF
 
  v9[0] = 2;
  v9[1] = 3;
  v9[2] = 1;
  v9[3] = 1;
  v9[4] = 1;
  v9[5] = 2;
  v9[6] = 3;
  v9[7] = 1;
  v9[8] = 1;
  v9[9] = 1;
  v9[10] = 2;
  v9[11] = 3;
  v9[12] = 3;
  v9[13] = 1;
  v9[14] = 1;
  v9[15] = 2;
  for ( i = 0; i < 4; ++i )
  {
    for ( j = 0; j < 4; ++j )
      v9[4 * i + 24 + j] = *(_BYTE *)(a1 + 4 * i + j);
  }
  for ( k = 0; k < 4; ++k )
  {
    for ( m = 0; m < 4; ++m )
    {
      v1 = sub_61118(v9[4 * k], v9[m + 0x18]);
      v2 = sub_61118(v9[4 * k + 1], v9[m + 0x1C]) ^ v1;
      v3 = sub_61118(v9[4 * k + 2], v9[m + 0x20]) ^ v2;
      *(_BYTE *)(a1 + 4 * k + m) = sub_61118(v9[4 * k + 3], v9[m + 0x24]) ^ v3;
    }
  }
  return 0;
}

里面有四个 sub_61118 的操作,继续跟进下去,发现这个函数做了一些改变,对Buf2进行了修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// attributes: thunk
char __cdecl sub_61118(unsigned __int8 a1, char a2)
{
  return sub_61D50(a1, a2);
}
char __cdecl sub_EB1D50(unsigned __int8 a1, char a2)
{
  int v3; // [esp+10h] [ebp-24h]
  int i; // [esp+14h] [ebp-20h]
  char v5; // [esp+1Bh] [ebp-19h]
 
  v5 = 0;
  for ( i = 0; i < 8; ++i )
  {
    if ( (a1 & 1) != 0 )
      v5 ^= a2;
    v3 = a2 & 0x80;
    a2 *= 2;
    if ( v3 )
      a2 ^= 0x1Bu;
    a1 >>= 1;
  }
  return v5;
}

sub_EB1D50看伪代码,的确不能发现问题,需要回到 Graphics里看,有一段异常代码处理

1
2
3
4
5
6
7
8
9
10
11
.text:00061DAC                             ; ---------------------------------------------------------------------------
.text:00061DAC
.text:00061DAC                             loc_61DAC:
.text:00061DAC                             ;   __except filter // owned by 61D98
.text:00061DAC 038 8B 45 EC                mov     eax, [ebp+ms_exc.exc_ptr]
.text:00061DAF 038 50                      push    eax
.text:00061DB0 03C E8 94 F4 FF FF          call    sub_61249
.text:00061DB0
.text:00061DB5 03C 83 C4 04                add     esp, 4
.text:00061DB8 038 C3                      retn
.text:00061DB8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.text:00061D98                             ;   __try { // __except at loc_61DB9
.text:00061D98 038 C7 45 FC 00 00 00 00    mov     [ebp+ms_exc.registration.TryLevel], 0
.text:00061D9F 038 33 DB                   xor     ebx, ebx
.text:00061DA1 038 F7 F3                   div     ebx
.text:00061DA1                             ;   } // starts at 61D98
.text:00061DA3 038 C7 45 FC FE FF FF FF    mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:00061DAA 038 EB 17                   jmp     short loc_61DC3
.text:00061DAA
.text:00061DB9                             ; ---------------------------------------------------------------------------
.text:00061DB9
.text:00061DB9                             loc_61DB9:
.text:00061DB9                             ;   __except(loc_61DAC) // owned by 61D98
.text:00061DB9 038 8B 65 E8                mov     esp, [ebp+ms_exc.old_esp]
.text:00061DBC 038 C7 45 FC FE FF FF FF    mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:00061DBC

这个异常代码做了什么?

1
2
3
4
5
6
7
8
9
10
11
// attributes: thunk
int sub_61249()
{
  return sub_623D0();
}
int sub_623D0()
{
  Buf2[0x10] = 0xF4;
  Buf2[0x11] = 0xF2;
  return 1;
}

Buf2就是用户SN经过AES加密之后的密文:
577CF56D56967745B0BDA1C789A5ABDCF4F24BFEBEF5F55C4D30420F2B3BE6CB
这个方法是将两个字节进行了替换,上面是经过替换之后的内容,而最初的内容是
577CF56D56967745B0BDA1C789A5ABDCF2F44BFEBEF5F55C4D30420F2B3BE6CB
以上代码全部分析完成

三、回顾整个代码框架

3.1、获取用户输入SN

3.2、计算内置字符串的MD5

key = gMD5("Enj0y_1t_4_fuuuN")

3.3、计算AES密文

encryptContent = gAES(key,SN)

3.4、比对密文与内置的内容是否一致

encryptContent == 577CF56D56967745B0BDA1C789A5ABDCF2F44BFEBEF5F55C4D30420F2B3BE6CB

四、解密代码编写

待补充


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2022-5-23 13:05 被htg编辑 ,原因: 完善算法内容
上传的附件:
收藏
点赞4
打赏
分享
最新回复 (3)
雪    币: 5568
活跃值: (2976)
能力值: ( LV12,RANK:394 )
在线值:
发帖
回帖
粉丝
htg 4 2022-5-25 21:48
2
0
雪    币: 29414
活跃值: (18615)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
kanxue 8 2022-5-25 22:08
3
0
感谢视频分享!
雪    币: 3864
活跃值: (5488)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
badboyl 2 2022-6-3 14:16
4
0
htg https://www.bilibili.com/video/BV1Vv4y1P7St?spm_id_from=333.999.0.0
感谢分享,大佬讲解得很棒。
游客
登录 | 注册 方可回帖
返回