| | import os |
| | import subprocess |
| | import zipfile |
| | import tempfile |
| | import shutil |
| | import xml.etree.ElementTree as ET |
| | import magic |
| | import requests |
| | from flask import Flask, request, jsonify, send_file, abort, render_template |
| | from werkzeug.utils import secure_filename |
| |
|
| | app = Flask(__name__) |
| |
|
| | |
| | UPLOAD_FOLDER = 'uploads' |
| | APKTOOL_VERSION = '2.9.1' |
| | APKTOOL_URL = f'https://github.com/iBotPeaches/Apktool/releases/download/v{APKTOOL_VERSION}/apktool_{APKTOOL_VERSION}.jar' |
| | APKTOOL_PATH = 'apktool.jar' |
| | KEYSTORE_PATH = 'debug.keystore' |
| | KEYSTORE_PASS = 'android' |
| | KEY_ALIAS = 'androiddebugkey' |
| |
|
| | |
| | os.makedirs(UPLOAD_FOLDER, exist_ok=True) |
| |
|
| | def download_apktool(): |
| | """Download Apktool if not present""" |
| | if not os.path.exists(APKTOOL_PATH): |
| | print(f"Downloading Apktool v{APKTOOL_VERSION}...") |
| | try: |
| | response = requests.get(APKTOOL_URL, stream=True) |
| | with open(APKTOOL_PATH, 'wb') as f: |
| | for chunk in response.iter_content(chunk_size=8192): |
| | f.write(chunk) |
| | print("Apktool downloaded successfully") |
| | except Exception as e: |
| | print(f"Failed to download Apktool: {str(e)}") |
| | raise |
| |
|
| | def verify_apk(file_path): |
| | """Verify if file is a valid APK""" |
| | mime = magic.Magic(mime=True) |
| | file_type = mime.from_file(file_path) |
| | return file_type == 'application/vnd.android.package-archive' |
| |
|
| | @app.route('/') |
| | def index(): |
| | return render_template('index.html') |
| |
|
| | @app.route('/api/upload', methods=['POST']) |
| | def upload_apk(): |
| | """Upload an APK file""" |
| | 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 file and file.filename.endswith('.apk'): |
| | filename = secure_filename(file.filename) |
| | filepath = os.path.join(UPLOAD_FOLDER, filename) |
| | file.save(filepath) |
| | |
| | |
| | if not verify_apk(filepath): |
| | os.remove(filepath) |
| | return jsonify({'error': 'Invalid APK file'}), 400 |
| | |
| | return jsonify({ |
| | 'success': True, |
| | 'filename': filename, |
| | 'filepath': filepath |
| | }) |
| | else: |
| | return jsonify({'error': 'Invalid file type. Only APK files are allowed.'}), 400 |
| |
|
| | @app.route('/api/decompile', methods=['POST']) |
| | def decompile_apk(): |
| | """Decompile an APK using apktool""" |
| | data = request.get_json() |
| | if not data or 'apk_path' not in data: |
| | return jsonify({'error': 'APK path required'}), 400 |
| | |
| | apk_path = data['apk_path'] |
| | if not os.path.exists(apk_path): |
| | return jsonify({'error': 'APK file not found'}), 404 |
| | |
| | output_dir = apk_path.replace('.apk', '_decompiled') |
| | |
| | try: |
| | |
| | download_apktool() |
| | |
| | |
| | result = subprocess.run( |
| | ['java', '-jar', APKTOOL_PATH, 'd', apk_path, '-o', output_dir, '-f'], |
| | capture_output=True, |
| | text=True |
| | ) |
| | |
| | if result.returncode != 0: |
| | return jsonify({ |
| | 'error': 'Decompilation failed', |
| | 'details': result.stderr |
| | }), 500 |
| | |
| | return jsonify({ |
| | 'success': True, |
| | 'output_dir': output_dir |
| | }) |
| | except Exception as e: |
| | return jsonify({'error': str(e)}), 500 |
| |
|
| | @app.route('/api/recompile', methods=['POST']) |
| | def recompile_apk(): |
| | """Recompile a decompiled APK""" |
| | data = request.get_json() |
| | if not data or 'decompiled_dir' not in data: |
| | return jsonify({'error': 'Decompiled directory required'}), 400 |
| | |
| | decompiled_dir = data['decompiled_dir'] |
| | output_apk = decompiled_dir.replace('_decompiled', '_modified.apk') |
| | |
| | try: |
| | result = subprocess.run( |
| | ['java', '-jar', APKTOOL_PATH, 'b', decompiled_dir, '-o', output_apk], |
| | capture_output=True, |
| | text=True |
| | ) |
| | |
| | if result.returncode != 0: |
| | return jsonify({ |
| | 'error': 'Recompilation failed', |
| | 'details': result.stderr |
| | }), 500 |
| | |
| | return jsonify({ |
| | 'success': True, |
| | 'output_apk': output_apk |
| | }) |
| | except Exception as e: |
| | return jsonify({'error': str(e)}), 500 |
| |
|
| | @app.route('/api/sign', methods=['POST']) |
| | def sign_apk(): |
| | """Sign an APK file""" |
| | data = request.get_json() |
| | if not data or 'apk_path' not in data: |
| | return jsonify({'error': 'APK path required'}), 400 |
| | |
| | apk_path = data['apk_path'] |
| | if not os.path.exists(apk_path): |
| | return jsonify({'error': 'APK file not found'}), 404 |
| | |
| | signed_apk = apk_path.replace('.apk', '_signed.apk') |
| | |
| | try: |
| | |
| | result = subprocess.run( |
| | ['jarsigner', '-verbose', '-sigalg', 'SHA1withRSA', '-digestalg', 'SHA1', |
| | '-keystore', KEYSTORE_PATH, '-storepass', KEYSTORE_PASS, |
| | apk_path, KEY_ALIAS], |
| | capture_output=True, |
| | text=True |
| | ) |
| | |
| | if result.returncode != 0: |
| | return jsonify({ |
| | 'error': 'Signing failed', |
| | 'details': result.stderr |
| | }), 500 |
| | |
| | |
| | result = subprocess.run( |
| | ['zipalign', '-v', '4', apk_path, signed_apk], |
| | capture_output=True, |
| | text=True |
| | ) |
| | |
| | if result.returncode != 0: |
| | return jsonify({ |
| | 'error': 'Zipalign failed', |
| | 'details': result.stderr |
| | }), 500 |
| | |
| | return jsonify({ |
| | 'success': True, |
| | 'signed_apk': signed_apk |
| | }) |
| | except Exception as e: |
| | return jsonify({'error': str(e)}), 500 |
| |
|
| | @app.route('/api/download', methods=['GET']) |
| | def download_apk(): |
| | """Download a processed APK file""" |
| | apk_path = request.args.get('apk_path') |
| | if not apk_path: |
| | abort(400, description="APK path parameter is required") |
| | |
| | if not os.path.exists(apk_path): |
| | abort(404, description="APK file not found") |
| | |
| | if not apk_path.endswith('.apk'): |
| | abort(400, description="Invalid file type") |
| | |
| | return send_file(apk_path, as_attachment=True) |
| |
|
| | @app.route('/api/extract_icon', methods=['POST']) |
| | def extract_icon(): |
| | """Extract icon from APK""" |
| | data = request.get_json() |
| | if not data or 'apk_path' not in data: |
| | return jsonify({'error': 'APK path required'}), 400 |
| | |
| | apk_path = data['apk_path'] |
| | if not os.path.exists(apk_path): |
| | return jsonify({'error': 'APK file not found'}), 404 |
| | |
| | try: |
| | with zipfile.ZipFile(apk_path, 'r') as zip_ref: |
| | icon_paths = [ |
| | 'res/mipmap-hdpi-v4/ic_launcher.png', |
| | 'res/mipmap-mdpi-v4/ic_launcher.png', |
| | 'res/mipmap-xhdpi-v4/ic_launcher.png', |
| | 'res/mipmap-xxhdpi-v4/ic_launcher.png', |
| | 'res/mipmap-xxxhdpi-v4/ic_launcher.png', |
| | 'res/drawable-hdpi/ic_launcher.png', |
| | 'res/drawable/ic_launcher.png' |
| | ] |
| | |
| | icon_file = None |
| | for path in icon_paths: |
| | if path in zip_ref.namelist(): |
| | icon_file = path |
| | break |
| | |
| | if not icon_file: |
| | return jsonify({'error': 'Icon not found in APK'}), 404 |
| | |
| | temp_dir = tempfile.mkdtemp() |
| | extracted_path = os.path.join(temp_dir, os.path.basename(icon_file)) |
| | with zip_ref.open(icon_file) as source, open(extracted_path, 'wb') as target: |
| | shutil.copyfileobj(source, target) |
| | |
| | return send_file(extracted_path, mimetype='image/png') |
| | except Exception as e: |
| | return jsonify({'error': str(e)}), 500 |
| | finally: |
| | if 'temp_dir' in locals(): |
| | shutil.rmtree(temp_dir, ignore_errors=True) |
| |
|
| | def create_debug_keystore(): |
| | """Create debug keystore if it doesn't exist""" |
| | if not os.path.exists(KEYSTORE_PATH): |
| | print("Creating debug keystore...") |
| | try: |
| | subprocess.run([ |
| | 'keytool', '-genkey', '-v', |
| | '-keystore', KEYSTORE_PATH, |
| | '-alias', KEY_ALIAS, |
| | '-keyalg', 'RSA', |
| | '-keysize', '2048', |
| | '-validity', '10000', |
| | '-storepass', KEYSTORE_PASS, |
| | '-keypass', KEYSTORE_PASS, |
| | '-dname', 'CN=Android Debug,O=Android,C=US' |
| | ], check=True) |
| | print("Debug keystore created successfully") |
| | except subprocess.CalledProcessError as e: |
| | print(f"Failed to create debug keystore: {str(e)}") |
| | raise |
| |
|
| | if __name__ == '__main__': |
| | |
| | try: |
| | download_apktool() |
| | create_debug_keystore() |
| | app.run(host="0.0.0.0", port=7860) |
| | except Exception as e: |
| | print(f"Failed to start application: {str(e)}") |