Skip to content

Instantly share code, notes, and snippets.

@SciresM
Last active February 20, 2020 21:29
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SciresM/58d446446b257c840abc90e92d9ced57 to your computer and use it in GitHub Desktop.
Save SciresM/58d446446b257c840abc90e92d9ced57 to your computer and use it in GitHub Desktop.
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