0x00 题目分析

比较简单的一题,主要是Z3的使用方法和换表AES

关于Z3:https://github.com/Z3Prover/z3

AES不必多说(

0x01 解

ELF,无壳

直接上IDA

由login Success的提示可以看到不仅要password,还要username

那么就一个个来看吧

首先是user name

在strlen后有一个if包含一个函数,这个函数应该就是对user name的判断了

根据strlen可知这个name的长度应该为16B

进去这个对user name内容的判断

这么多等式,首先想到用Z3解,再仔细看看,可以发现这些等式每四个为一组,所以就可以写脚本先把name搞出来

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
from z3 import *
a1 = [BitVec(('x%s' % i), 8) for i in range(16)]
out = []
s = Solver()
s.add(a1[2] + a1[1] + a1[0] + a1[3] == 447)
s.add(101 * a1[2] + a1[0] + 9 * a1[1] + 8 * a1[3] == 12265)
s.add(5 * a1[2] + 3 * a1[0] + 4 * a1[1] + 6 * a1[3] == 2000)
s.add(88 * a1[2] + 12 * a1[0] + 11 * a1[1] + 87 * a1[3] == 21475)
s.add(a1[6] + 59 * a1[5] + 100 * a1[4] + a1[7] == 7896)
s.add(443 * a1[4] + 200 * a1[5] + 10 * a1[6] + 16 * a1[7] == 33774)
s.add(556 * a1[5] + 333 * a1[4] + 8 * a1[6] + 7 * a1[7] == 44758)
s.add(a1[6] + a1[5] + 202 * a1[4] + a1[7] == 9950)
s.add(78 * a1[10] + 35 * a1[9] + 23 * a1[8] + 89 * a1[11] == 24052)
s.add(78 * a1[8] + 59 * a1[9] + 15 * a1[10] + 91 * a1[11] == 25209)
s.add(111 * a1[10] + 654 * a1[9] + 123 * a1[8] + 222 * a1[11] == 113427)
s.add(6 * a1[9] + 72 * a1[8] + 5 * a1[10] + 444 * a1[11] == 54166)
s.add(56 * a1[14] + 35 * a1[12] + 6 * a1[13] + 121 * a1[15] == 11130)
s.add(169 * a1[14] + 158 * a1[13] + 98 * a1[12] + 124 * a1[15] == 27382)
s.add(147 * a1[13] + 65 * a1[12] + 131 * a1[14] + 129 * a1[15] == 23564)
s.add(137 * a1[14] + 132 * a1[13] + 620 * a1[12] + 135 * a1[15] == 51206)
if s.check() == sat:
result = s.model()
print(result)
for i in range(16):
out.append(result[a1[i]].as_long())
print(chr(out[i]), end='')
# user01_nkctf2024

接下来进入pass的判断,根据strlen可知password的长度为26B

然后有一个BYTE1的判断,95为下划线(_),因此合理怀疑这是一个分隔,password由两个部分组成

之后是将输入放入了dest,不过只放了9位

v17的数据放进了v13

接下来的一个for循环,将dest做了一些运算之后放入了s2中,之后s2拿去memcmp了

对比对象是这玩意

那么就可以reverse一下拿到一个9B的东西了

1
2
3
4
5
6
7
8
key = [0x7e, 0x5a, 0x6e, 0x77, 0x3a, 0x79, 0x35, 0x76, 0x7c]

for i in range(9):
key[i] = (key[i] - 9 + i) ^ i
for i in key:
print(chr(i), end='')
print()
# uSer1p4ss

再之后就是一个函数再接一个memcmp

先看看memcmp的对比对象(这是后面复现截的图,变量名不一样,内容不变)

看不出什么加密

再分析一下,首先可以确定的就是对9B判断之后的这个函数就是一个加密函数,而这个函数把变量s也就是name丢进去进行了运算,因此可以判断name是一个key

然后进这个函数看看

a1是密钥,a2是密钥长度,被放进了dest里面

然后有很多函数,先看第一个放密钥进去的

这个byte_5040是这样的

大小是16x16

再结合第一个函数内的10

应该是个AES

关于AES:https://cdn.jsdelivr.net/gh/WoaW04/Picture/image202403272204371.png

不过这个表和标准的AES表不一样

应该是换表AES

那么就搞一下inv_sbox然后去解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# get inv_sbox
s_box = [
0x31, 0x52, 0x5a, 0xc8, 0x0b, 0xac, 0xf3, 0x3a, 0x8b, 0x54, 0x27, 0x9b, 0xab, 0x95, 0xde, 0x83,
0x60, 0xcb, 0x53, 0x7f, 0xc4, 0xe3, 0x0a, 0x97, 0xe0, 0x29, 0xd5, 0x68, 0xc5, 0xdf, 0xf4, 0x7b,
0xaa, 0xd6, 0x42, 0x78, 0x6c, 0xe9, 0x70, 0x17, 0xd7, 0x37, 0x24, 0x49, 0x75, 0xa9, 0x89, 0x67,
0x03, 0xfa, 0xd9, 0x91, 0xb4, 0x5b, 0xc2, 0x4e, 0x92, 0xfc, 0x46, 0xb1, 0x73, 0x08, 0xc7, 0x74,
0x09, 0xaf, 0xec, 0xf5, 0x4d, 0x2d, 0xea, 0xa5, 0xda, 0xef, 0xa6, 0x2b, 0x7e, 0x0c, 0x8f, 0xb0,
0x04, 0x06, 0x62, 0x84, 0x15, 0x8e, 0x12, 0x1d, 0x44, 0xc0, 0xe2, 0x38, 0xd4, 0x47, 0x28, 0x45,
0x6e, 0x9d, 0x63, 0xcf, 0xe6, 0x8c, 0x18, 0x82, 0x1b, 0x2c, 0xee, 0x87, 0x94, 0x10, 0xc1, 0x20,
0x07, 0x4a, 0xa4, 0xeb, 0x77, 0xbc, 0xd3, 0xe1, 0x66, 0x2a, 0x6b, 0xe7, 0x79, 0xcc, 0x86, 0x16,
0xd0, 0xd1, 0x19, 0x55, 0x3c, 0x9f, 0xfb, 0x30, 0x98, 0xbd, 0xb8, 0xf1, 0x9e, 0x61, 0xcd, 0x90,
0xce, 0x7c, 0x8d, 0x57, 0xae, 0x6a, 0xb3, 0x3d, 0x76, 0xa7, 0x71, 0x88, 0xa2, 0xba, 0x4f, 0x3e,
0x40, 0x64, 0x0f, 0x48, 0x21, 0x35, 0x36, 0x2f, 0xe8, 0x14, 0x5d, 0x51, 0xd8, 0xb5, 0xfe, 0xd2,
0x96, 0x93, 0xa1, 0xb6, 0x43, 0x0d, 0x4c, 0x80, 0xc9, 0xff, 0xa3, 0xdd, 0x72, 0x05, 0x59, 0xbf,
0x0e, 0x26, 0x34, 0x1f, 0x13, 0xe5, 0xdc, 0xf2, 0xc6, 0x50, 0x1e, 0xe4, 0x85, 0xb7, 0x39, 0x8a,
0xca, 0xed, 0x9c, 0xbb, 0x56, 0x23, 0x1a, 0xf0, 0x32, 0x58, 0xb2, 0x65, 0x33, 0x6f, 0x41, 0xbe,
0x3f, 0x6d, 0x11, 0x00, 0xad, 0x5f, 0xc3, 0x81, 0x25, 0xa8, 0xa0, 0x9a, 0xf6, 0xf7, 0x5e, 0x99,
0x22, 0x2e, 0x4b, 0xf9, 0x3b, 0x02, 0x7a, 0xb9, 0x5c, 0x69, 0xf8, 0x1c, 0xdb, 0x01, 0x7d, 0xfd
]

inv_s_box = []
for i in range(256):
inv_s_box.append(s_box.index(i))
for i in range(len(inv_s_box)):
if (i % 16 == 0 and i != 0):
print()
print('0x{:02x}'.format(inv_s_box[i]), end=', ')
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
# decrypt
N_ROUNDS = 10

key = b'\x75\x73\x65\x72\x30\x31\x5f\x6e\x6b\x63\x74\x66\x32\x30\x32\x34'
ciphertext = b'\xb0\xcc\x93\xea\xe9\x2f\xef\x56\x99\x39\x6e\x02\x3b\x4f\x9e\x42'

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

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


def shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]


def inv_shift_rows(s):
s[1][1], s[2][1], s[3][1], s[0][1] = s[0][1], s[1][1], s[2][1], s[3][1]
s[2][2], s[3][2], s[0][2], s[1][2] = s[0][2], s[1][2], s[2][2], s[3][2]
s[3][3], s[0][3], s[1][3], s[2][3] = s[0][3], s[1][3], s[2][3], s[3][3]


def xtime(a): return (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)


def mix_single_column(a):
# 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(s):
for i in range(4):
mix_single_column(s[i])


def inv_mix_columns(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

mix_columns(s)


def bytes2matrix(text):
""" Converts a 16-byte array into a 4x4 matrix. """
return [list(text[i:i+4]) for i in range(0, len(text), 4)]


def matrix2bytes(m):
return bytes(sum(m, []))


def add_round_key(s, k):
for i in range(4):
for j in range(4):
s[i][j] ^= k[i][j]


def inv_sub(s):
for i in range(4):
for j in range(4):
s[i][j] = inv_s_box[s[i][j]]


def expand_key(master_key):
"""
Expands and returns a list of key matrices for the given master_key.
"""
# Round constants https://en.wikipedia.org/wiki/AES_key_schedule#Round_constants
r_con = (
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,
)
# Initialize round keys with raw key material.
key_columns = bytes2matrix(master_key)
iteration_size = len(master_key) // 4
# Each iteration has exactly as many columns as the key material.
columns_per_iteration = len(key_columns)
i = 1
while len(key_columns) < (N_ROUNDS + 1) * 4:
# Copy previous word.
word = list(key_columns[-1])

# Perform schedule_core once every "row".
if len(key_columns) % iteration_size == 0:
# Circular shift.
word.append(word.pop(0))
# Map to S-BOX.
word = [s_box[b] for b in word]
# XOR with first byte of R-CON, since the others bytes of R-CON are 0.
word[0] ^= r_con[i]
i += 1
elif len(master_key) == 32 and len(key_columns) % iteration_size == 4:
# Run word through S-box in the fourth iteration when using a
# 256-bit key.
word = [s_box[b] for b in word]

# XOR with equivalent word from previous iteration.
word = bytes(i ^ j for i, j in zip(word, key_columns[-iteration_size]))
key_columns.append(word)

# Group key words in 4x4 byte matrices.
return [key_columns[4*i: 4*(i+1)] for i in range(len(key_columns) // 4)]


def decrypt(key, ciphertext):
round_keys = expand_key(key) # Remember to start from the last round key and work backwards through them when decrypting
# Convert ciphertext to state matrix
Cipher_matrix = bytes2matrix(ciphertext)
# Initial add round key step
add_round_key(Cipher_matrix, round_keys[-1])
for i in range(N_ROUNDS - 1, 0, -1):
inv_shift_rows(Cipher_matrix)
inv_sub(Cipher_matrix)
add_round_key(Cipher_matrix, round_keys[i])
inv_mix_columns(Cipher_matrix)
# Run final round (skips the InvMixColumns step)
inv_shift_rows(Cipher_matrix)
inv_sub(Cipher_matrix)
add_round_key(Cipher_matrix, round_keys[0])

# Convert state matrix to plaintext
plaintext = matrix2bytes(Cipher_matrix)
return plaintext


print(key.hex(), ciphertext.hex())
print(decrypt(key, ciphertext))
# b'9ee779cd2abcde48'

最后将password拼接一下

1
# uSer1p4ss_9ee779cd2abcde48

最后的flag就是

1
2
# MD5(user01_nkctf2024uSer1p4ss_9ee779cd2abcde48)
# NKCTF{2961bba0add6265ba83bc6198e0ec758}