93 lines
3.0 KiB
Python
93 lines
3.0 KiB
Python
import argparse
|
|
import json
|
|
from enum import Enum
|
|
from pathlib import Path
|
|
from construct import BitStruct, Bytes, Const
|
|
from construct import Enum as CEnum
|
|
from construct import Flag, If, Int8ub, Int16ub, Optional, Padded, Padding, Struct, this
|
|
from Cryptodome.PublicKey import RSA
|
|
from cdm.wks import ClientIdentification
|
|
|
|
class DeviceTypes(Enum):
|
|
CHROME = 1
|
|
ANDROID = 2
|
|
|
|
WidevineDeviceStruct = Struct(
|
|
'signature' / Const(b'WVD'),
|
|
'version' / Int8ub,
|
|
'type' / CEnum(
|
|
Int8ub,
|
|
**{t.name: t.value for t in DeviceTypes}
|
|
),
|
|
'security_level' / Int8ub,
|
|
'flags' / Padded(1, Optional(BitStruct(
|
|
Padding(7),
|
|
'send_key_control_nonce' / Flag
|
|
))),
|
|
'private_key_len' / Int16ub,
|
|
'private_key' / Bytes(this.private_key_len),
|
|
'client_id_len' / Int16ub,
|
|
'client_id' / Bytes(this.client_id_len),
|
|
'vmp_len' / Optional(Int16ub),
|
|
'vmp' / If(this.vmp_len, Optional(Bytes(this.vmp_len)))
|
|
)
|
|
|
|
WidevineDeviceStructVersion = 1
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(description='Widevine Device Information Parser')
|
|
parser.add_argument('file', type=Path, help='Path to WVD file')
|
|
return parser.parse_args()
|
|
|
|
def write_key_and_blob_files(out_dir, device):
|
|
private_key_file = out_dir / 'device_private_key'
|
|
print(f'\n[INFO] Writing private key to: {private_key_file}')
|
|
private_key = RSA.import_key(device.private_key)
|
|
private_key_file.write_text(private_key.export_key('PEM').decode())
|
|
|
|
client_id_blob_file = out_dir / 'device_client_id_blob'
|
|
print(f'[INFO] Writing client ID blob to: {client_id_blob_file}')
|
|
client_id_blob_file.write_bytes(device.client_id)
|
|
|
|
if device.vmp:
|
|
vmp_blob_file = out_dir / 'device_vmp_blob'
|
|
print(f'[INFO] Writing VMP blob to: {vmp_blob_file}')
|
|
vmp_blob_file.write_bytes(device.vmp)
|
|
|
|
def write_json_file(out_dir, name, client_id, device):
|
|
wv_json_file = out_dir / 'wv.json'
|
|
description = f'{name} ({client_id.Token._DeviceCertificate.SystemId})'
|
|
print(f'[INFO] Writing JSON file to: {wv_json_file}')
|
|
wv_json_file.write_text(json.dumps({
|
|
'name': name,
|
|
'description': description,
|
|
'security_level': device.security_level,
|
|
'session_id_type': device.type.lower(),
|
|
'private_key_available': True,
|
|
'vmp': bool(device.vmp),
|
|
'send_key_control_nonce': device.type == DeviceTypes.ANDROID
|
|
}, indent=2))
|
|
|
|
def main():
|
|
args = parse_args()
|
|
|
|
name = args.file.with_suffix('').name
|
|
out_dir = Path.cwd() / 'cdm' / 'devices' / 'android_generic'
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
with args.file.open('rb') as fd:
|
|
device = WidevineDeviceStruct.parse_stream(fd)
|
|
|
|
print(f'\n[INFO] Starting Widevine Device Information Parsing')
|
|
write_key_and_blob_files(out_dir, device)
|
|
|
|
client_id = ClientIdentification()
|
|
client_id.ParseFromString(device.client_id)
|
|
|
|
write_json_file(out_dir, name, client_id, device)
|
|
|
|
print('[INFO] Done')
|
|
|
|
if __name__ == '__main__':
|
|
main()
|