mirror of https://github.com/Diazole/dumper.git
Allow the CDM version and fn to be passed via args
This commit is contained in:
parent
5cdbff2a09
commit
2899fc93a2
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
29
README.md
29
README.md
|
@ -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
|
||||
|
|
12
dump_keys.py
12
dump_keys.py
|
@ -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')
|
||||
|
||||
|
|
Loading…
Reference in New Issue