虎符2022逆向部分wp

本文最后更新于:2022年3月28日 晚上

reverse

2048

加载webview,一个2048游戏。MainActivity中点击十次进入TestActivity,但是点击后无效,猜测可能隐藏按钮,不知道在哪个地方,也不需要分析layout文件,直接使用命令adb shell am start -n com.test.hufu22/.TestActivity即可进入游戏界面。TestActivity中有JS接口,

image-20220327020205251

我们去assets下查看的时候会发现,其中game_manager.js被混淆,应该存在关键的逻辑,然后去分析Native文件。

image-20220327020402202

init_array有大量字符串解密,几乎有用的字符串都被加密了。

image-20220319232458767

image-20220319232511937

image-20220319232714840

然后调用了一个函数,在so文件中存在多个类似的函数,是反调试,反Frida hook的,直接将这些调用全部Nop掉。之后进入JNI_OnLoad,动态注册了一个check函数,这里要经过init_array的字符串解密后才能看出来是check。

image-20220327020631724

分析check函数:

image-20220327020813797

首先传入的参数的长度是64,然后调用了sub_E69C83E4,跟进分析:

image-20220327020946771

长度64字符串经过一个函数变为48,首先猜测是base64,虽然其中的编码表很奇怪。得到base64解密的结果,然后进行AES加密(S盒被修改了,当时就没注意),key就是这里的byte_E69D1310,它会被动态修改,后面会提到。做完这些工作之后,将AES加密的结果作为socket函数的参数(我修改了函数名为socket),跟进分析:

image-20220327021807740

这里面有一些函数,会定义一个数据格式,然后使用sendto发送,也就是给的流量文件。大致的格式有如下两种:

1
2
HUFU + 0 + time(0) + md5(strings1)[:4] + len(strings1)+ strings1
HUFU + 1 + time(0) + md5(ff+strings2)[:4] + len(strings2) + strings2

跟进上述函数进行分析:

image-20220327022123609

可以得知,strings2就是对第三个和第四个参数加密的结果,第三个参数是 i+0xdead,第四个参数是 *(i+v19),v19就是socket传入的参数。将数据包中的数据提取出来,对以下部分进行解密:

image-20220327022655581

so文件我们分析的差不多了,接下来去分析JS代码,看到这混淆直接去世,使用这个网站能去一些混淆(https://deobfuscate.io/),但是其中有大量的base64字符串,分析前两个函数:

image-20220327023832361

猜测作用应该是将开头的base64字符串数组进行顺序颠倒,然后base64解密,代码中会有大量的数组索引,我们对这些名称进行恢复,(使用网站每次反混淆后这个名称不固定):

image-20220327024323869

编写代码恢复函数名称,使用如下脚本可以恢复大多数函数,写的并不是很完美(会出现xxxx) + "xx"这种字符串),使用正则匹配可以更完善:

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
import base64

with open("base-name", "r+") as f:
s = f.readlines()
result = []
for i in s:
dec = base64.b64decode(i.strip())
result.append(dec)
ddd = []
for i in range(285-127):
ddd.append(result[127+i])
for i in range(127):
ddd.append(result[i])
with open("re_name") as f:
data = f.read()
new_data = ""
length = len(data) -15
i = 0
while i < length:
if data[i:i+6] == "jesus(":
inx = int(data[i+9:i+11].replace('"', ""), 16)
new_data += ddd[inx].decode("utf8")
i += 12
else:
new_data += data[i]
i += 1
print(new_data)

解密之后先去找docheck,在GameManager.prototype.checkseq这个函数里调用了dockeck:

image-20220327025308756

没有交叉引用,都是根据搜索字符串定位的。。。大致逻辑分析清楚了,只有seq长度大于100并且经过一个计算在128范围内才能调用docheck呢,并且会打印cheat code,我们再去看哪里调用了checkseq。

image-20220327025606964

然后再搜索seq,看看seq怎么添加数据,在GameManager.prototype[move)]中发现,move可能就是滑动方块:

image-20220327025749405

在跟踪move:

image-20220327030103955

这里看到了inputmanager,我们在其他js文件里可以发现:

image-20220327030310982

所以seq记录的是滑动的方向,我想了一下,这里必须是滑块移动才算数吗,然后我进行了100次右滑,点击newgame,成功输出cheat code,

image-20220327030518705

image-20220327032940537

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
所以整体逻辑是:
JS中:
1. 得到滑块方向数组
2. 经过一个简单计算,4个一组,得到一字符串
3. 对字符串进行xtea加密,xtea的key是通过init函数获得,图补上面。
4. 调用pre,最后调用了prenative()函数,设置了byte_E69D1310,上面提到过,aes的key。
5. 再进行base64加密
6. 调用docheck函数,参数是加密结果
native中:
7. docheck中调用了check函数
8. 进行base64解密
9. 进行aes解密
10. 定义数据格式
11. aes再次加密数据
12. 发送数据包

编写脚本解题,首先提取数据包中aes加密的数据:

1
2
3
4
5
6
7
8
9
from Crypto.Cipher import AES

arr1 = ['111afaa675802d7a976c98c8c43e2da2', '521f3381f24ec5108baf200b6ec64db1', '6187ab272c1f0733ba6aaef76f188c59', '8fd161add05a735d3d383e948290fc31', 'be1eaf993a810db8ad5c7bb3504f7d90', 'c46b64bed6c6afec72bdc89b066ce7f3', 'b1049417ed4f022389adf0f4d5c4da9e', '1afd765f3cede067216b1ee0d2d42810', 'e7be9ed7e94fa2023baa57f90b5851c8', '34206c8223f731c478beb284a57f83c5', 'd59dcad948aaf35a3f3ee82c1f967b38', 'df7c66422e37ae41af3ca4311dcd4104', 'f2319387bbcf9e7606f4256bf8790afd', 'd328266ebafe1ca097e12e4059b64c3e', '5895762381fcebb40bc6ca46f129388d', 'bf33b95ff573f418682ad60c447f44a9', '208b18ec59fbf0617baf1c8c4d9b5903', 'd4de18deb5c37cbc4eab9d022a8edeaa', '554b25cc7d29625c902ce647871d6aae', 'af9074a520dc0be68c012756b80d2de9', 'd8b27655d8cde7f2cdc7a74754953337', 'ca8ee61dc6728813a8e41e9be80a6764', 'fc95be5241f8a59eb580eba5fb91fddf', '2ba133fa3879e6347b3ae8e55fbc944a', 'c60df505a2932ca68e3530af6bc41bbb', 'c64a847747cc70e603bab516386ca721', '1737b1a23ec2bd493ab1a6275b6e2eb2', '2cf65d658c1fc558d9f43321000e9e68', '6beee41275f03f30485af8bee805805c', '8b821cac8196275904bbb53a11b831d5', 'd230fab6b044854fd518219a12942c65', 'a0435d523e66126d7e6fe850492319a2', '0fe33dede172c8d2bca1f1cda054f641', 'ca4633933b0464fc3158e4ccb987fc8f', '6e3f1bc661417ef0f5d2aa23630fa113', '95729dfa6ee0629f5f7fb8b443bddf7d', 'aee28a1366cc508ff196e160dd9e5ac4', '9aa2b87453e0f0dabcf02667ba668c15', 'd1a2347614fa59ecfb2ac964c1198fa0', 'c1aff18b31c51a7ce47d3a2277515b55', 'ac25f6f85a0668f26b53bda11d5f10b0', 'af38d6d29ebfbbc44a85538b69adf05a', 'f17f0bec7ade982167e8903b0bd55368', 'af6009e720b4d142d249c55afcc0892f', 'f42702d51a7c62683ab60b3ecbd5180d', '8969dcc7f808b88aff97be2d0942431c', 'acadd756fd5a86401c88b0d0469c7152', '7b9772ccb2282ff68ed5d77132785c04']
key = b'KZoLJZlLkRlMOtuD'
mode = AES.MODE_ECB
aes = AES.new(key, mode)
for i in range(len(arr1)):
result = aes.decrypt(bytes.fromhex(arr1[i]))
print(bytes.hex(result)[8:10], end="")

然后使用修改S盒的AES解密,解密代码太长不贴了,贴一个计算逆S盒,key是Hello from 2048!

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

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

unsigned char sbox_inv[256] = { 0 };

for(int i = 0; i < 256; i++)
{
unsigned char temp1 = sbox[i];

sbox_inv[temp1] = i;
}

for(int i = 0; i < 256; i++)
{
printf("0x%X%X, ", sbox_inv[i] / 16, sbox_inv[i] % 16);
}

return 0;
}

再进行Xtea解密,key是(up, up, down, d

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

/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */

void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], sum=0, delta=0x9e3779b9;
for (i=0; i < num_rounds; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
sum += delta;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
}
v[0]=v0; v[1]=v1;
}

void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], delta=0x9e3779b9, sum=delta*num_rounds;
for (i=0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
v[0]=v0; v[1]=v1;
}

int main()
{
unsigned int key1[4] = {0x2c707528, 0x2c707520, 0x776f6420, 0x64202c6e};
uint32_t data[12] = {478081731, 2441396269, 3071091750, 1487425519, 1182288333, 4166601318, 2690946486, 3573993259, 1608397483, 1806237595, 2906591919, 683759453};
unsigned int r=32;
for(int i=0;i<12;i+=2){
decipher(r, &data[i], key1);
}
for(int i=0;i<12;i++){
for(int j=0;j<4;j++){
printf("%c", (data[i] >> (8*j) & 0xff));
}
}
return 0;
}

解密即可得到flag。

fpbe

拿到题目分析了半天,发现要进行sha256爆破,但显然不可能,,然后去搜索这个bpf是什么东西,最后确定关键点。

image-20220321102824105

image-20220321102937275

在这里面会加载一个bpf类的ELF文件,dump出来发现IDA也不解析,又去搜索反汇编,llvm-objdump就可以。

image-20220321105435126

逻辑直接给了,解密即可,可用z3约束求解。

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
from z3 import *
from pwn import *
s =Solver()
x = [BitVec("x%s" % i, 64) for i in range(4)]
r2 = x[2]
r2 <<= 32
r2 >>= 32
r3 = x[3]
r3 <<= 32
r3 >>= 32
r4 = r3
r4 *= 28096
r5 = r2
r5 *= 64392
r5 += r4
r4 = x[1]
r4 <<= 32
r4 >>= 32
r0 = r4
r0 *= 29179
r5 += r0
r1 = x[0]
r1 <<= 32
r1 >>= 32
r0 = r1
r0 *= 52366
r5 += r0
r6 = 1
r0 = 209012997183893
print(r5)
s.add(r5 == r0)
r5 = r3
r5 *= 61887
r0 = r2
r0 *= 27365
r0 += r5
r5 = r4
r5 *= 44499
r0 += r5
r5 = r1
r5 *= 37508
r0 += r5
r5 = 181792633258816
s.add(r0 == r5)
r5 = r3
r5 *= 56709
r0 = r2
r0 *= 32808
r0 += r5
r5 = r4
r5 *= 25901
r0 += r5
r5 = r1
r5 *= 59154
r0 += r5
r5 = 183564558159267
s.add(r0==r5)
r5 = r3
r5 *= 33324
r0 = r2
r0 *= 51779
r0 += r5
r5 = r4
r5 *= 31886
r0 += r5
r5 = r1
r5 *= 62010
r0 += r5
r5 = 204080879923831
s.add(r0==r5)
if (s.check() == sat):
model = s.model()
print(model)
flag = b""
for i in range(4):
if (model[x[3-i]] != None):
flag += p32(model[x[3-i]].as_long().real)
print(flag)

the_shellcode

themida,也不会脱壳,废物一个。。。

开启两个进程,attach这两个进程,OD用于定位关键代码部分,结合OD定位的地址在ida中查看伪代码。

首先进行base64解密:

image-20220328181413271

然后每个字符循环左移3bit,

image-20220328181442946

xxtea魔改加密:

image-20220328181528967

知道了加密过程,我们求解出shellcode:

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
#include <stdio.h>
#include <stdint.h>
#define DELTA 0x9e3779b9
//#define MX (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)))
#define MX (((z >> 6 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)))
#define ROR3(x) (((x) >> 3) | ((x) << 5)) & 0xff
void btea(uint32_t *v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) /* Coding Part */
{
rounds = 6 + 52 / n;
sum = DELTA;
z = v[n - 1];
do
{
e = (sum >> 2) & 3;
for (p = 0; p < n - 1; p++)
{
y = v[p + 1];
z = v[p] += MX;
}
y = v[0];
z = v[n - 1] += MX;
//printf("%x\n", y);
sum -= 0x61C88647;
} while (--rounds);
}
else if (n < -1) /* Decoding Part */
{
n = -n;
rounds = 6 + 52 / n;
sum = rounds * DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--)
{
z = v[p - 1];
y = v[p] -= MX;
}
z = v[n - 1];
y = v[0] -= MX;
sum += 0x61C88647;
} while (--rounds);
}
}

int main()
{
unsigned int v[66] = {
0x4B6B89A1, 0x74C15453, 0x4092A06E, 0x429B0C07, 0x40281E84, 0x8B5B44C9, 0x66FEB37B, 0x3C77A603,
0x79C5892D, 0x0D7ADA97, 0x1D51AA56, 0x02D4D703, 0x4FA526BA, 0x32FAD64A, 0x0C0F6091, 0x562B7593,
0xDB9ADD67, 0x76165563, 0xA5F79315, 0x3AEB991D, 0x1AB721D4, 0xAACD9D2C, 0x825C2B27, 0x76A7761A,
0xB4005F18, 0x117F3763, 0x512CC540, 0xC594A16F, 0xD0E24F8C, 0x9CA3E2E9, 0x0A9CC2D5, 0x4629E61D,
0x637129E3, 0xCA4E8AD7, 0xF5DFAF71, 0x474E68AB, 0x542FBC3A, 0xD6741617, 0xAD0DBBE5, 0x62F7BBE3,
0xC8D68C07, 0x880E950E, 0xF80F25BA, 0x767A264C, 0x9A7CE014, 0x5C8BC9EE, 0x5D9EF7D4, 0xB999ACDE,
0xB2EC8E13, 0xEE68232D, 0x927C5FCE, 0xC9E3A85D, 0xAC74B56B, 0x42B6E712, 0xCD2898DA, 0xFCF11C58,
0xF57075EE, 0x5076E678, 0xD4D66A35, 0x95105AB9, 0x1BB04403, 0xB240B959, 0x7B4E261A, 0x23D129D8,
0xF5E752CD, 0x4EA78F70
};
uint32_t const k[4] = {0x74, 0x6f, 0x72, 0x61};
int n = -66; //n的绝对值表示v的长度,取正表示加密,取负表示解密
btea(v, n, k);
for(int i=0;i<66;i++){
for(int j=0;j<4;j++){
printf("\\x%02x", ROR3((v[i] >> (8*j)) & 0xff));
}
}
return 0;
}

然后再加载shellcode:

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
//C语言执行shellcode的五种方法

#include <windows.h>
#include <stdio.h>


unsigned char shellcode[] = "\x60\xfc\x68\x4c\x77\x26\x07\x33\xd2\x64\x8b\x52\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x33\xff\x33\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x03\xf8\xe2\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x03\xc2\x8b\x40\x78\x85\xc0\x0f\x84\xbe\x00\x00\x00\x03\xc2\x50\x8b\x48\x18\x8b\x58\x20\x03\xda\x83\xf9\x00\x0f\x84\xa9\x00\x00\x00\x49\x8b\x34\x8b\x03\xf2\x33\xff\x33\xc0\xac\xc1\xcf\x0d\x03\xf8\x3a\xc4\x75\xf4\x03\x7c\x24\x04\x3b\x7c\x24\x0c\x75\xd9\x33\xff\x33\xc9\x83\xc2\x50\x0f\xb6\x04\x0a\xc1\xcf\x0d\x03\xf8\x41\x83\xf9\x0e\x75\xf1\xc1\xcf\x0d\x57\x33\xff\x33\xc9\x8b\x54\x24\x3c\x52\x0f\xb6\x1c\x0e\xb8\x67\x66\x66\x66\xf7\xeb\xd1\xfa\x8b\xc2\xc1\xe8\x1f\x03\xc2\x8d\x04\x80\x2b\xd8\x5a\x0f\xb6\x04\x0a\x2b\xc3\xc1\xcf\x0d\x03\xf8\x41\x83\xf9\x0e\x75\xd4\xc1\xcf\x0d\x3b\x3c\x24\x74\x16\x68\x25\x73\x00\x00\x8b\xc4\x68\x6e\x6f\x00\x00\x54\x50\x8b\x5c\x24\x48\xff\xd3\xeb\x14\x68\x25\x73\x00\x00\x8b\xc4\x68\x79\x65\x73\x00\x54\x50\x8b\x5c\x24\x48\xff\xd3\x58\x58\x58\x58\x58\x58\x58\x58\x58\x61\xc3\x58\x5f\x5a\x8b\x12\xe9\x0b\xff\xff\xff";

typedef void(__stdcall *CODE) ();


////第一种方法
void RunShellCode_1()
{

PVOID p = NULL;
if ((p = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)) == NULL)
MessageBoxA(NULL, "申请内存失败", "提醒", MB_OK);
if (!(memcpy(p, shellcode, sizeof(shellcode))))
MessageBoxA(NULL, "写内存失败", "提醒", MB_OK);

CODE code = (CODE)p;

code();

}

int main()
{
RunShellCode_1();
return 0;
}

断点调试:

image-20220328194836226

其实就是比较这两个,通过调试直接获取数据,解密:

1
2
3
4
5
6
s1 = "LoadLibraryExA"
a1 = [0x69, 0x73, 0x20, 0x70, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x20, 0x63, 0x61, 0x6E]
flag = ""
for i in range(len(a1)):
flag += chr((ord(s1[i]) % 5 + a1[i]) & 0xff)
print(flag)

最后根据提示求md5即可:

1
2
3
4
5
6
import hashlib
s1 = b"jt\"psojvcq!gan"
s2 = b"YPxoTHcmBzPSZItSMItSDItSFItyKA+3SiYz/zPArDxhfAIsIMHPDQP44vBSV4tSEItCPAPCi0B4hcAPhL4AAAADwlCLSBiLWCAD2oP5AA+EqQAAAEmLNIsD8jP/M8Cswc8NA/g6xHX0A3wkBDt8JAx12TP/M8mDwlAPtgQKwc8NA/hBg/kOdfHBzw1XM/8zyYtUJDxSD7YcDrhnZmZm9+vR+ovCwegfA8KNBIAr2FoPtgQKK8PBzw0D+EGD+Q511MHPDTs8JHQWaCVzAACLxGhubwAAVFCLXCRI/9PrFGglcwAAi8RoeWVzAFRQi1wkSP/TWFhYWFhYWFhYYcNYX1qLEukL////"
md5 = hashlib.md5()
md5.update(s2+s1)
print(md5.hexdigest())

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!