pywks/extractwvd.py

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()