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:
|
class Device:
|
||||||
def __init__(self):
|
def __init__(self, dynamic_function_name, cdm_version):
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.saved_keys = {}
|
self.saved_keys = {}
|
||||||
self.frida_script = open(
|
self.widevine_libraries = ['libwvhidl.so']
|
||||||
'./Helpers/script.js',
|
|
||||||
'r',
|
|
||||||
encoding="utf_8"
|
|
||||||
).read()
|
|
||||||
self.widevine_libraries = [
|
|
||||||
'libwvhidl.so'
|
|
||||||
]
|
|
||||||
self.usb_device = frida.get_usb_device()
|
self.usb_device = frida.get_usb_device()
|
||||||
self.name = self.usb_device.name
|
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):
|
def export_key(self, key, client_id):
|
||||||
save_dir = os.path.join(
|
save_dir = os.path.join(
|
||||||
'key_dumps',
|
'key_dumps',
|
||||||
|
@ -65,7 +63,9 @@ class Device:
|
||||||
public_key = root.Msg.ClientId.Token._DeviceCertificate.PublicKey
|
public_key = root.Msg.ClientId.Token._DeviceCertificate.PublicKey
|
||||||
key = RSA.importKey(public_key)
|
key = RSA.importKey(public_key)
|
||||||
cur = self.saved_keys.get(key.n)
|
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):
|
def find_widevine_process(self, process_name):
|
||||||
process = self.usb_device.attach(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.
|
// The TextEncoder/Decoder API isn't supported so it has to be polyfilled.
|
||||||
// Taken from https://gist.github.com/Yaffle/5458286#file-textencodertextdecoder-js
|
// Taken from https://gist.github.com/Yaffle/5458286#file-textencodertextdecoder-js
|
||||||
|
@ -85,6 +97,8 @@ function prepareKeyRequest(address) {
|
||||||
this.ret = args[5];
|
this.ret = args[5];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
const message = 'Defaulting to args[4] for PrepareKeyRequest.'
|
||||||
|
send('message_info', new TextEncoder().encode(message));
|
||||||
this.ret = args[4];
|
this.ret = args[4];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -102,22 +116,29 @@ function prepareKeyRequest(address) {
|
||||||
function hookLibFunctions(lib) {
|
function hookLibFunctions(lib) {
|
||||||
const name = lib['name'];
|
const name = lib['name'];
|
||||||
const baseAddr = lib['base'];
|
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) {
|
Module.enumerateExportsSync(name).forEach(function (module) {
|
||||||
try {
|
try {
|
||||||
let hookedModule;
|
let hookedModule;
|
||||||
if (module.name.includes('UsePrivacyMode')) {
|
if (module.name.includes('UsePrivacyMode')) {
|
||||||
disablePrivacyMode(module.address);
|
disablePrivacyMode(module.address);
|
||||||
hookedModule = module.name
|
hookedModule = module.name;
|
||||||
} else if (module.name.includes('PrepareKeyRequest')) {
|
} else if (module.name.includes('PrepareKeyRequest')) {
|
||||||
prepareKeyRequest(module.address);
|
prepareKeyRequest(module.address);
|
||||||
hookedModule = module.name
|
hookedModule = module.name;
|
||||||
} else if (module.name.match(/^[a-z]+$/)) {
|
} else if (DYNAMIC_FUNCTION_NAME !== '' && module.name.includes(DYNAMIC_FUNCTION_NAME)) {
|
||||||
getPrivateKey(module.address);
|
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) {
|
if (hookedModule) {
|
||||||
|
@ -128,6 +149,19 @@ function hookLibFunctions(lib) {
|
||||||
console.log("Error: " + e + " at F: " + module.name);
|
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) {
|
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.
|
* `CDM_VERSION` can be retrieved using a DRM Info app.
|
||||||
|
|
||||||
## Requirements
|
## Requirements:
|
||||||
Use pip to install the dependencies:
|
Use pip to install the dependencies:
|
||||||
|
|
||||||
`pip3 install -r requirements.txt`
|
`pip3 install -r requirements.txt`
|
||||||
|
|
||||||
## Usage
|
## Usage:
|
||||||
|
|
||||||
* Enable USB debugging
|
* Enable USB debugging
|
||||||
* Start frida-server on the device
|
* Start frida-server on the device
|
||||||
* Execute dump_keys.py
|
* Execute dump_keys.py
|
||||||
* Start streaming some DRM-protected content
|
* 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
|
* Android 9
|
||||||
* CDM 14.0.0
|
* CDM 14.0.0
|
||||||
* Android 10
|
* Android 10
|
||||||
|
|
12
dump_keys.py
12
dump_keys.py
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
from Helpers.Device import Device
|
from Helpers.Device import Device
|
||||||
|
@ -10,10 +11,17 @@ logging.basicConfig(
|
||||||
level=logging.DEBUG,
|
level=logging.DEBUG,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
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")
|
logger = logging.getLogger("main")
|
||||||
device = Device()
|
device = Device(dynamic_function_name, cdm_version)
|
||||||
logger.info('Connected to %s', device.name)
|
logger.info('Connected to %s', device.name)
|
||||||
logger.info('Scanning all processes')
|
logger.info('Scanning all processes')
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue