#!/usr/local/bin/python3 import re import urlquick import urllib import sys import time import ctypes import math import json import base64 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad # Some Sttaic Arrays # URL to get license key input LICC_URL = 'https://cassie.channel5.com/api/v2/media/my5desktopng/%s.json?timestamp=%s' LIVE_LICC_URL = 'https://cassie.channel5.com/api/v2/live_media/my5desktopng/%s.json?timestamp=%s' KEYURL = "https://player.akamaized.net/html5player/core/html5-c5-player.js" CERT_URL = 'https://c5apps.channel5.com/wv/c5-wv-app-cert-20170524.bin' def arrayCopy(original): out = [None] * len(original); for x in range(0, len(original)): out[x] = original[x] return(out) def twos_comp(val, bits): """compute the 2's complement of int value val""" if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255 val = val - (1 << bits) # compute negative value return val # make a number like a javbascript inter only 32 bits long def u_to_l(u): return u - (1<<32) if u >= (1<<31) else u # make array23 from cassie def cassiearray(H7): L7 = [] for z0 in range(len(H7)): try: tempx= L7[z0 >> 2] except IndexError: tempx = L7.append(0) L7[z0 >> 2] |= (255 & ord(H7[z0])) << (24 - z0 % 4 * 8) return(L7) def cassieoarraymod(A23): q5N = 1240 e5N = 728 A32 = [] for x in A23: A32.append(x) if (len(A23) > 22): for x in range(22,30): A32.append(0) A32[(e5N & 0xffffffff) >> 5] |= 128 << 24 - e5N % 32 A32.append(1240) else: A32[21] |= 128 for x in range(22,31): A32.append(0) A32.append(1208) return(A32) def init(j5A): Q6t = 16 X6t = [] f6t = [] for g6t in range(Q6t): try: tempx= j5A[g6t] except IndexError: tempx = 0 tempy = u_to_l(tempx) ^ 1549556828 tempz = u_to_l(tempx) ^ 909522486 X6t.append(u_to_l(tempy)) f6t.append(u_to_l(tempz)) return(X6t, f6t) # create auth token at last def strinigify(U8E): Z8E = 0 P8E = 32 i8E = [] S8E = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" while (Z8E < P8E): try: tempx = U8E[Z8E + 2 >> 2] except IndexError: tempx = 0 L8E = 0 while (L8E < 4 and Z8E + .75 * L8E < P8E): A8E = (U8E[Z8E >> 2] >> 24 - Z8E % 4 * 8 & 255) << 16 | (U8E[Z8E + 1 >> 2] >> 24 - (Z8E + 1) % 4 * 8 & 255) << 8 | tempx >> 24 - (Z8E + 2) % 4 * 8 & 255 i8E.append( S8E[A8E >> 6 * (3 - L8E) & 63] ) L8E += 1 Z8E += 3 # padd to 44 with = but these get stripped later so dont bother now #i8E.append("=") return(i8E) # susbstitute some characters in auth string before requesting license def change(auth): result = ''.join(str(x) for x in auth) result = result.replace("+", "-") result = result.replace("/", "_") result = re.sub(r'=={0,}$', '', result) return(result) # build array from lic key def parse(M5A): # always is the same so no need to generate each time parsemap = [None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, 62, None, None, None, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, None, None, None, 64, None, None, None, 0, 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, None, None, None, None, None, None, 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] j5A = [0] * 4 z5A = 0 f5A = 0 F5A = M5A.index('=') for f5A in range(F5A): if ((f5A % 4) != 0): B5A = parsemap[ord(M5A[f5A - 1])] << f5A % 4 * 2 G5A = parsemap[ord(M5A[f5A])] >> 6 - f5A % 4 * 2 Z5A = B5A | G5A temp_x = 24 - z5A % 4 * 8 j5A[z5A >> 2] |= ctypes.c_int(Z5A << temp_x).value z5A += 1 return(j5A) # all we need is array of ascii value of each character in the long story def url_parse(queryStr): i = 0 data = [] unicodestring=urllib.parse.unquote(queryStr) for unicodechar in unicodestring: data.append(ord(unicodechar)) i += 1 return data def randomMath(y6k): y6kInt = int(y6k) b = int(4294967296 * (y6k - (0 | y6kInt) )) c = twos_comp(b, 32) return(c) def srState(H6k): u6k = int(math.sqrt(H6k)) Q6k = 2 state = True while (Q6k <= u6k): if ((H6k % Q6k) == 0): state = False Q6k = u6k + 2 Q6k += 1 return(state) def magic64(): B6k = [0] * 64 S6k = [0] * 64 V6k = 2 X6k = 0 while (X6k < 64): if ( srState(V6k) ): B6k[X6k] = randomMath( pow(V6k, 0.5) ) # approximating 1/3 looks okay S6k[X6k] = randomMath( pow(V6k, 0.3333333333333333)) X6k += 1 V6k += 1 return(S6k) def array32a1(a1,c8): out = [] for x in a1: out.append(x) for x in c8 : out.append(x) out.append(-2147483648) for x in range(6): out.append(0) out.append(768) return(out) def doProcessBlock(D5r,o0r,S6k,Y5k): I6k = [] a6k = D5r[0] k6k = D5r[1] M6k = D5r[2] z5k = D5r[3] c6k = D5r[4] R5k = D5r[5] f5k = D5r[6] q5k = D5r[7] for L6k in range (64): if (L6k < 16): x = L6k + Y5k I6k.append( o0r[L6k + Y5k] ) else: s6k = I6k[L6k - 15] h5k = (ctypes.c_int(s6k << 25).value | (s6k & 0xffffffff) >> 7) ^ (ctypes.c_int(s6k << 14).value | (s6k & 0xffffffff) >> 18) ^ (s6k & 0xffffffff) >> 3 t5k = I6k[L6k - 2] K5k = (ctypes.c_int(t5k << 15).value | (t5k & 0xffffffff) >> 17) ^ (ctypes.c_int(t5k << 13).value | (t5k & 0xffffffff) >> 19) ^ (t5k & 0xffffffff) >> 10 I6k.append( h5k + I6k[L6k - 7] + K5k + I6k[L6k - 16]) g5k = a6k & k6k ^ a6k & M6k ^ k6k & M6k P5k = (ctypes.c_int(a6k << 30).value | (a6k & 0xffffffff) >> 2) ^ (ctypes.c_int(a6k << 19).value | (a6k & 0xffffffff) >> 13) ^ (ctypes.c_int(a6k << 10).value | (a6k & 0xffffffff) >> 22) m5k = q5k + ((ctypes.c_int(c6k << 26).value | (c6k & 0xffffffff) >> 6) ^ (ctypes.c_int(c6k << 21).value | (c6k & 0xffffffff) >> 11) ^ (ctypes.c_int(c6k << 7).value | (c6k & 0xffffffff) >> 25)) + (c6k & R5k ^ ~c6k & f5k) + S6k[L6k] + I6k[L6k]; q5k = f5k f5k = R5k R5k = c6k c6k = ctypes.c_int( (z5k + m5k) ).value z5k = M6k M6k = k6k k6k = a6k a6k = ctypes.c_int(m5k + (P5k + g5k)).value # finally make new magic array of 8 D5r[0] = ctypes.c_int(D5r[0] + a6k).value D5r[1] = ctypes.c_int(D5r[1] + k6k).value D5r[2] = ctypes.c_int(D5r[2] + M6k).value D5r[3] = ctypes.c_int(D5r[3] + z5k).value D5r[4] = ctypes.c_int(D5r[4] + c6k).value D5r[5] = ctypes.c_int(D5r[5] + R5k).value D5r[6] = ctypes.c_int(D5r[6] + f5k).value D5r[7] = ctypes.c_int(D5r[7] + q5k).value return(D5r) def getdata(ui): # We need 2 things from the js file, the long string of gibberish and the short # string to OR with, # I assume short string is always 6 digits long to regexp search on # The lomg string is really long so i look for at least 3000 characters resp = urlquick.get(KEYURL) #content = resp.content content = resp.content.decode("utf-8", "ignore") # short string m = re.compile(r';}}}\)\(\'(......)\'\)};').search(content) ss = m.group(1) # long string m = re.compile(r'\(\){return "(.{3000,})";\}').search(content) s = str(m.group(1)) z = url_parse(s) l = len(z) y = 0 sout = "" for x in range(l): if (y > 5): y = 0 p1 = z[x] p2 = ord(ss[y]) k = p1 ^ p2 # Only worried about printable characters for what we need if ( (k > 31) and (k < 127) ): sout = sout + chr(k) #else: # sout = sout + str(k) y = y + 1 #extract the key from sout m = re.compile(r'SSL_MA..(.{24})..(.{24})').search(sout) k1 = m.group(1) k2 = m.group(2) # we need an 8 byte array that makes magic m = re.compile(r'2689\)\]=\[(\d+),(\d+),(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)\]').search(content) # lets make an array magicArray = [] for x in range(1,9): magicArray.append( twos_comp(int( m.group(x) ),32) ) fourarray = parse(k1) a1,a2 = init(fourarray) S6k = magic64() timeStamp = str( int(time.time()) ) CALL_URL = LICC_URL % (ui, timeStamp) #CALL_URL = LIVE_LICC_URL % (ui, timeStamp) print("Things for the Bell's man to workout") print("From - \n", CALL_URL) print("and array - \n", magicArray) array23 = cassiearray(CALL_URL) array32 = cassieoarraymod(array23) # iterate around doProcessBlock magicArrayF = arrayCopy(magicArray) onemagic8 = doProcessBlock(magicArrayF,a2,S6k,0) twomagic8array = arrayCopy(array32) twomagic8 = doProcessBlock(onemagic8,twomagic8array,S6k,0) threemagic8 = doProcessBlock(twomagic8,twomagic8array,S6k,16) magicArrayF = arrayCopy(magicArray) fourmagic8array = array32a1(a1,threemagic8) fourmagic8 = doProcessBlock(magicArrayF,fourmagic8array,S6k,0) fivemagic8 = doProcessBlock(fourmagic8,fourmagic8array,S6k,16) authKey = strinigify(fivemagic8) str1 = change(authKey) print("We get str1 is\n", str1) LICFULL_URL = CALL_URL + "&auth=" + str1 #return (LICFULL_URL) # New Shit for iv etc return (LICFULL_URL,k2) # New Shit for iv etc def makeManyArrays(): N94 = [0] * 256 x44 = [0] * 256 X44 = [0] * 256 Y94 = 0 E94 = 0 for s94 in range (256): x = ctypes.c_int(s94 << 1).value if (s94 < 128): N94[s94] = x else: N94[s94] = x ^ 283 def mangle(st): #result = iv + "====" result = st result = result.replace("-", "+") result = result.replace("_", "/") return (result) def stringify480(I0,M0): s0 = [0] N0 = 0 while ( ((N0 & 0xffffffff) >> 2) < len(I0)): o0 = ( I0[(N0 & 0xffffffff) >> 2] & 0xffffffff) >> 24 - N0 % 4 * 8 & 255 s0.append( chr(o0) ) N0 += 1 result = ''.join(str(x) for x in s0) trimstr = result[:result.rindex('}')+1] return(s0,trimstr) def getUseful(s): # back to json parsing to also do live urls ..... keyserver = 'NA' streamUrl ='NA' subtitile = 'NA' #fixed = s[1:] #data = json.loads(fixed) data = json.loads(s) jsonData = data['assets'] for x in jsonData: if (x['drm'] == "widevine" ): keyserver = (x['keyserver']) u = (x['renditions']) for i in u: streamUrl = i['url'] return (streamUrl, keyserver, subtitile) def padAndB64(data, length=16): data=base64.b64decode(data) paddingToUse = b'\x00'*(length-len(data)) #Undoubtedly there is a better way of doing null byte padding, but it's not in the padding routines... return data+paddingToUse def part2(iv, aesKey, rdata): realIv = padAndB64(mangle(iv)) realAesKey = padAndB64(mangle(aesKey)) realRData = base64.b64decode(mangle(rdata)) cipher = AES.new(realAesKey, AES.MODE_CBC, iv=realIv) dataToParse=unpad(cipher.decrypt(realRData),16) (stream, drmurl, sub) = getUseful(dataToParse.decode('utf-8')) return (stream, drmurl, sub) def ivdata(URL): resp = urlquick.get(URL) root = json.loads(resp.text) iv = root['iv'] data = root['data'] return(iv, data) # For now hardcoded showid received show_id = "C5278980003" #show_id = "C2" LICFULL_URL, aesKey = getdata(show_id) print("LICFULL_URL is \n", LICFULL_URL) (iv,data)=ivdata(LICFULL_URL) (stream,drmurl,suburl) = part2(iv,aesKey,data) resp = urlquick.get(CERT_URL) content = resp.content cert_data = (base64.b64encode(content)).decode('ascii') print ("stream =", stream) print ("license_url =", drmurl)