mirror of https://github.com/Diazole/dumper.git
Update script to support Android 10, 11, 12
This commit is contained in:
parent
d667c67655
commit
6f2a34ad01
|
@ -0,0 +1,83 @@
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import base64
|
||||||
|
import frida
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Helpers.wv_proto2_pb2 import SignedLicenseRequest
|
||||||
|
|
||||||
|
|
||||||
|
class Device:
|
||||||
|
def __init__(self):
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.saved_keys = {}
|
||||||
|
self.frida_script = open(
|
||||||
|
'./Helpers/script.js',
|
||||||
|
'r',
|
||||||
|
encoding="utf_8"
|
||||||
|
).read()
|
||||||
|
self.widevine_libraries = [
|
||||||
|
'libwvhidl.so'
|
||||||
|
]
|
||||||
|
self.usb_device = frida.get_usb_device()
|
||||||
|
self.name = self.usb_device.name
|
||||||
|
|
||||||
|
def export_key(self, key, client_id):
|
||||||
|
system_id = client_id.Token._DeviceCertificate.SystemId
|
||||||
|
save_dir = os.path.join(
|
||||||
|
'key_dumps',
|
||||||
|
f'{self.name}/private_keys/{system_id}/{str(key.n)[:10]}'
|
||||||
|
)
|
||||||
|
|
||||||
|
if not os.path.exists(save_dir):
|
||||||
|
os.makedirs(save_dir)
|
||||||
|
|
||||||
|
with open(os.path.join(save_dir, 'client_id.bin'), 'wb+') as writer:
|
||||||
|
writer.write(client_id.SerializeToString())
|
||||||
|
|
||||||
|
with open(os.path.join(save_dir, 'private_key.pem'), 'wb+') as writer:
|
||||||
|
writer.write(key.exportKey('PEM'))
|
||||||
|
self.logger.info('Key pairs saved at %s', save_dir)
|
||||||
|
|
||||||
|
def on_message(self, msg, data):
|
||||||
|
if msg['payload'] == 'private_key':
|
||||||
|
key = RSA.import_key(data)
|
||||||
|
if key.n not in self.saved_keys:
|
||||||
|
encoded_key = base64.b64encode(data).decode('utf-8')
|
||||||
|
self.logger.debug('Retrieved key: %s', encoded_key)
|
||||||
|
self.saved_keys[key.n] = key
|
||||||
|
elif msg['payload'] == 'device_info':
|
||||||
|
self.license_request_message(data)
|
||||||
|
elif msg['payload'] == 'message_info':
|
||||||
|
self.logger.info(data.decode())
|
||||||
|
|
||||||
|
def license_request_message(self, data):
|
||||||
|
root = SignedLicenseRequest()
|
||||||
|
root.ParseFromString(data)
|
||||||
|
public_key = root.Msg.ClientId.Token._DeviceCertificate.PublicKey
|
||||||
|
self.logger.debug(
|
||||||
|
'Retrieved key: %s',
|
||||||
|
base64.b64encode(public_key).decode('utf-8')
|
||||||
|
)
|
||||||
|
key = RSA.importKey(public_key)
|
||||||
|
cur = self.saved_keys.get(key.n)
|
||||||
|
self.export_key(cur, root.Msg.ClientId)
|
||||||
|
|
||||||
|
def find_widevine_process(self, process_name):
|
||||||
|
process = self.usb_device.attach(process_name)
|
||||||
|
script = process.create_script(self.frida_script)
|
||||||
|
script.load()
|
||||||
|
loaded_modules = []
|
||||||
|
try:
|
||||||
|
for lib in self.widevine_libraries:
|
||||||
|
loaded_modules.append(script.exports.getmodulebyname(lib))
|
||||||
|
finally:
|
||||||
|
process.detach()
|
||||||
|
return loaded_modules
|
||||||
|
|
||||||
|
def hook_to_process(self, process, library):
|
||||||
|
session = self.usb_device.attach(process)
|
||||||
|
script = session.create_script(self.frida_script)
|
||||||
|
script.on('message', self.on_message)
|
||||||
|
script.load()
|
||||||
|
script.exports.hooklibfunctions(library)
|
||||||
|
return session
|
|
@ -1,90 +0,0 @@
|
||||||
import io
|
|
||||||
import json
|
|
||||||
import struct
|
|
||||||
from base64 import b64decode, b64encode
|
|
||||||
from binascii import hexlify
|
|
||||||
|
|
||||||
|
|
||||||
def create_table():
|
|
||||||
a = []
|
|
||||||
for i in range(256):
|
|
||||||
k = i << 24
|
|
||||||
for _ in range(8):
|
|
||||||
k = (k << 1) ^ 0x4c11db7 if k & 0x80000000 else k << 1
|
|
||||||
a.append(k & 0xffffffff)
|
|
||||||
return a
|
|
||||||
|
|
||||||
|
|
||||||
def crc32_mpeg(data, length):
|
|
||||||
crc_val = 0xFFFFFFFF
|
|
||||||
crctab = create_table()
|
|
||||||
for i in range(length):
|
|
||||||
crc_val = (crctab[(data[i] & 0xFF) ^ (crc_val >> 24)] ^ (crc_val << 8)) & 0xFFFFFFFF
|
|
||||||
return crc_val
|
|
||||||
|
|
||||||
|
|
||||||
class Keybox:
|
|
||||||
def __init__(self, keybox_data: any):
|
|
||||||
if isinstance(keybox_data, str):
|
|
||||||
self.__keybox = b64decode(keybox_data)
|
|
||||||
elif isinstance(keybox_data, io.BufferedReader):
|
|
||||||
self.__keybox = keybox_data.read()
|
|
||||||
elif isinstance(keybox_data, dict):
|
|
||||||
self.__keybox = self.__generate_crc(keybox_data)
|
|
||||||
else:
|
|
||||||
print(type(keybox_data))
|
|
||||||
raise ValueError('unable to read the file/string, etc')
|
|
||||||
|
|
||||||
self.__parse()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __generate_crc(keybox) -> bytes:
|
|
||||||
device_id = keybox['device_id']
|
|
||||||
device_token = keybox['device_token']
|
|
||||||
device_key = keybox['device_key']
|
|
||||||
key_box = bytes.fromhex(device_id) + bytes.fromhex(device_key) + bytes.fromhex(device_token) + b'kbox'
|
|
||||||
crc = crc32_mpeg(key_box, len(key_box))
|
|
||||||
key_box += struct.pack('>I', crc)
|
|
||||||
key_box += keybox['security_level'].encode()
|
|
||||||
return key_box
|
|
||||||
|
|
||||||
def __parse(self):
|
|
||||||
self.device_id = self.__keybox[0:32]
|
|
||||||
# this is the aes key
|
|
||||||
self.device_key = self.__keybox[32:48]
|
|
||||||
self.device_token = self.__keybox[48:120]
|
|
||||||
self.keybox_tag = self.__keybox[120:124]
|
|
||||||
self.crc32 = struct.unpack('>I', self.__keybox[124:128])[0]
|
|
||||||
self.crc32_raw = hexlify(self.__keybox[124:128])
|
|
||||||
# this is optional, most likely not required
|
|
||||||
self.level_tag = self.__keybox[128:132]
|
|
||||||
self.flags = struct.unpack(">L", self.__keybox[48:120][0:4])[0]
|
|
||||||
self.version = struct.unpack(">I", self.__keybox[48:52])[0]
|
|
||||||
self.system_id = struct.unpack(">I", self.__keybox[52:56])[0]
|
|
||||||
# or unique_id as in wv pdf, encrypted by pre-provisioning key
|
|
||||||
self.provisioning_id = self.__keybox[56:72]
|
|
||||||
# encrypted with unique id, contains device key, device key hash, and flags
|
|
||||||
self.encrypted_bits = self.__keybox[72:120]
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return json.dumps({
|
|
||||||
'device_id': b64encode(self.device_id).decode(),
|
|
||||||
'device_id_size': len(self.device_id),
|
|
||||||
'device_key': b64encode(self.device_key).decode(),
|
|
||||||
'device_token': b64encode(self.device_token).decode(),
|
|
||||||
'device_token_size': len(self.device_token),
|
|
||||||
'kbox_tag': self.keybox_tag.decode(),
|
|
||||||
'crc32': self.crc32,
|
|
||||||
'crc32_raw': self.crc32_raw.decode(),
|
|
||||||
'lvl1_tag': self.level_tag.decode(),
|
|
||||||
'flags': self.flags,
|
|
||||||
'released': True if self.flags & 2 == 2 else False,
|
|
||||||
'version': self.version,
|
|
||||||
'system_id': self.system_id,
|
|
||||||
'provisioning_id': b64encode(self.provisioning_id).decode(),
|
|
||||||
'encrypted_bits': b64encode(self.encrypted_bits).decode(),
|
|
||||||
'keybox': b64encode(self.__keybox).decode()
|
|
||||||
}, indent=4)
|
|
||||||
|
|
||||||
def get_keybox(self):
|
|
||||||
return self.__keybox
|
|
|
@ -1,187 +0,0 @@
|
||||||
import os
|
|
||||||
import json
|
|
||||||
from Crypto.PublicKey import RSA
|
|
||||||
from google.protobuf import message
|
|
||||||
import logging
|
|
||||||
from Helpers.Keybox import Keybox
|
|
||||||
from Helpers.wv_proto2_pb2 import SignedLicenseRequest
|
|
||||||
|
|
||||||
|
|
||||||
class Scan:
|
|
||||||
def __init__(self, device_name):
|
|
||||||
self.logger = logging.getLogger(__name__)
|
|
||||||
self.KEY_DUMP_LOC = 'keydump/'
|
|
||||||
self.device_name = device_name
|
|
||||||
self.saved_keys = {}
|
|
||||||
self.frida_script = open('Helpers/script.js', 'r').read()
|
|
||||||
self.device = {
|
|
||||||
'device_id': None,
|
|
||||||
'device_token': None,
|
|
||||||
'device_key': os.urandom(16).hex(),
|
|
||||||
'security_level': ''
|
|
||||||
}
|
|
||||||
self.widevine_libraries = [
|
|
||||||
'libwvhidl.so',
|
|
||||||
'libwvdrmengine.so',
|
|
||||||
'liboemcrypto.so',
|
|
||||||
'libmediadrm.so',
|
|
||||||
'libwvdrm_L1.so',
|
|
||||||
'libWVStreamControlAPI_L1.so',
|
|
||||||
'libdrmwvmplugin.so',
|
|
||||||
'libwvm.so'
|
|
||||||
]
|
|
||||||
|
|
||||||
def export_key(self, k):
|
|
||||||
root = SignedLicenseRequest()
|
|
||||||
root.ParseFromString(k['id'])
|
|
||||||
cid = root.Msg.ClientId
|
|
||||||
system_id = cid.Token._DeviceCertificate.SystemId
|
|
||||||
save_dir = os.path.join('key_dumps', f'{self.device_name}/private_keys/{system_id}/{str(k["key"].n)[:10]}')
|
|
||||||
|
|
||||||
if not os.path.exists(save_dir):
|
|
||||||
os.makedirs(save_dir)
|
|
||||||
|
|
||||||
with open(os.path.join(save_dir, 'client_id.bin'), 'wb+') as writer:
|
|
||||||
writer.write(cid.SerializeToString())
|
|
||||||
|
|
||||||
with open(os.path.join(save_dir, 'private_key.pem'), 'wb+') as writer:
|
|
||||||
writer.write(k['key'].exportKey('PEM'))
|
|
||||||
self.logger.info('Key pairs saved at ' + save_dir)
|
|
||||||
|
|
||||||
def on_message(self, msg, data):
|
|
||||||
try:
|
|
||||||
if msg['payload'] == 'priv':
|
|
||||||
self.logger.debug('processing private key')
|
|
||||||
self.private_key_message(msg, data)
|
|
||||||
elif msg['payload'] == 'id':
|
|
||||||
self.logger.debug('processing id')
|
|
||||||
self.license_request_message(data)
|
|
||||||
elif msg['payload'] == 'device_id':
|
|
||||||
self.logger.debug('processing device id')
|
|
||||||
self.device_id_message(data)
|
|
||||||
elif msg['payload'] == 'device_token':
|
|
||||||
self.logger.debug('processing device token')
|
|
||||||
self.device_token_message(data)
|
|
||||||
elif msg['payload'] == 'security_level':
|
|
||||||
tag = data.decode()
|
|
||||||
if tag == 'L1':
|
|
||||||
self.device['security_level'] = 'LVL1'
|
|
||||||
else:
|
|
||||||
self.device['security_level'] = 'LVL3'
|
|
||||||
elif msg['payload'] == 'aes_key':
|
|
||||||
self.aes_key_message(data)
|
|
||||||
elif msg['payload'] == 'message':
|
|
||||||
payload = json.loads(data.decode())
|
|
||||||
self.logger.debug(
|
|
||||||
json.dumps(
|
|
||||||
payload,
|
|
||||||
indent=4
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif msg['payload'] == 'message_info':
|
|
||||||
self.logger.info(data.decode())
|
|
||||||
|
|
||||||
except:
|
|
||||||
self.logger.error('unable to process the message')
|
|
||||||
self.logger.error(msg)
|
|
||||||
self.logger.error(data)
|
|
||||||
|
|
||||||
def private_key_message(self, private_key_message, data):
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
key = RSA.importKey(data)
|
|
||||||
cur = self.saved_keys.get(key.n, {})
|
|
||||||
if 'id' in cur:
|
|
||||||
if 'key' not in cur:
|
|
||||||
cur['key'] = key
|
|
||||||
self.saved_keys[key.n] = cur
|
|
||||||
self.export_key(cur)
|
|
||||||
else:
|
|
||||||
self.saved_keys[key.n] = {'key': key}
|
|
||||||
except:
|
|
||||||
self.logger.error('unable to load private key')
|
|
||||||
self.logger.error(data)
|
|
||||||
pass
|
|
||||||
except:
|
|
||||||
self.logger.error('payload of type priv failed')
|
|
||||||
self.logger.error(private_key_message)
|
|
||||||
|
|
||||||
def license_request_message(self, data):
|
|
||||||
with open('license_request.bin', 'wb+') as f:
|
|
||||||
f.write(data)
|
|
||||||
root = SignedLicenseRequest()
|
|
||||||
try:
|
|
||||||
root.ParseFromString(data)
|
|
||||||
except message.DecodeError:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
key = RSA.importKey(root.Msg.ClientId.Token._DeviceCertificate.PublicKey)
|
|
||||||
cur = self.saved_keys.get(key.n, {})
|
|
||||||
if 'key' in cur:
|
|
||||||
if 'id' not in cur:
|
|
||||||
cur['id'] = data
|
|
||||||
self.saved_keys[key.n] = cur
|
|
||||||
self.export_key(cur)
|
|
||||||
else:
|
|
||||||
self.saved_keys[key.n] = {'id': data}
|
|
||||||
except Exception as error:
|
|
||||||
self.logger.error(error)
|
|
||||||
|
|
||||||
def device_id_message(self, data_buffer):
|
|
||||||
if not self.device['device_id']:
|
|
||||||
self.device['device_id'] = data_buffer.hex()
|
|
||||||
if self.device['device_id'] and self.device['device_token'] and self.device['device_key']:
|
|
||||||
self.save_key_box()
|
|
||||||
|
|
||||||
def device_token_message(self, data_buffer):
|
|
||||||
if not self.device['device_token']:
|
|
||||||
self.device['device_token'] = data_buffer.hex()
|
|
||||||
if self.device['device_id'] and self.device['device_token']:
|
|
||||||
self.save_key_box()
|
|
||||||
|
|
||||||
def aes_key_message(self, data_buffer):
|
|
||||||
if not self.device['device_key']:
|
|
||||||
self.device['device_key'] = data_buffer.hex()
|
|
||||||
if self.device['device_id'] and self.device['device_token']:
|
|
||||||
self.save_key_box()
|
|
||||||
|
|
||||||
def find_widevine_process(self, dev, process_name):
|
|
||||||
process = dev.attach(process_name)
|
|
||||||
script = process.create_script(self.frida_script)
|
|
||||||
script.load()
|
|
||||||
loaded = []
|
|
||||||
try:
|
|
||||||
for lib in self.widevine_libraries:
|
|
||||||
try:
|
|
||||||
loaded.append(script.exports.widevinelibrary(lib))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
process.detach()
|
|
||||||
return loaded
|
|
||||||
|
|
||||||
def hook_to_process(self, device, process, library):
|
|
||||||
session = device.attach(process)
|
|
||||||
script = session.create_script(self.frida_script)
|
|
||||||
script.on('message', self.on_message)
|
|
||||||
script.load()
|
|
||||||
script.exports.inject(library, process)
|
|
||||||
return session
|
|
||||||
|
|
||||||
def save_key_box(self):
|
|
||||||
try:
|
|
||||||
if self.device['device_id'] is not None and self.device['device_token'] is not None:
|
|
||||||
self.logger.info('saving key box')
|
|
||||||
keybox = Keybox(self.device)
|
|
||||||
box = os.path.join('key_dumps', f'{self.device_name}/key_boxes/{keybox.system_id}')
|
|
||||||
self.logger.debug(f'saving to {box}')
|
|
||||||
if not os.path.exists(box):
|
|
||||||
os.makedirs(box)
|
|
||||||
with open(os.path.join(box, f'{keybox.system_id}.bin'), 'wb') as writer:
|
|
||||||
writer.write(keybox.get_keybox())
|
|
||||||
with open(os.path.join(box, f'{keybox.system_id}.json'), 'w') as writer:
|
|
||||||
writer.write(keybox.__repr__())
|
|
||||||
self.logger.info(f'saved keybox to {box}')
|
|
||||||
except Exception as error:
|
|
||||||
self.logger.error('unable to save keybox')
|
|
||||||
self.logger.error(error)
|
|
|
@ -1,5 +1,8 @@
|
||||||
const KNOWN_DYNAMIC_FUNC = ['ulns', 'cwkfcplc', 'dnvffnze', 'kgaitijd', 'polorucp'];
|
const DYNAMIC_FUNCTION_NAME = 'CHANGE_ME'
|
||||||
|
const CDM_VERSION = 'CHANGE_ME'
|
||||||
|
|
||||||
|
// 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() {
|
function TextEncoder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,315 +38,20 @@ TextEncoder.prototype.encode = function (string) {
|
||||||
return octets;
|
return octets;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TextDecoder() {
|
function getPrivateKey(address) {
|
||||||
}
|
|
||||||
|
|
||||||
TextDecoder.prototype.decode = function (octets) {
|
|
||||||
var string = "";
|
|
||||||
var i = 0;
|
|
||||||
while (i < octets.length) {
|
|
||||||
var octet = octets[i];
|
|
||||||
var bytesNeeded = 0;
|
|
||||||
var codePoint = 0;
|
|
||||||
if (octet <= 0x7F) {
|
|
||||||
bytesNeeded = 0;
|
|
||||||
codePoint = octet & 0xFF;
|
|
||||||
} else if (octet <= 0xDF) {
|
|
||||||
bytesNeeded = 1;
|
|
||||||
codePoint = octet & 0x1F;
|
|
||||||
} else if (octet <= 0xEF) {
|
|
||||||
bytesNeeded = 2;
|
|
||||||
codePoint = octet & 0x0F;
|
|
||||||
} else if (octet <= 0xF4) {
|
|
||||||
bytesNeeded = 3;
|
|
||||||
codePoint = octet & 0x07;
|
|
||||||
}
|
|
||||||
if (octets.length - i - bytesNeeded > 0) {
|
|
||||||
var k = 0;
|
|
||||||
while (k < bytesNeeded) {
|
|
||||||
octet = octets[i + k + 1];
|
|
||||||
codePoint = (codePoint << 6) | (octet & 0x3F);
|
|
||||||
k += 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
codePoint = 0xFFFD;
|
|
||||||
bytesNeeded = octets.length - i;
|
|
||||||
}
|
|
||||||
string += String.fromCodePoint(codePoint);
|
|
||||||
i += bytesNeeded + 1;
|
|
||||||
}
|
|
||||||
return string
|
|
||||||
}
|
|
||||||
|
|
||||||
function containsLib(library){
|
|
||||||
return Process.getModuleByName(library);
|
|
||||||
}
|
|
||||||
|
|
||||||
function containsFunction(name, address) {
|
|
||||||
var result = false;
|
|
||||||
for (var i = 0; i < KNOWN_DYNAMIC_FUNC.length; i++) {
|
|
||||||
result = KNOWN_DYNAMIC_FUNC[i] === name;
|
|
||||||
if (result) {
|
|
||||||
sender_payload({
|
|
||||||
from: 'Dynamic Function',
|
|
||||||
message: 'L3 RSA Key export function found: ' + name
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function inject(lib, process_name){
|
|
||||||
// printer('Running ' + lib['name'] + ' at ' + lib['base'], 'Hook');
|
|
||||||
sender_payload_info(
|
|
||||||
'Running ' + lib['name'] + ' at ' + lib['base']
|
|
||||||
);
|
|
||||||
Hooker(lib, process_name)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Hooker(lib, process_name) {
|
|
||||||
const name = lib['name'];
|
|
||||||
Module.enumerateExportsSync(name).forEach(function(exp){
|
|
||||||
try {
|
|
||||||
var module_address = exp.address;
|
|
||||||
if (exp.name === '_lcc00' || exp.name === '_oecc00') {
|
|
||||||
GetLevel3_IsInApp(module_address, process_name);
|
|
||||||
} else if (exp.name === '_lcc01' || exp.name === '_oecc01') {
|
|
||||||
GetLevel3_Initialize(module_address, process_name);
|
|
||||||
} else if (exp.name === '_lcc49' || exp.name === '_oecc49') {
|
|
||||||
GetLevel3_GetProvisioningMethod(module_address, process_name);
|
|
||||||
} else if (exp.name === '_lcc38' || exp.name === '_oecc38') {
|
|
||||||
GetLevel3_GetNumberOfOpenSessions(module_address, process_name);
|
|
||||||
} else if (exp.name === '_lcc37' || exp.name === '_oecc37') {
|
|
||||||
GetLevel3_GetMaxNumberOfSessions(module_address, process_name);
|
|
||||||
} else if (exp.name === '_lcc22' || exp.name === '_oecc22') {
|
|
||||||
GetApiVersion(module_address, process_name);
|
|
||||||
} else if (exp.name === '_lcc46' || exp.name === '_oecc46') {
|
|
||||||
GetSecurityPatchLevel(module_address, process_name);
|
|
||||||
} else if (exp.name === '_lcc23' || exp.name === '_oecc23') {
|
|
||||||
GetSecurityLevel(module_address, process_name);
|
|
||||||
} else if (exp.name === '_lcc90' || exp.name === '_oecc90') {
|
|
||||||
GetLevel3_BuildInformation(module_address, process_name);
|
|
||||||
} else if (exp.name === '_lcc52' || exp.name === '_oecc52') {
|
|
||||||
GetSupportedCertificates(module_address, process_name);
|
|
||||||
} else if (exp.name === '_lcc02' || exp.name === '_oecc02') {
|
|
||||||
GetLevel3_Terminate_Status(module_address, process_name);
|
|
||||||
} else if (exp.name === '_lcc07' || exp.name === '_oecc07') {
|
|
||||||
GetLevel3_GetDeviceID(module_address, process_name);
|
|
||||||
} else if (exp.name === '_lcc04' || exp.name === '_oecc04') {
|
|
||||||
GetLevel3_GetKeyData(module_address, process_name)
|
|
||||||
} else if (exp.name === 'OEMCrypto_LoadKeys_Back_Compat') {
|
|
||||||
GetLevel3_LoadKeys(module_address, process_name);
|
|
||||||
} else if (exp.name === '_lcc12' || exp.name === '_oecc12') {
|
|
||||||
GetLevel3_GenerateDerivedKeys(module_address, process_name);
|
|
||||||
} else if (exp.name === '_lcc13' || exp.name === '_oecc13') {
|
|
||||||
GetLevel3_GenerateSignature(module_address, process_name);
|
|
||||||
} else if (exp.name === '_lcc50' || exp.name === '_oecc50') {
|
|
||||||
GetLevel3_GetOEMPublicCertificate(module_address, process_name);
|
|
||||||
} else if (exp.name === '_lcc19' || exp.name === '_oecc19') {
|
|
||||||
GetLevel3_LoadDeviceRSAKey(module_address, process_name)
|
|
||||||
} else if (exp.name === '_lcc18' || exp.name === '_oecc18') {
|
|
||||||
GetLevel3_RewrapDeviceRSAKey(module_address, process_name);
|
|
||||||
} else if (exp.name === 'AES_unwrap_key') {
|
|
||||||
AES_unwrap_key(module_address, process_name)
|
|
||||||
} else if (containsFunction(exp.name, exp.address)) {
|
|
||||||
polorucp(module_address, process_name);
|
|
||||||
} else if (exp.name.includes('UsePrivacyMode')) {
|
|
||||||
UsePrivacyMode(module_address, process_name);
|
|
||||||
} else if (exp.name === 'CdmInfo') {
|
|
||||||
CdmInfo(module_address, process_name);
|
|
||||||
} else if (exp.name.includes('PrepareKeyRequest')) {
|
|
||||||
PrepareKeyRequest(module_address, process_name);
|
|
||||||
} else if (exp.name.includes("_ZN14video_widevine25SignedProvisioningMessageC2Ev")) {
|
|
||||||
SignedProvisioningMessage(module_address, process_name)
|
|
||||||
} else if (exp.name === 'AES_set_encrypt_key') {
|
|
||||||
AES_set_encrypt_key(module_address, process_name)
|
|
||||||
} else if (exp.name.includes('jnyxqs')) {
|
|
||||||
// this needs to be changed to an array of methods since they all differ between oemcryptos for l1 and l3
|
|
||||||
jnyxqs(module_address)
|
|
||||||
} else if (exp.name === 'fwemrknr') {
|
|
||||||
fwemrknr(module_address, process_name)
|
|
||||||
} else if (exp.name === 'pbntpypb') {
|
|
||||||
pbntpypb(module_address, process_name)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Error: " + e + " at F: " + exp.name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function pbntpypb(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function(args) {
|
|
||||||
this.data = {
|
|
||||||
'1': args[0]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLeave: function(returnResult) {
|
|
||||||
console.log(hexdump(returnResult));
|
|
||||||
// console.log('onleave')
|
|
||||||
// console.log('first parameter');
|
|
||||||
// const data = Memory.readPointer(this.data['1']);
|
|
||||||
// const param1 = hexdump(data);
|
|
||||||
// console.log(param1);
|
|
||||||
// console.log('ended')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function fwemrknr(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function(args) {
|
|
||||||
this.data = {
|
|
||||||
'0': args[0],
|
|
||||||
'1': args[1],
|
|
||||||
'2': args[2],
|
|
||||||
'3': args[3],
|
|
||||||
'4': args[4],
|
|
||||||
'5': args[5],
|
|
||||||
'6': args[6],
|
|
||||||
'7': args[7],
|
|
||||||
'8': args[8],
|
|
||||||
'9': args[9],
|
|
||||||
'10': args[10],
|
|
||||||
'11': args[11],
|
|
||||||
'12': args[12],
|
|
||||||
'13': args[13],
|
|
||||||
'14': args[14],
|
|
||||||
'15': args[15],
|
|
||||||
'16': args[16]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLeave: function(returnResult) {
|
|
||||||
// console.log('onleave')
|
|
||||||
// console.log('first parameter');
|
|
||||||
// const data = Memory.readPointer(this.data['1']);
|
|
||||||
// const param1 = hexdump(data);
|
|
||||||
// console.log(param1);
|
|
||||||
// console.log('ended')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function jnyxqs(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function(args) {
|
|
||||||
this.data = {
|
|
||||||
'1': args[0],
|
|
||||||
'2': args[1]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLeave: function(returnResult) {
|
|
||||||
printer('jnyxqs', process_name);
|
|
||||||
console.log(hexdump(returnResult));
|
|
||||||
console.log(Memory.readByteArray(this.data['1'], this.data['2'].toInt32()));
|
|
||||||
console.log(this.data['2'].toInt32());
|
|
||||||
send('aes_key', Memory.readByteArray(this.data['1'], this.data['2'].toInt32()))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function ithomqf(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function(args) {
|
|
||||||
this.data = {
|
|
||||||
'1': args[0],
|
|
||||||
'2': args[1]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLeave: function(returnResult) {
|
|
||||||
printer('ithomqf', process_name);
|
|
||||||
console.log(hexdump(returnResult));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function AES_set_encrypt_key(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
Interceptor.attach(ptr(address), {
|
||||||
onEnter: function (args) {
|
onEnter: function (args) {
|
||||||
// both of these are pointers
|
|
||||||
this.data = {
|
|
||||||
"userKey": args[0],
|
|
||||||
"bits": args[1],
|
|
||||||
'key': args[2]
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLeave: function (returnResult) {
|
|
||||||
const size = this.data['bits'].toInt32() / 8;
|
|
||||||
const userKey = Memory.readByteArray(this.data['userKey'], size);
|
|
||||||
const key = Memory.readByteArray(this.data['key'], size);
|
|
||||||
printer('return result: ' + returnResult);
|
|
||||||
const data = {
|
|
||||||
from: process_name,
|
|
||||||
message: 'AES_set_encrypt_key',
|
|
||||||
payload: {
|
|
||||||
'size': size,
|
|
||||||
'user_key': byteArrayToHex(userKey),
|
|
||||||
'key': byteArrayToHex(key)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sender_payload(data)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function SignedProvisioningMessage(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function (args) {
|
|
||||||
this.data = args[0]
|
|
||||||
},
|
|
||||||
onLeave: function () {
|
|
||||||
printer('SignedProvisioningMessage', process_name);
|
|
||||||
console.log(this.data);
|
|
||||||
console.log(hexdump(this.data));
|
|
||||||
console.log(hexdump(Memory.readPointer(this.data)));
|
|
||||||
console.log(hexdump(Memory.readPointer(Memory.readPointer(this.data))));
|
|
||||||
console.log(Memory.readByteArray(Memory.readPointer(Memory.readPointer(this.data)), 2000));
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function readStdString(str) {
|
|
||||||
const size = str.add(Process.pointerSize).readUInt();
|
|
||||||
return str.add(Process.pointerSize * 2).readPointer().readByteArray(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
function printer(message, origination){
|
|
||||||
console.log('['+origination+']:[INFO]:', message)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CdmInfo(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function(args) {
|
|
||||||
console.log('CdmInfo');
|
|
||||||
console.log(JSON.stringify(args))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function polorucp(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function(args) {
|
|
||||||
if (!args[6].isNull()) {
|
if (!args[6].isNull()) {
|
||||||
const size = args[6].toInt32();
|
const size = args[6].toInt32();
|
||||||
if (size >= 1000 && size <= 2000 && !args[5].isNull()) {
|
if (size >= 1000 && size <= 2000 && !args[5].isNull()) {
|
||||||
const k = args[5].readByteArray(size);
|
const buf = args[5].readByteArray(size);
|
||||||
const view = new Uint8Array(k);
|
const bytes = new Uint8Array(buf);
|
||||||
if (view[0] === 0x30 && view[1] === 0x82) {
|
// The first two bytes of the DER encoding are 0x30 and 0x82 (MII).
|
||||||
const data = {
|
if (bytes[0] === 0x30 && bytes[1] === 0x82) {
|
||||||
from: process_name,
|
const binaryString = a2bs(bytes)
|
||||||
data: 'Captured Private Key'
|
const keyLength = getKeyLength(binaryString);
|
||||||
};
|
const key = bytes.slice(0, keyLength);
|
||||||
sender_payload(data);
|
send('private_key', key);
|
||||||
send('priv', k);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -351,590 +59,96 @@ function polorucp(address, process_name) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function PrepareKeyRequest(address, process_name) {
|
// nop privacy mode.
|
||||||
Interceptor.attach(ptr(address), {
|
// PrivacyMode encrypts the payload with the public key returned by the license server which we don't want.
|
||||||
onEnter: function (args) {
|
function disablePrivacyMode(address) {
|
||||||
this.ret = args[4];
|
|
||||||
},
|
|
||||||
onLeave: function () {
|
|
||||||
if (this.ret) {
|
|
||||||
const message = readStdString(this.ret);
|
|
||||||
const data = {
|
|
||||||
from: process_name,
|
|
||||||
message: 'PrepareKeyRequest, Captured License Request'
|
|
||||||
};
|
|
||||||
sender_payload(data);
|
|
||||||
send('id', message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function UsePrivacyMode(address, process_name) {
|
|
||||||
Interceptor.attach(address, {
|
Interceptor.attach(address, {
|
||||||
onLeave: function (retval) {
|
onLeave: function (retval) {
|
||||||
const data = {
|
|
||||||
from: process_name,
|
|
||||||
message: 'Replacing PrivacyMode'
|
|
||||||
};
|
|
||||||
sender_payload(data);
|
|
||||||
retval.replace(ptr(0));
|
retval.replace(ptr(0));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function AES_unwrap_key(address, process_name) {
|
function prepareKeyRequest(address) {
|
||||||
Interceptor.attach(ptr(address), {
|
Interceptor.attach(ptr(address), {
|
||||||
onEnter: function (args) {
|
onEnter: function (args) {
|
||||||
console.log('entering aes unwrap key')
|
switch (CDM_VERSION) {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function GetLevel3_Initialize(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function(args) {
|
|
||||||
sender_payload(
|
|
||||||
{
|
|
||||||
from: process_name,
|
|
||||||
message: 'OEMCrypto_Initialize'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function GetApiVersion(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onLeave: function (retval) {
|
|
||||||
// const message = 'OEMCryptoVersion: ' + retval.toInt32();
|
|
||||||
const data = {
|
|
||||||
from: process_name,
|
|
||||||
message: 'OEMCryptoVersion',
|
|
||||||
payload: {
|
|
||||||
'Version': retval.toInt32()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sender_payload(data)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function GetSecurityPatchLevel(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onLeave: function (retval) {
|
|
||||||
sender_payload({
|
|
||||||
from: process_name,
|
|
||||||
message: 'OEMSecurityPatchLevel',
|
|
||||||
payload:{
|
|
||||||
'Patch_Level': retval.toInt32()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function GetSecurityLevel(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onLeave: function (retval) {
|
|
||||||
const level = Memory.readUtf8String(retval);
|
|
||||||
sender_payload({
|
|
||||||
from: process_name,
|
|
||||||
message: 'OEMSecurityLevel',
|
|
||||||
payload: {
|
|
||||||
'Level': level
|
|
||||||
}
|
|
||||||
});
|
|
||||||
send('security_level', new TextEncoder().encode(level))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function GetLevel3_BuildInformation(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onLeave: function (retval) {
|
|
||||||
const message = 'OEMCrypto_BuildInformation: ' + Memory.readUtf8String(retval);
|
|
||||||
sender_payload({
|
|
||||||
from: process_name,
|
|
||||||
message: message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function GetSupportedCertificates(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onLeave: function (retval) {
|
|
||||||
const message = 'OEMSupportedCertificates: ' + OEMCrypto_RSA_Support[retval.toInt32()];
|
|
||||||
sender_payload({
|
|
||||||
from: process_name,
|
|
||||||
message: message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function GetLevel3_IsInApp(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onLeave: function (retval) {
|
|
||||||
sender_payload({
|
|
||||||
from: process_name,
|
|
||||||
message: 'OEMCrypto_IsInApp',
|
|
||||||
payload: {
|
|
||||||
'in_app': Boolean(retval)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function GetLevel3_GetProvisioningMethod(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onLeave: function (retval) {
|
|
||||||
sender_payload({
|
|
||||||
from: process_name,
|
|
||||||
message: 'OEMCrypto_GetProvisioningMethod',
|
|
||||||
payload: {
|
|
||||||
'Method': OEMCrypto_ProvisioningMethod[retval.toInt32()]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function GetLevel3_GetNumberOfOpenSessions(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onLeave: function (retval) {
|
|
||||||
const message = 'OEMCrypto_GetNumberOfOpenSessions: ' + retval.toInt32();
|
|
||||||
sender_payload({
|
|
||||||
from: process_name,
|
|
||||||
message: message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function GetLevel3_GetMaxNumberOfSessions(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function (args) {
|
|
||||||
this.maximum = args[0]
|
|
||||||
},
|
},
|
||||||
onLeave: function () {
|
onLeave: function () {
|
||||||
const message = 'OEMCrypto_GetMaxNumberOfSessions: ' + Memory.readPointer(this.maximum).toInt32();
|
if (this.ret) {
|
||||||
sender_payload({
|
const size = Memory.readU32(ptr(this.ret).add(Process.pointerSize))
|
||||||
from: process_name,
|
const arr = Memory.readByteArray(this.ret.add(Process.pointerSize * 2).readPointer(), size)
|
||||||
message: message
|
send('device_info', arr);
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function GetLevel3_Terminate_Status(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function (args) {
|
|
||||||
this.maximum = args[0]
|
|
||||||
},
|
|
||||||
onLeave: function (retvalue) {
|
|
||||||
const message = 'OEMCrypto_Terminate_Status: ' + OEMCryptoResult[retvalue.toInt32()];
|
|
||||||
sender_payload({
|
|
||||||
from: process_name,
|
|
||||||
message: message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function GetLevel3_GetDeviceID(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function(args) {
|
|
||||||
this.deviceId = args[0];
|
|
||||||
this.idLength = args[1]
|
|
||||||
},
|
|
||||||
onLeave: function (retval) {
|
|
||||||
var idLength = Memory.readPointer(this.idLength).toInt32();
|
|
||||||
const deviceIdArray = Memory.readByteArray(this.deviceId, idLength);
|
|
||||||
const deviceId = byteArrayToHex(deviceIdArray);
|
|
||||||
const data = {
|
|
||||||
from: process_name,
|
|
||||||
message: 'OEMCrypto_GetDeviceID',
|
|
||||||
payload: {
|
|
||||||
'Status': OEMCryptoResult[retval.toInt32()],
|
|
||||||
'Length': idLength,
|
|
||||||
'DeviceId': deviceId
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sender_payload(data);
|
|
||||||
send('device_id', deviceIdArray)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function GetLevel3_GetKeyData(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function (args) {
|
|
||||||
this.keyData = args[0];
|
|
||||||
this.keyDataLength = args[1];
|
|
||||||
},
|
|
||||||
onLeave: function (retvalue) {
|
|
||||||
const keyDataLength = Memory.readPointer(this.keyDataLength).toInt32();
|
|
||||||
const keyDataArray = Memory.readByteArray(this.keyData, keyDataLength);
|
|
||||||
const device_token = byteArrayToHex(keyDataArray);
|
|
||||||
const data = {
|
|
||||||
from: process_name,
|
|
||||||
message: 'OEMCrypto_GetKeyData',
|
|
||||||
payload: {
|
|
||||||
'Status': OEMCryptoResult[retvalue.toInt32()],
|
|
||||||
'Size': keyDataLength,
|
|
||||||
'Device_Token': device_token
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sender_payload(data);
|
|
||||||
send('device_token', keyDataArray)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function GetLevel3_LoadKeys(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function (args) {
|
|
||||||
this.data = {
|
|
||||||
'session': args[0],
|
|
||||||
'message': args[1],
|
|
||||||
'message_length': args[2],
|
|
||||||
'signature': args[3],
|
|
||||||
'signature_length': args[4],
|
|
||||||
'ivs': args[5],
|
|
||||||
'keys': args[6],
|
|
||||||
'num_keys': args[7],
|
|
||||||
'key_array': args[8],
|
|
||||||
'pst': args[9],
|
|
||||||
'srm_restriction_data': args[10],
|
|
||||||
'license_type': args[11]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLeave: function (retvalue) {
|
|
||||||
const message_length = this.data['message_length'].toInt32();
|
|
||||||
const message = Memory.readByteArray(this.data['message'], message_length);
|
|
||||||
const signature_length = this.data['signature_length'].toInt32();
|
|
||||||
const signature = Memory.readByteArray(this.data['signature'], signature_length);
|
|
||||||
// const ivs = this.data['ivs'];
|
|
||||||
// const keys = Memory.readPointer(this.data['keys']);
|
|
||||||
// const num_keys = this.data['num_keys'].toInt32();
|
|
||||||
// const key_array = Memory.readPointer(this.data['key_array']);
|
|
||||||
// const pst = this.data['pst'];
|
|
||||||
// const srm_restriction_data = this.data['srm_restriction_data'];
|
|
||||||
const license_type = OEMCrypto_LicenseType[this.data['license_type'].toInt32()];
|
|
||||||
const data = {
|
|
||||||
from: process_name,
|
|
||||||
message: 'OEMCrypto_LoadKeys',
|
|
||||||
payload: {
|
|
||||||
'Status': OEMCryptoResult[retvalue.toInt32()],
|
|
||||||
'Type': license_type,
|
|
||||||
'Message': byteArrayToHex(message),
|
|
||||||
'Signature': byteArrayToHex(signature)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sender_payload(data)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function GetLevel3_GenerateDerivedKeys(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function (args) {
|
|
||||||
this.data = {
|
|
||||||
'session': args[0],
|
|
||||||
'mac_key_context': args[1],
|
|
||||||
'mac_key_context_length': args[2],
|
|
||||||
'enc_key_context': args[3],
|
|
||||||
'enc_key_context_length': args[4]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLeave: function (retvalue) {
|
|
||||||
const mac_length = this.data['mac_key_context_length'].toInt32();
|
|
||||||
const mac_context = Memory.readByteArray(this.data['mac_key_context'], mac_length);
|
|
||||||
const enc_length = this.data['enc_key_context_length'].toInt32();
|
|
||||||
const enc_context = Memory.readByteArray(this.data['enc_key_context'], enc_length);
|
|
||||||
const data = {
|
|
||||||
from: process_name,
|
|
||||||
message: 'GetLevel3_GenerateDerivedKeys',
|
|
||||||
payload: {
|
|
||||||
'Status': OEMCryptoResult[retvalue.toInt32()],
|
|
||||||
'Session': this.data['session'].toInt32(),
|
|
||||||
'Mac_Length': mac_length,
|
|
||||||
'Mac_Context': mac_context,
|
|
||||||
'Enc_Length': enc_length,
|
|
||||||
'Enc_Context': enc_context
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sender_payload(data)
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function GetLevel3_GenerateSignature(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function (args) {
|
|
||||||
this.data = {
|
|
||||||
'session': args[0],
|
|
||||||
'message': args[1],
|
|
||||||
'message_length': args[2],
|
|
||||||
'signature': args[3],
|
|
||||||
'signature_lenght': args[4]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLeave: function (retvalue) {
|
|
||||||
const message_length = this.data['message_length'].toInt32();
|
|
||||||
const message = Memory.readByteArray(this.data['message'], message_length);
|
|
||||||
const signature_lenght = Memory.readPointer(this.data['signature_lenght']).toInt32();
|
|
||||||
const signature = Memory.readByteArray(this.data['signature'], signature_lenght);
|
|
||||||
const data = {
|
|
||||||
from: process_name,
|
|
||||||
message: 'GetLevel3_GenerateSignature',
|
|
||||||
payload: {
|
|
||||||
'Status': OEMCryptoResult[retvalue.toInt32()],
|
|
||||||
'Session': this.data['session'].toInt32(),
|
|
||||||
message: {
|
|
||||||
'length': message_length,
|
|
||||||
'context': byteArrayToHex(message)
|
|
||||||
},
|
|
||||||
signature: {
|
|
||||||
'length': signature_lenght,
|
|
||||||
'context': byteArrayToHex(signature)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sender_payload(data)
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function GetLevel3_GetOEMPublicCertificate(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function (args) {
|
|
||||||
this.data = {
|
|
||||||
'session': args[0],
|
|
||||||
'public_cert': args[1],
|
|
||||||
'public_cert_length': args[2]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLeave: function (retvalue) {
|
|
||||||
const result = OEMCryptoResult[retvalue.toInt32()];
|
|
||||||
const data = {
|
|
||||||
from: process_name,
|
|
||||||
message: 'GetLevel3_GetOEMPublicCertificate',
|
|
||||||
payload: {
|
|
||||||
'Status': result
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sender_payload(data);
|
|
||||||
if (result === OEMCryptoResult["0"]) {
|
|
||||||
const public_cert_length = Memory.readPointer(this.data['public_cert_length']).toInt32();
|
|
||||||
const public_cert = Memory.readByteArray(this.data['public_cert'], public_cert_length);
|
|
||||||
const data2 = {
|
|
||||||
from: process_name,
|
|
||||||
message: 'GetLevel3_GetOEMPublicCertificate',
|
|
||||||
payload: {
|
|
||||||
'Session': this.data['session'].toInt32(),
|
|
||||||
'Public_Cert_Length': public_cert_length,
|
|
||||||
'Cert': public_cert
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sender_payload(data2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetLevel3_LoadDeviceRSAKey(address, process_name) {
|
function hookLibFunctions(lib) {
|
||||||
Interceptor.attach(ptr(address), {
|
const name = lib['name'];
|
||||||
onEnter: function (args) {
|
const baseAddr = lib['base'];
|
||||||
this.data = {
|
const message = 'Hooking ' + name + ' at ' + baseAddr;
|
||||||
'session': args[0],
|
|
||||||
'wrapped_rsa_key': args[1],
|
|
||||||
'wrapped_rsa_key_length': args[2]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLeave: function (retvalue) {
|
|
||||||
const wrapped_rsa_key_length = this.data['wrapped_rsa_key_length'].toInt32();
|
|
||||||
const wrapped_rsa_key = Memory.readByteArray(this.data['wrapped_rsa_key'], wrapped_rsa_key_length);
|
|
||||||
const data = {
|
|
||||||
from: process_name,
|
|
||||||
message: 'GetLevel3_LoadDeviceRSAKey',
|
|
||||||
payload: {
|
|
||||||
'Status': OEMCryptoResult[retvalue.toInt32()],
|
|
||||||
'Session': this.data['session'].toInt32(),
|
|
||||||
'Length': wrapped_rsa_key_length,
|
|
||||||
'Context': byteArrayToHex(wrapped_rsa_key)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sender_payload(data)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function GetLevel3_RewrapDeviceRSAKey(address, process_name) {
|
|
||||||
Interceptor.attach(ptr(address), {
|
|
||||||
onEnter: function (args) {
|
|
||||||
this.data = {
|
|
||||||
'session': args[0],
|
|
||||||
'message': args[1],
|
|
||||||
'message_length': args[2],
|
|
||||||
'signature': args[3],
|
|
||||||
'signature_length': args[4],
|
|
||||||
'nonce': args[5],
|
|
||||||
'enc_rsa_key': args[6],
|
|
||||||
'enc_rsa_key_length': args[7],
|
|
||||||
'enc_rsa_key_iv': args[8],
|
|
||||||
'wrapped_rsa_key': args[9],
|
|
||||||
'wrapped_rsa_key_length': args[10]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLeave: function (retvalue) {
|
|
||||||
const status = OEMCryptoResult[retvalue.toInt32()];
|
|
||||||
if (status === OEMCryptoResult["0"]) {
|
|
||||||
const message_length = this.data['message_length'].toInt32();
|
|
||||||
const message = Memory.readByteArray(this.data['message'], message_length);
|
|
||||||
const signature_length = this.data['signature_length'].toInt32();
|
|
||||||
const signature = Memory.readByteArray(this.data['signature'], signature_length);
|
|
||||||
const enc_rsa_key_length = this.data['enc_rsa_key_length'].toInt32();
|
|
||||||
const enc_rsa_key = Memory.readByteArray(this.data['enc_rsa_key'], enc_rsa_key_length);
|
|
||||||
const wrapped_rsa_key_length = Memory.readPointer(this.data['wrapped_rsa_key_length']).toInt32();
|
|
||||||
const wrapped_rsa_key = Memory.readByteArray(this.data['wrapped_rsa_key'], wrapped_rsa_key_length);
|
|
||||||
const data = {
|
|
||||||
from: process_name,
|
|
||||||
message: 'GetLevel3_RewrapDeviceRSAKey',
|
|
||||||
status: status,
|
|
||||||
session: this.data['session'].toInt32(),
|
|
||||||
payload: {
|
|
||||||
enc_rsa_key: {
|
|
||||||
'length': enc_rsa_key_length,
|
|
||||||
'key': enc_rsa_key
|
|
||||||
},
|
|
||||||
wrapped_rsa_key: {
|
|
||||||
'length': wrapped_rsa_key_length,
|
|
||||||
'key': wrapped_rsa_key
|
|
||||||
},
|
|
||||||
signature: {
|
|
||||||
'length': signature_length,
|
|
||||||
'signature': signature
|
|
||||||
},
|
|
||||||
message: {
|
|
||||||
'lenght': message_length,
|
|
||||||
'message': message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sender_payload(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function byteArrayToHex(data) {
|
|
||||||
var array = new Uint8Array(data);
|
|
||||||
var result = '';
|
|
||||||
for (var i = 0; i < array.length; ++i)
|
|
||||||
result += ('0' + (array[i] & 0xFF).toString(16)).slice(-2);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sender_payload(data) {
|
|
||||||
var encoded = new TextEncoder().encode(JSON.stringify(data));
|
|
||||||
send('message', encoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sender_payload_info(message) {
|
|
||||||
send('message_info', new TextEncoder().encode(message))
|
send('message_info', new TextEncoder().encode(message))
|
||||||
|
|
||||||
|
Module.enumerateExportsSync(name).forEach(function (module) {
|
||||||
|
const privacy_mode = 'UsePrivacyMode'
|
||||||
|
const prepare_key_request = 'PrepareKeyRequest'
|
||||||
|
try {
|
||||||
|
let hookedModule;
|
||||||
|
if (module.name.includes(DYNAMIC_FUNCTION_NAME)) {
|
||||||
|
getPrivateKey(module.address);
|
||||||
|
hookedModule = DYNAMIC_FUNCTION_NAME
|
||||||
|
} else if (module.name.includes(privacy_mode)) {
|
||||||
|
disablePrivacyMode(module.address);
|
||||||
|
hookedModule = privacy_mode
|
||||||
|
} else if (module.name.includes(prepare_key_request)) {
|
||||||
|
prepareKeyRequest(module.address);
|
||||||
|
hookedModule = prepare_key_request
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const OEMCrypto_ProvisioningMethod = {
|
function getModuleByName(lib) {
|
||||||
0: 'OEMCrypto_ProvisioningError', // Device cannot be provisioned.
|
return Process.getModuleByName(lib);
|
||||||
1: 'OEMCrypto_DrmCertificate', // Device has baked in DRM certificate
|
}
|
||||||
// (level 3 only)
|
|
||||||
2: 'OEMCrypto_Keybox', // Device has factory installed unique keybox.
|
|
||||||
3: 'OEMCrypto_OEMCertificate' // Device has factory installed OEM certificate.
|
|
||||||
};
|
|
||||||
|
|
||||||
const OEMCrypto_RSA_Support = {
|
function a2bs(bytes) {
|
||||||
1: 'OEMCrypto_Supports_RSA_2048bit',
|
let b = '';
|
||||||
2: 'OEMCrypto_Supports_RSA_3072bit',
|
for (let i = 0; i < bytes.byteLength; i++)
|
||||||
10: 'OEMCrypto_Supports_RSA_CAST'
|
b += String.fromCharCode(bytes[i]);
|
||||||
};
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
const OEMCryptoResult = {
|
function getKeyLength(key) {
|
||||||
0: 'OEMCrypto_SUCCESS',
|
let pos = 1 // Skip the tag
|
||||||
1: 'OEMCrypto_ERROR_INIT_FAILED',
|
let buf = key.charCodeAt(pos++);
|
||||||
2: 'OEMCrypto_ERROR_TERMINATE_FAILED',
|
let len = buf & 0x7F; // Short tag length
|
||||||
3: 'OEMCrypto_ERROR_OPEN_FAILURE',
|
|
||||||
4: 'OEMCrypto_ERROR_CLOSE_FAILURE',
|
|
||||||
5: 'OEMCrypto_ERROR_ENTER_SECURE_PLAYBACK_FAILED', // deprecated
|
|
||||||
6: 'OEMCrypto_ERROR_EXIT_SECURE_PLAYBACK_FAILED', // deprecated
|
|
||||||
7: 'OEMCrypto_ERROR_SHORT_BUFFER',
|
|
||||||
8: 'OEMCrypto_ERROR_NO_DEVICE_KEY', // no keybox device key.
|
|
||||||
9: 'OEMCrypto_ERROR_NO_ASSET_KEY',
|
|
||||||
10: 'OEMCrypto_ERROR_KEYBOX_INVALID',
|
|
||||||
11: 'OEMCrypto_ERROR_NO_KEYDATA',
|
|
||||||
12: 'OEMCrypto_ERROR_NO_CW',
|
|
||||||
13: 'OEMCrypto_ERROR_DECRYPT_FAILED',
|
|
||||||
14: 'OEMCrypto_ERROR_WRITE_KEYBOX',
|
|
||||||
15: 'OEMCrypto_ERROR_WRAP_KEYBOX',
|
|
||||||
16: 'OEMCrypto_ERROR_BAD_MAGIC',
|
|
||||||
17: 'OEMCrypto_ERROR_BAD_CRC',
|
|
||||||
18: 'OEMCrypto_ERROR_NO_DEVICEID',
|
|
||||||
19: 'OEMCrypto_ERROR_RNG_FAILED',
|
|
||||||
20: 'OEMCrypto_ERROR_RNG_NOT_SUPPORTED',
|
|
||||||
21: 'OEMCrypto_ERROR_SETUP',
|
|
||||||
22: 'OEMCrypto_ERROR_OPEN_SESSION_FAILED',
|
|
||||||
23: 'OEMCrypto_ERROR_CLOSE_SESSION_FAILED',
|
|
||||||
24: 'OEMCrypto_ERROR_INVALID_SESSION',
|
|
||||||
25: 'OEMCrypto_ERROR_NOT_IMPLEMENTED',
|
|
||||||
26: 'OEMCrypto_ERROR_NO_CONTENT_KEY',
|
|
||||||
27: 'OEMCrypto_ERROR_CONTROL_INVALID',
|
|
||||||
28: 'OEMCrypto_ERROR_UNKNOWN_FAILURE',
|
|
||||||
29: 'OEMCrypto_ERROR_INVALID_CONTEXT',
|
|
||||||
30: 'OEMCrypto_ERROR_SIGNATURE_FAILURE',
|
|
||||||
31: 'OEMCrypto_ERROR_TOO_MANY_SESSIONS',
|
|
||||||
32: 'OEMCrypto_ERROR_INVALID_NONCE',
|
|
||||||
33: 'OEMCrypto_ERROR_TOO_MANY_KEYS',
|
|
||||||
34: 'OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED',
|
|
||||||
35: 'OEMCrypto_ERROR_INVALID_RSA_KEY',
|
|
||||||
36: 'OEMCrypto_ERROR_KEY_EXPIRED',
|
|
||||||
37: 'OEMCrypto_ERROR_INSUFFICIENT_RESOURCES',
|
|
||||||
38: 'OEMCrypto_ERROR_INSUFFICIENT_HDCP',
|
|
||||||
39: 'OEMCrypto_ERROR_BUFFER_TOO_LARGE',
|
|
||||||
40: 'OEMCrypto_WARNING_GENERATION_SKEW', // Warning, not an error.
|
|
||||||
41: 'OEMCrypto_ERROR_GENERATION_SKEW',
|
|
||||||
42: 'OEMCrypto_LOCAL_DISPLAY_ONLY',
|
|
||||||
43: 'OEMCrypto_ERROR_ANALOG_OUTPUT',
|
|
||||||
44: 'OEMCrypto_ERROR_WRONG_PST',
|
|
||||||
45: 'OEMCrypto_ERROR_WRONG_KEYS',
|
|
||||||
46: 'OEMCrypto_ERROR_MISSING_MASTER',
|
|
||||||
47: 'OEMCrypto_ERROR_LICENSE_INACTIVE',
|
|
||||||
48: 'OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE',
|
|
||||||
49: 'OEMCrypto_ERROR_ENTRY_IN_USE',
|
|
||||||
50: 'OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE', // Reserved. Do not use.
|
|
||||||
51: 'OEMCrypto_KEY_NOT_LOADED', // obsolete. use error 26.
|
|
||||||
52: 'OEMCrypto_KEY_NOT_ENTITLED',
|
|
||||||
53: 'OEMCrypto_ERROR_BAD_HASH',
|
|
||||||
54: 'OEMCrypto_ERROR_OUTPUT_TOO_LARGE',
|
|
||||||
55: 'OEMCrypto_ERROR_SESSION_LOST_STATE',
|
|
||||||
56:'OEMCrypto_ERROR_SYSTEM_INVALIDATED',
|
|
||||||
};
|
|
||||||
|
|
||||||
const OEMCrypto_LicenseType = {
|
buf = 0;
|
||||||
0: 'OEMCrypto_ContentLicense',
|
for (let i = 0; i < len; ++i)
|
||||||
1: 'OEMCrypto_EntitlementLicense'
|
buf = (buf * 256) + key.charCodeAt(pos++);
|
||||||
};
|
return pos + Math.abs(buf);
|
||||||
|
}
|
||||||
|
|
||||||
rpc.exports.inject = inject;
|
rpc.exports.hooklibfunctions = hookLibFunctions;
|
||||||
|
rpc.exports.getmodulebyname = getModuleByName;
|
||||||
rpc.exports.widevinelibrary = containsLib;
|
|
||||||
|
|
20
README.md
20
README.md
|
@ -2,8 +2,15 @@
|
||||||
|
|
||||||
Dumper is a Frida script to dump L3 CDMs from any Android device.
|
Dumper is a Frida script to dump L3 CDMs from any Android device.
|
||||||
|
|
||||||
## Dependencies
|
## ** IMPORTANT **
|
||||||
|
You MUST update `DYNAMIC_FUNCTION_NAME` and `CDM_VERSION` in `script.js` to the relevant values for your device.
|
||||||
|
|
||||||
|
* `CDM_VERSION` can be retrieved using a DRM Info app.
|
||||||
|
* `DYNAMIC_FUNCTION_NAME` value is unique to your device and can be found in the file `libwvhidl.so` on your device.
|
||||||
|
|
||||||
|
If you've managed to get as far as updating `DYNAMIC_FUNCTION_NAME` but can't find your function name, create an issue and provide me with your `libwvhidl.so` file and I will give you the function name you need.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
Use pip to install the dependencies:
|
Use pip to install the dependencies:
|
||||||
|
|
||||||
`pip3 install -r requirements.txt`
|
`pip3 install -r requirements.txt`
|
||||||
|
@ -15,11 +22,16 @@ Use pip to install the dependencies:
|
||||||
* Execute dump_keys.py
|
* Execute dump_keys.py
|
||||||
* Start streaming some DRM-protected content
|
* Start streaming some DRM-protected content
|
||||||
|
|
||||||
|
## Known Working Versions
|
||||||
|
* Android 10
|
||||||
|
* CDM 15.0.0
|
||||||
|
* Android 11
|
||||||
|
* CDM 16.0.0
|
||||||
|
* Android 12
|
||||||
|
* CDM 16.1.0
|
||||||
|
|
||||||
## Temporary disabling L1 to use L3 instead
|
## Temporary disabling L1 to use L3 instead
|
||||||
A few phone brands let us use the L1 keybox even after unlocking the bootloader (like Xiaomi). In this case, installation of a Magisk module called [liboemcrypto-disabler](https://github.com/umylive/liboemcrypto-disabler) is necessary.
|
A few phone brands let us use the L1 keybox even after unlocking the bootloader (like Xiaomi). In this case, installation of a Magisk module called [liboemcrypto-disabler](https://github.com/umylive/liboemcrypto-disabler) is necessary.
|
||||||
|
|
||||||
## Known issues
|
|
||||||
It seems like Google made some changes in their OEMCrypto library and it broke the script. Further investigation is needed to make it work on Android 11+, feel free to open PRs.
|
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
Thanks to the original author of the code.
|
Thanks to the original author of the code.
|
||||||
|
|
31
dump_keys.py
31
dump_keys.py
|
@ -1,9 +1,8 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import frida
|
|
||||||
import logging
|
import logging
|
||||||
from Helpers.Scanner import Scan
|
from Helpers.Device import Device
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format='%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s',
|
format='%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s',
|
||||||
|
@ -11,17 +10,21 @@ logging.basicConfig(
|
||||||
level=logging.DEBUG,
|
level=logging.DEBUG,
|
||||||
)
|
)
|
||||||
|
|
||||||
device = frida.get_usb_device()
|
|
||||||
scanner = Scan(device.name)
|
def main():
|
||||||
logging.info(f'Connected to {device.name}')
|
logger = logging.getLogger("main")
|
||||||
logging.info('scanning all processes for the following libraries')
|
device = Device()
|
||||||
for process in device.enumerate_processes():
|
logger.info('Connected to %s', device.name)
|
||||||
logging.debug(process)
|
logger.info('Scanning all processes')
|
||||||
|
|
||||||
|
for process in device.usb_device.enumerate_processes():
|
||||||
if 'drm' in process.name:
|
if 'drm' in process.name:
|
||||||
libraries = scanner.find_widevine_process(device, process.name)
|
for library in device.find_widevine_process(process.name):
|
||||||
if libraries:
|
device.hook_to_process(process.name, library)
|
||||||
for library in libraries:
|
logger.info('Functions Hooked, load the DRM stream test on Bitmovin!')
|
||||||
scanner.hook_to_process(device, process.name, library)
|
|
||||||
logging.info('Hooks completed')
|
|
||||||
while True:
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
while True:
|
||||||
time.sleep(1000)
|
time.sleep(1000)
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
frida
|
frida
|
||||||
protobuf
|
protobuf == 3.19.3
|
||||||
pycryptodome
|
pycryptodome
|
||||||
|
|
Loading…
Reference in New Issue