Major RCS/SMS exploitation rewrite (v2.0): - bugle_db direct extraction (plaintext messages, no decryption needed) - CVE-2024-0044 run-as privilege escalation (Android 12-13) - AOSP RCS provider queries (content://rcs/) - Archon app relay for Shizuku-elevated bugle_db access - 7-tab web UI: Extract, Database, Forge, Modify, Exploit, Backup, Monitor - SQL query interface for extracted databases - Full backup/restore/clone with SMS Backup & Restore XML support - Known CVE database (CVE-2023-24033, CVE-2024-49415, CVE-2025-48593) - IMS/RCS diagnostics, Phenotype verbose logging, Pixel tools New modules: Starlink hack, SMS forge, SDR drone detection Archon Android app: RCS messaging module with Shizuku integration Updated manuals to v2.3, 60 web blueprints confirmed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
305 lines
9.0 KiB
Python
305 lines
9.0 KiB
Python
"""SMS Backup Forge routes."""
|
|
import os
|
|
import tempfile
|
|
from flask import Blueprint, request, jsonify, render_template, send_file, current_app
|
|
from web.auth import login_required
|
|
|
|
sms_forge_bp = Blueprint('sms_forge', __name__, url_prefix='/sms-forge')
|
|
|
|
_forge = None
|
|
|
|
|
|
def _get_forge():
|
|
global _forge
|
|
if _forge is None:
|
|
from modules.sms_forge import get_sms_forge
|
|
_forge = get_sms_forge()
|
|
return _forge
|
|
|
|
|
|
@sms_forge_bp.route('/')
|
|
@login_required
|
|
def index():
|
|
return render_template('sms_forge.html')
|
|
|
|
|
|
@sms_forge_bp.route('/status')
|
|
@login_required
|
|
def status():
|
|
return jsonify(_get_forge().get_status())
|
|
|
|
|
|
@sms_forge_bp.route('/messages')
|
|
@login_required
|
|
def messages():
|
|
forge = _get_forge()
|
|
address = request.args.get('address') or None
|
|
date_from = request.args.get('date_from')
|
|
date_to = request.args.get('date_to')
|
|
keyword = request.args.get('keyword') or None
|
|
date_from_int = int(date_from) if date_from else None
|
|
date_to_int = int(date_to) if date_to else None
|
|
if address or date_from_int or date_to_int or keyword:
|
|
msgs = forge.find_messages(address, date_from_int, date_to_int, keyword)
|
|
else:
|
|
msgs = forge.get_messages()
|
|
return jsonify({'ok': True, 'messages': msgs, 'count': len(msgs)})
|
|
|
|
|
|
@sms_forge_bp.route('/sms', methods=['POST'])
|
|
@login_required
|
|
def add_sms():
|
|
data = request.get_json(silent=True) or {}
|
|
forge = _get_forge()
|
|
result = forge.add_sms(
|
|
address=data.get('address', ''),
|
|
body=data.get('body', ''),
|
|
msg_type=int(data.get('type', 1)),
|
|
timestamp=int(data['timestamp']) if data.get('timestamp') else None,
|
|
contact_name=data.get('contact_name', '(Unknown)'),
|
|
read=int(data.get('read', 1)),
|
|
locked=int(data.get('locked', 0)),
|
|
)
|
|
return jsonify(result)
|
|
|
|
|
|
@sms_forge_bp.route('/mms', methods=['POST'])
|
|
@login_required
|
|
def add_mms():
|
|
data = request.get_json(silent=True) or {}
|
|
forge = _get_forge()
|
|
attachments = data.get('attachments', [])
|
|
result = forge.add_mms(
|
|
address=data.get('address', ''),
|
|
body=data.get('body', ''),
|
|
attachments=attachments,
|
|
msg_box=int(data.get('msg_box', 1)),
|
|
timestamp=int(data['timestamp']) if data.get('timestamp') else None,
|
|
contact_name=data.get('contact_name', '(Unknown)'),
|
|
)
|
|
return jsonify(result)
|
|
|
|
|
|
@sms_forge_bp.route('/conversation', methods=['POST'])
|
|
@login_required
|
|
def add_conversation():
|
|
data = request.get_json(silent=True) or {}
|
|
forge = _get_forge()
|
|
result = forge.add_conversation(
|
|
address=data.get('address', ''),
|
|
contact_name=data.get('contact_name', '(Unknown)'),
|
|
messages=data.get('messages', []),
|
|
start_timestamp=int(data['start_timestamp']) if data.get('start_timestamp') else None,
|
|
)
|
|
return jsonify(result)
|
|
|
|
|
|
@sms_forge_bp.route('/generate', methods=['POST'])
|
|
@login_required
|
|
def generate():
|
|
data = request.get_json(silent=True) or {}
|
|
forge = _get_forge()
|
|
result = forge.generate_conversation(
|
|
address=data.get('address', ''),
|
|
contact_name=data.get('contact_name', '(Unknown)'),
|
|
template=data.get('template', ''),
|
|
variables=data.get('variables', {}),
|
|
start_timestamp=int(data['start_timestamp']) if data.get('start_timestamp') else None,
|
|
)
|
|
return jsonify(result)
|
|
|
|
|
|
@sms_forge_bp.route('/message/<int:idx>', methods=['PUT'])
|
|
@login_required
|
|
def modify_message(idx):
|
|
data = request.get_json(silent=True) or {}
|
|
forge = _get_forge()
|
|
result = forge.modify_message(
|
|
index=idx,
|
|
new_body=data.get('body'),
|
|
new_timestamp=int(data['timestamp']) if data.get('timestamp') else None,
|
|
new_contact=data.get('contact_name'),
|
|
)
|
|
return jsonify(result)
|
|
|
|
|
|
@sms_forge_bp.route('/message/<int:idx>', methods=['DELETE'])
|
|
@login_required
|
|
def delete_message(idx):
|
|
forge = _get_forge()
|
|
result = forge.delete_messages([idx])
|
|
return jsonify(result)
|
|
|
|
|
|
@sms_forge_bp.route('/replace-contact', methods=['POST'])
|
|
@login_required
|
|
def replace_contact():
|
|
data = request.get_json(silent=True) or {}
|
|
forge = _get_forge()
|
|
result = forge.replace_contact(
|
|
old_address=data.get('old_address', ''),
|
|
new_address=data.get('new_address', ''),
|
|
new_name=data.get('new_name'),
|
|
)
|
|
return jsonify(result)
|
|
|
|
|
|
@sms_forge_bp.route('/shift-timestamps', methods=['POST'])
|
|
@login_required
|
|
def shift_timestamps():
|
|
data = request.get_json(silent=True) or {}
|
|
forge = _get_forge()
|
|
address = data.get('address') or None
|
|
result = forge.shift_timestamps(
|
|
address=address,
|
|
offset_minutes=int(data.get('offset_minutes', 0)),
|
|
)
|
|
return jsonify(result)
|
|
|
|
|
|
@sms_forge_bp.route('/import', methods=['POST'])
|
|
@login_required
|
|
def import_file():
|
|
forge = _get_forge()
|
|
if 'file' not in request.files:
|
|
return jsonify({'ok': False, 'error': 'No file uploaded'})
|
|
f = request.files['file']
|
|
if not f.filename:
|
|
return jsonify({'ok': False, 'error': 'Empty filename'})
|
|
upload_dir = current_app.config.get('UPLOAD_FOLDER', tempfile.gettempdir())
|
|
save_path = os.path.join(upload_dir, f.filename)
|
|
f.save(save_path)
|
|
ext = os.path.splitext(f.filename)[1].lower()
|
|
if ext == '.csv':
|
|
result = forge.import_csv(save_path)
|
|
else:
|
|
result = forge.import_xml(save_path)
|
|
try:
|
|
os.unlink(save_path)
|
|
except OSError:
|
|
pass
|
|
return jsonify(result)
|
|
|
|
|
|
@sms_forge_bp.route('/export/<fmt>')
|
|
@login_required
|
|
def export_file(fmt):
|
|
forge = _get_forge()
|
|
upload_dir = current_app.config.get('UPLOAD_FOLDER', tempfile.gettempdir())
|
|
if fmt == 'csv':
|
|
out_path = os.path.join(upload_dir, 'sms_forge_export.csv')
|
|
result = forge.export_csv(out_path)
|
|
mime = 'text/csv'
|
|
dl_name = 'sms_backup.csv'
|
|
else:
|
|
out_path = os.path.join(upload_dir, 'sms_forge_export.xml')
|
|
result = forge.export_xml(out_path)
|
|
mime = 'application/xml'
|
|
dl_name = 'sms_backup.xml'
|
|
if not result.get('ok'):
|
|
return jsonify(result)
|
|
return send_file(out_path, mimetype=mime, as_attachment=True, download_name=dl_name)
|
|
|
|
|
|
@sms_forge_bp.route('/merge', methods=['POST'])
|
|
@login_required
|
|
def merge():
|
|
forge = _get_forge()
|
|
files = request.files.getlist('files')
|
|
if not files:
|
|
return jsonify({'ok': False, 'error': 'No files uploaded'})
|
|
upload_dir = current_app.config.get('UPLOAD_FOLDER', tempfile.gettempdir())
|
|
saved = []
|
|
for f in files:
|
|
if f.filename:
|
|
path = os.path.join(upload_dir, f'merge_{f.filename}')
|
|
f.save(path)
|
|
saved.append(path)
|
|
result = forge.merge_backups(saved)
|
|
for p in saved:
|
|
try:
|
|
os.unlink(p)
|
|
except OSError:
|
|
pass
|
|
return jsonify(result)
|
|
|
|
|
|
@sms_forge_bp.route('/templates')
|
|
@login_required
|
|
def templates():
|
|
return jsonify(_get_forge().get_templates())
|
|
|
|
|
|
@sms_forge_bp.route('/stats')
|
|
@login_required
|
|
def stats():
|
|
return jsonify(_get_forge().get_backup_stats())
|
|
|
|
|
|
@sms_forge_bp.route('/validate', methods=['POST'])
|
|
@login_required
|
|
def validate():
|
|
forge = _get_forge()
|
|
if 'file' not in request.files:
|
|
return jsonify({'ok': False, 'error': 'No file uploaded'})
|
|
f = request.files['file']
|
|
if not f.filename:
|
|
return jsonify({'ok': False, 'error': 'Empty filename'})
|
|
upload_dir = current_app.config.get('UPLOAD_FOLDER', tempfile.gettempdir())
|
|
save_path = os.path.join(upload_dir, f'validate_{f.filename}')
|
|
f.save(save_path)
|
|
result = forge.validate_backup(save_path)
|
|
try:
|
|
os.unlink(save_path)
|
|
except OSError:
|
|
pass
|
|
return jsonify(result)
|
|
|
|
|
|
@sms_forge_bp.route('/bulk-import', methods=['POST'])
|
|
@login_required
|
|
def bulk_import():
|
|
forge = _get_forge()
|
|
if 'file' not in request.files:
|
|
return jsonify({'ok': False, 'error': 'No file uploaded'})
|
|
f = request.files['file']
|
|
if not f.filename:
|
|
return jsonify({'ok': False, 'error': 'Empty filename'})
|
|
upload_dir = current_app.config.get('UPLOAD_FOLDER', tempfile.gettempdir())
|
|
save_path = os.path.join(upload_dir, f.filename)
|
|
f.save(save_path)
|
|
result = forge.bulk_add(save_path)
|
|
try:
|
|
os.unlink(save_path)
|
|
except OSError:
|
|
pass
|
|
return jsonify(result)
|
|
|
|
|
|
@sms_forge_bp.route('/templates/save', methods=['POST'])
|
|
@login_required
|
|
def save_template():
|
|
data = request.get_json(silent=True) or {}
|
|
forge = _get_forge()
|
|
key = data.get('key', '').strip()
|
|
template_data = data.get('template', {})
|
|
if not key:
|
|
return jsonify({'ok': False, 'error': 'Template key is required'})
|
|
result = forge.save_custom_template(key, template_data)
|
|
return jsonify(result)
|
|
|
|
|
|
@sms_forge_bp.route('/templates/<key>', methods=['DELETE'])
|
|
@login_required
|
|
def delete_template(key):
|
|
forge = _get_forge()
|
|
result = forge.delete_custom_template(key)
|
|
return jsonify(result)
|
|
|
|
|
|
@sms_forge_bp.route('/clear', methods=['POST'])
|
|
@login_required
|
|
def clear():
|
|
_get_forge().clear_messages()
|
|
return jsonify({'ok': True})
|