mirror of
https://github.com/hyugogirubato/KeyDive.git
synced 2026-07-15 18:40:02 +02:00
release v3.0.0
This commit is contained in:
@@ -56,7 +56,7 @@ To extract unencrypted challenges using KeyDive and HTTP Toolkit, you can follow
|
||||
6. **Use Extracted Data:**
|
||||
- Use the extracted data with KeyDive by running:
|
||||
```shell
|
||||
keydive --device <DEVICE_ID> --challenge path/to/challenge
|
||||
keydive --serial <DEVICE_ID> --challenge path/to/challenge
|
||||
```
|
||||
Replace `path/to/challenge` with the actual path to the extracted challenge data file.
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ To utilize custom functions with KeyDive, particularly when extracting Widevine
|
||||
Run KeyDive to detect the library path on the Android device:
|
||||
|
||||
```shell
|
||||
keydive --device <DEVICE_ID>
|
||||
keydive --serial <DEVICE_ID>
|
||||
```
|
||||
|
||||
Replace `<DEVICE_ID>` with the ID of your Android device connected via ADB.
|
||||
@@ -77,9 +77,9 @@ Ensure you have Ghidra installed on your system. If not, download it from the [G
|
||||
Once you have the `functions.xml` file:
|
||||
|
||||
- Ensure KeyDive is set up according to its documentation.
|
||||
- When running KeyDive, use the `--functions` argument to specify the path to your `functions.xml` file. For example:
|
||||
- When running KeyDive, use the `--symbols` argument to specify the path to your `functions.xml` file. For example:
|
||||
```shell
|
||||
keydive --device <DEVICE_ID> --functions /path/to/functions_x86.xml
|
||||
keydive --serial <DEVICE_ID> --symbols /path/to/functions_x86.xml
|
||||
```
|
||||
- Proceed with the key extraction process as detailed in KeyDive's usage instructions.
|
||||
|
||||
|
||||
+56
-53
@@ -1,25 +1,26 @@
|
||||
import argparse
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
from flask import Flask, Response, request, redirect
|
||||
from rich_argparse import RichHelpFormatter
|
||||
|
||||
from keydive.__main__ import configure_logging
|
||||
from keydive.utils import configure_logging
|
||||
|
||||
# Suppress urllib3 warnings
|
||||
logging.getLogger("urllib3.connectionpool").setLevel(logging.ERROR)
|
||||
logging.getLogger('urllib3.connectionpool').setLevel(logging.ERROR)
|
||||
|
||||
# Initialize Flask application
|
||||
app = Flask(__name__)
|
||||
|
||||
# Define paths, constants, and global flags
|
||||
PARENT = Path(__file__).parent
|
||||
VERSION = "1.0.1"
|
||||
VERSION = '1.0.2'
|
||||
KEYBOX = False
|
||||
DELAY = 10
|
||||
|
||||
@@ -30,9 +31,9 @@ def health_check() -> Response:
|
||||
Health check endpoint to confirm the server is running.
|
||||
|
||||
Returns:
|
||||
Response: A simple "pong" message with a 200 OK status.
|
||||
Response: A simple 'pong' message with a 200 OK status.
|
||||
"""
|
||||
return Response(response="pong", status=200, content_type="text/html; charset=utf-8")
|
||||
return Response(response='pong', status=200, content_type='text/html; charset=utf-8')
|
||||
|
||||
|
||||
@app.route('/shaka-demo-assets/angel-one-widevine/<path:file>', methods=['GET'])
|
||||
@@ -41,17 +42,17 @@ def shaka_demo_assets(file) -> Response:
|
||||
Serves cached assets for Widevine demo content. If the requested file is
|
||||
not available locally, it fetches it from a remote server and caches it.
|
||||
|
||||
Parameters:
|
||||
Args:
|
||||
file (str): File path requested by the client.
|
||||
|
||||
Returns:
|
||||
Response: File content as a byte stream, or a 404 error if not found.
|
||||
"""
|
||||
logger = logging.getLogger("Shaka")
|
||||
logger.info("%s %s", request.method, request.path)
|
||||
logger = logging.getLogger('Shaka')
|
||||
logger.info('%s %s', request.method, request.path)
|
||||
|
||||
try:
|
||||
path = PARENT / ".assets" / file
|
||||
path = PARENT / '.assets' / file
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if path.is_file():
|
||||
@@ -60,20 +61,20 @@ def shaka_demo_assets(file) -> Response:
|
||||
else:
|
||||
# Fetch the file from remote storage if not cached locally
|
||||
r = requests.get(
|
||||
url=f"https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine/{file}",
|
||||
url=f'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine/{file}',
|
||||
headers={
|
||||
"Accept": "*/*",
|
||||
"User-Agent": "KalturaDeviceInfo/1.4.1 (Linux;Android 10) ExoPlayerLib/2.9.3"
|
||||
'Accept': '*/*',
|
||||
'User-Agent': 'KalturaDeviceInfo/1.4.1 (Linux;Android 10) ExoPlayerLib/2.9.3'
|
||||
}
|
||||
)
|
||||
r.raise_for_status()
|
||||
path.write_bytes(r.content) # Cache the downloaded content
|
||||
content = r.content
|
||||
logger.debug("Downloaded assets: %s", path)
|
||||
logger.debug('Downloaded assets: %s', path)
|
||||
|
||||
return Response(response=content, status=200, content_type="application/octet-stream")
|
||||
return Response(response=content, status=200, content_type='application/octet-stream')
|
||||
except Exception as e:
|
||||
return Response(response=str(e), status=404, content_type="text/html; charset=utf-8")
|
||||
return Response(response=str(e), status=404, content_type='text/html; charset=utf-8')
|
||||
|
||||
|
||||
@app.route('/certificateprovisioning/v1/devicecertificates/create', methods=['POST'])
|
||||
@@ -87,16 +88,16 @@ def certificate_provisioning() -> Response:
|
||||
Response: JSON response if provisioning is complete, else a redirection.
|
||||
"""
|
||||
global KEYBOX, DELAY
|
||||
logger = logging.getLogger("Google")
|
||||
logger.info("%s %s", request.method, request.path)
|
||||
logger = logging.getLogger('Google')
|
||||
logger.info('%s %s', request.method, request.path)
|
||||
|
||||
if KEYBOX:
|
||||
logger.warning("Provisioning request aborted to prevent keybox spam")
|
||||
return Response(response="Internal Server Error", status=500, content_type="text/html; charset=utf-8")
|
||||
logger.warning('Provisioning request aborted to prevent keybox spam')
|
||||
return Response(response='Internal Server Error', status=500, content_type='text/html; charset=utf-8')
|
||||
|
||||
# Generate a curl command from the incoming request for debugging or testing
|
||||
user_agent = request.headers.get("User-Agent", "Unknown")
|
||||
url = request.url.replace("http://", "https://")
|
||||
user_agent = request.headers.get('User-Agent', 'Unknown')
|
||||
url = request.url.replace('http://', 'https://')
|
||||
prompt = [
|
||||
'curl',
|
||||
'--request', 'POST',
|
||||
@@ -109,15 +110,15 @@ def certificate_provisioning() -> Response:
|
||||
]
|
||||
|
||||
# Save the curl command for potential replay or inspection
|
||||
curl = PARENT / "curl.txt"
|
||||
curl.write_text(" \\\n ".join(prompt))
|
||||
logger.debug("Saved curl command to: %s", curl)
|
||||
curl = PARENT / 'curl.txt'
|
||||
curl.write_text(' \\\n '.join(prompt))
|
||||
logger.debug('Saved curl command to: %s', curl)
|
||||
|
||||
# Wait for provisioning response data with retries
|
||||
logger.warning("Waiting for provisioning response...")
|
||||
provision = PARENT / "provisioning.json"
|
||||
logger.warning('Waiting for provisioning response...')
|
||||
provision = PARENT / 'provisioning.json'
|
||||
provision.unlink(missing_ok=True)
|
||||
provision.write_bytes(b"") # Create empty file for manual input if needed
|
||||
provision.write_bytes(b'') # Create empty file for manual input if needed
|
||||
|
||||
# Poll for the presence of a response up to DELAY times with 1-second intervals
|
||||
for _ in range(DELAY):
|
||||
@@ -127,13 +128,13 @@ def certificate_provisioning() -> Response:
|
||||
# Cleanup after successful response
|
||||
curl.unlink(missing_ok=True)
|
||||
provision.unlink(missing_ok=True)
|
||||
return Response(response=content, status=200, content_type="application/json")
|
||||
return Response(response=content, status=200, content_type='application/json')
|
||||
except Exception as e:
|
||||
pass # Continue waiting if file is empty or not yet ready
|
||||
time.sleep(1)
|
||||
|
||||
# Redirect to the secure URL if response is not available
|
||||
logger.warning("Redirecting to avoid timeout")
|
||||
logger.warning('Redirecting to avoid timeout')
|
||||
return redirect(url, code=302)
|
||||
|
||||
|
||||
@@ -143,32 +144,32 @@ def main() -> None:
|
||||
to set global parameters and configures logging, then starts the Flask server.
|
||||
"""
|
||||
global VERSION, DELAY, KEYBOX
|
||||
parser = argparse.ArgumentParser(description="Local DRM provisioning video player.")
|
||||
parser = ArgumentParser(
|
||||
description='Local DRM provisioning video player.',
|
||||
formatter_class=RichHelpFormatter)
|
||||
|
||||
# Global arguments for the application
|
||||
group_global = parser.add_argument_group("Global")
|
||||
group_global.add_argument('--host', required=False, type=str, default="127.0.0.1", metavar="<host>", help="Host address for the server to bind to.")
|
||||
group_global.add_argument('--port', required=False, type=int, default=9090, metavar="<port>", help="Port number for the server to listen on.")
|
||||
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('--version', required=False, action="store_true", help="Display Server version information.")
|
||||
global_group = parser.add_argument_group('Global Options')
|
||||
global_group.add_argument('--host', type=str, default='127.0.0.1', metavar='<host>', help='Host address for the server to bind to.')
|
||||
global_group.add_argument('--port', type=int, default=9090, metavar='<port>', help='Port number for the server to listen on.')
|
||||
global_group.add_argument('-v', '--verbose', action='store_true', help='Enable detailed logging for debugging.')
|
||||
global_group.add_argument('-l', '--log', type=Path, metavar='<dir>', help='Directory to save log files.')
|
||||
global_group.add_argument('--version', action='store_true', help='Show tool version and exit.')
|
||||
|
||||
# Advanced options
|
||||
group_advanced = parser.add_argument_group("Advanced")
|
||||
group_advanced.add_argument('-d', '--delay', required=False, type=int, metavar="<delay>", default=10, help="Delay (in seconds) between successive checks for provisioning responses.")
|
||||
group_advanced.add_argument('-k', '--keybox', required=False, action="store_true", help="Enable keybox mode, which aborts provisioning requests to prevent spam.")
|
||||
advanced_group = parser.add_argument_group('Advanced Options')
|
||||
advanced_group.add_argument('-d', '--delay', type=int, metavar='<delay>', default=10, help='Delay (in seconds) between successive checks for provisioning responses.')
|
||||
advanced_group.add_argument('-k', '--keybox', action='store_true', help='Enable keybox mode, which aborts provisioning requests to prevent spam.')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Handle version display
|
||||
# Handle version flag early and exit
|
||||
if args.version:
|
||||
print(f"Server {VERSION}")
|
||||
exit(0)
|
||||
print(f'Server {VERSION}')
|
||||
return
|
||||
|
||||
# Configure logging (file and console)
|
||||
# Set up logging (to file if specified, otherwise stdout)
|
||||
log_path = configure_logging(path=args.log, verbose=args.verbose)
|
||||
logger = logging.getLogger("Server")
|
||||
logger.info("Version: %s", VERSION)
|
||||
logger = logging.getLogger('Server')
|
||||
logger.info('Version: %s', VERSION)
|
||||
|
||||
try:
|
||||
# Set global variables based on parsed arguments
|
||||
@@ -176,18 +177,20 @@ def main() -> None:
|
||||
KEYBOX = args.keybox
|
||||
|
||||
# Start Flask app with specified host, port, and debug mode
|
||||
logging.getLogger("werkzeug").setLevel(logging.INFO if args.verbose else logging.ERROR)
|
||||
logging.getLogger('werkzeug').setLevel(logging.INFO if args.verbose else logging.ERROR)
|
||||
app.run(host=args.host, port=args.port, debug=False)
|
||||
except KeyboardInterrupt:
|
||||
# Graceful exit on user interrupt (Ctrl+C)
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.critical(e, exc_info=args.verbose)
|
||||
|
||||
# Final logging and exit
|
||||
# Log the path to the log file if it was created
|
||||
if log_path:
|
||||
logger.info("Log file: %s" % log_path)
|
||||
logger.info("Exiting")
|
||||
logger.info('Log file: %s' % log_path)
|
||||
|
||||
logger.info('Exiting')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user