Allow the CDM version and fn to be passed via args

This commit is contained in:
Diazole 2022-10-30 02:23:28 +00:00
parent 5cdbff2a09
commit 2899fc93a2
4 changed files with 87 additions and 22 deletions

View File

@ -7,20 +7,18 @@ from Helpers.wv_proto2_pb2 import SignedLicenseRequest
class Device:
def __init__(self):
def __init__(self, dynamic_function_name, cdm_version):
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.widevine_libraries = ['libwvhidl.so']
self.usb_device = frida.get_usb_device()
self.name = self.usb_device.name
with open('./Helpers/script.js', 'r', encoding="utf_8") as script:
self.frida_script = script.read()
self.frida_script = self.frida_script.replace(r'${DYNAMIC_FUNCTION_NAME}', dynamic_function_name)
self.frida_script = self.frida_script.replace(r'${CDM_VERSION}', cdm_version)
def export_key(self, key, client_id):
save_dir = os.path.join(
'key_dumps',
@ -65,7 +63,9 @@ class Device:
public_key = root.Msg.ClientId.Token._DeviceCertificate.PublicKey
key = RSA.importKey(public_key)
cur = self.saved_keys.get(key.n)
self.export_key(cur, root.Msg.ClientId)
if cur is not None:
self.export_key(cur, root.Msg.ClientId)
def find_widevine_process(self, process_name):
process = self.usb_device.attach(process_name)

View File

@ -1,4 +1,16 @@
const CDM_VERSION = ''
const DYNAMIC_FUNCTION_NAME = '${DYNAMIC_FUNCTION_NAME}';
const CDM_VERSION = '${CDM_VERSION}';
// These strings are function names that have been succesfully dumped.
const KNOWN_DYNAMIC_FUNCTION_NAMES = [
'rnmsglvj',
'polorucp',
'kqzqahjq',
'pldrclfq',
'kgaitijd',
'dnvffnze',
'cwkfcplc'
];
// The TextEncoder/Decoder API isn't supported so it has to be polyfilled.
// Taken from https://gist.github.com/Yaffle/5458286#file-textencodertextdecoder-js
@ -85,6 +97,8 @@ function prepareKeyRequest(address) {
this.ret = args[5];
break;
default:
const message = 'Defaulting to args[4] for PrepareKeyRequest.'
send('message_info', new TextEncoder().encode(message));
this.ret = args[4];
break;
}
@ -102,22 +116,29 @@ function prepareKeyRequest(address) {
function hookLibFunctions(lib) {
const name = lib['name'];
const baseAddr = lib['base'];
const message = 'Hooking ' + name + ' at ' + baseAddr;
let message = 'Hooking ' + name + ' at ' + baseAddr;
let hookedProvidedModule = false;
let funcNames = [];
send('message_info', new TextEncoder().encode(message))
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
hookedModule = module.name;
} else if (module.name.includes('PrepareKeyRequest')) {
prepareKeyRequest(module.address);
hookedModule = module.name
} else if (module.name.match(/^[a-z]+$/)) {
hookedModule = module.name;
} else if (DYNAMIC_FUNCTION_NAME !== '' && module.name.includes(DYNAMIC_FUNCTION_NAME)) {
getPrivateKey(module.address);
hookedModule = module.name
hookedModule = module.name;
hookedProvidedModule = true;
} else if (DYNAMIC_FUNCTION_NAME === '' && module.name.match(/^[a-z]+$/)) {
getPrivateKey(module.address);
hookedModule = module.name;
funcNames.push(hookedModule);
}
if (hookedModule) {
@ -128,6 +149,19 @@ function hookLibFunctions(lib) {
console.log("Error: " + e + " at F: " + module.name);
}
});
if (DYNAMIC_FUNCTION_NAME !== '' && !hookedProvidedModule) {
const message = "Unable to find '" + DYNAMIC_FUNCTION_NAME + "'";
send('message_info', new TextEncoder().encode(message));
}
if (DYNAMIC_FUNCTION_NAME === '') {
const possibleFuncNames = KNOWN_DYNAMIC_FUNCTION_NAMES.filter(x => funcNames.includes(x));
if (possibleFuncNames.length) {
message = "Your function name is most likely: " + "'" + possibleFuncNames.join('\', \'') + "'";
send('message_info', new TextEncoder().encode(message));
}
}
}
function getModuleByName(lib) {

View File

@ -7,19 +7,42 @@ The function parameters can differ between CDM versions. The default is [4] but
* `CDM_VERSION` can be retrieved using a DRM Info app.
## Requirements
## Requirements:
Use pip to install the dependencies:
`pip3 install -r requirements.txt`
## Usage
## Usage:
* Enable USB debugging
* Start frida-server on the device
* Execute dump_keys.py
* Start streaming some DRM-protected content
## Known Working Versions
The script will hook every function in your 'libwvhidl.so' module by default, effectively brute forcing the private key function name.
```
python3 .\dump_keys.py [OPTIONS]
```
You can pass the function name to hook using the `--function-name` argument.
```
python3 .\dump_keys.py --function-name 'polorucp'
```
The script defaults to `args[4]` if no `--cdm-version` is provided. This will only have an effect if your version is `16.1.0`.
```
python3 .\dump_keys.py --cdm-version '16.1.0'
```
## Options:
```
-h, --help Print this help text and exit.
--cdm-version The CDM version of the device e.g. '16.1.0'.
--function-name The name of the function to hook to retrieve the private key.
```
## Known Working Versions:
* Android 9
* CDM 14.0.0
* Android 10

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python3
import argparse
import time
import logging
from Helpers.Device import Device
@ -10,10 +11,17 @@ logging.basicConfig(
level=logging.DEBUG,
)
def main():
parser = argparse.ArgumentParser(description='Android Widevine L3 dumper.')
parser.add_argument('--cdm-version', help='The CDM version of the device e.g. \'14.0.0\'', default='14.0.0')
parser.add_argument('--function-name', help='The name of the function to hook to retrieve the private key.', default='')
args = parser.parse_args()
dynamic_function_name = args.function_name
cdm_version = args.cdm_version
logger = logging.getLogger("main")
device = Device()
device = Device(dynamic_function_name, cdm_version)
logger.info('Connected to %s', device.name)
logger.info('Scanning all processes')