LilacCTF2026 逆向 Wp + 复现

ezPython

解包

先用 pyinstxtractor 解包,解包后的文件中找到 main.pyc, 用 pylingual 在线反编译

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
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: 'main.py'
# Bytecode version: 3.9.0beta5 (3425)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

import struct
from crypto import *
from sys import *
import base64
import myalgo
welcome_msg = 'V2VsYzBtMyBUbyBUaGUgV29ybGQgb2YgTDFsYWMgPDM='
input_msg = ':i(G#8T&KiF<F_)F`JToCggs;'
right_msg = 'UmlnaHQsIGNvbmdyYXR1bGF0aW9ucyE='
wrong_msg = 'V3JvbmcgRmxhZyE='
print(b64decode(welcome_msg).decode())
flag = input(a85decode(input_msg).decode())
if not (flag.startswith('LilacCTF{') and flag.endswith('}') and (len(flag) == 26)):
print(b64decode(wrong_msg).decode())
else:
flag = flag[9:25]
res = [761104570, 1033127419, 3729026053, 795718415]
key = struct.unpack('<IIII', b'1111222233334444')
input = list(struct.unpack('<IIII', flag.encode()))
myalgo.btea(input, 4, key)
if input[0] == res[0] and input[1] == res[1] and (input[2] == res[2]) and (input[3] == res[3]):
print(b64decode(right_msg).decode())
else:
print(b64decode(wrong_msg).decode())

反编译出来的逻辑还是比较清晰的,注意到它还 import 了一个 myalgo 模块,所以再反编译一下 myalgo.py 的内容:

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
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: 'myalgo.py'
# Bytecode version: 3.9.0beta5 (3425)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

import dis
import struct
def MX(y, z, sum, k, p, e):
return (z >> 5 ^ y >> 2) + (y << 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z)
def btea(v, n, k):
u32 = lambda x: x & 4294967295
y = v[0]
sum = 0
DELTA = 1163219540
if n > 1:
z = v[n - 1]
q = 6 + 52 // n
while q > 0:
q -= 1
sum = u32(sum + DELTA)
e = u32(sum >> 2) & 3
p = 0
while p < n - 1:
y = v[p + 1]
z = v[p] = u32(v[p] + MX(y, z, sum, k, p, e))
p += 1
y = v[0]
z = v[n - 1] = u32(v[n - 1] + MX(y, z, sum, k, p, e))
return True
else:
return False
if __name__ == '__main__':
print('WOW')

魔改

最开始的时候根据上面的信息解 flag,但是无论怎么解都解不出来,卡了很久,后面才注意到 cypto 库是魔改的

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
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: 'crypto.py'
# Bytecode version: 3.9.0beta5 (3425)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

from types import CodeType
import dis
import sys
from myalgo import *
import re
import struct
import binascii
class RC4:
def __init__(self, key: bytes):
"""\n 初始化 RC4 类\n :param key: 密钥,字节类型\n """
self.key = key
self.s = list(range(256))
self._ksa()
def _ksa(self):
"""\n 密钥调度算法 (Key Scheduling Algorithm, KSA)\n """
j = 0
key_length = len(self.key)
for i in range(256):
j = (j + self.s[i] + self.key[i % key_length]) % 256
self.s[i], self.s[j] = (self.s[j], self.s[i])
def _prga(self):
"""\n 伪随机数生成算法 (Pseudo-Random Generation Algorithm, PRGA)\n :yield: 生成的伪随机字节\n """
i = j = 0
while True:
i = (i + 1) % 256
j = (j + self.s[i]) % 256
self.s[i], self.s[j] = (self.s[j], self.s[i])
yield self.s[(self.s[i] + self.s[j]) % 256]
def encrypt(self, plaintext: bytes) -> bytes:
"""\n 加密明文\n :param plaintext: 明文,字节类型\n :return: 密文,字节类型\n """
keystream = self._prga()
return bytes([p ^ next(keystream) for p in plaintext])
def decrypt(self, ciphertext: bytes) -> bytes:
"""\n 解密密文\n :param ciphertext: 密文,字节类型\n :return: 明文,字节类型\n """
return self.encrypt(ciphertext)
class ArrangeSimpleDES:
def __init__(self):
self.ip = [58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7]
self.ip1 = [40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25]
self.E = [32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1]
self.P = [16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25]
self.K = '0111010001101000011010010111001101101001011100110110100101110110'
self.k1 = [57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4]
self.k2 = [14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32]
self.k0 = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
self.S = [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 4, 1, 14, 8, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 14, 12, 5, 0, 14, 10, 6, 6, 4, 2, 1, 10, 6, 3, 11, 5, 1, 10, 14, 5, 6, 4, 2, 1, 13, 1, 10, 6, 3, 11, 6, 4, 2, 1, 13, 3, 8, 10, 6, 1, 11,
def __substitution(self, table: str, self_table: list) -> str:
"""\n :param table: 需要进行置换的列表,是一个01字符串\n :param self_table: 置换表,在__init__中初始化了\n :return: 返回置换后的01字符串\n """
sub_result = ''
for i in self_table:
sub_result += table[i - 1]
return sub_result
def str2bin(self, string: str) -> str:
"""\n 将明文转为二进制字符串:\n :param string: 任意字符串\n :return:二进制字符串\n """
plaintext_list = list(bytes(string, 'utf8'))
result = []
for num in plaintext_list:
result.append(bin(num)[2:].zfill(8))
return ''.join(result)
def bin2str(self, binary: str) -> str:
"""\n 二进制字符串转成字符串\n :param binary:\n :return:\n """
list_bin = [binary[i:i + 8] for i in range(0, len(binary), 8)]
list_int = []
for b in list_bin:
list_int.append(int(b, 2))
result = bytes(list_int).decode()
return result
def __bin2int(self, binary: str) -> list:
"""\n 由于加密之后的二进制无法直接转成字符,有不可见字符在,utf8可能无法解码,所以需要将二进制字符串每8位转成int型号列表,用于转成bytes再转hex\n :param binary: 二进制字符串\n :return: int型列表\n """
list_bin = [binary[i:i + 8] for i in range(0, len(binary), 8)]
list_int = []
for b in list_bin:
list_int.append(int(b, 2))
return list_int
def __int2bin(self, list_int: list) -> str:
result = []
for num in list_int:
result.append(bin(num)[2:].zfill(8))
return ''.join(result)
def __get_block_list(self, binary: str) -> list:
"""\n 对明文二进制串进行切分,每64位为一块,DES加密以64位为一组进行加密的\n :type binary: 二进制串\n """
len_binary = len(binary)
if len_binary % 64!= 0:
binary_block = binary + '0' * (64 - len_binary % 64)
return [binary_block[i:i + 64] for i in range(0, len(binary_block), 64)]
else:
return [binary[j:j + 64] for j in range(0, len(binary), 64)]
def modify_secretkey(self):
"""\n 修改默认密钥函数\n :return: None\n """
print('当前二进制形式密钥为:{}'.format(self.K))
print('当前字符串形式密钥为:{}'.format(self.bin2str(self.K)))
newkey = input('输入新的密钥(长度为8):')
if len(newkey)!= 8:
print('密钥长度不符合,请重新输入:')
self.modify_secretkey()
else:
bin_key = self.str2bin(newkey)
self.K = bin_key
print('当前二进制形式密钥为:{}'.format(self.K))
def __f_funtion(self, right: str, key: str):
"""\n :param right: 明文二进制的字符串加密过程的右半段\n :param key: 当前轮数的密钥\n :return: 进行E扩展,与key异或操作,S盒操作后返回32位01字符串\n """
e_result = self.__substitution(right, self.E)
xor_result = self.__xor_function(e_result, key)
s_result = self.__s_box(xor_result)
p_result = self.__substitution(s_result, self.P)
return p_result
def __get_key_list(self):
"""\n :return: 返回加密过程中16轮的子密钥\n """
key = self.__substitution(self.K, self.k1)
left_key = key[0:28]
right_key = key[28:56]
keys = []
for i in range(1, 17):
move = self.k0[i - 1]
move_left = left_key[move:28] + left_key[0:move]
move_right = right_key[move:28] + right_key[0:move]
left_key = move_left
right_key = move_right
move_key = left_key + right_key
ki = self.__substitution(move_key, self.k2)
keys.append(ki)
return keys
def __xor_function(self, xor1: str, xor2: str):
"""\n :param xor1: 01字符串\n :param xor2: 01字符串\n :return: 异或操作返回的结果\n """
size = len(xor1)
result = ''
for i in range(0, size):
result += '0' if xor1[i] == xor2[i] else '1'
return result
def __s_box(self, xor_result: str):
"""\n :param xor_result: 48位01字符串\n :return: 返回32位01字符串\n """
result = ''
for i in range(0, 8):
block = xor_result[i * 6:(i + 1) * 6]
line = int(block[0] + block[5], 2)
colmn = int(block[1:5], 2)
res = bin(self.S[i][line * 16 + colmn])[2:]
if len(res) < 4:
res = '0' * (4 - len(res)) + res
result += res
return result
def __iteration(self, bin_plaintext: str, key_list: list):
"""\n :param bin_plaintext: 01字符串,64位\n :param key_list: 密钥列表,共16个\n :return: 进行F函数以及和left异或操作之后的字符串\n """
left = bin_plaintext[0:32]
right = bin_plaintext[32:64]
for i in range(0, 16):
next_lift = right
f_result = self.__f_funtion(right, key_list[i])
next_right = self.__xor_function(left, f_result)
left = next_lift
right = next_right
bin_plaintext_result = left + right
return bin_plaintext_result[32:] + bin_plaintext_result[:32]
def encode(self, plaintext):
"""\n :param plaintext: 明文字符串\n :return: 密文字符串\n """
bin_plaintext = self.str2bin(plaintext)
bin_plaintext_block = self.__get_block_list(bin_plaintext)
ciphertext_bin_list = []
key_list = self.__get_key_list()
for block in bin_plaintext_block:
sub_ip = self.__substitution(block, self.ip)
ite_result = self.__iteration(sub_ip, key_list)
sub_ip1 = self.__substitution(ite_result, self.ip1)
ciphertext_bin_list.append(sub_ip1)
ciphertext_bin = ''.join(ciphertext_bin_list)
result = self.__bin2int(ciphertext_bin)
return bytes(result).hex().upper()
def decode(self, ciphertext):
"""\n :param ciphertext: 密文字符串\n :return: 明文字符串\n """
b_ciphertext = binascii.a2b_hex(ciphertext)
bin_ciphertext = self.__int2bin(list(b_ciphertext))
bin_plaintext_list = []
key_list = self.__get_key_list()
key_list = key_list[::(-1)]
bin_ciphertext_block = [bin_ciphertext[i:i + 64] for i in range(0, len(bin_ciphertext), 64)]
for block in bin_ciphertext_block:
sub_ip = self.__substitution(block, self.ip)
ite = self.__iteration(sub_ip, key_list)
sub_ip1 = self.__substitution(ite, self.ip1)
bin_plaintext_list.append(sub_ip1)
bin_plaintext = ''.join(bin_plaintext_list).replace('00000000', '')
return self.bin2str(bin_plaintext)
_a85chars = None
_a85chars2 = None
_A85START = b'<~'
_A85END = b'~>'
bytes_types = (bytes, bytearray)
def _bytes_from_decode_data(s):
if isinstance(s, str):
try:
return s.encode('ascii')
except UnicodeEncodeError:
raise ValueError('string argument should contain only ASCII characters')
else:
if isinstance(s, bytes_types):
return s
else:
try:
return memoryview(s).tobytes()
except TypeError:
raise TypeError('argument should be a bytes-like object or ASCII string, not %r' % s.__class__.__name__) from None
def b64decode(s, altchars=None, validate=False):
"""Decode the Base64 encoded bytes-like object or ASCII string s.\n\n Optional altchars must be a bytes-like object or ASCII string of length 2\n which specifies the alternative alphabet used instead of the \'+\' and \'/\'\n characters.\n\n The result is returned as a bytes object. A binascii.Error is raised if\n s is incorrectly padded.\n\n If validate is False (the default), characters that are neither in the\n normal base-64 alphabet nor the alternative alphabet are discarded prior\n to the padding check. If validate is True, these non-alphabet characters\n in the input result in a binascii.Error.\n """
s = _bytes_from_decode_data(s)
if altchars is not None:
altchars = _bytes_from_decode_data(altchars)
assert len(altchars) == 2, repr(altchars)
s = s.translate(bytes.maketrans(altchars, b'+/'))
if validate and (not re.fullmatch(b'[A-Za-z0-9+/]*={0,2}', s)):
raise binascii.Error('Non-base64 digit found')
else:
return binascii.a2b_base64(s)
def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\x0b'):
# irreducible cflow, using cdg fallback
"""\n """
b = _bytes_from_decode_data(b)
if adobe:
if not b.endswith(_A85END):
raise ValueError('Ascii85 encoded byte sequences must end with {!r}'.format(_A85END))
else:
if b.startswith(_A85START):
b = b[2:(-2)]
else:
b = b[:(-2)]
packI = struct.Struct('!I').pack
decoded = []
decoded_append = decoded.append
curr = []
curr_append = curr.append
curr_clear = curr.clear
for x in b + b'uuuu':
if 33 <= x <= 117:
curr_append(x)
if len(curr) == 5:
acc = 0
for x in curr:
acc = 85 * acc + (x - 33)
try:
decoded_append(packI(acc))
except struct.error:
raise ValueError('Ascii85 overflow') from None
curr_clear()
if x == 122:
if curr:
raise ValueError('z inside Ascii85 5-tuple')
else:
decoded_append(b'\x00\x00\x00\x00')
else:
if foldspaces and x == 121:
if curr:
raise ValueError('y inside Ascii85 5-tuple')
else:
decoded_append(b' ')
else:
if x in ignorechars:
continue
else:
raise ValueError('Non-Ascii85 digit found: %c' % x)
payload = MX.__code__.co_code
magic_code1 = b'?'
magic_code2 = b'>'
payload = payload[:4] + magic_code2 + payload[5:10] + magic_code1 + payload[11:18] + magic_code2 + payload[19:24] + magic_code1 + payload[25:]
payload = payload[:3] + b'\x03' + payload[4:9] + b'\x01' + payload[10:17] + b'\x04' + payload[18:23] + b'\x02' + payload[24:]
fn_code = MX.__code__
MX.__code__ = CodeType(int(fn_code.co_argcount), int(fn_code.co_posonlyargcount), int(fn_code.co_kwonlyargcount), int(fn_code.co_nlocals), int(fn_code.co_stacksize), payload, fn_code.co_consts, fn_code.co_names, fn_code.co_varnames, fn_code.co_filename, fn_code.co_name, int(fn_code.co_firstlineno), fn_code.co_lnotab, fn_code.co_freevars, fn_code.co_cellvars)
result = b''.join(decoded)
padding = 4 - len(curr)
if padding:
result = result[:-padding]
return result
def chacha20_decrypt(key, counter, nonce, ciphertext):
return chacha20_encrypt(key, counter, nonce, ciphertext)
def chacha20_encrypt(key, counter, nonce, plaintext):
byte_length = len(plaintext)
full_blocks = byte_length // 64
remainder_bytes = byte_length % 64
encrypted_message = b''
for i in range(full_blocks):
key_stream = serialize(chacha20_block(key, counter + i, nonce))
plaintext_block = plaintext[i * 64:i * 64 + 64]
encrypted_block = [plaintext_block[j] ^ key_stream[j] for j in range(64)]
encrypted_message += bytes(encrypted_block)
if remainder_bytes!= 0:
key_stream = serialize(chacha20_block(key, counter + full_blocks, nonce))
plaintext_block = plaintext[full_blocks * 64:byte_length]
encrypted_block = [plaintext_block[j] ^ key_stream[j] for j in range(remainder_bytes)]
encrypted_message += bytes(encrypted_block)
return encrypted_message
def chacha20_block(key, counter, nonce):
BLOCK_CONSTANTS = [1634760805, 857760878, 2036477234, 1797285236]
init_state = BLOCK_CONSTANTS + key + [counter] + nonce
current_state = init_state[:]
for i in range(10):
inner_block(current_state)
for i in range(16):
current_state[i] = add_32(current_state[i], init_state[i])
return current_state
def inner_block(state):
quarterround(state, 0, 4, 8, 12)
quarterround(state, 1, 5, 9, 13)
quarterround(state, 2, 6, 10, 14)
quarterround(state, 3, 7, 11, 15)
quarterround(state, 0, 5, 10, 15)
quarterround(state, 1, 6, 11, 12)
quarterround(state, 2, 7, 8, 13)
quarterround(state, 3, 4, 9, 14)
def xor_32(x, y):
return (x ^ y) & 4294967295
def add_32(x, y):
return x + y & 4294967295
def rot_l32(x, n):
return (x << n | x >> 32 - n) & 4294967295
def quarterround(state, i1, i2, i3, i4):
a = state[i1]
b = state[i2]
c = state[i3]
d = state[i4]
a = add_32(a, b)
d = xor_32(d, a)
d = rot_l32(d, 16)
c = add_32(c, d)
b = xor_32(b, c)
b = rot_l32(b, 12)
a = add_32(a, b)
d = xor_32(d, a)
d = rot_l32(d, 8)
c = add_32(c, d)
b = xor_32(b, c)
b = rot_l32(b, 7)
state[i1] = a
state[i2] = b
state[i3] = c
state[i4] = d
def serialize(block):
return b''.join([word.to_bytes(4, 'little') for word in block])
def encrypt(v, k):
v0 = v[0]
v1 = v[1]
key0, key1, key2, key3 = (k[0], k[1], k[2], k[3])
sum = 0
delta = 2654435769
for _ in range(32):
sum = sum + delta & 4294967295
v0 = v0 + ((v1 << 3) + key0 ^ v1 + sum ^ (v1 >> 4) + key1 ^ 596) & 4294967295
v1 = v1 + ((v0 << 3) + key2 ^ v0 + sum ^ (v0 >> 4) + key3 ^ 2310) & 4294967295
return (v0, v1)
def decrypt(v, k):
v0 = v[0]
v1 = v[1]
key0, key1, key2, key3 = (k[0], k[1], k[2], k[3])
sum = 3337565984
delta = 2654435769
for _ in range(32):
v1 = v1 - ((v0 << 3) + key2 ^ v0 + sum ^ (v0 >> 4) + key3 ^ 2310) & 4294967295
v0 = v0 - ((v1 << 3) + key0 ^ v1 + sum ^ (v1 >> 4) + key1 ^ 596) & 4294967295
sum = sum - delta & 4294967295
return (v0, v1)
def encrypt_all(v, k):
encrypted = []
for i in range(0, len(v), 2):
encrypted.extend(encrypt(v[i:i + 2], k))
return encrypted
def decrypt_all(v, k):
decrypted = []
for i in range(0, len(v), 2):
decrypted.extend(decrypt(v[i:i + 2], k))
return decrypted

真实的逻辑是,在 main.py 调用 a85decode 时,该函数内部通过 python 反射修改了 myalgo.MX 函数的字节码 原本的 z >> 5 变为 z << 3 原本的 y >> 2 变为 y >> 5 原本的 y << 3 变为 y << 4 原本的 z << 4 变为 z >> 2 所以篡改后的 MX_new = ((z << 3 ^ y >> 5) + (y << 4 ^ z >> 2)) ^ ((sum ^ y) + (k[(p & 3) ^ e] ^ z))

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import struct

def MX(y, z, sum_val, k, p, e):
part1 = ((z << 3) ^ (y >> 5)) & 0xFFFFFFFF
part2 = ((y << 4) ^ (z >> 2)) & 0xFFFFFFFF
part3 = (sum_val ^ y) & 0xFFFFFFFF
part4 = (k[(p & 3) ^ e] ^ z) & 0xFFFFFFFF
return ((part1 + part2) & 0xFFFFFFFF) ^ ((part3 + part4) & 0xFFFFFFFF)

def decrypt():
v = [761104570, 1033127419, 3729026053, 795718415]
key = struct.unpack('<IIII', b'1111222233334444')
DELTA = 1163219540
n = 4
q = 6 + 52 // n
sum_val = (DELTA * q) & 0xFFFFFFFF

for _ in range(q):
e = (sum_val >> 2) & 3
v[3] = (v[3] - MX(v[0], v[2], sum_val, key, 3, e)) & 0xFFFFFFFF
v[2] = (v[2] - MX(v[3], v[1], sum_val, key, 2, e)) & 0xFFFFFFFF
v[1] = (v[1] - MX(v[2], v[0], sum_val, key, 1, e)) & 0xFFFFFFFF
v[0] = (v[0] - MX(v[1], v[3], sum_val, key, 0, e)) & 0xFFFFFFFF
sum_val = (sum_val - DELTA) & 0xFFFFFFFF

return struct.pack('<IIII', *v).decode()

inner_flag = decrypt()
print(f"LilacCTF{{{inner_flag}}}")

得到 flag 为 LilacCTF{e@sy_Pyth0n_SMC!}

JustROM

反编译

题目附件只给了个 rom.bin,开始啥没头绪,后面拷打 ai 确定架构,push ai 试了好久好久…最后得知是 SPARC,扔进 Ghidra 反编译 FUN_00004238

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
undefined8 FUN_00004238(int param_1,int param_2)

{
uint uVar1;
uint uVar2;
uint local_44 [4];
uint local_34;
uint local_30;
uint local_2c;
uint local_28;
uint local_24;
uint local_20;
uint local_1c;
uint local_18;
uint local_14;
uint local_10;
uint local_c;
uint local_8;
int local_4;

for (local_4 = 0; local_4 < 0x10; local_4 = local_4 + 1) {
local_44[local_4] = *(uint *)(param_2 + local_4 * 4);
}
for (local_4 = 0; local_4 < 8; local_4 = local_4 + 2) {
local_44[0] = local_44[0] + local_34;
uVar1 = local_14 ^ local_44[0];
local_14 = uVar1;
FUN_00004060(uVar1,0x10);
local_24 = local_24 + uVar1;
uVar2 = local_34 ^ local_24;
local_34 = uVar2;
local_14 = uVar1;
FUN_00004060(uVar2,0xc);
local_44[0] = local_44[0] + uVar2;
uVar1 = local_14 ^ local_44[0];
local_34 = uVar2;
local_14 = uVar1;
FUN_00004060(uVar1,8);
local_24 = local_24 + uVar1;
uVar2 = local_34 ^ local_24;
local_34 = uVar2;
local_14 = uVar1;
FUN_00004060(uVar2,7);
local_44[1] = local_44[1] + local_30;
uVar1 = local_10 ^ local_44[1];
local_34 = uVar2;
local_10 = uVar1;
FUN_00004060(uVar1,0x10);
local_20 = local_20 + uVar1;
uVar2 = local_30 ^ local_20;
local_30 = uVar2;
local_10 = uVar1;
FUN_00004060(uVar2,0xc);
local_44[1] = local_44[1] + uVar2;
uVar1 = local_10 ^ local_44[1];
local_30 = uVar2;
local_10 = uVar1;
FUN_00004060(uVar1,8);
local_20 = local_20 + uVar1;
uVar2 = local_30 ^ local_20;
local_30 = uVar2;
local_10 = uVar1;
FUN_00004060(uVar2,7);
local_44[2] = local_44[2] + local_2c;
uVar1 = local_c ^ local_44[2];
local_30 = uVar2;
local_c = uVar1;
FUN_00004060(uVar1,0x10);
local_1c = local_1c + uVar1;
uVar2 = local_2c ^ local_1c;
local_2c = uVar2;
local_c = uVar1;
FUN_00004060(uVar2,0xc);
local_44[2] = local_44[2] + uVar2;
uVar1 = local_c ^ local_44[2];
local_2c = uVar2;
local_c = uVar1;
FUN_00004060(uVar1,8);
local_1c = local_1c + uVar1;
uVar2 = local_2c ^ local_1c;
local_2c = uVar2;
local_c = uVar1;
FUN_00004060(uVar2,7);
local_44[3] = local_44[3] + local_28;
uVar1 = local_8 ^ local_44[3];
local_2c = uVar2;
local_8 = uVar1;
FUN_00004060(uVar1,0x10);
local_18 = local_18 + uVar1;
uVar2 = local_28 ^ local_18;
local_28 = uVar2;
local_8 = uVar1;
FUN_00004060(uVar2,0xc);
local_44[3] = local_44[3] + uVar2;
uVar1 = local_8 ^ local_44[3];
local_28 = uVar2;
local_8 = uVar1;
FUN_00004060(uVar1,8);
local_18 = local_18 + uVar1;
uVar2 = local_28 ^ local_18;
local_28 = uVar2;
local_8 = uVar1;
FUN_00004060(uVar2,7);
local_44[0] = local_44[0] + local_30;
uVar1 = local_8 ^ local_44[0];
local_28 = uVar2;
local_8 = uVar1;
FUN_00004060(uVar1,0x10);
local_1c = local_1c + uVar1;
uVar2 = local_30 ^ local_1c;
local_30 = uVar2;
local_8 = uVar1;
FUN_00004060(uVar2,0xc);
local_44[0] = local_44[0] + uVar2;
uVar1 = local_8 ^ local_44[0];
local_30 = uVar2;
local_8 = uVar1;
FUN_00004060(uVar1,8);
local_1c = local_1c + uVar1;
uVar2 = local_30 ^ local_1c;
local_30 = uVar2;
local_8 = uVar1;
FUN_00004060(uVar2,7);
local_44[1] = local_44[1] + local_2c;
uVar1 = local_14 ^ local_44[1];
local_30 = uVar2;
local_14 = uVar1;
FUN_00004060(uVar1,0x10);
local_18 = local_18 + uVar1;
uVar2 = local_2c ^ local_18;
local_2c = uVar2;
local_14 = uVar1;
FUN_00004060(uVar2,0xc);
local_44[1] = local_44[1] + uVar2;
uVar1 = local_14 ^ local_44[1];
local_2c = uVar2;
local_14 = uVar1;
FUN_00004060(uVar1,8);
local_18 = local_18 + uVar1;
uVar2 = local_2c ^ local_18;
local_2c = uVar2;
local_14 = uVar1;
FUN_00004060(uVar2,7);
local_44[2] = local_44[2] + local_28;
uVar1 = local_10 ^ local_44[2];
local_2c = uVar2;
local_10 = uVar1;
FUN_00004060(uVar1,0x10);
local_24 = local_24 + uVar1;
uVar2 = local_28 ^ local_24;
local_28 = uVar2;
local_10 = uVar1;
FUN_00004060(uVar2,0xc);
local_44[2] = local_44[2] + uVar2;
uVar1 = local_10 ^ local_44[2];
local_28 = uVar2;
local_10 = uVar1;
FUN_00004060(uVar1,8);
local_24 = local_24 + uVar1;
uVar2 = local_28 ^ local_24;
local_28 = uVar2;
local_10 = uVar1;
FUN_00004060(uVar2,7);
local_44[3] = local_44[3] + local_34;
uVar1 = local_c ^ local_44[3];
local_28 = uVar2;
local_c = uVar1;
FUN_00004060(uVar1,0x10);
local_20 = local_20 + uVar1;
uVar2 = local_34 ^ local_20;
local_34 = uVar2;
local_c = uVar1;
FUN_00004060(uVar2,0xc);
local_44[3] = local_44[3] + uVar2;
uVar1 = local_c ^ local_44[3];
local_34 = uVar2;
local_c = uVar1;
FUN_00004060(uVar1,8);
local_20 = local_20 + uVar1;
uVar2 = local_34 ^ local_20;
local_34 = uVar2;
local_c = uVar1;
FUN_00004060(uVar2,7);
local_34 = uVar2;
}
for (local_4 = 0; local_4 < 0x10; local_4 = local_4 + 1) {
*(uint *)(param_1 + local_4 * 4) = local_44[local_4] + *(int *)(param_2 + local_4 * 4);
}
return CONCAT44(param_2,param_1);
}

FUN_00004060

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
undefined8 FUN_00004060(uint param_1,undefined4 param_2)

{
return CONCAT44(param_2,param_1 >> (-(byte)param_2 & 0x1f) | param_1 << ((byte)param_2 & 0x1f)) ;
}

FUN_0000409c
undefined8 FUN_0000409c(undefined4 *param_1,undefined4 param_2)

{
undefined4 uVar1;

uVar1 = 0x40008018;
FUN_00004028();
*param_1 = uVar1;
uVar1 = 0x4000801c;
FUN_00004028();
param_1[1] = uVar1;
uVar1 = 0x40008020;
FUN_00004028();
param_1[2] = uVar1;
uVar1 = 0x40008024;
FUN_00004028();
param_1[3] = uVar1;
param_1[4] = _DAT_40010000;
param_1[5] = _DAT_40010004;
param_1[6] = _DAT_40010008;
param_1[7] = _DAT_4001000c;
param_1[8] = _DAT_40010010;
param_1[9] = _DAT_40010014;
param_1[10] = _DAT_40010018;
param_1[0xb] = _DAT_4001001c;
param_1[0xc] = 1;
param_1[0xd] = 0x41414141;
param_1[0xe] = 0x42424242;
param_1[0xf] = 0x43434343;
return CONCAT44(param_2,param_1);
}

FUN_00004060 是一个循环左移操作,FUN_00004238 实现了 ChaCha20 的轮函数,FUN_0000409c 初始化了 ChaCha 的初始状态 这里从 DAT_40010000 开始到 DAT_4001001c 有部分数据在 Ghidra 中没找到

在 010 中搜索内存找到了

1
2
3
11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF 00
DE AD BE EF CA FE BA BE 0B AD F0 0D 13 37 13 37
41 41 41 41 42 42 42 42 43 43 43 43 00 00 00 00

关键函数

FUN_00004ab0 是关键函数

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
undefined8 FUN_00004ab0(undefined4 param_1,undefined4 param_2)

{
byte local_c8 [128];
undefined auStack_48 [68];
int local_4;

local_c8[0x20] = 0x54;
local_c8[0x21] = 0x68;
local_c8[0x22] = 0x65;
local_c8[0x23] = 0x72;
local_c8[0x24] = 0x65;
local_c8[0x25] = 0x5f;
local_c8[0x26] = 0x69;
local_c8[0x27] = 0x73;
local_c8[0x28] = 0x5f;
local_c8[0x29] = 0x6e;
local_c8[0x2a] = 0x6f;
local_c8[0x2b] = 0x74;
local_c8[0x2c] = 0x68;
local_c8[0x2d] = 0x69;
local_c8[0x2e] = 0x6e;
local_c8[0x2f] = 0x67;
local_c8[0x30] = 0x5f;
local_c8[0x31] = 0x79;
local_c8[0x32] = 0x6f;
local_c8[0x33] = 0x75;
local_c8[0x34] = 0x5f;
local_c8[0x35] = 0x77;
local_c8[0x36] = 0x61;
local_c8[0x37] = 0x6e;
local_c8[0x38] = 0x6e;
local_c8[0x39] = 0x61;
local_c8[0x3a] = 0x5f;
local_c8[0x3b] = 0x67;
local_c8[0x3c] = 0x65;
local_c8[0x3d] = 0x74;
local_c8[0x3e] = 0x2e;
local_c8[0x3f] = 0x2e;
local_c8[0] = 0x37;
local_c8[1] = 0x32;
local_c8[2] = 0x9b;
local_c8[3] = 0xf6;
local_c8[4] = 0x36;
local_c8[5] = 0xa9;
local_c8[6] = 0x18;
local_c8[7] = 0xfc;
local_c8[8] = 0xf2;
local_c8[9] = 0xe7;
local_c8[10] = 0x49;
local_c8[0xb] = 0x73;
local_c8[0xc] = 0x61;
local_c8[0xd] = 0x49;
local_c8[0xe] = 0xf8;
local_c8[0xf] = 0xd4;
local_c8[0x10] = 0x4c;
local_c8[0x11] = 0xf2;
local_c8[0x12] = 0x6a;
local_c8[0x13] = 0xc9;
local_c8[0x14] = 0x3c;
local_c8[0x15] = 0x4c;
local_c8[0x16] = 0x62;
local_c8[0x17] = 0x83;
local_c8[0x18] = 0x78;
local_c8[0x19] = 0x12;
local_c8[0x1a] = 0x5c;
local_c8[0x1b] = 5;
local_c8[0x1c] = 0x5f;
local_c8[0x1d] = 0x30;
local_c8[0x1e] = 0x95;
local_c8[0x1f] = 0x9d;
FUN_0000409c(auStack_48);
for (local_4 = 0; local_4 < 0x20; local_4 = local_4 + 1) {
local_c8[local_4 + 0x20] = local_c8[local_4 + 0x20] ^ local_c8[local_4];
}
FUN_00004238(local_c8 + 0x40,auStack_48);
for (local_4 = 0; local_4 < 0x20; local_4 = local_4 + 1) {
*(byte *)(local_4 + 0x42000000) = *(byte *)(local_4 + 0x42000000) ^ local_c8[local_4 + 0x40];
}
for (local_4 = 0; local_4 < 0x20; local_4 = local_4 + 1) {
if ((int)*(char *)(local_4 + 0x42000000) != (uint)local_c8[local_4 + 0x20])
goto code_r0x00004d14;
}
DAT_42000100 = 0x57;
DAT_42000101 = 0x69;
DAT_42000102 = 0x6e;
for (local_4 = 0; local_4 < 0xffff; local_4 = local_4 + 1) {
}
code_r0x00004d14:
return CONCAT44(param_2,param_1);
}

S (0x20-0x3F): "There_is_nothing_you_wanna_get.." K (0x00-0x1F): 37 32 9b f6 36 a9 18 fc f2 e7 49 73 61 49 f8 d4 4c f2 6a c9 3c 4c 62 83 78 12 5c 05 5f 30 95 9dSK 进行按位异或:Target[i] = S[i] ^ K[i],调用 FUN_00004238 生成 ChaCha 密钥流,最终对比推导出 Input[i] = Target[i] ^ Stream[i]

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import struct

def rotate_left(x, n):
return ((x << n) & 0xFFFFFFFF) | (x >> (32 - n))

def chacha_qr(x, a, b, c, d):
x[a] = (x[a] + x[b]) & 0xFFFFFFFF; x[d] = rotate_left(x[d] ^ x[a], 16)
x[c] = (x[c] + x[d]) & 0xFFFFFFFF; x[b] = rotate_left(x[b] ^ x[c], 12)
x[a] = (x[a] + x[b]) & 0xFFFFFFFF; x[d] = rotate_left(x[d] ^ x[a], 8)
x[c] = (x[c] + x[d]) & 0xFFFFFFFF; x[b] = rotate_left(x[b] ^ x[c], 7)

def chacha8_block(state):
working_state = list(state)
for _ in range(4):
# 列混合
chacha_qr(working_state, 0, 4, 8, 12)
chacha_qr(working_state, 1, 5, 9, 13)
chacha_qr(working_state, 2, 6, 10, 14)
chacha_qr(working_state, 3, 7, 11, 15)
# 对角线混合
chacha_qr(working_state, 0, 5, 10, 15)
chacha_qr(working_state, 1, 6, 11, 12)
chacha_qr(working_state, 2, 7, 8, 13)
chacha_qr(working_state, 3, 4, 9, 14)
return [(working_state[i] + state[i]) & 0xFFFFFFFF for i in range(16)]

# 由 local_c8[0x20] ^ local_c8[0x00] 得到
k_bytes = [0x37, 0x32, 0x9b, 0xf6, 0x36, 0xa9, 0x18, 0xfc, 0xf2, 0xe7, 0x49, 0x73, 0x61, 0x49, 0xf8, 0xd4,
0x4c, 0xf2, 0x6a, 0xc9, 0x3c, 0x4c, 0x62, 0x83, 0x78, 0x12, 0x5c, 0x05, 0x5f, 0x30, 0x95, 0x9d]
s_string = b"There_is_nothing_you_wanna_get.."
target = bytes([k_bytes[i] ^ s_string[i] for i in range(32)])

# "expand 32-byte k"
constants = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]

key_hex = "112233445566778899AABBCCDD00EEFFDEADBEEFCAFEBABEB0ADF00D13371337" # 修正了你的部分输入

key_bytes = bytes.fromhex("11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF 00 DE AD BE EF CA FE BA BE 0B AD F0 0D 13 37 13 37")
key_words = list(struct.unpack(">8I", key_bytes))

counter = [1]
nonce = [0x41414141, 0x42424242, 0x43434343]

state = constants + key_words + counter + nonce

stream_words = chacha8_block(state)

stream = struct.pack("<16I", *stream_words)

flag = bytes([target[i] ^ stream[i] for i in range(32)])

print(f"Flag: {flag}")

Flag 为 LilacCTF{d0ntl@@kl1kechch4atall}

C++++

反编译

直接 ida 反编译,从 main 函数开始,动调找逻辑 sub_7FF72E7EFC30->sub_7FF72E6FC6C0 主要逻辑在 sub_7FF72E6FC6C0 #### sub_7FF72E6FC6C0

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
__int64 sub_7FF646B1C6C0()
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]

sub_7FF646BE4AB0(&off_7FF646D517A0);
sub_7FF646BE4FF0(10LL);
printf(off_7FF646D47600);
printf(off_7FF646D461E0);
printf(off_7FF646D47600);
printf_0(off_7FF646D46020);
v0 = (char **)sub_7FF646BE4AF0(); // input
if ( !v0 )
v0 = &off_7FF646D46008;
result = sub_7FF646B27650((__int64)v0, 3); // 格式化清理
v2 = result;
if ( result && *(_DWORD *)(result + 8) )
{
v3 = sub_7FF646AA3B40(qword_7FF646CE9950, 0x10uLL);// new
*(_OWORD *)(v3 + 16) = xmmword_7FF646C99460;
v4 = (unsigned __int16 **)sub_7FF646B1C850((unsigned __int16 **)v3, 0x41);// xor 0x41 -> "WONDERFUL&&PEACE"
v5 = sub_7FF646AA3B40(qword_7FF646CE9950, 0x10uLL);// memalloc
*(_OWORD *)(v5 + 16) = xmmword_7FF646C99450;
v6 = (unsigned __int16 **)sub_7FF646B1C850((unsigned __int16 **)v5, 0x31);// xor 0x31 -> "somedanctoforget"
v7 = sub_7FF646AA3A20(&byte_7FF646CA9038);
sub_7FF646B1C490(v7, v4, v6); // string_opt
v8 = (_QWORD **)sub_7FF646AA1A9E();
v9 = (*(__int64 (__fastcall **)(_QWORD *, __int64))(*v8[1] + 112LL))(v8[1], v2);
sub_7FF646B1C4E0(v7, v9);
v11 = sub_7FF646B2DD10(v10);
string_replace(v11, (__int64)&off_7FF646D46DE8, (__int64 **)&off_7FF646D46008);
if ( (unsigned int)sub_7FF646B240D0(v12, (__int64)&off_7FF646D47738) )
{
sub_7FF646BE4FF0(11LL);
printf(off_7FF646D460E0); // WELCOME
}
else
{
sub_7FF646BE4FF0(12LL);
printf(off_7FF646D46068); // DENIED
}
return sub_7FF646BE5060();
}
return result;
}

sub_7FF646B1C4E0

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
void __fastcall sub_7FF646B1C4E0(__int64 a1, char *a2)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]

v14[0] = 0LL;
v2 = 0xFFFFFFFFFFFFFF4LL;
do
{
v18[v2] = 0LL;
v18[v2 + 1] = 0LL;
v18[v2 + 2] = 0LL;
v2 += 3LL;
}
while ( v2 * 16 );
v5 = *((_DWORD *)a2 + 2);
v6 = 16 * ((v5 + 15) / 16);
v7 = sub_7FF646AA3B40(byte_7FF646CE9950, v6);
v8 = (unsigned int *)sub_7FF646AA3B40(byte_7FF646CE9950, v6);
sub_7FF646B203E0((unsigned int *)a2, 0, v8, 0, v5);
v17 = v8;
if ( v8[2] )
v9 = v17 + 4;
else
v9 = 0LL;
v16 = v7;
if ( *(_DWORD *)(v7 + 8) )
v10 = v16 + 16;
else
v10 = 0LL;
v11 = *(_QWORD *)(a1 + 8);
v15 = v11;
if ( v11 && *(_DWORD *)(v15 + 8) )
v12 = v15 + 16;
else
v12 = 0LL;
sub_7FF646B1C890(v14, v12, *(unsigned int *)(v11 + 8)); // 密钥扩展
for ( i = 0; i < v6; i += 16 )
sub_7FF646B1CA40(v14, &v9[i / 4u], v10 + i); // 分组加密
v17 = 0LL;
v16 = 0LL;
v15 = 0LL;
if ( *(_QWORD *)&v18[0] != _security_cookie )
cexit_0();
}

sub_7FF646B1CA40

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
void __fastcall sub_7FF646B1CA40(__int64 a1, __int64 a2, __int64 a3)
{
int i; // ecx
int j; // ebp
int v7; // r12d
int v8; // eax
int v9; // eax
int v10; // eax
int k; // eax
__int64 v12; // [rsp+28h] [rbp-50h]
__int64 v13; // [rsp+30h] [rbp-48h]
__int64 v14; // [rsp+38h] [rbp-40h]

v12 = 0LL;
v13 = 0LL;
for ( i = 0; i < 4; ++i )
*((_DWORD *)&v12 + i) = *(_DWORD *)(a1 + 4LL * i + 16) ^ *(_DWORD *)(a2 + 4LL * i);
for ( j = 0; j < 16; ++j )
{
v7 = sub_7FF646B1CC20(a1, (unsigned int)v12, 0LL); // sub_7FF646B1CC20 进行 S 盒变换
v8 = sub_7FF646B1CC20(a1, (unsigned int)__ROL4__(HIDWORD(v12), 8), 0LL);
HIDWORD(v13) = __ROL4__(HIDWORD(v13), 5);
LODWORD(v13) = (*(_DWORD *)(a1 + 4LL * (2 * j + 8) + 16) + v7 + v8) ^ v13;
HIDWORD(v13) ^= *(_DWORD *)(a1 + 4LL * (2 * j + 9) + 16) + v7 + 2 * v8;
LODWORD(v13) = __ROL4__(v13, 27);
if ( j < 15 )
{
v9 = v12;
LODWORD(v12) = v13;
LODWORD(v13) = v9;
v10 = HIDWORD(v12);
HIDWORD(v12) = HIDWORD(v13);
HIDWORD(v13) = v10;
}
}
for ( k = 0; k < 4; ++k )
*(_DWORD *)(a3 + 4LL * k) = *(_DWORD *)(a1 + 4LL * (k + 4) + 16) ^ *((_DWORD *)&v12 + k);
if ( v14 != _security_cookie )
cexit_0();
}

分析

1
2
for ( i = 0; i < 4; ++i )
*((_DWORD *)&v12 + i) = *(_DWORD *)(a1 + 4LL * i + 16) ^ *(_DWORD *)(a2 + 4LL * i);

在进入主循环前,输入的数据块与轮密钥的前 4 个 DWORD 进行异或

进入 16 轮主循环后: v7v8 是通过 S 盒变换((sub_7FF646B1CC20))得到的值,然后使用 __ROL4__循环左移,注意 HIDWORD(v13) 左移 5 位,最后整个 v13 左移 27 位,每一轮都会从 a1 中提取两个不同的轮密钥:*(_DWORD *)(a1 + 4LL * (2 * j + 8) + 16)*(_DWORD *)(a1 + 4LL * (2 * j + 9) + 16), 在每一轮结束时(除了最后一轮),v12v13 交换

1
2
for ( k = 0; k < 4; ++k )
*(_DWORD *)(a3 + 4LL * k) = *(_DWORD *)(a1 + 4LL * (k + 4) + 16) ^ *((_DWORD *)&v12 + k);

最后再次与一组轮密钥进行异或

sub_7FF646B1CC20

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
__int64 __fastcall sub_7FF646B1CC20(int *a1, int a2, __int64 a3)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]

v5 = (unsigned __int8)a2;
v6 = BYTE1(a2);
v7 = BYTE2(a2);
v8 = HIBYTE(a2);
if ( a3 )
{
v9 = *(_DWORD *)(a3 + 8);
if ( v9 >= 4 )
{
v10 = *(_DWORD **)(sub_7FF646AA1557() + 8);
v11 = v10[6];
v12 = 1 - v11;
v13 = v10[4];
if ( 1 - v11 >= v13 )
goto LABEL_71;
v14 = v10[5];
v15 = v10[7];
v16 = v5 - v15;
if ( v16 >= v14 )
goto LABEL_71;
v5 = *(unsigned __int8 *)(a3 + 28) ^ *((unsigned __int8 *)v10 + v16 + v14 * v12 + 32);
v17 = v6 - v15;
if ( v17 >= v14 )
goto LABEL_71;
v89 = *(_DWORD *)(a3 + 28);
v6 = (unsigned __int8)(BYTE1(v89) ^ *((_BYTE *)v10 + v17 + v14 * v12 + 32));
if ( -v11 >= v13 )
goto LABEL_71;
v18 = v7 - v15;
if ( v18 >= v14 )
goto LABEL_71;
v7 = (unsigned __int8)(BYTE2(v89) ^ *((_BYTE *)v10 + v18 + v14 * -v11 + 32));
v19 = v14 * v12;
v20 = v8 - v15;
if ( v20 >= v14 )
goto LABEL_71;
v8 = (unsigned __int8)(HIBYTE(v89) ^ *((_BYTE *)v10 + v20 + v19 + 32));
}
if ( v9 < 3 )
{
v95 = v8;
}
else
{
v21 = *(_DWORD **)(sub_7FF646AA1557() + 8);
v22 = v21[6];
v23 = 1 - v22;
v24 = v21[4];
if ( 1 - v22 >= v24 )
goto LABEL_71;
v25 = v21[5];
v26 = v21[7];
v27 = v5 - v26;
if ( v27 >= v25 )
goto LABEL_71;
v5 = *(unsigned __int8 *)(a3 + 24) ^ *((unsigned __int8 *)v21 + v27 + v25 * v23 + 32);
v28 = v6 - v26;
if ( v28 >= v25 )
goto LABEL_71;
v29 = *(_DWORD *)(a3 + 24);
v6 = (unsigned __int8)(BYTE1(v29) ^ *((_BYTE *)v21 + v28 + v25 * v23 + 32));
if ( -v22 >= v24 )
goto LABEL_71;
v30 = v7 - v26;
if ( v30 >= v25 )
goto LABEL_71;
v7 = (unsigned __int8)(BYTE2(v29) ^ *((_BYTE *)v21 + v30 + v25 * -v22 + 32));
v31 = v8 - v26;
if ( v31 >= v25 )
goto LABEL_71;
v95 = (unsigned __int8)(HIBYTE(v29) ^ *((_BYTE *)v21 + v31 + v25 * v23 + 32));
}
}
else
{
v95 = HIBYTE(a2);
}
v32 = *(_DWORD **)(sub_7FF646AA1577() + 8);
v33 = *(_DWORD **)(sub_7FF646AA1557() + 8);
v34 = v33[6];
v35 = v33[4];
if ( -v34 >= v35 )
goto LABEL_71;
v36 = -v34;
v37 = v33[5];
v38 = v33[7];
v39 = v5 - v38;
if ( v39 >= v37 )
goto LABEL_71;
v40 = *((_BYTE *)v33 + v39 + v37 * v36 + 32);
if ( a3 )
{
v41 = v32;
v42 = v33;
v94 = v40;
if ( *(_DWORD *)(a3 + 8) <= 1u )
goto LABEL_71;
v43 = *(_DWORD *)(a3 + 20);
}
else
{
v41 = v32;
v42 = v33;
v94 = v40;
v43 = a1[1];
}
v88 = v41;
v44 = 1 - v34;
if ( v44 >= v35 )
goto LABEL_71;
v45 = (unsigned __int8)(v94 ^ v43) - v38;
if ( v45 >= v37 )
goto LABEL_71;
v46 = *((_BYTE *)v42 + v45 + v37 * v44 + 32);
if ( a3 )
{
v47 = v88;
if ( !*(_DWORD *)(a3 + 8) )
goto LABEL_71;
v93 = *(_DWORD *)(a3 + 16);
}
else
{
v47 = v88;
v93 = *a1;
}
v48 = -v47[6];
if ( v48 >= v47[4] )
goto LABEL_71;
v49 = v47[5];
v50 = v49 * v48;
v51 = (unsigned __int8)(v93 ^ v46) - v47[7];
if ( v51 >= v49 )
goto LABEL_71;
v52 = v47[v51 + 8 + v50];
v53 = v6 - v38;
if ( v53 >= v37 )
goto LABEL_71;
v54 = *((_BYTE *)v33 + v53 + v37 * v44 + 32);
if ( a3 )
{
v55 = v32;
v56 = v33;
v92 = v54;
if ( *(_DWORD *)(a3 + 8) <= 1u )
goto LABEL_71;
v57 = *(_DWORD *)(a3 + 20) >> 8;
}
else
{
v55 = v32;
v56 = v33;
v92 = v54;
v57 = (unsigned int)a1[1] >> 8;
}
v91 = v52;
v58 = (unsigned __int8)(v57 ^ v92) - v38;
if ( v58 >= v37 )
goto LABEL_71;
v59 = *((_BYTE *)v56 + v58 + v37 * v44 + 32);
if ( a3 )
{
v90 = v91;
if ( !*(_DWORD *)(a3 + 8) )
goto LABEL_71;
v60 = *(_DWORD *)(a3 + 16) >> 8;
}
else
{
v60 = (unsigned int)*a1 >> 8;
v90 = v91;
}
v61 = 1 - v55[6];
if ( v61 >= v55[4] )
goto LABEL_71;
v62 = v49 * v61;
v63 = (unsigned __int8)(v60 ^ v59) - v55[7];
if ( v63 >= v49 )
goto LABEL_71;
v64 = v55[v63 + 8 + v62] ^ v90;
v65 = v7 - v38;
if ( v65 >= v37 )
goto LABEL_71;
v66 = *((_BYTE *)v33 + v65 + v37 * v36 + 32);
if ( a3 )
{
v67 = v33;
if ( *(_DWORD *)(a3 + 8) <= 1u )
goto LABEL_71;
v68 = HIWORD(*(_DWORD *)(a3 + 20));
}
else
{
v67 = v33;
v68 = HIWORD(a1[1]);
}
v69 = (unsigned __int8)(v68 ^ v66) - v38;
if ( v69 >= v37 )
goto LABEL_71;
v70 = *((_BYTE *)v67 + v69 + v37 * v36 + 32);
if ( a3 )
{
v71 = v32;
if ( !*(_DWORD *)(a3 + 8) )
goto LABEL_71;
v72 = HIWORD(*(_DWORD *)(a3 + 16));
}
else
{
v71 = v32;
v72 = HIWORD(*a1);
}
v73 = 2 - v71[6];
if ( v73 >= v71[4] )
goto LABEL_71;
v74 = v49 * v73;
v75 = (unsigned __int8)(v72 ^ v70) - v71[7];
if ( v75 >= v49 )
goto LABEL_71;
v76 = v71[v75 + 8 + v74] ^ v64;
v77 = v33;
v78 = v37 * v44;
if ( v95 - v38 >= v37 )
goto LABEL_71;
v79 = *((_BYTE *)v33 + (unsigned int)(v95 - v38 + v78) + 32);
if ( a3 )
{
if ( *(_DWORD *)(a3 + 8) <= 1u )
goto LABEL_71;
v80 = HIBYTE(*(_DWORD *)(a3 + 20));
}
else
{
v80 = HIBYTE(a1[1]);
}
v81 = v37 * v36;
v82 = (unsigned __int8)(v80 ^ v79) - v38;
if ( v82 >= v37 )
goto LABEL_71;
v83 = *((_BYTE *)v77 + v82 + v81 + 32);
if ( a3 )
{
if ( !*(_DWORD *)(a3 + 8) )
goto LABEL_71;
v84 = HIBYTE(*(_DWORD *)(a3 + 16));
}
else
{
v84 = HIBYTE(*a1);
}
v85 = 3 - v32[6];
if ( v85 >= v32[4] || (v86 = (unsigned __int8)(v84 ^ v83) - v32[7], v86 >= v49) )
LABEL_71:
sub_7FF646BAEC50();
return v32[v86 + 8 + v49 * v85] ^ (unsigned int)v76;
}

关键函数分析

结合 ai 分析出这是 Twofish 加密算法中核心函数 g 函数(或称 h 函数)的 T-Table 优化版实现,它负责处理 32 位数据,将其拆分为 4 个字节,通过 S 盒变换、密钥异或,最后通过 MDS 矩阵(这里被优化成了 4 个 T-Table 查询)

详细的逻辑分析: #### 输入参数

a1: 指向一个整数数组(可能是子密钥数组的一部分,用于非 a3 模式) a2: 输入的 32 位数据(即 X) a3: 指向加密上下文结构体(Context) - *(a3 + 8): 可能代表密钥长度或 S 盒的层数(Twofish 的 S 盒根据密钥长度有 2, 3, 4 层变换) - *(a3 + 16): 对应 k_sbox[0] - *(a3 + 20): 对应 k_sbox[1] - *(a3 + 24) / *(a3 + 28): 对应更高级别的 S 盒密钥(如果密钥长度更长)

字节预处理(S 盒前置变换)

代码开头将 a2 拆分为四个字节 v5, v6, v7, v8

1
2
3
4
v5 = (unsigned __int8)a2; // Byte 0
v6 = BYTE1(a2); // Byte 1
v7 = BYTE2(a2); // Byte 2
v8 = HIBYTE(a2); // Byte 3
随后根据 *(a3 + 8) 的值,对这些字节进行了多层变换,在 Twofish 中,如果密钥是 256 位,S 盒会有 4 层;192 位是 3 层;128 位是 2 层 如果 v9 >= 4,字节会先异或 *(a3 + 28) 然后查一次 S 盒 如果 v9 >= 3,字节会异或 *(a3 + 24) 再查一次 S 盒 这对应了 Twofish 规范中 h 函数根据密钥长度进行的迭代

核心 T-Table 查找

通过 sub_7FF646AA1557() 获取 S 盒表,通过 sub_7FF646AA1577() 获取 MDS 组合表(T-Table) 对于每一个字节,它执行了以下逻辑(以第一个字节 v5 为例): 第一次 S 盒变换: v40 = Sbox[v5]。 异或密钥: v40 ^ key1 (即 (a3 + 20) 的对应字节) 第二次 S 盒变换: v46 = Sbox[v40 ^ key1] 异或密钥: v46 ^ key0 (即 (a3 + 16) 的对应字节) T-Table 最终查询: v52 = T_Table_0[v46 ^ key0]

结果合并

代码最后将四个字节的 T-Table 查询结果进行异或合并:

1
2
// 伪代码逻辑
return T0[idx0] ^ T1[idx1] ^ T2[idx2] ^ T3[idx3];
这是 Twofish 的 MDS 矩阵相乘的 T-Table 表现形式

与 Twofish 算法的对应关系

反汇编代码位置 Twofish 算法组件 说明
a3 + 16 S-word 0 密钥生成的 S 盒相关密钥
a3 + 20 S-word 1 密钥生成的 S 盒相关密钥
v32 (from ...1577) T-Tables 预计算好的 MDS 矩阵与 S 盒组合表
v33 (from ...1557) S-Boxes 基础 S 盒(RS 矩阵生成的固定 S 盒)
sub_7FF646BAEC50() Panic/Error 数组越界或完整性检查失败时的报错函数

S 盒提取

sub_7FF646B1CC20 中进行 S 盒变换,动调找到两个 256 字节的 S 盒和逆 S 盒

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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
import struct
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
]
INV_SBOX = [
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
]

def rot_left32(x, n):
return ((x << n) & 0xFFFFFFFF) | (x >> (32 - n))

def rot_right32(x, n):
return (x >> n) | ((x << (32 - n)) & 0xFFFFFFFF)

def gf_derive_step(val, poly=0x11B):
val &= 0xFF
v_high = (poly >> 1) if (val & 2) else 0
v_mid = v_high ^ (val >> 2)
v_low = (poly >> 2) if (val & 1) else 0
return (v_low ^ v_mid) & 0xFF

def generate_transformation_tables():
tables = [[0] * 256 for _ in range(4)]
for i in range(256):
s_val = SBOX[i]
t_val = gf_derive_step(s_val, 0x11B)
mixed_a = s_val ^ t_val
poly_bit = 0x8D if (s_val & 1) else 0
mixed_b = (t_val ^ s_val ^ (s_val >> 1) ^ poly_bit) & 0xFF

entry = ((s_val << 24) | (mixed_b << 16) | mixed_a | (s_val << 8)) & 0xFFFFFFFF
tables[0][i] = entry
tables[1][i] = ((mixed_b << 24) | (mixed_a << 16) | s_val | (mixed_b << 8)) & 0xFFFFFFFF
tables[2][i] = ((mixed_a << 24) | (s_val << 16) | mixed_b | (mixed_a << 8)) & 0xFFFFFFFF
tables[3][i] = entry
return tables

def custom_whitening_transform(key_word_a, key_word_b):
state = 0
for i in range(2):
input_word = key_word_a if i > 0 else key_word_b
state ^= input_word
for _ in range(4):
high_byte = (state >> 24) & 0xFF
mul_2 = (high_byte * 2) & 0xFF
if high_byte & 0x80:
mul_2 ^= 0x11D

mul_2 &= 0xFF
div_2 = (high_byte >> 1) & 0x7F
if high_byte & 1:
div_2 ^= 0x8E

xor_res = (div_2 ^ mul_2) & 0xFF

b0 = (state ^ xor_res) & 0xFF
b1 = ((state >> 8) & 0xFF ^ mul_2) & 0xFF
b2 = ((state >> 16) & 0xFF ^ xor_res) & 0xFF
b3 = high_byte # 保留原高位

state = (b2 << 16 | b3 << 24 | b1 << 8 | b0) & 0xFFFFFFFF
return state

def g_transform(word, k0, k1, t_tables):
bytes_val = [
word & 0xFF,
(word >> 8) & 0xFF,
(word >> 16) & 0xFF,
(word >> 24) & 0xFF
]

k0_bytes = [(k0 >> (i * 8)) & 0xFF for i in range(4)]
k1_bytes = [(k1 >> (i * 8)) & 0xFF for i in range(4)]

# 字节 0: S -> InvS -> T0
idx0 = INV_SBOX[SBOX[bytes_val[0]] ^ k1_bytes[0]] ^ k0_bytes[0]
# 字节 1: InvS -> InvS -> T1
idx1 = INV_SBOX[INV_SBOX[bytes_val[1]] ^ k1_bytes[1]] ^ k0_bytes[1]
# 字节 2: S -> S -> T2
idx2 = SBOX[SBOX[bytes_val[2]] ^ k1_bytes[2]] ^ k0_bytes[2]
# 字节 3: InvS -> S -> T3
idx3 = SBOX[INV_SBOX[bytes_val[3]] ^ k1_bytes[3]] ^ k0_bytes[3]

return (t_tables[0][idx0] ^ t_tables[1][idx1] ^ t_tables[2][idx2] ^ t_tables[3][idx3]) & 0xFFFFFFFF

def expand_key(key_bytes, t_tables):

words = [int.from_bytes(key_bytes[i:i + 4], "little") for i in range(0, 16, 4)]
even_words = [words[0], words[2]]
odd_words = [words[1], words[3]]

sbox_keys = [
custom_whitening_transform(even_words[1], odd_words[1]),
custom_whitening_transform(even_words[0], odd_words[0])
]

subkeys = [0] * 40
for i in range(20):
val_a = (0x0A0A0A0A * i) & 0xFFFFFFFF
val_b = (val_a + 0x0D0D0D0D) & 0xFFFFFFFF

pk_a = g_transform(val_a, even_words[0], even_words[1], t_tables)
pk_b = rot_left32(g_transform(val_b, odd_words[0], odd_words[1], t_tables), 8)

subkeys[2 * i] = (pk_a + pk_b) & 0xFFFFFFFF
subkeys[2 * i + 1] = rot_left32((pk_a + 2 * pk_b) & 0xFFFFFFFF, 13)

return sbox_keys, subkeys

def decrypt_block(cipher_block, sbox_keys, subkeys, t_tables):
data = [
int.from_bytes(cipher_block[0:4], "little") ^ subkeys[4],
int.from_bytes(cipher_block[4:8], "little") ^ subkeys[5],
int.from_bytes(cipher_block[8:12], "little") ^ subkeys[6],
int.from_bytes(cipher_block[12:16], "little") ^ subkeys[7]
]

for r in range(15, -1, -1):
if r < 15:
data[0], data[2] = data[2], data[0]
data[1], data[3] = data[3], data[1]

data[2] = rot_right32(data[2], 27)

f_a = g_transform(data[0], sbox_keys[0], sbox_keys[1], t_tables)
f_b = g_transform(rot_left32(data[1], 8), sbox_keys[0], sbox_keys[1], t_tables)

data[3] ^= (subkeys[2 * r + 9] + f_a + 2 * f_b) & 0xFFFFFFFF
data[2] ^= (subkeys[2 * r + 8] + f_a + f_b) & 0xFFFFFFFF

data[3] = rot_right32(data[3], 5)

out = [
data[0] ^ subkeys[0],
data[1] ^ subkeys[1],
data[2] ^ subkeys[2],
data[3] ^ subkeys[3]
]

return b"".join(x.to_bytes(4, "little") for x in out)

def main():
t_tables = generate_transformation_tables()

key = b"WONDERFUL&&PEACE"
cipher_hex = "A20492152735B4F6ECBAA359DB64417BDF277A73B085666034CF38E748D8FBD4"
cipher_bytes = bytes.fromhex(cipher_hex)

sbox_keys, subkeys = expand_key(key, t_tables)

plaintext = b""
for i in range(0, len(cipher_bytes), 16):
plaintext += decrypt_block(cipher_bytes[i:i + 16], sbox_keys, subkeys, t_tables)

plaintext = plaintext.rstrip(b"\x00")
print(f"Ciphertext Hex: {cipher_hex}")
print(f"Plaintext Hex: {plaintext.hex()}")

try:
print(f"Plaintext String: {plaintext.decode('utf-8')}")
except UnicodeDecodeError:
print("Plaintext contains non-UTF-8 characters.")

if __name__ == "__main__":
main()


# Key: WONDERFUL&&PEACE
# Ciphertext Hex: A20492152735B4F6ECBAA359DB64417BDF277A73B085666034CF38E748D8FBD4
# Plaintext Hex: 4c696c61634354467b495f6a7533745f77406e6e415f62335f68617050797d
# Plaintext String: LilacCTF{I_ju3t_w@nnA_b3_hapPy}

flag 为 LilacCTF{I_ju3t_w@nnA_b3_hapPy}

NineApple

初步分析

附件的文件结构如下

1
2
3
4
5
6
embedded.mobileprovision
Info.plist
Nine
PkgInfo
_CodeSignature
└─CodeResources
查了一下是 iOS 应用程序包的内部结构,主要分析的二进制文件是 Nine ida 反编译,是个用 swift 写的程序,搜索字符串看到了 Right 和 Wrong,还有其他一些看着像有用信息的字符串,根据命名可以大概猜到这个是跟手势解锁相关的,后续分析也验证了这一点

根据字符串交叉引用定位到相关函数

相关函数

sub_100006D08

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
__int64 sub_100006D08()
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]

v3 = v0;
KeyPath = swift_getKeyPath(&unk_100009AA0);
v5 = swift_getKeyPath(&unk_100009AC8);
static Published.subscript.getter(v79, v3, KeyPath, v5);
swift_release(KeyPath);
swift_release(v5);
v6 = (char *)v79[0];
v7 = *(_QWORD *)(v79[0] + 16LL);
if ( v7 )
{
v79[0] = &_swiftEmptyArrayStorage;
v8 = v79;
sub_1000081C8(0, v7, 0);
v9 = 32;
v10 = (_QWORD *)v79[0];
while ( 1 )
{
v11 = *(_QWORD *)&v6[v9];
v12 = __OFADD__(v11, 1);
v13 = v11 + 1;
if ( v12 )
break;
v77 = v13;
v8 = &v77;
v14 = dispatch thunk of CustomStringConvertible.description.getter(
&type metadata for Int,
&protocol witness table for Int);
v79[0] = v10;
v2 = v10[2];
v16 = v10[3];
v1 = (unsigned __int64 *)(v2 + 1);
if ( v2 >= v16 >> 1 )
{
v8 = v79;
v76 = v14;
v18 = v3;
v19 = v15;
sub_1000081C8(v16 > 1, v2 + 1, 1);
v15 = v19;
v3 = v18;
v14 = v76;
v10 = (_QWORD *)v79[0];
}
v10[2] = v1;
v17 = (char *)&v10[2 * v2];
*((_QWORD *)v17 + 4) = v14;
*((_QWORD *)v17 + 5) = v15;
v9 += 8;
if ( !--v7 )
{
swift_bridgeObjectRelease(v6);
goto LABEL_9;
}
}
__break(1u);
LABEL_60:
__break(1u);
LABEL_61:
__break(1u);
LABEL_62:
__break(1u);
LABEL_63:
__break(1u);
}
else
{
swift_bridgeObjectRelease(v79[0]);
v10 = &_swiftEmptyArrayStorage;
LABEL_9:
v79[0] = v10;
v20 = sub_1000050C0(&unk_100010E60);
v21 = sub_1000055DC(&unk_100010E68, &unk_100010E60, &protocol conformance descriptor for [A]);
v7 = BidirectionalCollection<>.joined(separator:)(0, 0xE000000000000000LL, v20, v21);
v6 = v22;
swift_bridgeObjectRelease(v10);
v1 = (unsigned __int64 *)OBJC_IVAR____TtC4Nine13LockViewModel_current_key;
v9 = *(_QWORD *)(v3 + OBJC_IVAR____TtC4Nine13LockViewModel_current_key);
v2 = OBJC_IVAR____TtC4Nine13LockViewModel_key_all;
v8 = *(unsigned __int64 **)(v3 + OBJC_IVAR____TtC4Nine13LockViewModel_key_all);
isUniquelyReferenced_nonNull_native = swift_isUniquelyReferenced_nonNull_native(v8);
*(_QWORD *)(v3 + v2) = v8;
if ( (isUniquelyReferenced_nonNull_native & 1) != 0 )
goto LABEL_10;
}
v8 = (unsigned __int64 *)sub_10000852C(0, v8[2] + 1, 1, v8, &unk_100010E78);
*(_QWORD *)(v3 + v2) = v8;
LABEL_10:
v25 = v8[2];
v24 = v8[3];
if ( v25 >= v24 >> 1 )
v8 = (unsigned __int64 *)sub_10000852C(v24 > 1, v25 + 1, 1, v8, &unk_100010E78);
v8[2] = v25 + 1;
v8[v25 + 4] = v9;
*(_QWORD *)(v3 + v2) = v8;
v26 = sub_1000050C0(&unk_100010E70);
v27 = swift_allocObject(v26, 64, 7);
*(_OWORD *)(v27 + 16) = xmmword_100009A20;
v9 = OBJC_IVAR____TtC4Nine13LockViewModel_current_idx;
v28 = *(_QWORD *)(v3 + OBJC_IVAR____TtC4Nine13LockViewModel_current_idx);
*(_QWORD *)(v27 + 56) = &type metadata for Int;
*(_QWORD *)(v27 + 32) = v28;
print(_:separator:terminator:)(v27, 32, 0xE100000000000000LL, 10, 0xE100000000000000LL);
swift_bridgeObjectRelease(v27);
v29 = swift_allocObject(v26, 64, 7);
*(_OWORD *)(v29 + 16) = xmmword_100009A20;
v30 = *(unsigned __int64 *)((char *)v1 + v3);
*(_QWORD *)(v29 + 56) = &type metadata for UInt64;
*(_QWORD *)(v29 + 32) = v30;
print(_:separator:terminator:)(v29, 32, 0xE100000000000000LL, 10, 0xE100000000000000LL);
swift_bridgeObjectRelease(v29);
v8 = (unsigned __int64 *)swift_getKeyPath(&unk_100009B30);
v31 = swift_getKeyPath(&unk_100009B58);
v79[0] = 0x696F47207065654BLL;
v79[1] = 0xEB0000000021676ELL;
swift_retain(v3);
result = static Published.subscript.setter(v79, v3, v8, v31);
v33 = *(_QWORD *)(v3 + v9);
v12 = __OFADD__(v33, 1);
v34 = v33 + 1;
if ( v12 )
{
__break(1u);
goto LABEL_66;
}
v73 = v26;
v74 = (unsigned __int64 *)v9;
v71 = v2;
v72 = (char *)v1;
*(_QWORD *)(v3 + v9) = v34;
*(unsigned __int64 *)((char *)v1 + v3) = 0;
v70 = OBJC_IVAR____TtC4Nine13LockViewModel_weight_idx;
*(_QWORD *)(v3 + OBJC_IVAR____TtC4Nine13LockViewModel_weight_idx) = 0;
v35 = *(_QWORD *)(v3 + OBJC_IVAR____TtC4Nine13LockViewModel_map_list);
v2 = v35 + 64;
v36 = 1LL << *(_BYTE *)(v35 + 32);
v37 = -1;
if ( v36 < 64 )
v37 = ~(-1LL << v36);
v38 = v37 & *(_QWORD *)(v35 + 64);
v75 = v3;
v39 = (unsigned __int64 *)(v3 + OBJC_IVAR____TtC4Nine13LockViewModel_current_flag);
v3 = (unsigned __int64)(v36 + 63) >> 6;
swift_bridgeObjectRetain(v35);
v40 = 0;
while ( 1 )
{
if ( v38 )
{
v9 = v40;
goto LABEL_36;
}
v42 = v40 + 1;
if ( __OFADD__(v40, 1) )
goto LABEL_60;
if ( v42 >= v3 )
goto LABEL_42;
v38 = *(_QWORD *)(v2 + 8 * v42);
v9 = v40 + 1;
if ( !v38 )
{
v9 = v40 + 2;
if ( v40 + 2 >= v3 )
goto LABEL_42;
v38 = *(_QWORD *)(v2 + 8 * v9);
if ( !v38 )
{
v9 = v40 + 3;
if ( v40 + 3 >= v3 )
goto LABEL_42;
v38 = *(_QWORD *)(v2 + 8 * v9);
if ( !v38 )
{
v9 = v40 + 4;
if ( v40 + 4 >= v3 )
goto LABEL_42;
v38 = *(_QWORD *)(v2 + 8 * v9);
if ( !v38 )
{
v9 = v40 + 5;
if ( v40 + 5 >= v3 )
goto LABEL_42;
v38 = *(_QWORD *)(v2 + 8 * v9);
if ( !v38 )
{
v9 = v40 + 6;
if ( v40 + 6 >= v3 )
goto LABEL_42;
v38 = *(_QWORD *)(v2 + 8 * v9);
if ( !v38 )
break;
}
}
}
}
}
LABEL_36:
v46 = __clz(__rbit64(v38));
v38 &= v38 - 1;
v47 = (v9 << 10) | (16 * v46);
v48 = (unsigned __int64 **)(*(_QWORD *)(v35 + 48) + v47);
v8 = *v48;
v1 = v48[1];
v49 = (_QWORD *)(*(_QWORD *)(v35 + 56) + v47);
if ( *v49 != v7 || v49[1] != (_QWORD)v6 )
{
v51 = _stringCompareWithSmolCheck(_:_:expecting:)();
v40 = v9;
if ( (v51 & 1) == 0 )
continue;
}
swift_beginAccess(v39, v79, 33, 0);
swift_bridgeObjectRetain(v1);
v41._countAndFlagsBits = (__int64)v8;
v41._object = v1;
v8 = v39;
String.append(_:)(v41);
swift_endAccess(v79);
swift_bridgeObjectRelease(v1);
v40 = v9;
}
v43 = v40 + 7;
v44 = (unsigned __int64 *)(v35 + 120 + 8 * v40);
while ( v43 < v3 )
{
v45 = *v44++;
v38 = v45;
++v43;
if ( v45 )
{
v9 = v43 - 1;
goto LABEL_36;
}
}
LABEL_42:
swift_bridgeObjectRelease(v6);
swift_release(v35);
v7 = v73;
v52 = swift_allocObject(v73, 64, 7);
*(_OWORD *)(v52 + 16) = xmmword_100009A20;
swift_beginAccess(v39, v79, 1, 0);
v54 = *v39;
v53 = v39[1];
v6 = (char *)&type metadata for String;
*(_QWORD *)(v52 + 56) = &type metadata for String;
*(_QWORD *)(v52 + 32) = v54;
*(_QWORD *)(v52 + 40) = v53;
swift_bridgeObjectRetain(v53);
print(_:separator:terminator:)(v52, 32, 0xE100000000000000LL, 10, 0xE100000000000000LL);
v55 = swift_bridgeObjectRelease(v52);
v1 = v74;
v9 = *(_QWORD *)(v75 + OBJC_IVAR____TtC4Nine13LockViewModel_target_all);
if ( *(unsigned __int64 *)((char *)v74 + v75) != *(_QWORD *)(v9 + 16) )
return sub_1000074C4(v55);
v8 = (unsigned __int64 *)swift_allocObject(v73, 64, 7);
*((_OWORD *)v8 + 1) = xmmword_100009A20;
v8[7] = (unsigned __int64)&type metadata for String;
v8[4] = 0x68635F7472617473LL;
v8[5] = 0xEB000000006B6365LL;
print(_:separator:terminator:)(v8, 32, 0xE100000000000000LL, 10, 0xE100000000000000LL);
swift_bridgeObjectRelease(v8);
v56 = *(_QWORD *)(v75 + v71);
v57 = *(_QWORD *)(v56 + 16);
if ( v57 )
{
v58 = 0;
v59 = 0;
v60 = v56 + 32;
while ( v59 < v57 )
{
v61 = v59 + 1;
if ( __OFADD__(v59, 1) )
goto LABEL_62;
if ( (signed __int64)v59 >= *(_QWORD *)(v9 + 16) )
goto LABEL_63;
if ( *(_QWORD *)(v9 + 32 + 8 * v59) == *(_QWORD *)(v60 + 8 * v59) )
{
++v59;
if ( v61 == v57 )
{
if ( (v58 & 1) == 0 )
goto LABEL_55;
LABEL_54:
v62 = swift_getKeyPath(&unk_100009B30);
v63 = swift_getKeyPath(&unk_100009B58);
v77 = 0xD000000000000015LL;
v78 = 0x800000010000AB20LL;
goto LABEL_56;
}
}
else
{
v58 = 1;
++v59;
if ( v61 == v57 )
goto LABEL_54;
}
}
goto LABEL_61;
}
LABEL_55:
v64 = *(Swift::String *)v39;
v77 = 0xD000000000000010LL;
v78 = 0x800000010000AB40LL;
String.append(_:)(v64);
v65 = v77;
v66 = v78;
v62 = swift_getKeyPath(&unk_100009B30);
v63 = swift_getKeyPath(&unk_100009B58);
v77 = v65;
v78 = v66;
LABEL_56:
swift_retain(v75);
result = static Published.subscript.setter(&v77, v75, v62, v63);
v67 = *(unsigned __int64 *)((char *)v74 + v75);
v12 = __OFADD__(v67, 1);
v68 = v67 + 1;
if ( !v12 )
{
*(unsigned __int64 *)((char *)v74 + v75) = v68;
*(_QWORD *)&v72[v75] = 0;
*(_QWORD *)(v75 + v70) = 0;
v69 = v39[1];
*v39 = 0;
v39[1] = 0xE000000000000000LL;
v55 = swift_bridgeObjectRelease(v69);
return sub_1000074C4(v55);
}
LABEL_66:
__break(1u);
return result;
}

是处理输入的主要逻辑 获取用户在九宫格输入数字序列,然后遍历输入的数字,将其转换为字符串并连接起来 其中有一个 map_list,会根据拼接好的字符串去查找对应的映射值,找到后会将结果存储在 current_flag 中,最后和 target_all 进行比较

sub_100006258

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
__int64 sub_100006258()
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]

v1 = sub_1000050C0(&unk_100010EB8);
v39 = *(_QWORD *)(v1 - 8);
v40 = v1;
v38 = (char *)&v32 - ((*(_QWORD *)(v39 + 64) + 15LL) & 0xFFFFFFFFFFFFFFF0LL);
v2 = sub_1000050C0(&unk_100010EB0);
v36 = *(_QWORD *)(v2 - 8);
v37 = v2;
v35 = (char *)&v32 - ((*(_QWORD *)(v36 + 64) + 15LL) & 0xFFFFFFFFFFFFFFF0LL);
v3 = sub_1000050C0(&unk_100010EA8);
v33 = *(_QWORD *)(v3 - 8);
v34 = v3;
v4 = (char *)&v32 - ((*(_QWORD *)(v33 + 64) + 15LL) & 0xFFFFFFFFFFFFFFF0LL);
v5 = sub_1000050C0(&unk_100010EA0);
v6 = *(_QWORD *)(v5 - 8);
v7 = (char *)&v32 - ((*(_QWORD *)(v6 + 64) + 15LL) & 0xFFFFFFFFFFFFFFF0LL);
v8 = sub_1000050C0(&unk_100010E98);
v9 = *(_QWORD *)(v8 - 8);
v10 = (char *)&v32 - ((*(_QWORD *)(v9 + 64) + 15LL) & 0xFFFFFFFFFFFFFFF0LL);
v11 = OBJC_IVAR____TtC4Nine13LockViewModel__nodePositions;
v41 = &_swiftEmptyArrayStorage;
v12 = sub_1000050C0(&unk_100010B88);
Published.init(initialValue:)(&v41, v12);
(*(void (__fastcall **)(__int64, char *, __int64))(v9 + 32))(v0 + v11, v10, v8);
v13 = OBJC_IVAR____TtC4Nine13LockViewModel__selectedNodes;
v41 = &_swiftEmptyArrayStorage;
v14 = sub_1000050C0(&unk_100010B98);
Published.init(initialValue:)(&v41, v14);
(*(void (__fastcall **)(__int64, char *, __int64))(v6 + 32))(v0 + v13, v7, v5);
v15 = OBJC_IVAR____TtC4Nine13LockViewModel__currentPosition;
v41 = 0;
v42 = 0;
v43 = 1;
v16 = sub_1000050C0(&unk_100010BA8);
Published.init(initialValue:)(&v41, v16);
(*(void (__fastcall **)(__int64, char *, __int64))(v33 + 32))(v0 + v15, v4, v34);
v17 = OBJC_IVAR____TtC4Nine13LockViewModel__message;
v41 = (void *)0xD00000000000001DLL;
v42 = 0x800000010000AB60LL;
v18 = v35;
Published.init(initialValue:)(&v41, &type metadata for String);
(*(void (__fastcall **)(__int64, char *, __int64))(v36 + 32))(v0 + v17, v18, v37);
v19 = OBJC_IVAR____TtC4Nine13LockViewModel__isAuthenticated;
LOBYTE(v41) = 0;
v20 = v38;
Published.init(initialValue:)(&v41, &type metadata for Bool);
(*(void (__fastcall **)(__int64, char *, __int64))(v39 + 32))(v0 + v19, v20, v40);
*(_QWORD *)(v0 + OBJC_IVAR____TtC4Nine13LockViewModel_current_key) = 0;
*(_QWORD *)(v0 + OBJC_IVAR____TtC4Nine13LockViewModel_current_idx) = 0;
v21 = (_QWORD *)(v0 + OBJC_IVAR____TtC4Nine13LockViewModel_current_flag);
*v21 = 0;
v21[1] = 0xE000000000000000LL;
*(_QWORD *)(v0 + OBJC_IVAR____TtC4Nine13LockViewModel_weight_idx) = 0;
*(_QWORD *)(v0 + OBJC_IVAR____TtC4Nine13LockViewModel_key_all) = &_swiftEmptyArrayStorage;
v22 = OBJC_IVAR____TtC4Nine13LockViewModel_weight;
v23 = sub_1000050C0(&unk_100010E78);
*(_QWORD *)(v0 + v22) = swift_initStaticObject(v23, &unk_100010320);
v24 = OBJC_IVAR____TtC4Nine13LockViewModel_target_all;
*(_QWORD *)(v0 + v24) = swift_initStaticObject(v23, &unk_100010390);
v25 = OBJC_IVAR____TtC4Nine13LockViewModel_map_list;
v26 = sub_1000050C0(&unk_100010EC0);
inited = swift_initStaticObject(v26, &unk_1000104C0);
v28 = sub_100008678();
v29 = sub_1000050C0(&unk_100010EC8);
v30 = swift_arrayDestroy(inited + 32, 39, v29);
*(_QWORD *)(v0 + v25) = v28;
*(_QWORD *)(v0 + OBJC_IVAR____TtC4Nine13LockViewModel_nodeSize) = 0x404E000000000000LL;
*(_QWORD *)(v0 + OBJC_IVAR____TtC4Nine13LockViewModel_gridSize) = 0x4072C00000000000LL;
sub_100006618(v30);
return v0;
}

负责初始化,代码的前半部分通过调用 Published.init(initialValue:) 初始化了几个与 UI 绑定的属性: _nodePositions: 初始化为空数组 _swiftEmptyArrayStorage,可能存储九宫格中每个点的位置 _selectedNodes: 初始化为空数组,存储用户当前划过的点 _currentPosition: 初始化为一个元组或结构体,值为 0, 0, 1,记录当前触摸位置 _message: 初始化为字符串 "Keep Going!" _isAuthenticated: 初始化为 false

之后是核心校验数据初始化 current_key / current_idx / weight_idx: 均初始化为 0 current_flag: 初始化为空字符串 key_all: 初始化为空数组 weight : 使用 unk_100010320 处的静态数据初始化 target_all:使用 unk_100010390 处的静态数据初始化,这个数组存储了解锁所需的正确手势序列 map_list:调用 sub_100008678 构建,将手势字符串(Key)映射为单个字符(Value),数据来源是 unk_1000104C0

在代码末尾,还设置了两个 UI 相关的浮点常数: nodeSize: 0x404E000000000000 -> 浮点数 60.0 gridSize: 0x4072C00000000000 -> 浮点数 300.0 这进一步证实了这是一个 300x300 像素的九宫格界面,每个圆点的大小为 60 像素

九宫格布局

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

sub_100004D74

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 __fastcall sub_100004D74(__int64 a1, __int64 a2, __int64 a3, char a4)
{
__int64 v7; // x23
__int64 v8; // x0
__int64 v9; // x19
double v10; // d0
double v11; // d1

v7 = type metadata accessor for LockViewModel(0);
v8 = sub_1000051BC(&unk_1000109F8, type metadata accessor for LockViewModel, &unk_100009A68);
v9 = StateObject.wrappedValue.getter(a2, a3, a4 & 1, v7, v8);
v10 = DragGesture.Value.location.getter();
sub_1000069A4(v10, v11);
return swift_release(v9);
}

这个函数是 SwiftUI DragGesture 的回调函数:通过 StateObject 获取 LockViewModel 实例 获取坐标:从 DragGesture.Value 中提取当前的触摸点坐标(x,y),将坐标传递给核心处理函数 sub_1000069A4

sub_1000069A4

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
__int64 __fastcall sub_1000069A4(double a1, double a2)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]

KeyPath = swift_getKeyPath(&unk_100009AE8);
v9 = swift_getKeyPath(&unk_100009B10);
*(double *)v33 = a1;
*(double *)&v33[1] = a2;
v34 = 0;
swift_retain(v2);
static Published.subscript.setter(v33, v2, KeyPath, v9);
v10 = swift_getKeyPath(&unk_100009B78);
v11 = swift_getKeyPath(&unk_100009BA0);
static Published.subscript.getter(v33, v2, v10, v11);
swift_release(v10);
swift_release(v11);
v12 = v33[0];
v13 = *(_QWORD *)(v33[0] + 16LL);
if ( !v13 )
return swift_bridgeObjectRelease(v12);
v14 = 0;
v15 = *(double *)(v2 + OBJC_IVAR____TtC4Nine13LockViewModel_nodeSize);
v16 = &unk_100009AA0;
v17 = &unk_100009AC8;
while ( 1 )
{
if ( v14 >= *(_QWORD *)(v12 + 16) )
goto LABEL_26;
v5 = v14 + 1;
if ( hypot(a1 - *(double *)(v12 + 32 + 16 * v14), a2 - *(double *)(v12 + 32 + 16 * v14 + 8)) < v15 )
break;
LABEL_4:
++v14;
if ( v5 == v13 )
return swift_bridgeObjectRelease(v12);
}
v3 = (__int64 *)swift_getKeyPath(&unk_100009AA0);
v4 = swift_getKeyPath(&unk_100009AC8);
static Published.subscript.getter(v33, v2, v3, v4);
swift_release(v3);
swift_release(v4);
v18 = *(_QWORD *)(v33[0] + 16LL);
if ( !v18 )
goto LABEL_16;
if ( *(_QWORD *)(v33[0] + 32LL) == v14 )
goto LABEL_3;
if ( v18 == 1 )
goto LABEL_16;
if ( *(_QWORD *)(v33[0] + 40LL) == v14 )
{
LABEL_3:
swift_bridgeObjectRelease(v33[0]);
goto LABEL_4;
}
if ( v18 != 2 )
{
v19 = (_QWORD *)(v33[0] + 48LL);
v20 = 2;
while ( 1 )
{
v21 = v20 + 1;
if ( __OFADD__(v20, 1) )
break;
if ( *v19 == v14 )
goto LABEL_3;
++v20;
++v19;
if ( v21 == v18 )
goto LABEL_16;
}
__break(1u);
LABEL_26:
__break(1u);
goto LABEL_27;
}
LABEL_16:
v22 = v33[0];
swift_bridgeObjectRelease(v12);
swift_bridgeObjectRelease(v22);
v12 = swift_getKeyPath(&unk_100009AA0);
v16 = (void *)swift_getKeyPath(&unk_100009AC8);
v17 = (void *)static Published.subscript.modify(v33, v2, v12, v16);
v3 = v23;
v4 = *v23;
isUniquelyReferenced_nonNull_native = swift_isUniquelyReferenced_nonNull_native(*v23);
*v3 = v4;
if ( (isUniquelyReferenced_nonNull_native & 1) != 0 )
goto LABEL_17;
LABEL_27:
v4 = sub_10000852C(0, *(_QWORD *)(v4 + 16) + 1LL, 1, v4, &unk_100010E88);
*v3 = v4;
LABEL_17:
v26 = *(_QWORD *)(v4 + 16);
v25 = *(_QWORD *)(v4 + 24);
if ( v26 >= v25 >> 1 )
{
v4 = sub_10000852C(v25 > 1, v26 + 1, 1, v4, &unk_100010E88);
*v3 = v4;
}
*(_QWORD *)(v4 + 16) = v26 + 1;
*(_QWORD *)(v4 + 8 * v26 + 32) = v14;
((void (__fastcall *)(_QWORD *, _QWORD))v17)(v33, 0);
swift_release(v12);
result = swift_release(v16);
v28 = OBJC_IVAR____TtC4Nine13LockViewModel_weight_idx;
v29 = *(_QWORD *)(v2 + OBJC_IVAR____TtC4Nine13LockViewModel_weight_idx);
if ( (v29 & 0x8000000000000000LL) != 0 )
{
__break(1u);
goto LABEL_29;
}
v30 = *(_QWORD *)(v2 + OBJC_IVAR____TtC4Nine13LockViewModel_weight);
if ( v29 >= *(_QWORD *)(v30 + 16) )
{
LABEL_29:
__break(1u);
goto LABEL_30;
}
v31 = *(_QWORD *)(v30 + 8 * v29 + 32);
if ( !is_mul_ok(v31, v5) )
{
LABEL_30:
__break(1u);
goto LABEL_31;
}
v32 = *(_QWORD *)(v2 + OBJC_IVAR____TtC4Nine13LockViewModel_current_key);
if ( !__CFADD__(v32, v31 * v5) )
{
*(_QWORD *)(v2 + OBJC_IVAR____TtC4Nine13LockViewModel_current_key) = v32 + v31 * v5;
*(_QWORD *)(v2 + v28) = v29 + 1;
return result;
}
LABEL_31:
__break(1u);
return result;
}

这个函数负责判断用户的手势划过了哪个圆点,并更新状态,一旦确定划过了一个新圆点,程序会执行以下操作: - 更新已选列表:将该点索引 v14 加入 _selectedNodes 数组 - 更新 current_keys$$ \text{CurrentKey} = \sum_{i=0}^{n} (\text{Weight}[i] \times (\text{NodeIndex}_i + 1)) $$

关键数据

map_list

路径字符串(九宫格序列) 对应字符
1478 i
582 l
147 a
2147859 c
6589 {
248 1
125879 0
2587413 S
321456987 _
789 /
27 \
18 N
7415963 d
825479 w
1475963 n
4758 3
23598 f
21745 r
475 y
14257 o
58746 u
47869 }
157 2
125478 4
14528 5
214587 6
458712 7
1238 9

target_all

对应 33 个 flag 字符的校验值

weight

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def solve():
# W0 减去偏移量 163840 (0x28000)
weights = [
10564859903, # W0 (0x275B477FF)
880404991, # W1
67723460, # W2
4837390, # W3
322492, # W4
20155, # W5
1185, # W6
65, # W7
3 # W8
]

targets = [
14599243207, 60002986363, 14560544087, 22317571743, 68376508563,
25193127450, 12705425144, 26108707849, 33544207307, 81606770389,
22317571743, 77570576608, 86640094905, 81606770389, 14560544087,
12705425144, 14560544087, 22317571743, 68376508563, 81606770389,
14587757950, 12705425144, 48799590969, 24155668525, 81606770389,
22505151037, 26108707849, 48760891849, 81606770389, 14248371181,
60362888175, 48995988997, 15440949078
]

mapping = {
'1478': 'L', '582': 'i', '147': 'l', '2147859': 'a', '6589': 'c',
'248': '{', '125879': '1', '2587413': '0', '321456987': 'S', '789': '_',
'7415963': 'N', '825479': 'd', '1475963': 'w', '4758': 'n',
'23598': '3', '21745': 'f', '475': 'r', '14257': 'y',
'58746': 'o', '47869': 'u', '157': '}', '125478': '2',
'14528': '4', '214587': '5', '458712': '6', '1238': '7',
'893256': '9', '74269': 'G', '32478965': 'V', '183': 'T',
'13258': 'P', '45217': 'M', '7418369': 'W', '1472963': 'Q',
'42689': 'H', '1745639': 'K', '24718': 'A'
}

def find_path(target, weight_idx):
if target == 0:
return ""
if weight_idx >= len(weights):
return None

w = weights[weight_idx]
start_digit = min(9, target // w + 1)
for d in range(start_digit, 0, -1):
res = find_path(target - d * w, weight_idx + 1)
if res is not None:
return str(d) + res
return None

flag = ""
for i, t in enumerate(targets):
path = find_path(t, 0)
if path:
char = mapping.get(path, f"[{path}]")
flag += char
print(f"Step {i+1:02d}: Target={t:<12} -> Path={path:<10} -> Char={char}")
else:
flag += "?"
print(f"Step {i+1:02d}: Target={t:<12} -> 解码失败")

print(f"Flag: {flag}")

if __name__ == "__main__":
solve()

flag 为 Lilac{10S_aNd_l1lac_w1n3_f0r_you}


LilacCTF2026 逆向 Wp + 复现
http://example.com/2026/01/26/LilacCTF2026/
作者
Eleven
发布于
2026年1月26日
许可协议