2020 空指针 5月RE公开赛

上周做了一下空指针的五月re公开赛。题目不是特别难,可能没什么人做,最后只有三解。当天我肝到晚上三点成功拿到一血(捂脸

EzGame

cinit及TLS回调函数有反调试,恢复一串RC4 key用来解密sub_402B50处的代码

直接调试可以逐一绕过。sub_402470部分对几个关键位置的代码求和,调试时注意不能破坏这里的数据(主要是断点)

依次在00401D98 00401EA1 0040229D 00402771 下断点,手动将对应反调试逻辑绕过,解密完成后将程序dump下来。

首先将key的每一个字节循环右移1位。

0040341C处出发异常,之前sub_402740sub_402DC0中添加了异常处理函数,接下来进入这部分逻辑。

00402900处开头将几个Dr寄存器的值异或并存入452FAC,这个在之后SEH中会用到。如果打了硬件断点这里就不为0了,需要注意。

接下来是一些异或加密。然而它并没有想象中的那么简单。一开始直接用z3发现递归太大了。

思考了一下可以将32个明文分别设定成1 << i,78改成1 << 32,这样加密的结果中为1的位数就是实际异或的内容。参考代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
t = 1 <<32
tmp = [1 << i for i in range(32)]
for i in range(1280):
for j in range(32):
t1 = tmp[j]
tmp[j]^=t
t = t1
res = [[] for i in range(32)]
for i in range(len(tmp)):
for j in range(32):
if (tmp[i] >> j) & 1:
res[i].append("x[%d]"%j)
if (tmp[i] >> 32) & 1:
res[i].append('78')
print("x_[%d] = "%i, end='')
for j in res[i][0:-1]:
print(j + "^", end='')
print(res[i][-1], end='')
print()

这样直接可以用z3解了。

接下来在TopLevelExceptionFilter中,通过一个对称分组密码加密数据。子密钥可以直接dump下来。

最终的解密脚本:

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
from binascii import *
from struct import pack, unpack

SBOX = [0x0E, 0x04, 0x0D, 0x01, 0x02, 0x0F, 0x0B, 0x08, 0x03, 0x0A, 0x06, 0x0C, 0x05, 0x09, 0x00, 0x07, 0x00, 0x0F, 0x07, 0x04, 0x0E, 0x02, 0x0D, 0x01, 0x0A, 0x06, 0x0C, 0x0B, 0x09, 0x05, 0x03, 0x08, 0x04, 0x01, 0x0E, 0x08, 0x0D, 0x06, 0x02, 0x0B, 0x0F, 0x0C, 0x09, 0x07, 0x03, 0x0A, 0x05, 0x00, 0x0F, 0x0C, 0x08, 0x02, 0x04, 0x09, 0x01, 0x07, 0x05, 0x0B, 0x03, 0x0E, 0x0A, 0x00, 0x06, 0x0D, 0x0F, 0x01, 0x08, 0x0E, 0x06, 0x0B, 0x03, 0x04, 0x09, 0x07, 0x02, 0x0D, 0x0C, 0x00, 0x05, 0x0A, 0x03, 0x0D, 0x04, 0x07, 0x0F, 0x02, 0x08, 0x0E, 0x0C, 0x00, 0x01, 0x0A, 0x06, 0x09, 0x0B, 0x05, 0x00, 0x0E, 0x07, 0x0B, 0x0A, 0x04, 0x0D, 0x01, 0x05, 0x08, 0x0C, 0x06, 0x09, 0x03, 0x02, 0x0F, 0x0D, 0x08, 0x0A, 0x01, 0x03, 0x0F, 0x04, 0x02, 0x0B, 0x06, 0x07, 0x0C, 0x00, 0x05, 0x0E, 0x09, 0x0A, 0x00, 0x09, 0x0E, 0x06, 0x03, 0x0F, 0x05, 0x01, 0x0D, 0x0C, 0x07, 0x0B, 0x04, 0x02, 0x08, 0x0D, 0x07, 0x00, 0x09, 0x03, 0x04, 0x06, 0x0A, 0x02, 0x08, 0x05, 0x0E, 0x0C, 0x0B, 0x0F, 0x01, 0x0D, 0x06, 0x04, 0x09, 0x08, 0x0F, 0x03, 0x00, 0x0B, 0x01, 0x02, 0x0C, 0x05, 0x0A, 0x0E, 0x07, 0x01, 0x0A, 0x0D, 0x00, 0x06, 0x09, 0x08, 0x07, 0x04, 0x0F, 0x0E, 0x03, 0x0B, 0x05, 0x02, 0x0C, 0x07, 0x0D, 0x0E, 0x03, 0x00, 0x06, 0x09, 0x0A, 0x01, 0x02, 0x08, 0x05, 0x0B, 0x0C, 0x04, 0x0F, 0x0D, 0x08, 0x0B, 0x05, 0x06, 0x0F, 0x00, 0x03, 0x04, 0x07, 0x02, 0x0C, 0x01, 0x0A, 0x0E, 0x09, 0x0A, 0x06, 0x09, 0x00, 0x0C, 0x0B, 0x07, 0x0D, 0x0F, 0x01, 0x03, 0x0E, 0x05, 0x02, 0x08, 0x04, 0x03, 0x0F, 0x00, 0x06, 0x0A, 0x01, 0x0D, 0x08, 0x09, 0x04, 0x05, 0x0B, 0x0C, 0x07, 0x02, 0x0E]

def foo(a):
b3 = (a >> 24) & 0xFF
b2 = (a >> 16) & 0xFF
b1 = (a >> 8) & 0xFF
b0 = a & 0xFF
b3 = SBOX[b3]
b2 = SBOX[b2]
b1 = SBOX[b1]
b0 = SBOX[b0]
v = b3 << 24 | b2 << 16 | b1 << 8 | b0
res = v
res ^= (v << 2 | v >> 30 ) & 0xffffffff
res ^= (v << 10 | v >> 22 ) & 0xffffffff
res ^= (v << 18 | v >> 14 ) & 0xffffffff
res ^= (v << 24 | v >> 8 ) & 0xffffffff
return res


key = [0xFAD5B915, 0x7E664A61, 0x98045E2F, 0x6E763BB6, 0xB1F556F8, 0x97EEEC49, 0x5F88798B, 0xACF3BC38, 0x5E7FDC73, 0x3EC3C4CF, 0xDB6A9A29, 0x89DCFB3B, 0xFF5F71B0, 0x5DC3E1E0, 0x7AC87306, 0x2877B252, 0x1B3E9790, 0x7745C8C9, 0x93E4BE29, 0x43D7195B, 0x555C1EFD, 0xB6A4AA43, 0x1FC87262, 0x0FF59A17, 0xFCBB7497, 0xFD07ED03, 0x1C4356CE, 0x291BBF56, 0x16B9FE73, 0x90882DCE, 0x3BC03EED, 0x07DC3851]
v2 = [178, 167, 94, 11, 150, 182, 62, 0, 21, 175, 235, 7, 36, 139, 37, 44, 145, 44, 122, 171, 157, 158, 6, 22, 25, 159, 205, 171, 156, 7, 33, 42]
c1 = bytes(v2[0:16])
c2 = bytes(v2[16:])
tbl = [0 for i in range(36)]
tbl[32], tbl[33], tbl[34], tbl[35] = unpack("<IIII", c1[::-1])
for i in range(31, -1, -1):
tbl[i] = tbl[i+4] ^ foo(tbl[i+1] ^ tbl[i+2] ^ tbl[i+3] ^ key[i])
res = pack(">IIII", tbl[0], tbl[1], tbl[2], tbl[3])


tbl = [0 for i in range(36)]
tbl[32], tbl[33], tbl[34], tbl[35] = unpack("<IIII", c2[::-1])
for i in range(31, -1, -1):
tbl[i] = tbl[i+4] ^ foo(tbl[i+1] ^ tbl[i+2] ^ tbl[i+3] ^ key[i])
res += pack(">IIII", tbl[0], tbl[1], tbl[2], tbl[3])
print(res)
res = list(res)

from z3 import *
x = [BitVec("x%d"%i, 8) for i in range(32)]
x_ = [0 for i in range(32)]
x_[0] = x[2]^x[10]^x[18]^x[26]
x_[1] = x[3]^x[11]^x[19]^x[27]
x_[2] = x[4]^x[12]^x[20]^x[28]
x_[3] = x[5]^x[13]^x[21]^x[29]
x_[4] = x[6]^x[14]^x[22]^x[30]
x_[5] = x[7]^x[15]^x[23]^x[31]
x_[6] = x[0]^x[8]^x[16]^x[24]^78
x_[7] = x[0]^x[1]^x[9]^x[17]^x[25]
x_[8] = x[1]^x[2]^x[10]^x[18]^x[26]
x_[9] = x[2]^x[3]^x[11]^x[19]^x[27]
x_[10] = x[3]^x[4]^x[12]^x[20]^x[28]
x_[11] = x[4]^x[5]^x[13]^x[21]^x[29]
x_[12] = x[5]^x[6]^x[14]^x[22]^x[30]
x_[13] = x[6]^x[7]^x[15]^x[23]^x[31]
x_[14] = x[0]^x[7]^x[8]^x[16]^x[24]^78
x_[15] = x[0]^x[1]^x[8]^x[9]^x[17]^x[25]
x_[16] = x[1]^x[2]^x[9]^x[10]^x[18]^x[26]
x_[17] = x[2]^x[3]^x[10]^x[11]^x[19]^x[27]
x_[18] = x[3]^x[4]^x[11]^x[12]^x[20]^x[28]
x_[19] = x[4]^x[5]^x[12]^x[13]^x[21]^x[29]
x_[20] = x[5]^x[6]^x[13]^x[14]^x[22]^x[30]
x_[21] = x[6]^x[7]^x[14]^x[15]^x[23]^x[31]
x_[22] = x[0]^x[7]^x[8]^x[15]^x[16]^x[24]^78
x_[23] = x[0]^x[1]^x[8]^x[9]^x[16]^x[17]^x[25]
x_[24] = x[1]^x[2]^x[9]^x[10]^x[17]^x[18]^x[26]
x_[25] = x[2]^x[3]^x[10]^x[11]^x[18]^x[19]^x[27]
x_[26] = x[3]^x[4]^x[11]^x[12]^x[19]^x[20]^x[28]
x_[27] = x[4]^x[5]^x[12]^x[13]^x[20]^x[21]^x[29]
x_[28] = x[5]^x[6]^x[13]^x[14]^x[21]^x[22]^x[30]
x_[29] = x[6]^x[7]^x[14]^x[15]^x[22]^x[23]^x[31]
x_[30] = x[0]^x[7]^x[8]^x[15]^x[16]^x[23]^x[24]^78
x_[31] = x[0]^x[1]^x[8]^x[9]^x[16]^x[17]^x[24]^x[25]
s = Solver()
for i in range(32):
s.add(x_[i] == res[i])
print(s.check())
m = s.model()
res = []
for i in x:
res.append(m[i].as_long())
for i in range(len(res)):
res[i] = (res[i] << 1 | res[i] >> 7) & 0xff
print(bytes(res))

npointer{2c6d0321d232fe2e3fe6b9190e210532}

赛后看wp得知这个加密算法是修改了SBOX的SM4。其实SM4有遇到过,但是加解密都是直接调库。手撸过SM4但是一直不知道它是SM4。现在明白了这种特征的(32次循环打表,4 = 0 hash(123subkey)就是SM4。以后再遇到做得就更快了。