Reversing Javascript Obfuscated Code

 Load webview, a 2048 game. Click ten times in MainActivity to enter TestActivity, but it is invalid after clicking. It is guessed that the button may be hidden. I don’t know where it is, and I don’t need to analyze the layout file. I can directly use the command adb shell am start -n com.test.hufu22/.TestActivityto enter the game interface. There is a JS interface in TestActivity,

When we look under assets, we will find that game_manager.js is confused, there should be key logic, and then analyze the Native file.

init_array has a large number of strings decrypted, almost all useful strings are encrypted.

Then a function is called. There are multiple similar functions in the so file, which are anti-debugging and anti-Frida hooks, and directly Nop all these calls. After entering JNI_OnLoad, a check function is dynamically registered, and it can be seen that it is a check only after the string of init_array is decrypted.

Analysis check function:

The length of the parameter passed in first is 64, and then sub_E69C83E4 is called, follow-up analysis:

The string of length 64 becomes 48 through a function, and the first guess is base64, although the encoding table in it is very strange. Get the result of base64 decryption, and then perform AES encryption (the S box was modified, but I didn’t pay attention at the time), the key is byte_E69D1310 here, which will be dynamically modified, which will be mentioned later. After completing these tasks, use the result of AES encryption as the parameter of the socket function (I modified the function name to socket), and follow up the analysis:

There are some functions in it, which will define a data format, and then use sendto to send, that is, the given traffic file. There are roughly two formats:

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

Follow up the above function for analysis:

It can be known that strings2 is the result of encrypting the third and fourth parameters, the third parameter is i+0xdead, the fourth parameter is *(i+v19), and v19 is the parameter passed in by the socket. Extract the data in the packet and decrypt the following parts:

We have almost analyzed the so file. Next, we will analyze the JS code and see that the obfuscation will die directly. Using this website can remove some obfuscation ( javascript deobfuscator), but there are a lot of base64 strings in it. Analysis The first two functions:

The guessing function should be to reverse the order of the base64 string array at the beginning, and then decrypt the base64. There will be a large number of array indexes in the code, and we will restore these names (the name is not fixed after each de-obfuscation using the website):

Write code to restore the function name. Use the following script to restore most functions. The writing is not perfect ( xxxx) + "xx"this kind of string will appear), and regular matching can be used to improve:

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)

After decryption, go to docheck first, and call dockeck in the function GameManager.prototype.checkseq:

There are no cross-references, everything is located based on the search string. The general logical analysis is clear. Only when the length of seq is greater than 100 and within the range of 128 after a calculation can docheck be called, and the cheat code will be printed. Let's see where checkseq is called.

Then search for seq to see how seq adds data. It is found in GameManager.prototype[move)] that move may be a sliding box:

While tracking move:

Here we see inputmanager, which we can find in other js files:

So seq records the direction of sliding. I thought about it, must it be the movement of the slider to count? Then I swipe right 100 times, click on newgame, and successfully output the cheat code.

1 
2
3
4
5
6
7
8
9
10
11
12
13
14
15
So the overall logic is:
In JS:
 1. Get the slider direction array
 2. After a simple calculation, a group of 4, get a string
 3. Encrypt the string with xtea. The key of xtea is obtained through the init function, as shown above.
 4. Call pre, and finally call the prenative() function, set byte_E69D1310, as mentioned above, the key of aes.
 5. Perform base64 encryption again
 6. Call the docheck function, the parameter is the encrypted result
In native:
 7. The check function is called in docheck
 8. Perform base64 decryption
 9. Perform aes decryption
 10. Define the data format
 11. AES encrypt data again
 12. Send packets
Write a script to solve the problem, first extract the AES encrypted data in the data packet:
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="")

Then use the modified AES decryption of the S box, the decryption code is too long to paste, paste a calculation inverse S box, the key is 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;
}

Then perform Xtea decryption, the key is (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;
}

Decrypt to get the flag.

fpbe

After getting the title and analyzing it for a long time, I found that it is necessary to blast sha256, but it is obviously impossible, and then search for what this bpf is, and finally determine the key points.

A bpf-like ELF file will be loaded in it, and when the dump comes out, it is found that IDA does not parse it, and then searches for disassembly, llvm-objdump will do.

The logic is directly given, just decrypt it, and solve it with z3 constraints.

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 5 40
42
43
44 4 4
45
_ 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, will not shed its shell, a waste one. .

Start two processes, attach these two processes, OD is used to locate the key code part, and view the pseudocode in ida combined with the address located by OD.

First perform base64 decryption:

Then each character is rotated to the left by 3 bits,

xxtea magic modification encryption:

Knowing the encryption process, we solve the 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 5 40
42
43
44 4 4
45
_ 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;
}

Then load the 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
//Five ways to execute shellcode in C language//Five ways to execute shellcode in C language
#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) ();


////the first method void RunShellCode_1()
{

PVOID p = NULL; if ((p = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)) == NULL) MessageBoxA(NULL, "Failed to apply for memory", "Reminder", MB_OK); if (!(memcpy(p, shellcode, sizeof(shellcode)))) MessageBoxA(NULL, "Failed to write memory", "Reminder", MB_OK);

CODE code = (CODE)p;

code();

}

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

Breakpoint debugging:

In fact, it is to compare these two, get the data directly through debugging, and decrypt:

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)

Finally, you can find md5 according to the prompt:

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())

Comments