From f265184abb3e8f54a97e0ba89f20d3922ae743c7 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Tue, 14 Jan 2025 01:23:23 +0530 Subject: [PATCH] clean up patching server --- patching-server/README.md | 7 + patching-server/server.py | 99 +++++++ root-module-manual/.gitignore | 3 - root-module-manual/README.md | 19 -- root-module-manual/main.py | 158 ----------- root-module-manual/server.py | 361 ------------------------- root-module-manual/src/module.prop | 6 - root-module-manual/src/post-data-fs.sh | 4 - root-module/customize.sh | 5 +- 9 files changed, 110 insertions(+), 552 deletions(-) create mode 100644 patching-server/README.md create mode 100644 patching-server/server.py delete mode 100644 root-module-manual/.gitignore delete mode 100644 root-module-manual/README.md delete mode 100644 root-module-manual/main.py delete mode 100644 root-module-manual/server.py delete mode 100644 root-module-manual/src/module.prop delete mode 100644 root-module-manual/src/post-data-fs.sh diff --git a/patching-server/README.md b/patching-server/README.md new file mode 100644 index 0000000..bda87ce --- /dev/null +++ b/patching-server/README.md @@ -0,0 +1,7 @@ +# Patcher Server + +This is the server side of the patcher. It is responsible for serving the patch files to the clients. + +# License + +Same as project license. Check [/LICENSE](/LICENSE) for more information. \ No newline at end of file diff --git a/patching-server/server.py b/patching-server/server.py new file mode 100644 index 0000000..2cebfc4 --- /dev/null +++ b/patching-server/server.py @@ -0,0 +1,99 @@ +from flask import Flask, request, jsonify, send_file +import os +import uuid +import logging +import re +import subprocess +import sys + +app = Flask(__name__) + +# Configure logging +class LogColors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKCYAN = '\033[96m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +class ColoredFormatter(logging.Formatter): + def format(self, record): + log_colors = { + 'DEBUG': LogColors.OKCYAN, + 'INFO': LogColors.OKGREEN, + 'WARNING': LogColors.WARNING, + 'ERROR': LogColors.FAIL, + 'CRITICAL': LogColors.FAIL + LogColors.BOLD + } + log_color = log_colors.get(record.levelname, LogColors.ENDC) + record.msg = f"{log_color}{record.msg}{LogColors.ENDC}" + return super().format(record) + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger() +handler = logger.handlers[0] +handler.setFormatter(ColoredFormatter('%(asctime)s - %(levelname)s - %(message)s')) + +def run_command(command): + logging.info(f"Running command: {command}") + result = subprocess.run(command, shell=True, capture_output=True, text=True) + if result.returncode != 0 and "Cannot determine entrypoint" not in result.stderr: + logging.error(f"Command failed: {command}\n{result.stderr}") + sys.exit(1) + logging.info(f"Command output: {result.stdout}") + return result.stdout + +def get_symbol_address(file_path, symbol_name): + logging.info(f"Getting address for symbol: {symbol_name}") + output = run_command(f"radare2 -q -e bin.cache=true -c 'is~{symbol_name}' -z {file_path}") + match = re.search(r'0x[0-9a-fA-F]+', output) + if match: + address = match.group(0) + logging.info(f"Found address for {symbol_name}: {address}") + return address + else: + logging.error(f"Symbol {symbol_name} not found in {file_path}") + sys.exit(1) + +def patch_address(file_path, address, patch_bytes): + logging.info(f"Patching address {address} with bytes: {patch_bytes}") + run_command(f"radare2 -q -e bin.cache=true -w -c 's {address}; wx {patch_bytes}; wci' {file_path}") + logging.info(f"Successfully patched address {address}") + +@app.route('/api', methods=['POST']) +def api(): + if 'file' not in request.files: + return jsonify({"error": "No file part"}), 400 + file = request.files['file'] + if file.filename == '': + return jsonify({"error": "No selected file"}), 400 + if not file.filename.endswith('.so'): + return jsonify({"error": "Invalid file type. Only .so files are allowed."}), 400 + + file_uuid = str(uuid.uuid4()) + file_path = os.path.join('uploads', f"{file_uuid}_{file.filename}") + file.save(file_path) + + try: + l2c_fcr_chk_chan_modes_address = get_symbol_address(file_path, "l2c_fcr_chk_chan_modes") + patch_address(file_path, l2c_fcr_chk_chan_modes_address, "20008052c0035fd6") + l2cu_send_peer_info_req_address = get_symbol_address(file_path, "l2cu_send_peer_info_req") + patch_address(file_path, l2cu_send_peer_info_req_address, "c0035fd6") + except Exception as e: + logger.error(f"Error patching file: {str(e)}") + return jsonify({"error": f"Error patching file: {str(e)}"}), 500 + + try: + return send_file(file_path, as_attachment=True, download_name=file.filename) + except Exception as e: + logger.error(f"Error sending file: {str(e)}") + return jsonify({"error": f"Error sending file: {str(e)}"}), 500 + +if not os.path.exists('uploads'): + os.makedirs('uploads') +if __name__ == '__main__': + app.run(debug=True, host='0.0.0.0', port=8080) diff --git a/root-module-manual/.gitignore b/root-module-manual/.gitignore deleted file mode 100644 index c27d6db..0000000 --- a/root-module-manual/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -uploads -*.zip -*.json \ No newline at end of file diff --git a/root-module-manual/README.md b/root-module-manual/README.md deleted file mode 100644 index 51e1f1f..0000000 --- a/root-module-manual/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Root Module Generator - -Use it by visiting [https://aln.kavishdevar.me](https://aln.kavishdevar.me) - -## Instructions – Website -- Open the website -- Upload the file -- Click on the patch button -- Wait for the patch to complete -- Download the module -- Install it in your root manager (e.g. KernelSU, Magisk, APatch, etc.) - -## Instructions – CLI -- Install `radare2`. Make sure you have it in your PATH. Check by running `radare2 -v`. -- Run `python3 main.py `. The module will be generated in the same directory as the input file. - -# License - -Same as project license. Check [/LICENSE](/LICENSE) for more information. \ No newline at end of file diff --git a/root-module-manual/main.py b/root-module-manual/main.py deleted file mode 100644 index 89abff6..0000000 --- a/root-module-manual/main.py +++ /dev/null @@ -1,158 +0,0 @@ -import logging -import os -import re -import shutil -import subprocess -import sys -import zipfile - -# Define color codes for logging -class LogColors: - HEADER = '\033[95m' - OKBLUE = '\033[94m' - OKCYAN = '\033[96m' - OKGREEN = '\033[92m' - WARNING = '\033[93m' - FAIL = '\033[91m' - ENDC = '\033[0m' - BOLD = '\033[1m' - UNDERLINE = '\033[4m' - -# Custom logging formatter to include colors -class ColoredFormatter(logging.Formatter): - def format(self, record): - log_colors = { - 'DEBUG': LogColors.OKCYAN, - 'INFO': LogColors.OKGREEN, - 'WARNING': LogColors.WARNING, - 'ERROR': LogColors.FAIL, - 'CRITICAL': LogColors.FAIL + LogColors.BOLD - } - log_color = log_colors.get(record.levelname, LogColors.ENDC) - record.msg = f"{log_color}{record.msg}{LogColors.ENDC}" - return super().format(record) - -def run_command(command): - """ - Runs a shell command and logs the output. - - Args: - command (str): The command to run. - - Returns: - str: The standard output from the command. - - Raises: - SystemExit: If the command fails. - """ - logging.info(f"Running command: {command}") - result = subprocess.run(command, shell=True, capture_output=True, text=True) - if result.returncode != 0 and "Cannot determine entrypoint" not in result.stderr: - logging.error(f"Command failed: {command}\n{result.stderr}") - sys.exit(1) - logging.info(f"Command output: {result.stdout}") - return result.stdout - -def get_symbol_address(file_path, symbol_name): - """ - Gets the address of a symbol in a binary file using radare2. - - Args: - file_path (str): The path to the binary file. - symbol_name (str): The name of the symbol to find. - - Returns: - str: The address of the symbol. - - Raises: - SystemExit: If the symbol is not found. - """ - logging.info(f"Getting address for symbol: {symbol_name}") - output = run_command(f"radare2 -q -e bin.cache=true -c 'is~{symbol_name}' -z {file_path}") - match = re.search(r'0x[0-9a-fA-F]+', output) - if match: - address = match.group(0) - logging.info(f"Found address for {symbol_name}: {address}") - return address - else: - logging.error(f"Symbol {symbol_name} not found in {file_path}") - sys.exit(1) - -def patch_address(file_path, address, patch_bytes): - """ - Patches a specific address in a binary file with given bytes using radare2. - - Args: - file_path (str): The path to the binary file. - address (str): The address to patch. - patch_bytes (str): The bytes to write at the address. - - Raises: - SystemExit: If the patching command fails. - """ - logging.info(f"Patching address {address} with bytes: {patch_bytes}") - run_command(f"radare2 -q -e bin.cache=true -w -c 's {address}; wx {patch_bytes}; wci' {file_path}") - logging.info(f"Successfully patched address {address}") - -def copy_file_to_src(file_path, library_name): - """ - Copies a file to the 'src/' directory with the specified library name. - - Args: - file_path (str): The path to the file to copy. - library_name (str): The name to use for the copied library. - """ - if os.path.exists('btl2capfix.zip'): - os.remove('btl2capfix.zip') - if os.path.exists('src/libbluetooth_jni.so'): - os.remove('src/libbluetooth_jni.so') - if os.path.exists('src/libbluetooth_qti.so'): - os.remove('src/libbluetooth_qti.so') - src_dir = 'src/' - if not os.path.exists(src_dir): - os.makedirs(src_dir) - dest_path = os.path.join(src_dir, library_name) - shutil.copy(file_path, dest_path) - logging.info(f"Copied {file_path} to {dest_path}") - -def zip_src_files(): - """ - Zips all files in the 'src/' directory into 'btl2capfix.zip' using the shell zip command. - """ - run_command('cd src && zip -y -r ../btl2capfix *') - -def main(): - """ - Main function to execute the script. It performs the following steps: - 1. Copies the input file to the 'src/' directory. - 2. Patches specific addresses in the binary file. - 4. Zips the files in the 'src/' directory into 'btl2capfix.zip'. - """ - logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') - logger = logging.getLogger() - handler = logger.handlers[0] - handler.setFormatter(ColoredFormatter('%(asctime)s - %(levelname)s - %(message)s')) - - if len(sys.argv) != 3: - logging.error("Usage: python main.py ") - sys.exit(1) - - file_path = sys.argv[1] - library_name = sys.argv[2] - - # Patch l2c_fcr_chk_chan_modes - l2c_fcr_chk_chan_modes_address = get_symbol_address(file_path, "l2c_fcr_chk_chan_modes") - patch_address(file_path, l2c_fcr_chk_chan_modes_address, "20008052c0035fd6") - - # Patch l2cu_send_peer_info_req - l2cu_send_peer_info_req_address = get_symbol_address(file_path, "l2cu_send_peer_info_req") - patch_address(file_path, l2cu_send_peer_info_req_address, "c0035fd6") - - # Copy file to src/ - copy_file_to_src(file_path, library_name) - - # Zip files under src/ - zip_src_files() - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/root-module-manual/server.py b/root-module-manual/server.py deleted file mode 100644 index 44f7a7e..0000000 --- a/root-module-manual/server.py +++ /dev/null @@ -1,361 +0,0 @@ -from flask import Flask, request, jsonify, send_file, make_response -import os -import json -import uuid -import time -import threading -import logging -from main import get_symbol_address, patch_address, copy_file_to_src, zip_src_files - -app = Flask(__name__) -PATCHED_LIBRARIES = {} -PERMALINK_EXPIRY = 600 # 10 minutes -PATCHES_JSON = 'patches.json' - -# Configure logging -class LogColors: - HEADER = '\033[95m' - OKBLUE = '\033[94m' - OKCYAN = '\033[96m' - OKGREEN = '\033[92m' - WARNING = '\033[93m' - FAIL = '\033[91m' - ENDC = '\033[0m' - BOLD = '\033[1m' - UNDERLINE = '\033[4m' - -class ColoredFormatter(logging.Formatter): - def format(self, record): - log_colors = { - 'DEBUG': LogColors.OKCYAN, - 'INFO': LogColors.OKGREEN, - 'WARNING': LogColors.WARNING, - 'ERROR': LogColors.FAIL, - 'CRITICAL': LogColors.FAIL + LogColors.BOLD - } - log_color = log_colors.get(record.levelname, LogColors.ENDC) - record.msg = f"{log_color}{record.msg}{LogColors.ENDC}" - return super().format(record) - -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') -logger = logging.getLogger() -handler = logger.handlers[0] -handler.setFormatter(ColoredFormatter('%(asctime)s - %(levelname)s - %(message)s')) - -def save_patch_info(permalink_id, file_path): - # Dummy function to maintain logs - logger.info(f"Patch info saved for permalink_id: {permalink_id}, file_path: {file_path}") - -@app.route('/') -def index(): - return ''' - - - - - - Library Patcher - - - -
-

Upload a library to patch

-
-
- - Click to upload a file -
- - -
-
-
-
-
- - -
- - - - ''' - -@app.route('/patch', methods=['POST']) -def patch(): - if 'file' not in request.files: - return jsonify({"error": "No file part"}), 400 - file = request.files['file'] - if file.filename == '': - return jsonify({"error": "No selected file"}), 400 - if not file.filename.endswith('.so'): - return jsonify({"error": "Invalid file type. Only .so files are allowed."}), 400 - - # Generate a unique file path - file_uuid = str(uuid.uuid4()) - file_path = os.path.join('uploads', f"{file_uuid}_{file.filename}") - file.save(file_path) - - # Determine the library name based on the checkbox - library_name = "libbluetooth_qti.so" if 'qti' in request.form else "libbluetooth_jni.so" - - # Patch the file - try: - l2c_fcr_chk_chan_modes_address = get_symbol_address(file_path, "l2c_fcr_chk_chan_modes") - patch_address(file_path, l2c_fcr_chk_chan_modes_address, "20008052c0035fd6") - l2cu_send_peer_info_req_address = get_symbol_address(file_path, "l2cu_send_peer_info_req") - patch_address(file_path, l2cu_send_peer_info_req_address, "c0035fd6") - except Exception as e: - logger.error(f"Error patching file: {str(e)}") - return jsonify({"error": f"Error patching file: {str(e)}"}), 500 - - # Create permalink - permalink_id = str(uuid.uuid4()) - PATCHED_LIBRARIES[permalink_id] = { - 'file_path': file_path, - 'library_name': library_name, - 'timestamp': time.time() - } - - # Save patch info - save_patch_info(permalink_id, file_path) - - # Schedule deletion - threading.Timer(PERMALINK_EXPIRY, delete_expired_permalink, args=[permalink_id]).start() - logger.info(f"Permalink {permalink_id} created, will expire in {PERMALINK_EXPIRY} seconds") - - return jsonify({'permalink': f'/download/{permalink_id}'}) - -@app.route('/api', methods=['POST']) -def api(): - if 'file' not in request.files: - return jsonify({"error": "No file part"}), 400 - file = request.files['file'] - if file.filename == '': - return jsonify({"error": "No selected file"}), 400 - if not file.filename.endswith('.so'): - return jsonify({"error": "Invalid file type. Only .so files are allowed."}), 400 - - # Generate a unique file path - file_uuid = str(uuid.uuid4()) - file_path = os.path.join('uploads', f"{file_uuid}_{file.filename}") - file.save(file_path) - - # Determine the library name based on the request data - data = request.form - library_name = data.get('library_name', 'libbluetooth_jni.so') - - # Patch the file - try: - l2c_fcr_chk_chan_modes_address = get_symbol_address(file_path, "l2c_fcr_chk_chan_modes") - patch_address(file_path, l2c_fcr_chk_chan_modes_address, "20008052c0035fd6") - l2cu_send_peer_info_req_address = get_symbol_address(file_path, "l2cu_send_peer_info_req") - patch_address(file_path, l2cu_send_peer_info_req_address, "c0035fd6") - except Exception as e: - logger.error(f"Error patching file: {str(e)}") - return jsonify({"error": f"Error patching file: {str(e)}"}), 500 - - # Log patch info - permalink_id = str(uuid.uuid4()) - save_patch_info(permalink_id, file_path) - - # Return the patched file directly - try: - return send_file(file_path, as_attachment=True, download_name=library_name) - except Exception as e: - logger.error(f"Error sending file: {str(e)}") - return jsonify({"error": f"Error sending file: {str(e)}"}), 500 - -@app.route('/download/', methods=['GET']) -def download(permalink_id): - if permalink_id not in PATCHED_LIBRARIES: - return "Permalink expired or invalid", 404 - - file_path = PATCHED_LIBRARIES[permalink_id]['file_path'] - library_name = PATCHED_LIBRARIES[permalink_id]['library_name'] - if not os.path.exists(file_path): - return "File not found", 404 - - try: - copy_file_to_src(file_path, library_name) - zip_src_files() - except Exception as e: - logger.error(f"Error preparing download: {str(e)}") - return f"Error preparing download: {str(e)}", 500 - - resp = make_response(send_file('btl2capfix.zip', as_attachment=True)) - resp.headers['Content-Disposition'] = f'attachment; filename=btl2capfix.zip' - return resp - -def delete_expired_permalink(permalink_id): - if permalink_id in PATCHED_LIBRARIES: - file_path = PATCHED_LIBRARIES[permalink_id]['file_path'] - if os.path.exists(file_path): - os.remove(file_path) - logger.info(f"Deleted file: {file_path}") - del PATCHED_LIBRARIES[permalink_id] - logger.info(f"Permalink {permalink_id} expired and removed from PATCHED_LIBRARIES") - -if not os.path.exists('uploads'): - os.makedirs('uploads') -if __name__ == '__main__': - app.run(debug=True, host='0.0.0.0', port=8080) diff --git a/root-module-manual/src/module.prop b/root-module-manual/src/module.prop deleted file mode 100644 index 616d101..0000000 --- a/root-module-manual/src/module.prop +++ /dev/null @@ -1,6 +0,0 @@ -id=btl2capfix -name=Bluetooth L2CAP workaround for AirPods -version=v1 -versionCode=1 -author=kavishdevar -description=Fixes the Bluetooth L2CAP connection issue with AirPods \ No newline at end of file diff --git a/root-module-manual/src/post-data-fs.sh b/root-module-manual/src/post-data-fs.sh deleted file mode 100644 index 85f3e5e..0000000 --- a/root-module-manual/src/post-data-fs.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/system/bin/sh - -mount -t overlay overlay -o lowerdir=/apex/com.android.btservices/lib64,upperdir=/data/adb/modules/btl2capfix/apex/com.android.btservices/lib64,workdir=/data/adb/modules/btl2capfix/apex/com.android.btservices/work /apex/com.android.btservices/lib64 -mount -t overlay overlay -o lowerdir=/apex/com.android.btservices@352090000/lib64,upperdir=/data/adb/modules/btl2capfix/apex/com.android.btservices@352090000/lib64,workdir=/data/adb/modules/btl2capfix/apex/com.android.btservices@352090000/work /apex/com.android.btservices@352090000/lib64 \ No newline at end of file diff --git a/root-module/customize.sh b/root-module/customize.sh index ac698b8..34c005e 100644 --- a/root-module/customize.sh +++ b/root-module/customize.sh @@ -124,4 +124,7 @@ else ui_print "Failed to receive patched file from the API." rm -rf "$TEMP_DIR" abort "Failed to patch the library." -fi \ No newline at end of file +fi + +rm -rf "$TEMP_DIR" +rm -rf "$UNZIP_DIR" \ No newline at end of file