0%

腾讯游戏安全2018

腾讯游戏安全2018比赛赛题题解,一直放在本地,没有上传。题目应该在gslab上还可以下到。

第一轮资格赛PC

MFC程序,根据xspy或者resource hacker寻找ID的方式找到验证函数逻辑:

函数逻辑

sub_1245510函数处开始进行check,首先是checkUserName:

Check

在checkUserName函数中确定userName长度为39,且会经过一个大写字母转化,而后对userName进行一个以’#’为分隔符的分割:

split

分割以后,会得到8个结构一样的结构体,如上图v24[0]是结构的起始位置,v24[1]是结束位置,每个结构的第一个位置为字符串,第5个位置为字符串长度,第六个位置为标志位,且输入的userName只能为A,B,C,D,E和数字。

分析完了checkUserName函数,我们继续分析下一个函数sub_1245040,这个函数会根据我们的userName,由固定的算法生成我们需要的5个值,算法见脚本。

之后便是对regCode进行一个类base64的解码了,解码之后的字符串要求为32的长度,且最后8个字节中,分别为”8102\x00\x00\x00\x00”,类base64解码见之后的脚本。

最后在sub_1242F20处进行类抛物线方程的求解,两个方程,两个未知数。

equation

最终的注册机脚本如下,根据我们输入的合法的userName,得到相应的regCode:

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

import struct

base64Table = "ZO6Kq79L&CPWvNopzQfghDRSG@di*kAB8rsFewxlm+/u5a^2YtTJUVEn0$HI34y#"

# calculate the UserName

print "Input your 39 bytes userName! e.g xxxx#xxxx#xxxx#xxxx#xxxx#xxxx#xxxx#xxxx\n"
print "x belong to A,B,C,D,E and number0-9!"
userName = raw_input()
userName = userName.split('#')
a1 = userName[0]
a2 = userName[1]
a3 = userName[2]
a4 = userName[3]
a5 = userName[4]
a6 = userName[5]
a7 = userName[6]
a8 = userName[7]

val1 = ord(a2[0]) * ord(a1[0]) << 16
val1 += ord(a1[1]) ^ ord(a3[1])
val1 += ord(a1[2]) % (ord(a4[2]) + 1) + 1
val1 += ord(a1[3]) / (ord(a5[3]) + 1)

val2 = (ord(a2[0]) ^ ord(a6[0])) << 16
val2 += ord(a2[1]) % (ord(a7[1]) + 3)
val2 += ord(a2[2]) / (ord(a8[2]) + 1) + 5
val2 += ord(a2[3]) + ord(a1[3])

val3 = ord(a3[0]) / (ord(a2[0]) + 3) << 16
val3 ^= ord(a3[1]) % ord(a4[1])
val3 += ord(a6[2]) + 12 + ord(a3[2])
val3 += ord(a3[3]) + ord(a8[3])

val4 = ord(a1[1]) ^ ord(a3[3])
val4 *= (ord(a2[3]) + ord(a4[1]))
val4 &= (ord(a5[2]) & ord(a6[2]))
val4 *= ord(a8[3])
val4 += val2
val4 = (ord(a7[0]) * val4) * val1
val4 = val4 - ((val4 - val2) % (2 * val1))

val5 = (ord(a5[0]) ^ ord(a4[0])) << 16
val5 *= (ord(a4[1]) % (ord(a5[1])+2))
val5 += ord(a4[2]) % (ord(a5[2])+5) + 7
val5 += ord(a5[3]) * ord(a4[3])

a6 = (val4 - val2) / (2*val1)
a7 = (val4*val4 + 4*val1*val3 - val2*val2) / (4*val1)
a8 = val3 + (val2 + val1 * val5 - val4) * val5

str = struct.pack("<Q",a6&0xffffffffffffffff)
str += struct.pack("<Q",a7&0xffffffffffffffff)
str += struct.pack("<Q",a8&0xffffffffffffffff)
str += "8102"
str += "\x00\x00\x00\x00"

# calculate the UserName

# Get regCode
print "Now get your regCode!"
enStr = ''
length = len(str)

for i in range(length/3):
tmp = str[:3]
# 3 to 4
num1 = ord(tmp[0]) >> 2
num2 = ((ord(tmp[0]) & 3) << 4) | ((ord(tmp[1]) & 0xF0) >> 4)
num3 = ((ord(tmp[1]) & 0xF) << 2) | (ord(tmp[2]) >> 6)
num4 = ord(tmp[2]) & 0x3F
# 3 to 4

val = (num1 & 0x38) >> 3
num1 = num1 ^ val

val = (num2 & 0x38) >> 3
num2 = num2 ^ val

val = (num3 & 0x38) >> 3
num3 = num3 ^ val

val = (num4 & 0x38) >> 3
num4 = num4 ^ val

enStr += base64Table[num1] + base64Table[num2] + base64Table[num3] + base64Table[num4]

str = str[3:]

if length%3 != 0:
if length%3 == 2:
tmp = str[:2]
num1 = ord(tmp[0]) >> 2
num2 = ((ord(tmp[0]) & 3) << 4) | ((ord(tmp[1]) & 0xF0) >> 4)
num3 = ((ord(tmp[1]) & 0xF) << 2)
val = (num1 & 0x38) >> 3
num1 = num1 ^ val

val = (num2 & 0x38) >> 3
num2 = num2 ^ val

val = (num3 & 0x38) >> 3
num3 = num3 ^ val
enStr += base64Table[num1] + base64Table[num2] + base64Table[num3]
enStr += '='
if length%3 == 1:
tmp = str[:1]
num1 = ord(tmp[0]) >> 2
num2 = ((ord(tmp[0]) & 3) << 4)
val = (num1 & 0x38) >> 3
num1 = num1 ^ val
val = (num2 & 0x38) >> 3
num2 = num2 ^ val
enStr += base64Table[num1] + base64Table[num2]
enStr += '=='

print "Encode regCode:",enStr
# Get regCode

example

example1

example2

第一轮资格赛PC-进阶版

MFC程序,根据xspy或者resource hacker寻找ID的方式找到验证函数逻辑:

函数逻辑

sub_1245510函数处开始进行check,首先是checkUserName:

Check

在checkUserName函数中确定userName长度为39,且会经过一个大写字母转化,而后对userName进行一个以’#’为分隔符的分割:

split

分割以后,会得到8个结构一样的结构体,如上图v24[0]是结构的起始位置,v24[1]是结束位置,每个结构的第一个位置为字符串,第5个位置为字符串长度,第六个位置为标志位,且输入的userName只能为A,B,C,D,E和数字。

分析完了checkUserName函数,我们继续分析下一个函数sub_1245040,这个函数会根据我们的userName,由固定的算法生成我们需要的5个值,算法见脚本。

之后便是对regCode进行一个类base64的解码了,解码之后的字符串要求为32的长度,且最后8个字节中,分别为”8102\x00\x00\x00\x00”,类base64解码见之后的脚本。

接下来即是AES128Encode函数,也是和基础版的不同之处。该变形AES主要进行了一下变形,声明以下几点:

  1. 程序使用AES加密的逻辑
  2. 密钥扩展进行了改变,具体的没自己写,采用抠出密钥的方式
  3. AddRoundKey的第0轮和第10 轮三,四位和cipher的异或交换了位置,密钥使用顺序与解密时相同
  4. SubBytes使用的替换表进行了更改
  5. ShiftRows行移位中,使用的是解密时的行移位,即向右移
  6. MixColumnsKey列混淆中,使用的时解密时的列混淆
  7. 在9轮加密的过程中,扩展密钥进行了变化,且进行了密钥的列混淆,混淆方式与密文所用列混淆相同

以上就是变形的AES加密算法,知道了加密,我们反推出解密即可,详见脚本。

最后在sub_1242F20处进行类抛物线方程的求解,两个方程,两个未知数。

equation

最终的注册机脚本如下,根据我们输入的合法的userName,得到相应的regCode:

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

import struct
import binascii

state = [ [0]*4 for _ in xrange(4) ]
state1 = [ [0]*4 for _ in xrange(4) ]

str = ''
str1 = ''
# 直接从程序里抠出来的轮密钥,tmpRoundKey用于二次加密时初始化RoundKey
tmpRoundKey = [
0x63,0x6C,0x65,0x77,0x67,0x65,0x6D,0x6F,0x62,0x61,0x6C,0x73,0x38,0x31,0x30,0x32,
0xE3,0x5B,0x72,0x34,0x84,0x3E,0x1F,0x5B,0xE6,0x5F,0x73,0x28,0xDE,0x6E,0x43,0x1A,
0x4E,0xEC,0x26,0x1E,0xCA,0xD2,0x39,0x45,0x2C,0x8D,0x4A,0x6D,0xF2,0xE3,0x09,0x77,
0x49,0x4A,0xE7,0xB7,0x83,0x98,0xDE,0xF2,0xAF,0x15,0x94,0x9F,0x5D,0xF6,0x9D,0xE8,
0xC3,0x9A,0x41,0xA6,0x40,0x02,0x9F,0x54,0xEF,0x17,0x0B,0xCB,0xB2,0xE1,0x96,0x23,
0x14,0xA1,0x41,0xF8,0x54,0xA3,0xDE,0xAC,0xBB,0xB4,0xD5,0x67,0x09,0x55,0x43,0x44,
0xB9,0xBF,0x14,0xFB,0xED,0x1C,0xCA,0x57,0x56,0xA8,0x1F,0x30,0x5F,0xFD,0x5C,0x74,
0xF8,0x1B,0x42,0x1C,0x15,0x07,0x88,0x4B,0x43,0xAF,0x97,0x7B,0x1C,0x52,0xCB,0x0F,
0x22,0x1F,0x59,0x6E,0x37,0x18,0xD1,0x25,0x74,0xB7,0x46,0x5E,0x68,0xE5,0x8D,0x51,
0xCB,0xB4,0x37,0x9A,0xFC,0xAC,0xE6,0xBF,0x88,0x1B,0xA0,0xE1,0xE0,0xFE,0x2D,0xB0,
0x39,0x42,0xEE,0x51,0xC5,0xEE,0x08,0xEE,0x4D,0xF5,0xA8,0x0F,0xAD,0x0B,0x85,0xBF]

RoundKey = [
0x63,0x6C,0x65,0x77,0x67,0x65,0x6D,0x6F,0x62,0x61,0x6C,0x73,0x38,0x31,0x30,0x32,
0xE3,0x5B,0x72,0x34,0x84,0x3E,0x1F,0x5B,0xE6,0x5F,0x73,0x28,0xDE,0x6E,0x43,0x1A,
0x4E,0xEC,0x26,0x1E,0xCA,0xD2,0x39,0x45,0x2C,0x8D,0x4A,0x6D,0xF2,0xE3,0x09,0x77,
0x49,0x4A,0xE7,0xB7,0x83,0x98,0xDE,0xF2,0xAF,0x15,0x94,0x9F,0x5D,0xF6,0x9D,0xE8,
0xC3,0x9A,0x41,0xA6,0x40,0x02,0x9F,0x54,0xEF,0x17,0x0B,0xCB,0xB2,0xE1,0x96,0x23,
0x14,0xA1,0x41,0xF8,0x54,0xA3,0xDE,0xAC,0xBB,0xB4,0xD5,0x67,0x09,0x55,0x43,0x44,
0xB9,0xBF,0x14,0xFB,0xED,0x1C,0xCA,0x57,0x56,0xA8,0x1F,0x30,0x5F,0xFD,0x5C,0x74,
0xF8,0x1B,0x42,0x1C,0x15,0x07,0x88,0x4B,0x43,0xAF,0x97,0x7B,0x1C,0x52,0xCB,0x0F,
0x22,0x1F,0x59,0x6E,0x37,0x18,0xD1,0x25,0x74,0xB7,0x46,0x5E,0x68,0xE5,0x8D,0x51,
0xCB,0xB4,0x37,0x9A,0xFC,0xAC,0xE6,0xBF,0x88,0x1B,0xA0,0xE1,0xE0,0xFE,0x2D,0xB0,
0x39,0x42,0xEE,0x51,0xC5,0xEE,0x08,0xEE,0x4D,0xF5,0xA8,0x0F,0xAD,0x0B,0x85,0xBF]

testSbox = []

testRsbox = [
0x29,0x6B,0xEE,0xBC,0x11,0xA2,0xE4,0xE1,0xB2,0x0B,0x2F,0xD7,0x65,0x3C,0x27,0x09,
0x73,0xE7,0x43,0xDE,0x3B,0x22,0x9E,0x99,0xF2,0x8B,0xAB,0xA7,0x6E,0x92,0x17,0x7E,
0xD3,0xA5,0x5D,0x16,0x93,0x13,0x1F,0x81,0xBF,0xA1,0x4E,0x57,0x4D,0xDC,0x15,0xBB,
0xAD,0x25,0x03,0xAE,0xD2,0x4A,0xF1,0xC4,0x7B,0xBD,0x07,0xD0,0xA4,0xE3,0xCA,0x69,
0x60,0x77,0xC2,0xEB,0x58,0xD1,0x68,0x7D,0x42,0x8D,0xC3,0xE2,0xB7,0x86,0xC5,0x08,
0xDD,0xF8,0x48,0xFC,0xB0,0xFF,0x24,0x6D,0x9B,0x59,0x0C,0x63,0x1A,0x4C,0x1D,0xB8,
0x76,0x0F,0xDA,0x38,0xE8,0xEF,0x9D,0xD8,0x9C,0x9A,0x1B,0x47,0x01,0x2A,0x39,0x23,
0x8C,0x35,0xCB,0x30,0xCE,0x05,0x98,0xCF,0x32,0xFD,0x31,0x10,0xF5,0xE5,0xA0,0x5E,
0x3F,0x72,0x82,0x19,0xDF,0xFA,0x3A,0xB4,0x95,0x54,0x94,0xC0,0xD6,0x61,0x33,0x64,
0x85,0x96,0xA6,0x91,0xC6,0xC7,0x3E,0x3D,0x87,0x34,0xAF,0x50,0x00,0x14,0x2C,0x06,
0xEA,0xA8,0x52,0x6A,0x62,0xB6,0x37,0xA9,0xF3,0x5C,0x6C,0xB3,0x67,0x40,0x04,0x2D,
0x44,0x66,0xEC,0x4B,0x49,0xF0,0x8E,0x1C,0x9F,0x90,0x28,0x74,0xC9,0x41,0x20,0x89,
0xD4,0x80,0x0D,0x78,0x1E,0xC1,0xF9,0xD5,0xFE,0xA3,0x46,0x0E,0x7F,0x83,0x55,0xB5,
0x2E,0x7A,0x97,0x88,0xC8,0xE9,0xE6,0x7C,0x53,0x0A,0xF7,0x18,0xD9,0x56,0xF4,0x75,
0x2B,0xE0,0x12,0x21,0x8F,0x8A,0x51,0xAC,0x36,0xED,0xCD,0x45,0x5F,0xCC,0x84,0xDB,
0x5A,0x70,0x5B,0xB9,0x02,0x79,0x4F,0xFB,0x6F,0xBA,0xF6,0xB1,0xBE,0xAA,0x26,0x71]

Key = None
Iv = None

#The number of columns comprising a state in AES. This is a constant in AES. Value=4
Nb = 4
#The number of 32 bit words in a key.
Nk = 4
#Key length in bytes [128 bit]
KEYLEN = 16
#The number of rounds in AES Cipher.
Nr = 10
output =[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
# encode and decode both use it

def getSBoxValue(num):
return testSbox[num]

def getSBoxInvert(num):
return testRsbox[num]#rsbox[num]

def KeyExpansion():
return 0


def AddRoundKey(round):
for i in range(0,4):
state[0][i] ^= RoundKey[round * Nb * 4 + i * Nb + 0]
state[1][i] ^= RoundKey[round * Nb * 4 + i * Nb + 1]
state[2][i] ^= RoundKey[round * Nb * 4 + i * Nb + 3]
state[3][i] ^= RoundKey[round * Nb * 4 + i * Nb + 2]

def ShiftRows():
#Rotate first row 1 columns to left
temp = state[0][1]
state[0][1] = state[1][1]
state[1][1] = state[2][1]
state[2][1] = state[3][1]
state[3][1] = temp

#Rotate second row 2 columns to left
temp = state[0][2]
state[0][2] = state[2][2]
state[2][2] = temp

temp = state[1][2]
state[1][2] = state[3][2]
state[3][2] = temp

#Rotate third row 3 columns to left
temp = state[0][3]
state[0][3] = state[3][3]
state[3][3] = state[2][3]
state[2][3] = state[1][3]
state[1][3] = temp

def xtime(x):
return (((x<<1) ^ (((x>>7) & 1) * 0x1b))%256)



def Multiply(x, y):
return (((y & 1) * x) ^
((y>>1 & 1) * xtime(x)) ^
((y>>2 & 1) * xtime(xtime(x))) ^
((y>>3 & 1) * xtime(xtime(xtime(x)))) ^
((y>>4 & 1) * xtime(xtime(xtime(xtime(x))))))


def InvMixColumns():
for i in range(0,4):
a = state[0][i]
b = state[1][i]
c = state[2][i]
d = state[3][i]

state[0][i] = Multiply(a, 0x02) ^ Multiply(b, 0x03) ^ Multiply(c, 0x01) ^ Multiply(d, 0x01)
state[1][i] = Multiply(a, 0x01) ^ Multiply(b, 0x02) ^ Multiply(c, 0x03) ^ Multiply(d, 0x01)
state[2][i] = Multiply(a, 0x01) ^ Multiply(b, 0x01) ^ Multiply(c, 0x02) ^ Multiply(d, 0x03)
state[3][i] = Multiply(a, 0x03) ^ Multiply(b, 0x01) ^ Multiply(c, 0x01) ^ Multiply(d, 0x02)

def InvSubBytes():
for i in range(0,4):
for j in range(0,4):
try:
state[j][i] = getSBoxInvert(state[j][i])
except:
pass


def InvShiftRowsKey(round):
for i in range(0,4):
state1[0][i] = RoundKey[round * Nb * 4 + i * Nb + 0]
state1[1][i] = RoundKey[round * Nb * 4 + i * Nb + 1]
state1[3][i] = RoundKey[round * Nb * 4 + i * Nb + 2]
state1[2][i] = RoundKey[round * Nb * 4 + i * Nb + 3]
for i in range(0,4):
RoundKey[round * Nb * 4 + i * Nb + 0] = state1[i][0]
RoundKey[round * Nb * 4 + i * Nb + 1] = state1[i][1]
RoundKey[round * Nb * 4 + i * Nb + 2] = state1[i][2]
RoundKey[round * Nb * 4 + i * Nb + 3] = state1[i][3]

def InvMixColumnsKey(round):
for i in range(0,4):
state1[0][i] = RoundKey[round * Nb * 4 + i * Nb + 0]
state1[1][i] = RoundKey[round * Nb * 4 + i * Nb + 1]
state1[2][i] = RoundKey[round * Nb * 4 + i * Nb + 2]
state1[3][i] = RoundKey[round * Nb * 4 + i * Nb + 3]

for i in range(0,4):
a = state1[i][0]
b = state1[i][1]
c = state1[i][2]
d = state1[i][3]
state1[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09)
state1[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d)
state1[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b)
state1[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e)
for i in range(0,4):
RoundKey[round * Nb * 4 + i * Nb + 0] = state1[0][i]
RoundKey[round * Nb * 4 + i * Nb + 1] = state1[1][i]
RoundKey[round * Nb * 4 + i * Nb + 2] = state1[2][i]
RoundKey[round * Nb * 4 + i * Nb + 3] = state1[3][i]

def new_AddRoundKey(round):
for i in range(0,4):
state[i][0] ^= RoundKey[round * Nb * 4 + i * Nb + 0]
state[i][1] ^= RoundKey[round * Nb * 4 + i * Nb + 1]
state[i][2] ^= RoundKey[round * Nb * 4 + i * Nb + 2]
state[i][3] ^= RoundKey[round * Nb * 4 + i * Nb + 3]

def InvCipher():
#Add the First round key to the state before starting the rounds.
AddRoundKey(0)

#There will be Nr rounds.
#The first Nr-1 rounds are identical.
#These Nr-1 rounds are executed in the loop below.
for round in range(1, Nr):
ShiftRows() #InvShiftRows()
InvSubBytes()
InvShiftRowsKey(round)
InvMixColumnsKey(round)
new_AddRoundKey(round)
InvMixColumns()

#The last round is given below.
#The MixColumns function is not here in the last round.
ShiftRows()
InvSubBytes()
AddRoundKey(Nr)


def AES128_ECB_decrypt(input,output):
#tmp2 = [ord(ch) for ch in tmp2]
#Copy input to output, and work in-memory on output
for i in range(0,4):
for j in range(0, 4):
state[j][i] = input[i*4 + j]

#The KeyExpansion routine must be called before encryption.
#KeyExpansion()
global str1
InvCipher()
for i in range(0, 4):
for j in range (0, 4):
output[i*4+j] = state[j][i]
for ch in output:
str1 += chr(ch)
#获得aes解密后的字符串


###############第一部分-解方程得到a6,a7,a8###############
# calculate the UserName

base64Table = "ZO6Kq79L&CPWvNopzQfghDRSG@di*kAB8rsFewxlm+/u5a^2YtTJUVEn0$HI34y#"

print "Input your 39 bytes userName! e.g xxxx#xxxx#xxxx#xxxx#xxxx#xxxx#xxxx#xxxx\n"
print "x belong to A,B,C,D,E and number0-9!"
userName = raw_input()
userName = userName.split('#')
a1 = userName[0]
a2 = userName[1]
a3 = userName[2]
a4 = userName[3]
a5 = userName[4]
a6 = userName[5]
a7 = userName[6]
a8 = userName[7]

val1 = ord(a2[0]) * ord(a1[0]) << 16
val1 += ord(a1[1]) ^ ord(a3[1])
val1 += ord(a1[2]) % (ord(a4[2]) + 1) + 1
val1 += ord(a1[3]) / (ord(a5[3]) + 1)

val2 = (ord(a2[0]) ^ ord(a6[0])) << 16
val2 += ord(a2[1]) % (ord(a7[1]) + 3)
val2 += ord(a2[2]) / (ord(a8[2]) + 1) + 5
val2 += ord(a2[3]) + ord(a1[3])

val3 = ord(a3[0]) / (ord(a2[0]) + 3) << 16
val3 ^= ord(a3[1]) % ord(a4[1])
val3 += ord(a6[2]) + 12 + ord(a3[2])
val3 += ord(a3[3]) + ord(a8[3])

val4 = ord(a1[1]) ^ ord(a3[3])
val4 *= (ord(a2[3]) + ord(a4[1]))
val4 &= (ord(a5[2]) & ord(a6[2]))
val4 *= ord(a8[3])
val4 += val2
val4 = (ord(a7[0]) * val4) * val1
val4 = val4 - ((val4 - val2) % (2 * val1))

val5 = (ord(a5[0]) ^ ord(a4[0])) << 16
val5 *= (ord(a4[1]) % (ord(a5[1])+2))
val5 += ord(a4[2]) % (ord(a5[2])+5) + 7
val5 += ord(a5[3]) * ord(a4[3])

a6 = (val4 - val2) / (2*val1)
a7 = (val4*val4 + 4*val1*val3 - val2*val2) / (4*val1)
a8 = val3 + (val2 + val1 * val5 - val4) * val5

str = struct.pack("<Q",a6&0xffffffffffffffff)
str += struct.pack("<Q",a7&0xffffffffffffffff)
str += struct.pack("<Q",a8&0xffffffffffffffff)
str += "8102"
str += "\x00\x00\x00\x00"
# calculate the UserName
###############第一部分-解方程得到a6,a7,a8###############

###############第二部分-AES解密解方程得到的a6,a7,a8###############
# AES128 decode

tmp1 = str[:16]
tmp2 = str[16:32]
tmp1 = [ord(ch) for ch in tmp1]
tmp2 = [ord(ch) for ch in tmp2]
#tmp1 = [0x57, 0xae, 0xfa, 0xc3, 0x35, 0xab, 0x72, 0xcd, 0xed, 0xa5, 0x0, 0x80, 0x36, 0xe3, 0x72, 0xdb]
#tmp2 = [0xae, 0x12, 0x7b, 0xa5, 0x20, 0x44, 0xd3, 0x8a, 0xdf, 0xd1, 0x31, 0x6b, 0x19, 0x69, 0x73, 0x51]
for i in range(0x100):
testSbox.append(testRsbox.index(i))
RoundKey = tmpRoundKey[:]


AES128_ECB_decrypt(tmp1,output)
for i in range(0x100):
testSbox.append(testRsbox.index(i))
RoundKey = tmpRoundKey[:]

AES128_ECB_decrypt(tmp2,output)

# AES128 decode
###############第二部分-AES解密解方程得到的a6,a7,a8###############


###############第三部分-编码AES解密的regCode###############
# base64encode Get regCode
print "Now get your regCode!"
enStr = ''
length = len(str1)

for i in range(length/3):
tmp = str1[:3]
# 3 to 4
num1 = ord(tmp[0]) >> 2
num2 = ((ord(tmp[0]) & 3) << 4) | ((ord(tmp[1]) & 0xF0) >> 4)
num3 = ((ord(tmp[1]) & 0xF) << 2) | (ord(tmp[2]) >> 6)
num4 = ord(tmp[2]) & 0x3F
# 3 to 4

val = (num1 & 0x38) >> 3
num1 = num1 ^ val

val = (num2 & 0x38) >> 3
num2 = num2 ^ val

val = (num3 & 0x38) >> 3
num3 = num3 ^ val

val = (num4 & 0x38) >> 3
num4 = num4 ^ val

enStr += base64Table[num1] + base64Table[num2] + base64Table[num3] + base64Table[num4]

str1 = str1[3:]

if length%3 != 0:
if length%3 == 2:
tmp = str1[:2]
num1 = ord(tmp[0]) >> 2
num2 = ((ord(tmp[0]) & 3) << 4) | ((ord(tmp[1]) & 0xF0) >> 4)
num3 = ((ord(tmp[1]) & 0xF) << 2)
val = (num1 & 0x38) >> 3
num1 = num1 ^ val

val = (num2 & 0x38) >> 3
num2 = num2 ^ val

val = (num3 & 0x38) >> 3
num3 = num3 ^ val
enStr += base64Table[num1] + base64Table[num2] + base64Table[num3]
enStr += '='
if length%3 == 1:
tmp = str1[:1]
num1 = ord(tmp[0]) >> 2
num2 = ((ord(tmp[0]) & 3) << 4)
val = (num1 & 0x38) >> 3
num1 = num1 ^ val
val = (num2 & 0x38) >> 3
num2 = num2 ^ val
enStr += base64Table[num1] + base64Table[num2]
enStr += '=='

print "Encode regCode:",enStr
# base64encode Get regCode
###############第三部分-编码AES解密的regCode###############

example

example1

example2

决赛第1题-PC-不区分

打开程序,分析了一阵,没发现什么东西,ProcessMonitor监测程序行为。

final_round1_pc1

发现opengl32.dll和glu32.dll这些和图形处理有关的函数。上网搜索学习了一波基于win32的opengl框架,重新命名了程序的大多数函数名称,整理如下图:

final_round1_pc2

final_round1_pc3

final_round1_pc4

final_round1_pc5

进入鼠标的回调函数,修改鼠标的移动范围:

final_round1_pc6

全部nop掉以后,我们可以没有范围地移动我们的鼠标了,运行esp.exe,得到:

final_round1_pc7

那么,我们的目的大概就是转换我们看这个图的角度,大概就能得到些什么信息了。现在重要的就是修改where we are?我们所在点的位置,在程序中找到如下图:

final_round1_pc8

尝试修改上面三处dword的位置。我利用远程线程注入的方式,修改这三处的位置,dll代码如下(其中注册键盘回调的方式失败,关键点换成了无限循环):

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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include<Windows.h>
#include<math.h>
#define GLEW_STATIC
#include<GL/glew.h>
#include<GLFW/glfw3.h>
#include<GLFW/glfw3native.h>

#pragma comment(lib,"glew32s.lib")
#pragma comment(lib,"glew32.lib")
#pragma comment(lib,"glfw3.lib")
#pragma comment(lib,"glfw3dll.lib")


float *x, *y, *z;

void myKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
/*
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
;
}
*/
if (key == GLFW_KEY_Q && action == GLFW_PRESS)
{
*z -= 2.0;
}
if (key == GLFW_KEY_E && action == GLFW_PRESS)
{
*z += 2.0;
}
if (key == GLFW_KEY_UP && action == GLFW_PRESS)
{
*y += 2.0;
}
if (key == GLFW_KEY_DOWN && action == GLFW_PRESS)
{
*y -= 2.0;
}
if (key == GLFW_KEY_LEFT && action == GLFW_PRESS)
{
*x -= 2.0;
}
if (key == GLFW_KEY_RIGHT && action == GLFW_PRESS)
{
*x += 2.0;
}
}

void AirRoam(void)
{
HMODULE hGlu32,hOpengl32,hEsp;
HWND hCurWindow;
hEsp = GetModuleHandleA(NULL);
int xOffset = 0x64CC4;
int yOffset = 0x64CC8;
int zOffset = 0x64CCC;
x = (float *)((DWORD)hEsp + xOffset);
y = (float *)((DWORD)hEsp + yOffset);
z = (float *)((DWORD)hEsp + zOffset);
hGlu32 = GetModuleHandleA("glu32.dlls");
hOpengl32 = GetModuleHandleA("opengl32.dll");
MessageBox(NULL, L"hello", L"hello", MB_OK);
hCurWindow = FindWindowA(NULL, "XDDDDDDDDD");

glfwSetKeyCallback((GLFWwindow *)hCurWindow, myKeyCallback);

while (1)
{
Sleep(1000);
*x += 3.0;
Sleep(1000);
*y -= 0.2;
Sleep(1000);
*z += 4.6;
}

}

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
AirRoam();
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

远程注入代码如下:

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

BOOL SetPrivilege(LPCTSTR lpszPrivilege,BOOL bEnablePrivilege)
{
TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;

if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken))
{
wprintf(L"OpenProcessToken Failed!CODE(%u)\n",GetLastError());
return FALSE;
}
if(!LookupPrivilegeValue(NULL,lpszPrivilege,&luid))
{
wprintf(L"LookupPrivilegeValue Failed!CODE(%u)\n",GetLastError());
return FALSE;
}
tp.PrivilegeCount=1;
tp.Privileges[0].Luid=luid;
if(bEnablePrivilege)
{
tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
}
else
{
tp.Privileges[0].Attributes=0;
}
//Enable the privilege开启或关闭权限
if(!AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),(PTOKEN_PRIVILEGES)NULL,(PDWORD)NULL))
{
wprintf(L"AdjustTokenPrivileges Failed!CODE(%u)\n",GetLastError());
return FALSE;
}
CloseHandle(hToken);
return TRUE;
}

BOOL Remotethread_Injection(int PID,char *pDllName)
{
HANDLE hProcess;
DWORD dwDllNameLen = strlen(pDllName) + 2;
BYTE *pBufaddr = NULL;
HANDLE hRemoteThread = NULL;
PTHREAD_START_ROUTINE pfnRemoteThreadProc = NULL;
__try
{
hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,(DWORD)PID);
if(!hProcess)
{
printf("OpenProcess Error!code(%d)\n",GetLastError());
return FALSE;
}
pBufaddr = (BYTE *)VirtualAllocEx(hProcess,NULL,dwDllNameLen,MEM_COMMIT,PAGE_READWRITE);
if(!pBufaddr)
{
printf("VirtualAllocEx Error!code(%d)\n",GetLastError());
CloseHandle(hProcess);
return FALSE;
}
if(!WriteProcessMemory(hProcess,pBufaddr,pDllName,dwDllNameLen,NULL))
{
printf("WriteProcessMemory Error!code(%d)\n",GetLastError());
VirtualFreeEx(hProcess,(PVOID)pBufaddr,0,MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
pfnRemoteThreadProc = (PTHREAD_START_ROUTINE)GetProcAddress((HMODULE)LoadLibraryA("kernel32.dll"),"LoadLibraryA");
if(!pfnRemoteThreadProc)
{
printf("GetProcAddress Error!code(%d)\n",GetLastError());
VirtualFreeEx(hProcess,(PVOID)pBufaddr,0,MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
hRemoteThread = CreateRemoteThread(hProcess,NULL,0,pfnRemoteThreadProc,(PVOID)pBufaddr,0,NULL);
if(!hRemoteThread)
{
printf("CreateRemoteThreadEx Error!code(%d)\n",GetLastError());
VirtualFreeEx(hProcess,(PVOID)pBufaddr,0,MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
WaitForSingleObject(hRemoteThread,INFINITE);
}
__finally
{
if(hRemoteThread)
{
CloseHandle(hRemoteThread);
}
if(pBufaddr)
{
VirtualFreeEx(hProcess,(PVOID)pBufaddr,0,MEM_RELEASE);
}
if(hProcess)
{
CloseHandle(hProcess);
}
}
return TRUE;
}

int main(int argc,char* argv[])
{
DWORD PID = 0;
if(!argv[1])
{
printf("USAGE: %s <dll_path>\n",argv[0]);
return 0;
}
printf("Please Input process PID(大于100):\n");
scanf("%d",&PID);
if(!SetPrivilege(SE_DEBUG_NAME,TRUE))
{
return 1;
}
if(Remotethread_Injection(PID,argv[1]))
{
printf("Inject Successful!\n");
}
return 0;
}

最终在合适的位置得到flag: flag:dogod

final_round1_flag

高校组-决赛第2题-基础版-PC

下载基础版和进阶版,Beyond_Compare比较二者的不同,发现仅tmgs.dll有所不同。

final_round2_pc1

在tmgs.dll里发现了maln,null,ths三个导出函数,对其进行回溯,发现了所有可能与check处理相关的消息函数,我们对这些函数全部下断,整理出进入这些函数的先后顺序如下:

1
2
3
4
5
(OnRep_IsBot2)->Check1->OnRep_IsRunning 第一关验证所经过的消息函数

(OnRep_IsState)->OnRep_IsDrive(5次)->OnRep_IsPlayer->OnRep_IsState->OnRep_IsRunning

(OnRep_Self2)Check3->OnRep_IsRunning (OnRep_Self2中是Check3)

final_round2_pc2

其中第一关的处理逻辑在Check1中,第二关的flag在Check2中,第三关的处理逻辑在Check3中。

第一关Check1

Check1中对我们输入的字符进行处理以后,调用了tmgs.dll中的导出函数maln,maln中的关键函数:

图片丢失,见谅!

在sub_100397B0处,第二个参数是luac脚本的起始地址,第一个参数是luac脚本的大小,很明显,程序调用嵌入式脚本luac对我们的输入进行了check,dump出luac,使用luadec对其进行反汇编。由于程序的lua5.3版本在生成luac的时候进行了strip,导致luadec直接反编译报错,使用-dis进行反汇编。对反汇编后的代码逆向(以下是部分反汇编的代码):

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
0 [-]: SETTABUP  U0 K0 K1     ; U0["k"] := "F8998657AFE06DD5AA593D88FB3DB3E4"
1 [-]: GETTABUP R1 U0 K3 ; R1 := U0["rc4"]
2 [-]: GETTABUP R2 U0 K0 ; R2 := U0["k"]
3 [-]: CALL R1 2 2 ; R1 := R1(R2)
4 [-]: SETTABUP U0 K2 R1 ; U0["rc4_enc"] := R1
5 [-]: GETTABUP R1 U0 K3 ; R1 := U0["rc4"]
6 [-]: GETTABUP R2 U0 K0 ; R2 := U0["k"]
7 [-]: CALL R1 2 2 ; R1 := R1(R2)
8 [-]: SETTABUP U0 K4 R1 ; U0["rc4_dec"] := R1
9 [-]: SETTABUP U0 K5 R0 ; U0["plain"] := R0
10 [-]: GETTABUP R1 U0 K2 ; R1 := U0["rc4_enc"]
11 [-]: GETTABUP R2 U0 K5 ; R2 := U0["plain"]
12 [-]: CALL R1 2 2 ; R1 := R1(R2)
13 [-]: SETTABUP U0 K6 R1 ; U0["encrypted"] := R1
14 [-]: NEWTABLE R1 24 0 ; R1 := {} (size = 24,0)
15 [-]: LOADK R2 K8 ; R2 := 30
16 [-]: LOADK R3 K9 ; R3 := 201
17 [-]: LOADK R4 K10 ; R4 := 134
18 [-]: LOADK R5 K11 ; R5 := 139
19 [-]: LOADK R6 K12 ; R6 := 51
20 [-]: LOADK R7 K13 ; R7 := 104
21 [-]: LOADK R8 K14 ; R8 := 209
22 [-]: LOADK R9 K15 ; R9 := 164
23 [-]: LOADK R10 K16 ; R10 := 173
24 [-]: LOADK R11 K17 ; R11 := 123
25 [-]: LOADK R12 K10 ; R12 := 134
26 [-]: LOADK R13 K18 ; R13 := 116
27 [-]: LOADK R14 K19 ; R14 := 7
28 [-]: LOADK R15 K20 ; R15 := 28
29 [-]: LOADK R16 K21 ; R16 := 238
30 [-]: LOADK R17 K22 ; R17 := 110
31 [-]: LOADK R18 K23 ; R18 := 135
32 [-]: LOADK R19 K24 ; R19 := 120
33 [-]: LOADK R20 K25 ; R20 := 129
34 [-]: LOADK R21 K26 ; R21 := 71
35 [-]: LOADK R22 K27 ; R22 := 107
36 [-]: LOADK R23 K28 ; R23 := 187
37 [-]: LOADK R24 K29 ; R24 := 237
38 [-]: LOADK R25 K30 ; R25 := 152
39 [-]: LOADK R26 K31 ; R26 := 111
40 [-]: LOADK R27 K32 ; R27 := 202
41 [-]: LOADK R28 K33 ; R28 := 218
42 [-]: LOADK R29 K34 ; R29 := 192
43 [-]: LOADK R30 K35 ; R30 := 212
44 [-]: LOADK R31 K36 ; R31 := 86
45 [-]: LOADK R32 K33 ; R32 := 218
46 [-]: LOADK R33 K14 ; R33 := 209
47 [-]: SETLIST R1 32 1 ; R1[0] to R1[31] := R2 to R33 ; R(a)[(c-1)*FPF+i] := R(a+i), 1 <= i <= b, a=1, b=32, c=1, FPF=50
48 [-]: SETTABUP U0 K7 R1 ; U0["dst"] := R1
49 [-]: SETTABUP U0 K37 K38 ; U0["res"] := ""
50 [-]: SETTABUP U0 K39 K40 ; U0["i"] := 1
51 [-]: GETTABUP R1 U0 K39 ; R1 := U0["i"]
52 [-]: GETTABUP R2 U0 K41 ; R2 := U0["string"]
53 [-]: GETTABLE R2 R2 K42 ; R2 := R2["len"]
54 [-]: GETTABUP R3 U0 K6 ; R3 := U0["encrypted"]
55 [-]: CALL R2 2 2 ; R2 := R2(R3)
56 [-]: LE 0 R1 R2 ; if R1 <= R2 then goto 58 else goto 80
57 [-]: JMP R0 22 ; PC += 22 (goto 80)
58 [-]: GETTABUP R1 U0 K41 ; R1 := U0["string"]
59 [-]: GETTABLE R1 R1 K44 ; R1 := R1["sub"]
60 [-]: GETTABUP R2 U0 K6 ; R2 := U0["encrypted"]
61 [-]: GETTABUP R3 U0 K39 ; R3 := U0["i"]
62 [-]: GETTABUP R4 U0 K39 ; R4 := U0["i"]
63 [-]: CALL R1 4 2 ; R1 := R1(R2 to R4)
64 [-]: SETTABUP U0 K43 R1 ; U0["v"] := R1
65 [-]: GETTABUP R1 U0 K41 ; R1 := U0["string"]
66 [-]: GETTABLE R1 R1 K45 ; R1 := R1["byte"]
67 [-]: GETTABUP R2 U0 K43 ; R2 := U0["v"]
68 [-]: CALL R1 2 2 ; R1 := R1(R2)
69 [-]: GETTABUP R2 U0 K7 ; R2 := U0["dst"]
70 [-]: GETTABUP R3 U0 K39 ; R3 := U0["i"]
71 [-]: GETTABLE R2 R2 R3 ; R2 := R2[R3]
72 [-]: EQ 1 R1 R2 ; if R1 ~= R2 then goto 74 else goto 76
73 [-]: JMP R0 2 ; PC += 2 (goto 76)
74 [-]: LOADBOOL R1 0 0 ; R1 := false
75 [-]: RETURN R1 2 ; return R1
76 [-]: GETTABUP R1 U0 K39 ; R1 := U0["i"]
77 [-]: ADD R1 R1 K40 ; R1 := R1 + 1
78 [-]: SETTABUP U0 K39 R1 ; U0["i"] := R1
79 [-]: JMP R0 -29 ; PC += -29 (goto 51)
80 [-]: LOADBOOL R1 1 0 ; R1 := true
81 [-]: RETURN R1 2 ; return R1
82 [-]: RETURN R0 1 ; return

该程序使用了rc4算法,其中key是F8998657AFE06DD5AA593D88FB3DB3E4,编写C程序解密。

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
#include <stdio.h>
#include <string.h>
#include<Windows.h>
void rc4_init(unsigned char* IV, unsigned char* Key, int Len)
{
int i = 0, j = 0;
unsigned char k[256] = { 0 };
unsigned char tmp = 0;
for (i = 0; i<256; i++)
{
IV[i] = i;
k[i] = Key[i%Len];
}
for (i = 0; i<256; i++)
{
j = (j + IV[i] + k[i]) % 256;
tmp = IV[i];
IV[i] = IV[j];
IV[j] = tmp;
}
}

void rc4_crypt(unsigned char* IV, unsigned char* Data, int Len)
{
int i = 0, j = 0, t = 0;
long k = 0;
unsigned char tmp;
for (k = 0; k<Len; k++)
{
i = (i + 1) % 256;
j = (j + IV[i]) % 256;
tmp = IV[i];
IV[i] = IV[j];
IV[j] = tmp;
t = (IV[i] + IV[j]) % 256;
Data[k] ^= IV[t];
printf("%c", Data[k]);
}
printf("\n");
}

int main()
{
unsigned char cipher[] = { 30, 201, 134, 139, 51, 104, 209, 164, 173, 123, 134, 116, 7, 28, 238, 110, 135, 120, 129, 71, 107, 187, 237, 152, 111, 202, 218, 192, 212, 86, 218, 209 };
char *Key = (char *)"F8998657AFE06DD5AA593D88FB3DB3E4";
char iv[256] = { 0, };
rc4_init((unsigned char *)iv, (unsigned char *)Key, 32);
rc4_crypt((unsigned char *)iv, cipher, 32);
system("pause");
return 0;
}

得到第一关的flag:C3F6B4473DB70B38B554F6F3C2E6058C

第二关Check

使用OllyDbg在OnRep_IsPlayer函数中跟踪函数处理流程,在一块内存中发现了如下字符串:

图片丢失,见谅!

之后,程序对这串字符进行了hash,猜测该字符就是flag,输入通关!

flag:tencent_mobile_game+-1133166080

第三关Check

第三关Check同样是luac脚本处理我们的输入逻辑,和第一关相同的dump方法,dump出luac脚本,进行反汇编,反汇编的代码有些长,就不贴出来了。这一关的luac模拟了虚拟机执行,对程序中硬编码的opCode进行的解释,所以我们的思路就是dump出opCode,然后找到虚拟机的Init和dispatch,弄懂dipatch的每个函数功能,制作出一个能模拟其执行的vm脚本,得到虚拟指令流,分析虚拟指令流,然后写出得到第三关flag的脚本。

虚拟机的大体情况如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
bs
{
new:0_0
move:0_1
pop:0_2 bs的pop是pop opcode中的数据
pop_raw:0_3 转化为整形
readByte:0_4
readInt:0_5 读4字节
readStr:0_6 readInt->pop_raw->读后面相应长度的字符串,并返回
bPos:0_7

}

kcjisaojeje17da653:0_11
kcjisaojejea2f65d7:0_12
kcjisaojejb8ffa3c2:0_13
kcjisaojeje37f48a6:0_14
kcjisaojejd56265d9:0_15
kcjisaojejb0cd87ba:0_16
kcjisaojejff577802:0_17
file2array:0_18
str2array:0_19
dump_table:0_20
dumpTable:0_21
klcvjbidfog:0_22
LOG:0_23
LOGPH:0_24
getByte:0_25
dw2Byte:0_26
putDword:0_27
putByte:0_28
saveEncrypt:0_29

stack
{
new:0_8
push:0_9 push栈中的数据
pop:0_10 pop栈中的数据

}

jviwjeowjie = weioeurwiuioiu

vm
{
new:0_30
exe:0_31 dispatch
b61531b9:0_32
e5a13297:0_33
a115183a:0_34
set:0_35
c30c39a5:0_36
e9dcc004:0_37
b8eb468b:0_38
e29d3db5:0_39
ue9dcc004:0_40
fb6ea852:0_41
ddbe0eb4:0_42

}

jviwjeowjie[0] = 0_32 push Int
jviwjeowjie[1] = 0_33 push env[readStr()]
jviwjeowjie[2] = set 0_35 env[readStr()] = stack.pop() #env[cur]
jviwjeowjie[3] = 0_36 pop栈顶2个元素 push(add())
jviwjeowjie[4] = 0_37 pop栈顶2个元素 push(sub())
jviwjeowjie[5] = 0_38 pop栈顶2个元素 push(mul())
jviwjeowjie[6] = 0_39 pop栈顶2个元素 push(div())
jviwjeowjie[7] = 0_22(0_11,7) pop 2个数 push(mod())
jviwjeowjie[8] = 0_40 push(~stack.pop())
jviwjeowjie[9] = 0_41 pop出标志位,根据标志位,跳转bs.move(bs.readInt) true+1 false+readInt()
jviwjeowjie[10] = 0_22(0_17,10) pop 2个数 push(check >=)
jviwjeowjie[11] = 0_22(0_16,11) pop 2个数 push(check <=)
jviwjeowjie[12] = 0_22(0_15,12) pop 2个数 push(check >)
jviwjeowjie[13] = 0_22(0_14,13) pop 2个数 push(check <)
jviwjeowjie[14] = 0_22(0_13,14) pop 2个数 push(check !=)
jviwjeowjie[15] = 0_22(0_12,15) pop 2个数 push(check ==)
jviwjeowjie[16] = 0_42 if readStr() == 'func' call(readStr())
jviwjeowjie[17] = 0_34 stack.push(readStr())

check 0_43

其中最重要的jviwjeowjie就是dispatch的每个函数了,知道了其大致的功能,便是着手写模拟其执行的脚本了,如下:

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

import struct
import os

stack = []
env = {}
mark = 0
ans = ''
b64 = ''
ciphertext = 'mCJ4lu/IDuuOVdLV8GvdlccdzuckNdckm/mddV90eXZylbLqJ75QdlZiMJ46rmL6k5gOlDY8uFZ0YG4EOrf0lEjN4bkMnF/X0mXWliucL5QHafQH1AEQlVQPTYkM58kMG3V3lik4WGlBFJiBOrf0lyLiWWZMcGi7Orf0lW/ViXWQVX/s1AEQlXkcd2Zi5rYiogfglF/ynF/ynF/y'

if __name__ == "__main__":
vm = open('opCode','rb')
print "give me your input"
plaintext = raw_input()
#tmp = vm.read(1)
while(1):
tmp = vm.read(1)
if len(tmp) == 0:
break
if tmp == '\x00':#已验证
temp = vm.read(4)
print "push DWORD:0x%X" % struct.unpack('<i',temp)[0]
stack.append((struct.unpack('<i',temp)[0]))
#print "current location:0x%X" % vm.tell()
#raw_input()

elif tmp == '\x01':#已验证
length = vm.read(4)
length = struct.unpack('<i',length)[0]
temp = vm.read(length)
stack.append(env[temp])
print "push(env[%s])" % (temp)
#print "current location:%X" % vm.tell()
#raw_input()

elif tmp == '\x02':#已验证
length = vm.read(4)
length = struct.unpack('<i',length)[0]
temp = vm.read(length)
env[temp] = stack.pop()
print "env[%s] = stack.pop()" % (temp)
#print "current location:%X" % vm.tell()
#raw_input()

elif tmp == '\x03':
a = stack.pop()
b = stack.pop()
stack.append(a+b)
print "pop 0x%X pop 0x%X push (a+b)0x%X" % (a,b,(a+b))
#print "current location:%X" % vm.tell()
#raw_input()

elif tmp == '\x04':
a = stack.pop()
b = stack.pop()
stack.append(b-a)
print "pop 0x%X pop 0x%X push (b-a)0x%X" % (a,b,(b-a))
#print "current location:%X" % vm.tell()
#raw_input()

elif tmp == '\x05':#已验证
a = stack.pop()
b = stack.pop()
stack.append(a*b)
print "pop 0x%X pop 0x%X push (a*b)%X" % (a,b,(a*b))
#print "current location:%X" % vm.tell()
#raw_input()

elif tmp == '\x06':
a = stack.pop()
b = stack.pop()
stack.append(b/a)
print "pop 0x%X pop 0x%X push (b/a)0x%X" % (a,b,(b/a))
#print "current location:%X" % vm.tell()
#raw_input()

elif tmp == '\x07':
a = stack.pop()
b = stack.pop()
stack.append(b%a)
print "pop 0x%X pop 0x%X push 0x%X" % (a,b,(b%a))
#print "current location:%X" % vm.tell()
#raw_input()

elif tmp == '\x08':
a = stack.pop()
a = ~a
stack.append(a)
print "push(~stack.pop()) 0x%X" % a
#print "current location:%X" % vm.tell()
#raw_input()

elif tmp == '\x09':#已验证
mark = stack.pop()
offset = vm.read(4)
offset = struct.unpack('<i',offset)[0]
if (mark == 0):
vm.seek(offset,os.SEEK_CUR)
print "jmp %X" % offset
else:
print "not jmp only pc+1"
#print "current location:%X" % vm.tell()
#raw_input()

elif tmp == '\x0a':
a = stack.pop()
b = stack.pop()
if a>=b:
mark = 0
else:
mark = 1
stack.append(mark)
print "pop 0x%X pop 0x%X cmp a>=b push%X" % (a,b,mark)
#print "current location:%X" % vm.tell()
#raw_input()

elif tmp == '\x0b':
a = stack.pop()
b = stack.pop()
if b>=a:
mark = 0
else:
mark = 1
stack.append(mark)
print "pop 0x%X pop 0x%X cmp b>=a push%X" % (a,b,mark)
#print "current location:%X" % vm.tell()
#raw_input()

elif tmp == '\x0c':
a = stack.pop()
b = stack.pop()
if a>b:
mark = 0
else:
mark = 1
stack.append(mark)
print "pop 0x%X pop 0x%X cmp a>b push%X" % (a,b,mark)
#print "current location:%X" % vm.tell()
#raw_input()

elif tmp == '\x0d':
a = stack.pop()
b = stack.pop()
if b>a:
mark = 0
else:
mark = 1
stack.append(mark)
print "pop 0x%X pop 0x%X cmp b>a push%X" % (a,b,mark)
#print "current location:%X" % vm.tell()
#raw_input()

elif tmp == '\x0e':#已验证
a = stack.pop()
b = stack.pop()
if a==b:
mark = 0
else:
mark = 1
stack.append(mark)
print "pop 0x%X pop 0x%X cmp a==b push%X" % (a,b,mark)
#print "current location:%X" % vm.tell()
#raw_input()

elif tmp == '\x0f':
a = stack.pop()
b = stack.pop()
if a!=b:
mark = 0
else:
mark = 1
stack.append(mark)
print "pop 0x%X pop 0x%X cmp a!=b push%X" % (a,b,mark)
#print "current location:%X" % vm.tell()
#raw_input()

elif tmp == '\x10': #已验证
length = vm.read(4)
length = struct.unpack('<i',length)[0]
print type(length)
funcName = vm.read(length)
print "funcName:",funcName
if funcName == 'getByte':
stack.pop()
if len(plaintext) == 0:
tmp = 0
else:
tmp = ord(plaintext[:1])
stack.append(tmp)
plaintext = plaintext[1:]
print "getByte %c" % tmp
#print "current location:%X" % vm.tell()
#raw_input()
elif funcName == 'rshift':
stack.pop()
tmp = stack.pop()
bit = stack.pop()
tmp = (tmp >> bit)
stack.append(tmp)
print "%X >> %d push (tmp >> bit)" % (tmp,bit)
#print "current location:%X" % vm.tell()
#raw_input()
elif funcName == 'band':
stack.pop()
tmp = stack.pop()
bit = stack.pop()
tmp = tmp & bit
stack.append(tmp)
print "%X & %d push (tmp & bit)" % (tmp,bit)
#print "current location:%X" % vm.tell()
#raw_input()
elif funcName == 'stringsub':
stack.pop()
tmp = stack.pop()
front = stack.pop()
back = stack.pop()
tmp = tmp[front-1:back]
stack.append(tmp)
print "push tmp[%X:%X]" % (front-1,back)
#print "current location:%X" % vm.tell()
#raw_input()
elif funcName == 'putByte':
stack.pop()
tmp = stack.pop()
ans += (tmp)
print "encrypted str:%c\nencrypted str:%s" % (tmp,ans)
#print "current location:%X" % vm.tell()
#raw_input()

elif tmp == '\x11':#已验证
length = vm.read(4)
length = struct.unpack('<i',length)[0]
b64 += vm.read(length)
stack.append(b64)
print "stack append b64"
'''
while(1):
temp = vm.read(1)
if temp == '\x00':
stack.append(str1)
break
str1 += temp
'''
#print "current location:%X" % vm.tell()
#raw_input()
print "encrypted str:%s" % ans

根据模拟分析其的虚拟指令流,最终得到爆破level3的脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/usr/bin/env python2
#-*- coding:utf-8 -*-
import string

b64 = 'ld4LiOz3F0bpyCNgWQBkr6HahGM1f85ocJ9/VUeTEmwqDPIsuvnZYRKjX7+ASt2x='
ciphertext = 'mCJ4lu/IDuuOVdLV8GvdlccdzuckNdckm/mddV90eXZylbLqJ75QdlZiMJ46rmL6k5gOlDY8uFZ0YG4EOrf0lEjN4bkMnF/X0mXWliucL5QHafQH1AEQlVQPTYkM58kMG3V3lik4WGlBFJiBOrf0lyLiWWZMcGi7Orf0lW/ViXWQVX/s1AEQlXkcd2Zi5rYiogfglF/ynF/ynF/y'
cmpStr = []
ans = ''
finalAns = ['0']*13
table = string.printable + '\x00'

def level3(plaintext):
ans = ''
tmp1 = ord(plaintext[0])
tmp2 = ord(plaintext[1])
tmp3 = ord(plaintext[2])
tmp4 = ord(plaintext[3])

res1 = 14*tmp1**3 + 5*tmp1**2 + 15*tmp1 + 125
for i in range(4):
ans += b64[(res1>>(i*6))&0x3F]

savebyte = (res1 >> 24) % 256

tmp = tmp2+tmp3*256+tmp4*(256**2)
res2 = (tmp%149)*256 + (tmp%213)*(256**2) + savebyte
for i in range(4):
ans += b64[(res2>>(i*6))&0x3F]
res2 = (tmp%142) + (tmp%66)*256 + (tmp%121)*(256**2)
for i in range(4):
ans += b64[(res2>>(i*6))&0x3F]
res2 = (tmp%143) + (tmp%78)*256 + (tmp%242)*(256**2)
for i in range(4):
ans += b64[(res2>>(i*6))&0x3F]

if ans in cmpStr:
cipherIndex = cmpStr.index(ans)
print "find:%s" % plaintext
finalAns[cipherIndex] = plaintext

plaintext = ''
for i in range(len(ciphertext)/16):
cmpStr.append(ciphertext[i*16:i*16+16])

print cmpStr
for x1 in table:
for x2 in table:
for x3 in table:
for x4 in table:
plaintext = x1 + x2 + x3 + x4
level3(plaintext)

print "plaintext: %s" % ''.join(finalAns)

最终得到第三关的flag:$llo punk hash….0F8F3FD185E10B4BAC8F4A8221E135B2

输入三个flag,通关!

高校组-决赛第2题-进阶版-PC

进阶版当时没有做出来,核心原理是修改了lua虚拟机的opcode,将之乱序。下来之后复现了一下核心部分,也就是lua源代码修改部分。我将之出在了北邮TSCTF2019比赛中,题目名为GameProtect。

re

GameProtect

一道Lua游戏保护逆向

程序加载了GameProtect配置文件。

lua_game_1

在该位置查看edx内存处的值:

lua_game_2

程序加载了GameProtect文件,GameProtect文件中有明显的校验flag的内容。查看一下导入表以及字符串。字符串可以发现:

lua_game_3

所以该脚本应该是一个被修改过的luac(结合luac文件格式),也就是lua5.1的解析器源代码被修改过,搭建lua5.1正向环境以及luadec反编译环境,修改lundump.h头部大小以及lua.h中的SIGNATURE,编译lua.exe以及luac.exe,以及luadec.exe,luadec.exe反汇编失败。

根据initial value must be a number字符串查找到源代码lvm.c中解释opcode的二进制代码,发现程序修改了OpCode的顺序,将未修改过Opcode顺序的lua.exe与GameProtect.exe解释opcode的二进制代码作比对。

lua_game_4

很明显,左边自己编译的是debug版本的标准lua.exe,右边是我们的题目GameProtect.exe(release版本),依次确认变化后的opcode对应的代码功能,debug和release版本比较需要一个个opcode验证是否一致,可以编译release版本的进行比较,会简单很多,如下图:

lua_game_5

发现每个opcode的二进制代码只是opcode变了,但是对应的操作没有变,整理opcode表如下:

lua_game_6

修改luadec源代码,替换lopcode.c和lopcode.h中opcode的顺序,再次编译luadec,反汇编GameProtect,只能部分反编译。反汇编如下(部分):

lua_game_7

脚本首先校验输入的字符串长度为36,前十六字节调用checkPart1函数校验,checkPart1函数校验是lua调用C,回到GameProtect.exe,看到如下位置,黄色位置即为checkPart1函数。

lua_game_8

后十六字节的校验则在GameProtect脚本里进行,都是很基础的运算,直接写脚本求解flag,以下是加解密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#!/usr/bin/env python2
#-*- coding:utf-8 -*-

flag = "TSCTF{eDit_Th3_EnginE_Of_LUA_s0Fun!}"
flagPart1 = flag[:18]
flagPart2 = flag[18:]

key1 = "Game"
cipherPart1 = []
cipherPart2 = []
ans1 = []
ans2 = []
# encode
for i in range(len(flagPart1)):
tmp = ord(flagPart1[i]) ^ ord(key1[i%len(key1)])
cipherPart1.append(tmp)
print cipherPart1
print [hex(ch) for ch in cipherPart1]

# decode
for i in range(len(flagPart1)):
tmp = cipherPart1[i] ^ ord(key1[i%len(key1)])
ans1.append(tmp)
ans1 = [chr(ch) for ch in ans1]
ans1 = ''.join(ans1)
print ans1

# encode
for i in range(len(flagPart2)):
tmp = ((~ord(flagPart2[i])) & 0xff)
a = (tmp >> 4) & 0xff
b = (tmp << 4) & 0xff
tmp = (a | b) & 0xff
cipherPart2.append(tmp)
print cipherPart2
#print [hex(ch) for ch in cipherPart2]

# decode
for i in range(len(flagPart2)):
a = (cipherPart2[i] >> 4) & 0xff
b = (cipherPart2[i] << 4) & 0xff
tmp = (a | b) & 0xff
ans2.append((~tmp) & 0xff)
ans2 = [chr(ch) for ch in ans2]
ans2 = ''.join(ans2)
print ans2