Add new skip option

This commit is contained in:
hyugogirubato 2024-10-18 20:26:41 +02:00
parent f1cfbf752c
commit 2a8a987766
5 changed files with 20 additions and 8 deletions

View File

@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.1.0] - Not release
### Added
- Added private key function.
- Option to Skip automatic detection of private function.
## [2.0.9] - 2024-09-25 ## [2.0.9] - 2024-09-25
### Added ### Added

View File

@ -72,6 +72,7 @@ Cdm options:
Output directory path for extracted data. Output directory path for extracted data.
-f <file>, --functions <file> -f <file>, --functions <file>
Path to Ghidra XML functions file. Path to Ghidra XML functions file.
-s, --skip Skip auto-detect of private function.
``` ```

View File

@ -84,6 +84,7 @@ def main() -> None:
opt_cdm.add_argument('-w', '--wvd', required=False, action='store_true', help='Generate a pywidevine WVD device file.') opt_cdm.add_argument('-w', '--wvd', required=False, action='store_true', help='Generate a pywidevine WVD device file.')
opt_cdm.add_argument('-o', '--output', required=False, type=Path, default=Path('device'), metavar='<dir>', help='Output directory path for extracted data.') opt_cdm.add_argument('-o', '--output', required=False, type=Path, default=Path('device'), metavar='<dir>', help='Output directory path for extracted data.')
opt_cdm.add_argument('-f', '--functions', required=False, type=Path, metavar='<file>', help='Path to Ghidra XML functions file.') opt_cdm.add_argument('-f', '--functions', required=False, type=Path, metavar='<file>', help='Path to Ghidra XML functions file.')
opt_cdm.add_argument('-s', '--skip', required=False, action='store_true', help='Skip auto-detect of private function.')
args = parser.parse_args() args = parser.parse_args()
if args.version: if args.version:
@ -107,7 +108,7 @@ def main() -> None:
cdm.set_challenge(data=args.challenge) cdm.set_challenge(data=args.challenge)
# Initialize Core instance for interacting with the device # Initialize Core instance for interacting with the device
core = Core(cdm=cdm, device=args.device, functions=args.functions) core = Core(cdm=cdm, device=args.device, functions=args.functions, skip=args.skip)
# Process watcher loop # Process watcher loop
logger.info('Watcher delay: %ss' % args.delay) logger.info('Watcher delay: %ss' % args.delay)

View File

@ -20,7 +20,7 @@ class Core:
Core class for handling DRM operations and device interactions. Core class for handling DRM operations and device interactions.
""" """
def __init__(self, cdm: Cdm, device: str = None, functions: Path = None): def __init__(self, cdm: Cdm, device: str = None, functions: Path = None, skip: bool = False):
""" """
Initializes a Core instance. Initializes a Core instance.
@ -28,10 +28,12 @@ class Core:
cdm (Cdm): Instance of Cdm for managing DRM related operations. cdm (Cdm): Instance of Cdm for managing DRM related operations.
device (str, optional): ID of the Android device to connect to via ADB. Defaults to None (uses USB device). device (str, optional): ID of the Android device to connect to via ADB. Defaults to None (uses USB device).
functions (Path, optional): Path to Ghidra XML functions file for symbol extraction. Defaults to None. functions (Path, optional): Path to Ghidra XML functions file for symbol extraction. Defaults to None.
skip (bool, optional): Flag to determine whether to skip predefined functions (e.g., OEM_CRYPTO_API).
""" """
self.logger = logging.getLogger(self.__class__.__name__) self.logger = logging.getLogger(self.__class__.__name__)
self.running = True self.running = True
self.cdm = cdm self.cdm = cdm
self.skip = skip
# Select device based on provided ID or default to the first USB device. # Select device based on provided ID or default to the first USB device.
self.device: Device = frida.get_device(id=device, timeout=5) if device else frida.get_usb_device(timeout=5) self.device: Device = frida.get_device(id=device, timeout=5) if device else frida.get_usb_device(timeout=5)
@ -61,7 +63,8 @@ class Core:
replacements = { replacements = {
'${OEM_CRYPTO_API}': json.dumps(list(OEM_CRYPTO_API)), '${OEM_CRYPTO_API}': json.dumps(list(OEM_CRYPTO_API)),
'${NATIVE_C_API}': json.dumps(list(NATIVE_C_API)), '${NATIVE_C_API}': json.dumps(list(NATIVE_C_API)),
'${SYMBOLS}': json.dumps(symbols) '${SYMBOLS}': json.dumps(symbols),
'${SKIP}': str(self.skip)
} }
for placeholder, value in replacements.items(): for placeholder, value in replacements.items():
@ -69,8 +72,7 @@ class Core:
return content return content
@staticmethod def __prepare_symbols(self, path: Path) -> list:
def __prepare_symbols(path: Path) -> list:
""" """
Parses the provided XML functions file to select relevant functions. Parses the provided XML functions file to select relevant functions.
@ -95,7 +97,7 @@ class Core:
functions = program['FUNCTIONS']['FUNCTION'] functions = program['FUNCTIONS']['FUNCTION']
# Find a target function from a predefined list # Find a target function from a predefined list
target = next((f['@NAME'] for f in functions if f['@NAME'] in OEM_CRYPTO_API), None) target = None if self.skip else next((f['@NAME'] for f in functions if f['@NAME'] in OEM_CRYPTO_API), None)
# Extract relevant functions # Extract relevant functions
selected = {} selected = {}
@ -106,7 +108,7 @@ class Core:
# Add function if it matches specific criteria # Add function if it matches specific criteria
if name not in selected and ( if name not in selected and (
name == target name == target
or any(keyword in name for keyword in CDM_FUNCTION_API) or any(None if self.skip else keyword in name for keyword in CDM_FUNCTION_API)
or (not target and re.match(r'^[a-z]+$', name) and args >= 6) or (not target and re.match(r'^[a-z]+$', name) and args >= 6)
): ):
selected[name] = { selected[name] = {

View File

@ -8,6 +8,7 @@
const OEM_CRYPTO_API = JSON.parse('${OEM_CRYPTO_API}'); const OEM_CRYPTO_API = JSON.parse('${OEM_CRYPTO_API}');
const NATIVE_C_API = JSON.parse('${NATIVE_C_API}'); const NATIVE_C_API = JSON.parse('${NATIVE_C_API}');
const SYMBOLS = JSON.parse('${SYMBOLS}'); const SYMBOLS = JSON.parse('${SYMBOLS}');
const SKIP = '${SKIP}' === 'True';
// Logging levels to synchronize with Python's logging module. // Logging levels to synchronize with Python's logging module.
@ -281,7 +282,7 @@ const hookLibrary = (name) => {
} }
functions = functions.filter(f => !NATIVE_C_API.includes(f.name)); functions = functions.filter(f => !NATIVE_C_API.includes(f.name));
const targets = functions.filter(f => OEM_CRYPTO_API.includes(f.name)).map(f => f.name); const targets = SKIP ? [] : functions.filter(f => OEM_CRYPTO_API.includes(f.name)).map(f => f.name);
const hooked = []; const hooked = [];
functions.forEach(func => { functions.forEach(func => {