Files
KeyDive/keydive/__main__.py
T

209 lines
8.8 KiB
Python
Raw Normal View History

2024-07-06 20:01:47 +02:00
import argparse
import logging
import time
2024-07-18 00:09:04 +02:00
2024-07-06 20:01:47 +02:00
from datetime import datetime
from pathlib import Path
2025-01-19 14:13:07 +01:00
from typing import Optional
2024-07-06 20:01:47 +02:00
import coloredlogs
import keydive
2024-07-18 00:09:04 +02:00
2024-10-26 15:21:06 +02:00
from keydive.adb import ADB
2024-07-06 20:01:47 +02:00
from keydive.cdm import Cdm
2024-10-26 15:21:06 +02:00
from keydive.constants import CDM_VENDOR_API, DRM_PLAYER
2024-07-06 20:01:47 +02:00
from keydive.core import Core
2025-01-19 14:13:07 +01:00
def configure_logging(path: Path = None, verbose: bool = False) -> Optional[Path]:
2024-07-06 20:01:47 +02:00
"""
Configures logging for the application.
2025-01-19 14:13:07 +01:00
Parameters:
2024-10-26 15:21:06 +02:00
path (Path, optional): The directory to store log files.
verbose (bool, optional): Flag to enable detailed debug logging.
2024-07-06 20:01:47 +02:00
Returns:
2025-01-19 14:13:07 +01:00
Path: The path of the log file, or None if no log file is created.
2024-07-06 20:01:47 +02:00
"""
2024-10-26 15:21:06 +02:00
# Set up the root logger with the desired logging level
2024-07-06 20:01:47 +02:00
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG if verbose else logging.INFO)
# Clear any existing handlers (optional, to avoid duplicate logs if reconfiguring)
if root_logger.hasHandlers():
root_logger.handlers.clear()
file_path = None
if path:
2024-10-26 15:21:06 +02:00
# Ensure the log directory exists
2024-07-06 20:01:47 +02:00
if path.is_file():
path = path.parent
path.mkdir(parents=True, exist_ok=True)
# Create a file handler
2025-01-19 14:13:07 +01:00
file_path = path / ("keydive_%s.log" % datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
2024-07-06 20:01:47 +02:00
file_path = file_path.resolve(strict=False)
file_handler = logging.FileHandler(file_path)
file_handler.setLevel(logging.DEBUG)
2024-10-26 15:21:06 +02:00
# Set log formatting
2024-07-06 20:01:47 +02:00
formatter = logging.Formatter(
2025-01-19 14:13:07 +01:00
fmt="%(asctime)s [%(levelname).1s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
2024-07-06 20:01:47 +02:00
)
file_handler.setFormatter(formatter)
# Add the file handler to the root logger
root_logger.addHandler(file_handler)
# Configure coloredlogs for console output
coloredlogs.install(
2025-01-19 14:13:07 +01:00
fmt="%(asctime)s [%(levelname).1s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
2024-07-06 20:01:47 +02:00
level=logging.DEBUG if verbose else logging.INFO,
logger=root_logger
)
return file_path
def main() -> None:
2024-10-26 15:21:06 +02:00
"""
Main entry point for the KeyDive application.
This application extracts Widevine L3 keys from an Android device.
It supports device management via ADB and allows hooking into Widevine processes.
"""
2025-01-19 14:13:07 +01:00
parser = argparse.ArgumentParser(description="Extract Widevine L3 keys from an Android device.")
2024-07-06 20:01:47 +02:00
2024-10-26 15:21:06 +02:00
# Global arguments for the application
2025-01-19 14:13:07 +01:00
group_global = parser.add_argument_group("Global")
group_global.add_argument('-d', '--device', required=False, type=str, metavar="<id>", help="Specify the target Android device ID for ADB connection.")
group_global.add_argument('-v', '--verbose', required=False, action="store_true", help="Enable verbose logging for detailed debug output.")
group_global.add_argument('-l', '--log', required=False, type=Path, metavar="<dir>", help="Directory to store log files.")
group_global.add_argument('--delay', required=False, type=float, metavar="<delay>", default=1, help="Delay (in seconds) between process checks.")
group_global.add_argument('--version', required=False, action="store_true", help="Display KeyDive version information.")
2024-10-26 15:21:06 +02:00
# Arguments specific to the CDM (Content Decryption Module)
2025-01-19 14:13:07 +01:00
group_cdm = parser.add_argument_group("Cdm")
group_cdm.add_argument('-o', '--output', required=False, type=Path, default=Path("device"), metavar="<dir>", help="Output directory for extracted data.")
group_cdm.add_argument('-w', '--wvd', required=False, action="store_true", help="Generate a pywidevine WVD device file.")
group_cdm.add_argument('-s', '--skip', required=False, action="store_true", help="Skip auto-detection of the private function.")
group_cdm.add_argument('-a', '--auto', required=False, action="store_true", help="Automatically start the Bitmovin web player.")
group_cdm.add_argument('-p', '--player', required=False, action="store_true", help="Install and start the Kaltura app automatically.")
2024-10-26 15:21:06 +02:00
# Advanced options
2025-01-19 14:13:07 +01:00
group_advanced = parser.add_argument_group("Advanced")
group_advanced.add_argument('-f', '--functions', required=False, type=Path, metavar="<file>", help="Path to Ghidra XML functions file.")
group_advanced.add_argument('-k', '--keybox', required=False, action="store_true", help="Enable export of the Keybox data if it is available.")
group_advanced.add_argument('--challenge', required=False, type=Path, metavar="<file>", help="Path to unencrypted challenge for extracting client ID.")
group_advanced.add_argument('--private-key', required=False, type=Path, metavar="<file>", help="Path to private key for extracting client ID.")
2024-10-26 15:21:06 +02:00
2024-07-06 20:01:47 +02:00
args = parser.parse_args()
2025-01-19 14:13:07 +01:00
# Handle version display
2024-07-06 20:01:47 +02:00
if args.version:
2025-01-19 14:13:07 +01:00
print(f"KeyDive {keydive.__version__}")
2024-07-06 20:01:47 +02:00
exit(0)
2025-01-19 14:13:07 +01:00
# Configure logging (file and console)
2024-07-06 20:01:47 +02:00
log_path = configure_logging(path=args.log, verbose=args.verbose)
2025-01-19 14:13:07 +01:00
logger = logging.getLogger("KeyDive")
logger.info("Version: %s", keydive.__version__)
2024-07-06 20:01:47 +02:00
try:
2024-10-26 15:21:06 +02:00
# Connect to the specified Android device
adb = ADB(device=args.device)
2024-07-06 20:01:47 +02:00
2025-01-19 14:13:07 +01:00
# Initialize Cdm instance for content decryption module (with optional arguments)
2024-10-27 19:38:24 +01:00
cdm = Cdm(keybox=args.keybox)
2024-07-06 20:01:47 +02:00
if args.challenge:
cdm.set_challenge(data=args.challenge)
2024-10-26 15:21:06 +02:00
if args.private_key:
cdm.set_private_key(data=args.private_key, name=None)
2024-07-06 20:01:47 +02:00
# Initialize Core instance for interacting with the device
2024-10-26 15:21:06 +02:00
core = Core(adb=adb, cdm=cdm, functions=args.functions, skip=args.skip)
2024-07-06 20:01:47 +02:00
2025-01-19 14:13:07 +01:00
# Setup actions based on user arguments (for DRM player, Bitmovin player, etc.)
2024-11-01 18:09:09 +01:00
if args.player:
2025-01-19 14:13:07 +01:00
package = DRM_PLAYER["package"]
2024-11-01 18:09:09 +01:00
# Check if the application is already installed
2025-01-19 14:13:07 +01:00
installed = package in adb.list_applications(user=True, system=False)
2024-11-01 18:09:09 +01:00
if not installed:
2025-01-19 14:13:07 +01:00
logger.debug("Application %s not found. Installing...", package)
installed = adb.install_application(path=DRM_PLAYER["path"], url=DRM_PLAYER["url"])
2024-11-01 18:09:09 +01:00
# Skip starting the application if installation failed
if installed:
# Start the application
2025-01-19 14:13:07 +01:00
logger.info("Starting application: %s", package)
2024-11-01 18:09:09 +01:00
adb.start_application(package)
elif args.auto:
2025-01-19 14:13:07 +01:00
logger.info("Opening the Bitmovin web player...")
adb.open_url("https://bitmovin.com/demos/drm")
logger.info("Setup completed")
2024-11-01 18:09:09 +01:00
2025-01-19 14:13:07 +01:00
# Process watcher loop: continuously checks for Widevine processes
logger.info("Watcher delay: %ss" % args.delay)
2024-10-26 15:21:06 +02:00
current = None # Variable to track the current Widevine process
2024-07-06 20:01:47 +02:00
while core.running:
2024-07-22 20:31:01 +02:00
# Check if for current process data has been exported
if current and cdm.export(args.output, args.wvd):
2024-10-26 15:21:06 +02:00
raise KeyboardInterrupt # Stop if export is complete
2024-07-22 20:31:01 +02:00
2024-10-26 15:21:06 +02:00
# Get the currently running Widevine processes
2025-01-19 14:13:07 +01:00
# https://github.com/hyugogirubato/KeyDive/issues/14#issuecomment-2146788792
2024-07-06 20:01:47 +02:00
processes = {
key: (name, pid)
2024-10-26 15:21:06 +02:00
for name, pid in adb.enumerate_processes().items()
2024-07-27 15:41:57 +02:00
for key in CDM_VENDOR_API.keys()
2025-01-19 14:13:07 +01:00
if key in name or key.replace("-service", "-service-lazy") in name
2024-07-06 20:01:47 +02:00
}
if not processes:
2025-01-19 14:13:07 +01:00
raise EnvironmentError("Unable to detect Widevine, refer to https://github.com/hyugogirubato/KeyDive/blob/main/docs/PACKAGE.md#drm-info")
2024-07-06 20:01:47 +02:00
# Check if the current process has changed
2024-07-22 20:31:01 +02:00
if current and current not in [v[1] for v in processes.values()]:
2025-01-19 14:13:07 +01:00
logger.warning("Widevine process has changed")
2024-07-22 20:31:01 +02:00
current = None
2024-07-06 20:01:47 +02:00
# If current process not found, attempt to hook into the detected processes
if not current:
2025-01-19 14:13:07 +01:00
logger.debug("Analysing...")
2024-07-06 20:01:47 +02:00
for key, (name, pid) in processes.items():
if current:
break
2024-07-08 18:25:33 +02:00
for vendor in CDM_VENDOR_API[key]:
2024-07-06 20:01:47 +02:00
if core.hook_process(pid=pid, vendor=vendor):
2025-01-19 14:13:07 +01:00
logger.info("Process: %s (%s)", pid, name)
2024-07-06 20:01:47 +02:00
current = pid
break
elif not core.running:
raise KeyboardInterrupt
if current:
2025-01-19 14:13:07 +01:00
logger.info("Successfully hooked")
2024-07-06 20:01:47 +02:00
else:
2025-01-19 14:13:07 +01:00
logger.warning("Widevine library not found, searching...")
2024-07-06 20:01:47 +02:00
# Delay before next iteration
time.sleep(args.delay)
except KeyboardInterrupt:
pass
except Exception as e:
logger.critical(e, exc_info=args.verbose)
# Final logging and exit
if log_path:
2025-01-19 14:13:07 +01:00
logger.info("Log file: %s" % log_path)
logger.info("Exiting")
2024-07-06 20:01:47 +02:00
2025-01-19 14:13:07 +01:00
if __name__ == "__main__":
2024-07-06 20:01:47 +02:00
main()