Last active
February 20, 2020 21:29
-
-
Save SciresM/58d446446b257c840abc90e92d9ced57 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from Crypto.Cipher import AES | |
import json, string, zlib, hashlib | |
# NOTE: You must set these as global variables ahead of time. | |
# TODO: Accept as parameters to API, have a stateful class or something | |
server_secret = '' | |
client_secret = '' | |
# Usage notes: | |
# Initially, communications only have request crypto, and payloads are plaintext gzip | |
# The HOME client generates a client secret and sends it to the server inside the "login" payload | |
# (field name is "crsf"). The server responds to this with a payload containing the server secret | |
# (field name "sesId"). The server also begins including in every response a field "ublg", this | |
# is an integer 1-3 which determines which kind of payload crypto to use for the next request. | |
# The client is expected for all future requests to include the server secret in its request, | |
# and to encrypt the payload using the most recent "ublg" field it received from the server. | |
# | |
# The client and server both keep track of a counter (initially 1), and tweak the crypto | |
# according to this value. | |
# | |
# However, the counters value is taken modulo 64 -- so there are only 64 possible tweaks. | |
# Because this is so low and there are only three possible types of crypto, this API just brute forces | |
# the correct one by trying all 3 * 64 = 192 combinations. This takes basically no time. | |
# | |
# A proper client would keep track of the counter/expected crypto state (and this would be required | |
# to encrypt) and not only decrypt. | |
# | |
# All characters not generated from the client/server secrets (including in request crypto) are obtained | |
# by seeding std::mt19937_t with 4 cryptographically random bytes, and output from that to index the string | |
# "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ". This """obfuscates""" the non-derived | |
# key characters, which are then mixed into the message to make them look like they're part of the base64. | |
# Decrypts the outer layer of a request | |
def memecrypto_homecoming_decrypt_request(txt): | |
assert len(txt) >= 0x30 | |
# Unpack the meme packing of the key/iv | |
enc_msg = ''.join(txt[n] for n in [0x00, 0x06, 0x07, 0x08, 0x0D, 0x14, 0x15, 0x1A, 0x1B, 0x1C, 0x22, 0x23, 0x24, 0x29, 0x2A, 0x2B]) | |
key = ''.join(txt[n] for n in [0x01, 0x02, 0x03, 0x04, 0x0C, 0x0E, 0x0F, 0x10, 0x17, 0x18, 0x19, 0x1D, 0x25, 0x26, 0x27, 0x28]) | |
iv = ''.join(txt[n] for n in [0x2C, 0x2D, 0x2E, 0x2F, 0x1E, 0x1F, 0x20, 0x21, 0x11, 0x12, 0x13, 0x16, 0x05, 0x09, 0x0A, 0x0B]) | |
enc_msg += txt[0x30:] | |
enc_msg += '===' # Ensure correct base64 padding for python | |
# Validate key/IV | |
for c in key + iv: | |
assert c in string.digits + string.lowercase + string.uppercase | |
return AES.new(key, AES.MODE_CBC, iv).decrypt(enc_msg.decode('base64')).rstrip('\x00') | |
# Implementations of further layers of crypto | |
def generate_type1(cl_secret, sr_secret, idx): | |
cl_double = 2 * cl_secret | |
sr_double = 2 * sr_secret | |
c_idx = (ord(hashlib.md5(cl_double[idx:idx+8]).digest()[ 0]) - 1) & 0x3F | |
s_idx = (ord(hashlib.md5(sr_double[idx:idx+8]).digest()[15]) - 1) & 0x3F | |
key_buf = cl_double[c_idx:c_idx+12] | |
iv_buf = sr_double[s_idx:s_idx+12] | |
return key_buf, iv_buf | |
def decrypt_type1(n, msg): | |
idx = (n - 1) & 0x3F | |
try: | |
key_buf, iv_buf = generate_type1(client_secret, server_secret, idx) | |
key_buf += msg[ 4: 8] | |
iv_buf += msg[12:16] | |
msg = msg[0:4] + msg[8:12] + msg[16:] | |
return zlib.decompress(AES.new(key_buf, AES.MODE_CBC, iv_buf).decrypt(msg.decode('base64')), 31).rstrip('\x00') | |
except: | |
return None | |
def break_type1(msg): | |
msg = msg.replace('\\','')+'===' | |
for i in xrange(0x40): | |
dec = decrypt_type1(i, msg) | |
if dec != None: | |
return i, dec | |
return None | |
def generate_type2(secret, idx, odd): | |
s_idx = ord(secret[idx]) & 0x3F | |
buf = '' | |
double = 2 * secret | |
for i in xrange(0, s_idx - (s_idx & 1), 2): | |
cur = idx + s_idx - 2 - i | |
buf += double[cur:cur+2][::-1] | |
if s_idx & 1: | |
buf += double[(idx + s_idx + ~(s_idx - (s_idx & 1))) & 0x3F] | |
ofs = 1 if odd else 0 | |
return ''.join(buf[i + ofs] for i in xrange(0, s_idx - ofs, 2))[:16] | |
def decrypt_type2(n, msg): | |
idx = (n - 1) & 0x3F | |
try: | |
key_buf = generate_type2(client_secret, idx, False) | |
iv_buf = generate_type2(server_secret, idx, True) | |
if len(key_buf) < 16: | |
x = 16 - len(key_buf) | |
key_buf += msg[:x] | |
msg = msg[x:] | |
if len(iv_buf) < 16: | |
x = 16 - len(iv_buf) | |
iv_buf += msg[4:4 + x] | |
msg = msg[:4] + msg[4 + x:] | |
return zlib.decompress(AES.new(key_buf, AES.MODE_CBC, iv_buf).decrypt(msg.decode('base64')), 31).rstrip('\x00') | |
except: | |
return None | |
def break_type2(msg): | |
msg = msg.replace('\\','')+'===' | |
for i in xrange(0x40): | |
dec = decrypt_type2(i, msg) | |
if dec != None: | |
return i, dec | |
return None | |
def generate_type3(cl_secret, sr_secret, idx): | |
cl_double = 2 * cl_secret | |
sr_double = 2 * sr_secret | |
c_idx, s_idx = 0, -1 | |
for c in cl_double[idx:idx+8]: | |
c_idx += cl_secret.count(c) | |
for c in sr_double[idx:idx+8]: | |
s_idx += sr_secret.count(c) | |
c_idx &= 0x3F | |
s_idx &= 0x3F | |
def c(i): | |
return cl_double[(c_idx + i) & 0x3F] | |
def s(i): | |
return sr_double[(s_idx + i) & 0x3F] | |
key_buf = c(-1) + c( 1) + s(12) + s(14) + c( 3) + c( 5) + s( 8) + s(10) + c( 7) + c( 9) | |
iv_buf = c( 0) + c( 2) + s(13) + s(15) + c( 4) + c( 6) + s( 9) + s(11) + c( 8) + c(10) | |
return key_buf, iv_buf | |
def decrypt_type3(n, msg): | |
idx = (n - 1) & 0x3F | |
try: | |
key_buf, iv_buf = generate_type3(client_secret, server_secret, idx) | |
key_buf += msg[ 2: 8] | |
iv_buf += msg[10:16] | |
msg = msg[0:2] + msg[8:10] + msg[16:] | |
return zlib.decompress(AES.new(key_buf, AES.MODE_CBC, iv_buf).decrypt(msg.decode('base64')), 31).rstrip('\x00') | |
except: | |
return None | |
def break_type3(msg): | |
msg = msg.replace('\\','')+'===' | |
for i in xrange(0x40): | |
dec = decrypt_type3(i, msg) | |
if dec != None: | |
return i, dec | |
return None | |
def break_payload_impl(payload): | |
payload = payload.replace('\\','')+'===' | |
for n, f in enumerate([break_type1, break_type2, break_type3]): | |
dec = f(payload) | |
if dec != None: | |
i, d = dec | |
return n+1, i, d | |
def memecrypto_homecoming_break_payload(enc_payload): | |
try: | |
return 0, 0, zlib.decompress(enc_payload.decode('base64'), 31).rstrip('\x00') | |
except: | |
pass | |
return break_payload_impl(enc_payload) | |
# Decrypts the payload inside a request | |
def memecrypto_homecoming_decrypt_payload(request): | |
dec_request = json.loads(memecrypto_homecoming_decrypt_request(request)) | |
enc_payload = str(dec_request['pm'].replace('\\','') + '===') | |
dec_payload = memecrypto_homecoming_break_payload(enc_payload) | |
assert dec_payload != None | |
ctype, idx, dec_payload = dec_payload | |
return dec_request, json.loads(dec_payload.replace('\\','')) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment