const CDM_VERSION = '' // The TextEncoder/Decoder API isn't supported so it has to be polyfilled. // Taken from https://gist.github.com/Yaffle/5458286#file-textencodertextdecoder-js function TextEncoder() { } TextEncoder.prototype.encode = function (string) { var octets = []; var length = string.length; var i = 0; while (i < length) { var codePoint = string.codePointAt(i); var c = 0; var bits = 0; if (codePoint <= 0x0000007F) { c = 0; bits = 0x00; } else if (codePoint <= 0x000007FF) { c = 6; bits = 0xC0; } else if (codePoint <= 0x0000FFFF) { c = 12; bits = 0xE0; } else if (codePoint <= 0x001FFFFF) { c = 18; bits = 0xF0; } octets.push(bits | (codePoint >> c)); c -= 6; while (c >= 0) { octets.push(0x80 | ((codePoint >> c) & 0x3F)); c -= 6; } i += codePoint >= 0x10000 ? 2 : 1; } return octets; } function getPrivateKey(address) { Interceptor.attach(ptr(address), { onEnter: function (args) { if (!args[6].isNull()) { const size = args[6].toInt32(); if (size >= 1000 && size <= 2000 && !args[5].isNull()) { const buf = args[5].readByteArray(size); const bytes = new Uint8Array(buf); // The first two bytes of the DER encoding are 0x30 and 0x82 (MII). if (bytes[0] === 0x30 && bytes[1] === 0x82) { try { const binaryString = a2bs(bytes) const keyLength = getKeyLength(binaryString); const key = bytes.slice(0, keyLength); send('private_key', key); } catch (error) { console.log(error) } } } } } }); } // nop privacy mode. // PrivacyMode encrypts the payload with the public key returned by the license server which we don't want. function disablePrivacyMode(address) { Interceptor.attach(address, { onLeave: function (retval) { retval.replace(ptr(0)); } }); } function prepareKeyRequest(address) { Interceptor.attach(ptr(address), { onEnter: function (args) { switch (CDM_VERSION) { case '14.0.0': case '15.0.0': case '16.0.0': this.ret = args[4]; break; case '16.1.0': this.ret = args[5]; break; default: this.ret = args[4]; break; } }, onLeave: function () { if (this.ret) { const size = Memory.readU32(ptr(this.ret).add(Process.pointerSize)) const arr = Memory.readByteArray(this.ret.add(Process.pointerSize * 2).readPointer(), size) send('device_info', arr); } } }); } function hookLibFunctions(lib) { const name = lib['name']; const baseAddr = lib['base']; const message = 'Hooking ' + name + ' at ' + baseAddr; send('message_info', new TextEncoder().encode(message)) Module.enumerateExportsSync(name).forEach(function (module) { try { let hookedModule; if (module.name.includes('UsePrivacyMode')) { disablePrivacyMode(module.address); hookedModule = module.name } else if (module.name.includes('PrepareKeyRequest')) { prepareKeyRequest(module.address); hookedModule = module.name } else if (module.name.match(/^[a-z]+$/)) { getPrivateKey(module.address); hookedModule = module.name } if (hookedModule) { const message = 'Hooked ' + hookedModule + ' at ' + module.address; send('message_info', new TextEncoder().encode(message)); } } catch (e) { console.log("Error: " + e + " at F: " + module.name); } }); } function getModuleByName(lib) { return Process.getModuleByName(lib); } function a2bs(bytes) { let b = ''; for (let i = 0; i < bytes.byteLength; i++) b += String.fromCharCode(bytes[i]); return b } function getKeyLength(key) { let pos = 1 // Skip the tag let buf = key.charCodeAt(pos++); let len = buf & 0x7F; // Short tag length buf = 0; for (let i = 0; i < len; ++i) buf = (buf * 256) + key.charCodeAt(pos++); return pos + Math.abs(buf); } rpc.exports.hooklibfunctions = hookLibFunctions; rpc.exports.getmodulebyname = getModuleByName;