VT-PR/scripts/VMPBlobGen/VMPBlobGen.py

115 lines
3.7 KiB
Python
Raw Normal View History

2025-03-18 00:17:27 +05:30
#!/usr/bin/env python3
import os
import sys
from hashlib import sha512
from vinetrimmer.utils.widevine.protos.widevine_pb2 import FileHashes
from vinetrimmer.utils.widevine.vmp import WidevineSignatureReader
"""
Script that generates a VMP blob for chromecdm
"""
WIN32_FILES = [
"chrome.exe",
"chrome.dll",
"chrome_child.dll",
"widevinecdmadapter.dll",
"widevinecdm.dll"
]
def sha512file(filename):
"""Compute SHA-512 digest of file."""
sha = sha512()
with open(filename, "rb") as fd:
for b in iter(lambda: fd.read(0x10000), b''):
sha.update(b)
return sha.digest()
def build_vmp_field(filenames):
"""
Create and fill out a FileHashes object.
`filenames` is an array of pairs of filenames like (file, file_signature)
such as ("module.dll", "module.dll.sig"). This does not validate the signature
against the codesign root CA, or even the sha512 hash against the current signature+signer
"""
file_hashes = FileHashes()
for basename, file, sig in filenames:
signature = WidevineSignatureReader.from_file(sig)
s = file_hashes.signatures.add()
s.filename = basename
s.test_signing = False # we can't check this without parsing signer
s.SHA512Hash = sha512file(file)
s.main_exe = signature.mainexe
s.signature = signature.signature
file_hashes.signer = signature.signer
return file_hashes.SerializeToString()
def get_files_with_signatures(path, required_files=None, random_order=False, sig_ext="sig"):
"""
use on chrome dir (a given version).
random_order would put any files it found in the dir with sigs,
it's not the right way to do it and the browser does not do this.
this function can still fail (generate wrong output) in subtle ways if
the Chrome dir has copies of the exe/sigs, especially if those copies are modified in some way
"""
if not required_files:
required_files = WIN32_FILES
all_files = []
sig_files = []
for dir_path, _, filenames in os.walk(path):
for filename in filenames:
full_path = os.path.join(dir_path, filename)
all_files.append(full_path)
if filename.endswith(sig_ext):
sig_files.append(full_path)
base_names = []
for path in sig_files:
orig_path = os.path.splitext(path)[0]
if orig_path not in all_files:
print("signature file {} lacks original file {}".format(path, orig_path))
base_names.append(path.name)
if not set(base_names).issuperset(set(required_files)):
# or should just make this warn as the next exception would be more specific
raise ValueError("Missing a binary/signature pair from {}".format(required_files))
files_to_hash = []
if random_order:
for path in sig_files:
orig_path = os.path.splitext(path)[0]
files_to_hash.append((os.path.basename(orig_path), orig_path, path))
else:
for basename in required_files:
found_file = False
for path in sig_files:
orig_path = os.path.splitext(path)[0]
if orig_path.endswith(basename):
files_to_hash.append((basename, orig_path, path))
found_file = True
break
if not found_file:
raise Exception("Failed to locate a file sig/pair for {}".format(basename))
return files_to_hash
def make_vmp_buff(browser_dir, file_msg_out):
with open(file_msg_out, "wb") as fd:
fd.write(build_vmp_field(get_files_with_signatures(browser_dir)))
if len(sys.argv) < 3:
print("Usage: {} BrowserPathWithVersion OutputPBMessage.bin".format(sys.argv[0]))
else:
make_vmp_buff(sys.argv[1], sys.argv[2])