- bugle_db uses SQLCipher/Android encrypted SQLite, not plaintext - Root extraction now pulls shared_prefs/ and files/ for key material - Archon relay prefers decrypted JSON dump from app context over raw DB copy - Updated module docstrings, web UI descriptions, and user manual Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
942 lines
45 KiB
HTML
942 lines
45 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}AUTARCH — RCS/SMS Exploit{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page-header">
|
|
<h1>RCS/SMS Exploitation</h1>
|
|
<p style="margin:0;font-size:0.85rem;color:var(--text-secondary)">
|
|
Extract, forge, modify, backup, and exploit SMS/RCS messages on connected Android devices.
|
|
Uses content providers (no root), Archon relay, CVE-2024-0044, and bugle_db direct access.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Device Status Banner -->
|
|
<div id="rcs-device-banner" class="section" style="padding:10px 16px;margin-bottom:16px;display:flex;align-items:center;gap:12px;flex-wrap:wrap">
|
|
<span id="rcs-dev-indicator" style="width:10px;height:10px;border-radius:50%;background:#555;display:inline-block"></span>
|
|
<span id="rcs-dev-label" style="font-size:0.85rem;color:var(--text-secondary)">Checking device...</span>
|
|
<span id="rcs-shizuku-badge" style="font-size:0.75rem;padding:2px 8px;border-radius:4px;background:#333;color:#888">Shizuku: --</span>
|
|
<span id="rcs-archon-badge" style="font-size:0.75rem;padding:2px 8px;border-radius:4px;background:#333;color:#888">Archon: --</span>
|
|
<span id="rcs-cve-badge" style="font-size:0.75rem;padding:2px 8px;border-radius:4px;background:#333;color:#888">CVE: --</span>
|
|
<span id="rcs-sms-app" style="font-size:0.75rem;color:var(--text-muted)">SMS App: --</span>
|
|
<button class="btn btn-small" onclick="rcsRefreshStatus()" style="margin-left:auto">Refresh</button>
|
|
</div>
|
|
|
|
<!-- Tab Bar -->
|
|
<div class="tab-bar">
|
|
<button class="tab active" data-tab-group="rcs" data-tab="extract" onclick="showTab('rcs','extract')">Extract</button>
|
|
<button class="tab" data-tab-group="rcs" data-tab="database" onclick="showTab('rcs','database')">Database</button>
|
|
<button class="tab" data-tab-group="rcs" data-tab="forge" onclick="showTab('rcs','forge')">Forge</button>
|
|
<button class="tab" data-tab-group="rcs" data-tab="modify" onclick="showTab('rcs','modify')">Modify</button>
|
|
<button class="tab" data-tab-group="rcs" data-tab="exploit" onclick="showTab('rcs','exploit')">Exploit</button>
|
|
<button class="tab" data-tab-group="rcs" data-tab="backup" onclick="showTab('rcs','backup')">Backup</button>
|
|
<button class="tab" data-tab-group="rcs" data-tab="monitor" onclick="showTab('rcs','monitor')">Monitor</button>
|
|
</div>
|
|
|
|
<!-- ========================== EXTRACT TAB ========================== -->
|
|
<div class="tab-content active" data-tab-group="rcs" data-tab="extract">
|
|
|
|
<div class="section">
|
|
<h3>SMS Messages</h3>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px">
|
|
<input id="rcs-search-addr" type="text" placeholder="Filter by address" style="flex:1;min-width:150px">
|
|
<input id="rcs-search-kw" type="text" placeholder="Search keyword" style="flex:1;min-width:150px">
|
|
<input id="rcs-search-thread" type="text" placeholder="Thread ID" style="width:90px">
|
|
<select id="rcs-msg-filter" style="width:120px">
|
|
<option value="all">All</option>
|
|
<option value="inbox">Inbox</option>
|
|
<option value="sent">Sent</option>
|
|
<option value="drafts">Drafts</option>
|
|
<option value="undelivered">Undelivered</option>
|
|
</select>
|
|
<button class="btn" onclick="rcsExtractMessages()">Extract</button>
|
|
<button class="btn btn-secondary" onclick="rcsExportMessages('json')">Export JSON</button>
|
|
<button class="btn btn-secondary" onclick="rcsExportMessages('csv')">Export CSV</button>
|
|
<button class="btn btn-secondary" onclick="rcsExportMessages('xml')">Export XML</button>
|
|
</div>
|
|
<div id="rcs-msg-count" style="font-size:0.8rem;color:var(--text-muted);margin-bottom:8px"></div>
|
|
<div id="rcs-messages-list" style="max-height:500px;overflow-y:auto"></div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>MMS Messages</h3>
|
|
<button class="btn" onclick="rcsExtractMMS()">Extract MMS</button>
|
|
<div id="rcs-mms-list" style="max-height:300px;overflow-y:auto;margin-top:8px"></div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>RCS Provider (AOSP content://rcs/)</h3>
|
|
<div style="display:flex;gap:8px;margin-bottom:8px">
|
|
<button class="btn" onclick="rcsExtractRCSProvider()">Query RCS Provider</button>
|
|
<button class="btn btn-secondary" onclick="rcsExtractRCSMessages()">RCS Messages</button>
|
|
<button class="btn btn-secondary" onclick="rcsExtractRCSParticipants()">RCS Participants</button>
|
|
</div>
|
|
<div id="rcs-provider-result" style="max-height:400px;overflow-y:auto"></div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Content Provider Enumeration</h3>
|
|
<p style="font-size:0.8rem;color:var(--text-muted)">Scan all known messaging content providers to see which are accessible at UID 2000.</p>
|
|
<button class="btn" onclick="rcsEnumerateProviders()">Enumerate All Providers</button>
|
|
<div id="rcs-providers-result" style="margin-top:8px"></div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- ========================== DATABASE TAB ========================== -->
|
|
<div class="tab-content" data-tab-group="rcs" data-tab="database">
|
|
|
|
<div class="section">
|
|
<h3>bugle_db Extraction</h3>
|
|
<p style="font-size:0.85rem;color:var(--text-secondary);margin-bottom:12px">
|
|
Extract the Google Messages RCS database. The database is <strong>encrypted at rest</strong> —
|
|
raw file extraction also requires the encryption key. Best method: Archon relay (queries from
|
|
decrypted app context). Fallback: CVE-2024-0044 (app-UID access) → root (DB + keys) → ADB backup.
|
|
</p>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px">
|
|
<button class="btn btn-danger" onclick="rcsExtractBugle()">Extract bugle_db</button>
|
|
<button class="btn" onclick="rcsExtractRCSFromBugle()">Extract RCS Only</button>
|
|
<button class="btn" onclick="rcsExtractConvosFromBugle()">Extract Conversations</button>
|
|
<button class="btn" onclick="rcsExtractEdits()">Extract Message Edits</button>
|
|
<button class="btn btn-secondary" onclick="rcsExtractAllBugle()">Full Export (all tables)</button>
|
|
</div>
|
|
<div id="rcs-bugle-result" style="max-height:400px;overflow-y:auto"></div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>SQL Query (extracted bugle_db)</h3>
|
|
<p style="font-size:0.8rem;color:var(--text-muted)">Run arbitrary SQL against a previously extracted bugle_db.</p>
|
|
<textarea id="rcs-sql-query" rows="4" style="width:100%;font-family:monospace;font-size:0.85rem"
|
|
placeholder="SELECT m.*, p.text FROM messages m JOIN parts p ON m._id=p.message_id WHERE m.message_protocol>=2 ORDER BY m.sent_timestamp DESC LIMIT 50"></textarea>
|
|
<div style="display:flex;gap:8px;margin-top:8px">
|
|
<button class="btn" onclick="rcsQueryBugle()">Run Query</button>
|
|
<select id="rcs-sql-preset" onchange="rcsSQLPreset(this.value)" style="font-size:0.85rem">
|
|
<option value="">-- Preset Queries --</option>
|
|
<option value="rcs">RCS Messages Only</option>
|
|
<option value="all_msgs">All Messages with Contacts</option>
|
|
<option value="edits">Message Edit History</option>
|
|
<option value="conversations">Conversations with Participants</option>
|
|
<option value="attachments">Attachments</option>
|
|
<option value="stats">Message Statistics</option>
|
|
</select>
|
|
</div>
|
|
<div id="rcs-sql-result" style="max-height:400px;overflow-y:auto;margin-top:8px"></div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Extracted Database Snapshots</h3>
|
|
<button class="btn btn-secondary" onclick="rcsListExtracted()">List Snapshots</button>
|
|
<div id="rcs-extracted-list" style="margin-top:8px"></div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- ========================== FORGE TAB ========================== -->
|
|
<div class="tab-content" data-tab-group="rcs" data-tab="forge">
|
|
|
|
<div class="section">
|
|
<h3>Forge SMS Message</h3>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px">
|
|
<input id="forge-address" type="text" placeholder="Phone number (+1234567890)">
|
|
<input id="forge-contact" type="text" placeholder="Contact name (optional)">
|
|
</div>
|
|
<textarea id="forge-body" rows="3" style="width:100%" placeholder="Message body"></textarea>
|
|
<div style="display:flex;gap:8px;margin-top:8px;flex-wrap:wrap;align-items:center">
|
|
<select id="forge-type">
|
|
<option value="1">Incoming (Inbox)</option>
|
|
<option value="2">Outgoing (Sent)</option>
|
|
<option value="3">Draft</option>
|
|
</select>
|
|
<input id="forge-timestamp" type="datetime-local" style="width:200px">
|
|
<label style="font-size:0.85rem"><input type="checkbox" id="forge-read" checked> Mark as read</label>
|
|
<button class="btn" onclick="rcsForgeSMS()">Forge SMS</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Forge RCS Message (via Archon)</h3>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px">
|
|
<input id="forge-rcs-address" type="text" placeholder="Phone number">
|
|
<select id="forge-rcs-direction">
|
|
<option value="incoming">Incoming</option>
|
|
<option value="outgoing">Outgoing</option>
|
|
</select>
|
|
</div>
|
|
<textarea id="forge-rcs-body" rows="3" style="width:100%" placeholder="RCS message body"></textarea>
|
|
<button class="btn" onclick="rcsForgeRCS()" style="margin-top:8px">Forge RCS</button>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Forge Conversation</h3>
|
|
<input id="forge-conv-address" type="text" placeholder="Phone number" style="width:100%;margin-bottom:8px">
|
|
<textarea id="forge-conv-msgs" rows="6" style="width:100%;font-family:monospace;font-size:0.85rem"
|
|
placeholder='[{"body":"Hey!","type":1},{"body":"Hi there","type":2},{"body":"How are you?","type":1}]'></textarea>
|
|
<button class="btn" onclick="rcsForgeConversation()" style="margin-top:8px">Forge Conversation</button>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Import SMS Backup XML</h3>
|
|
<p style="font-size:0.8rem;color:var(--text-muted)">Import from SMS Backup & Restore XML format.</p>
|
|
<textarea id="forge-import-xml" rows="4" style="width:100%;font-family:monospace;font-size:0.85rem"
|
|
placeholder='Paste XML content here or load from file'></textarea>
|
|
<div style="display:flex;gap:8px;margin-top:8px">
|
|
<button class="btn" onclick="rcsImportXML()">Import XML</button>
|
|
<input type="file" id="forge-xml-file" accept=".xml" onchange="rcsLoadXMLFile(this)" style="font-size:0.85rem">
|
|
</div>
|
|
<div id="forge-import-result" style="margin-top:8px"></div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Forge Log</h3>
|
|
<div style="display:flex;gap:8px;margin-bottom:8px">
|
|
<button class="btn btn-secondary" onclick="rcsRefreshForgeLog()">Refresh</button>
|
|
<button class="btn btn-secondary" onclick="rcsClearForgeLog()">Clear Log</button>
|
|
</div>
|
|
<div id="forge-log-list" style="max-height:300px;overflow-y:auto"></div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- ========================== MODIFY TAB ========================== -->
|
|
<div class="tab-content" data-tab-group="rcs" data-tab="modify">
|
|
|
|
<div class="section">
|
|
<h3>Modify Message</h3>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:8px">
|
|
<input id="mod-msg-id" type="number" placeholder="Message ID">
|
|
<select id="mod-type">
|
|
<option value="">-- Change type --</option>
|
|
<option value="1">Incoming</option>
|
|
<option value="2">Outgoing</option>
|
|
<option value="3">Draft</option>
|
|
</select>
|
|
<input id="mod-timestamp" type="datetime-local">
|
|
</div>
|
|
<textarea id="mod-body" rows="2" style="width:100%" placeholder="New body (leave empty to keep)"></textarea>
|
|
<button class="btn" onclick="rcsModifyMessage()" style="margin-top:8px">Modify</button>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Change Sender</h3>
|
|
<div style="display:flex;gap:8px;align-items:center">
|
|
<input id="mod-sender-id" type="number" placeholder="Message ID" style="width:120px">
|
|
<input id="mod-new-address" type="text" placeholder="New sender address" style="flex:1">
|
|
<button class="btn" onclick="rcsChangeSender()">Change</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Shift Timestamps</h3>
|
|
<div style="display:flex;gap:8px;align-items:center">
|
|
<input id="mod-shift-addr" type="text" placeholder="Address" style="flex:1">
|
|
<input id="mod-shift-mins" type="number" placeholder="Offset (minutes)" style="width:140px">
|
|
<button class="btn" onclick="rcsShiftTimestamps()">Shift</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Bulk Actions</h3>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
|
<input id="mod-thread-id" type="number" placeholder="Thread ID" style="width:120px">
|
|
<button class="btn" onclick="rcsMarkAllRead()">Mark All Read</button>
|
|
<button class="btn btn-danger" onclick="rcsWipeThread()">Wipe Thread</button>
|
|
<button class="btn btn-danger" onclick="rcsDeleteConversation()">Delete Conversation</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Delete Single Message</h3>
|
|
<div style="display:flex;gap:8px;align-items:center">
|
|
<input id="mod-del-id" type="number" placeholder="Message ID" style="width:150px">
|
|
<button class="btn btn-danger" onclick="rcsDeleteMessage()">Delete</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- ========================== EXPLOIT TAB ========================== -->
|
|
<div class="tab-content" data-tab-group="rcs" data-tab="exploit">
|
|
|
|
<div class="section">
|
|
<h3>CVE-2024-0044 — run-as Privilege Escalation</h3>
|
|
<p style="font-size:0.85rem;color:var(--text-secondary);margin-bottom:8px">
|
|
Newline injection in PackageInstallerService allows run-as access to any app's private data.
|
|
Works on Android 12-13 with security patch before October 2024.
|
|
</p>
|
|
<div id="rcs-cve-status" style="margin-bottom:8px;padding:8px;border-radius:4px;background:var(--bg-secondary)"></div>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
|
<button class="btn" onclick="rcsCVECheck()">Check Vulnerability</button>
|
|
<button class="btn btn-danger" onclick="rcsCVEExploit()">Execute Exploit</button>
|
|
<button class="btn btn-secondary" onclick="rcsCVECleanup()">Cleanup Traces</button>
|
|
</div>
|
|
<div id="rcs-cve-result" style="margin-top:8px"></div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>RCS Spoofing</h3>
|
|
<div style="display:grid;grid-template-columns:1fr auto;gap:8px;margin-bottom:8px">
|
|
<input id="exploit-spoof-addr" type="text" placeholder="Target phone number">
|
|
<button class="btn" onclick="rcsSpoofTyping()">Spoof Typing</button>
|
|
</div>
|
|
<div style="display:grid;grid-template-columns:1fr auto;gap:8px">
|
|
<input id="exploit-spoof-msgid" type="text" placeholder="Message ID for read receipt">
|
|
<button class="btn" onclick="rcsSpoofReadReceipt()">Spoof Read Receipt</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>RCS Identity & Signal Protocol</h3>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:8px">
|
|
<button class="btn" onclick="rcsCloneIdentity()">Clone RCS Identity</button>
|
|
<button class="btn" onclick="rcsExtractSignalState()">Extract Signal Protocol State</button>
|
|
<button class="btn btn-secondary" onclick="rcsInterceptArchival()">Register Archival Listener</button>
|
|
</div>
|
|
<div id="rcs-identity-result" style="max-height:300px;overflow-y:auto"></div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Known RCS CVEs</h3>
|
|
<button class="btn btn-secondary" onclick="rcsShowCVEs()">Show CVE Database</button>
|
|
<div id="rcs-cves-list" style="margin-top:8px"></div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>IMS/RCS Diagnostics</h3>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:8px">
|
|
<button class="btn" onclick="rcsGetIMSStatus()">IMS Status</button>
|
|
<button class="btn" onclick="rcsGetCarrierConfig()">Carrier RCS Config</button>
|
|
<button class="btn" onclick="rcsGetRCSState()">RCS State</button>
|
|
<button class="btn btn-secondary" onclick="rcsEnableLogging()">Enable Verbose Logging</button>
|
|
<button class="btn btn-secondary" onclick="rcsCaptureLogs()">Capture RCS Logs</button>
|
|
<button class="btn btn-secondary" onclick="rcsPixelDiag()">Pixel Diagnostics</button>
|
|
</div>
|
|
<div id="rcs-diag-result" style="max-height:400px;overflow-y:auto;font-family:monospace;font-size:0.8rem;white-space:pre-wrap"></div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- ========================== BACKUP TAB ========================== -->
|
|
<div class="tab-content" data-tab-group="rcs" data-tab="backup">
|
|
|
|
<div class="section">
|
|
<h3>Full Backup</h3>
|
|
<p style="font-size:0.85rem;color:var(--text-secondary)">
|
|
Back up all SMS/MMS/RCS messages from the device. Content providers capture SMS/MMS;
|
|
Archon relay or bugle_db extraction captures RCS.
|
|
</p>
|
|
<div style="display:flex;gap:8px;margin-top:8px">
|
|
<button class="btn" onclick="rcsFullBackup('json')">Backup (JSON)</button>
|
|
<button class="btn" onclick="rcsFullBackup('xml')">Backup (XML — SMS Backup & Restore format)</button>
|
|
<button class="btn btn-secondary" onclick="rcsArchonBackup()">Archon Full Backup (incl. RCS)</button>
|
|
</div>
|
|
<div id="rcs-backup-result" style="margin-top:8px"></div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Restore / Clone</h3>
|
|
<div style="display:flex;gap:8px;align-items:center;margin-bottom:8px">
|
|
<input id="backup-restore-path" type="text" placeholder="Backup filename or full path" style="flex:1">
|
|
<button class="btn" onclick="rcsRestore()">Restore</button>
|
|
</div>
|
|
<button class="btn btn-secondary" onclick="rcsCloneDevice()">Clone to Another Device</button>
|
|
<div id="rcs-restore-result" style="margin-top:8px"></div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Set Default SMS App</h3>
|
|
<div style="display:flex;gap:8px;align-items:center">
|
|
<select id="backup-sms-app" style="flex:1">
|
|
<option value="com.darkhal.archon">Archon (enables full RCS access)</option>
|
|
<option value="com.google.android.apps.messaging">Google Messages</option>
|
|
<option value="com.android.messaging">AOSP Messages</option>
|
|
<option value="com.samsung.android.messaging">Samsung Messages</option>
|
|
</select>
|
|
<button class="btn" onclick="rcsSetDefaultApp()">Set Default</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Saved Backups</h3>
|
|
<button class="btn btn-secondary" onclick="rcsListBackups()">Refresh</button>
|
|
<div id="rcs-backups-list" style="margin-top:8px"></div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Exported Files</h3>
|
|
<button class="btn btn-secondary" onclick="rcsListExports()">Refresh</button>
|
|
<div id="rcs-exports-list" style="margin-top:8px"></div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- ========================== MONITOR TAB ========================== -->
|
|
<div class="tab-content" data-tab-group="rcs" data-tab="monitor">
|
|
|
|
<div class="section">
|
|
<h3>SMS/RCS Monitor</h3>
|
|
<p style="font-size:0.85rem;color:var(--text-secondary)">
|
|
Monitor incoming SMS/RCS messages in real-time via logcat interception.
|
|
</p>
|
|
<div style="display:flex;gap:8px;margin-bottom:8px">
|
|
<button class="btn" id="rcs-monitor-btn" onclick="rcsToggleMonitor()">Start Monitor</button>
|
|
<button class="btn btn-secondary" onclick="rcsRefreshMonitor()">Refresh</button>
|
|
<button class="btn btn-secondary" onclick="rcsClearMonitor()">Clear</button>
|
|
<span id="rcs-monitor-count" style="font-size:0.85rem;color:var(--text-muted);align-self:center"></span>
|
|
</div>
|
|
<div id="rcs-monitor-feed" style="max-height:500px;overflow-y:auto;font-family:monospace;font-size:0.8rem"></div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- ========================== INLINE JS ========================== -->
|
|
<script>
|
|
const RCS_BASE = '/rcs-tools';
|
|
let rcsMonitorRunning = false;
|
|
let rcsMonitorInterval = null;
|
|
|
|
// ── Helpers ─────────────────────────────────────────────────────────
|
|
function rcsPost(path, data) {
|
|
return postJSON(RCS_BASE + path, data || {});
|
|
}
|
|
function rcsGet(path) {
|
|
return fetchJSON(RCS_BASE + path);
|
|
}
|
|
|
|
function renderTable(rows, columns) {
|
|
if (!rows || !rows.length) return '<p style="color:var(--text-muted)">No data</p>';
|
|
let cols = columns || Object.keys(rows[0]);
|
|
let h = '<table class="data-table" style="width:100%;font-size:0.8rem"><thead><tr>';
|
|
cols.forEach(c => h += '<th>' + esc(c) + '</th>');
|
|
h += '</tr></thead><tbody>';
|
|
rows.forEach(r => {
|
|
h += '<tr>';
|
|
cols.forEach(c => {
|
|
let v = r[c];
|
|
if (v === null || v === undefined) v = '';
|
|
let s = String(v);
|
|
if (s.length > 120) s = s.substring(0, 120) + '...';
|
|
h += '<td>' + esc(s) + '</td>';
|
|
});
|
|
h += '</tr>';
|
|
});
|
|
h += '</tbody></table>';
|
|
return h;
|
|
}
|
|
|
|
function renderJSON(obj) {
|
|
return '<pre style="max-height:300px;overflow:auto;font-size:0.8rem;padding:8px;background:var(--bg-secondary);border-radius:4px">'
|
|
+ esc(JSON.stringify(obj, null, 2)) + '</pre>';
|
|
}
|
|
|
|
function renderMsgRow(m) {
|
|
let dir = parseInt(m.type) === 2 ? 'outgoing' : 'incoming';
|
|
let color = dir === 'outgoing' ? '#2196f3' : '#4caf50';
|
|
let arrow = dir === 'outgoing' ? '→' : '←';
|
|
return '<div style="padding:6px 10px;border-bottom:1px solid var(--border);display:flex;gap:8px;align-items:flex-start">'
|
|
+ '<span style="color:' + color + ';font-weight:bold;min-width:20px">' + arrow + '</span>'
|
|
+ '<div style="flex:1;min-width:0">'
|
|
+ '<div style="font-size:0.8rem;color:var(--text-muted)">'
|
|
+ '<strong>' + esc(m.address || '') + '</strong>'
|
|
+ ' · ID:' + esc(m._id || '')
|
|
+ ' · Thread:' + esc(m.thread_id || '')
|
|
+ (m.date_formatted ? ' · ' + esc(m.date_formatted) : '')
|
|
+ '</div>'
|
|
+ '<div style="font-size:0.85rem;margin-top:2px;word-break:break-word">' + esc(m.body || '') + '</div>'
|
|
+ '</div></div>';
|
|
}
|
|
|
|
// ── Status ──────────────────────────────────────────────────────────
|
|
async function rcsRefreshStatus() {
|
|
try {
|
|
const r = await rcsGet('/status');
|
|
const ind = document.getElementById('rcs-dev-indicator');
|
|
const lbl = document.getElementById('rcs-dev-label');
|
|
const shBadge = document.getElementById('rcs-shizuku-badge');
|
|
const arBadge = document.getElementById('rcs-archon-badge');
|
|
const cvBadge = document.getElementById('rcs-cve-badge');
|
|
const smsApp = document.getElementById('rcs-sms-app');
|
|
if (r.connected) {
|
|
const d = r.device || {};
|
|
ind.style.background = '#4caf50';
|
|
lbl.textContent = (d.model || 'Device') + ' (' + (d.serial || '?') + ') — Android ' + (d.android_version || '?');
|
|
smsApp.textContent = 'SMS App: ' + (d.default_sms_app || '?');
|
|
} else {
|
|
ind.style.background = '#f44336';
|
|
lbl.textContent = r.error || 'Not connected';
|
|
}
|
|
// Shizuku
|
|
const sh = r.shizuku || {};
|
|
if (sh.running) { shBadge.textContent = 'Shizuku: Running'; shBadge.style.background = '#1b5e20'; shBadge.style.color = '#4caf50'; }
|
|
else if (sh.installed) { shBadge.textContent = 'Shizuku: Installed'; shBadge.style.background = '#33691e'; shBadge.style.color = '#8bc34a'; }
|
|
else { shBadge.textContent = 'Shizuku: N/A'; shBadge.style.background = '#333'; shBadge.style.color = '#888'; }
|
|
// Archon
|
|
const ar = r.archon || {};
|
|
if (ar.installed) { arBadge.textContent = 'Archon: ' + (ar.version || 'OK'); arBadge.style.background = '#0d47a1'; arBadge.style.color = '#42a5f5'; }
|
|
else { arBadge.textContent = 'Archon: N/A'; arBadge.style.background = '#333'; arBadge.style.color = '#888'; }
|
|
// CVE
|
|
const cv = r.cve_2024_0044 || {};
|
|
if (cv.vulnerable) { cvBadge.textContent = 'CVE-0044: VULN'; cvBadge.style.background = '#b71c1c'; cvBadge.style.color = '#ef5350'; }
|
|
else { cvBadge.textContent = 'CVE-0044: Patched'; cvBadge.style.background = '#1b5e20'; cvBadge.style.color = '#4caf50'; }
|
|
} catch(e) {
|
|
document.getElementById('rcs-dev-label').textContent = 'Error: ' + e.message;
|
|
}
|
|
}
|
|
|
|
// ── Extract ─────────────────────────────────────────────────────────
|
|
async function rcsExtractMessages() {
|
|
const addr = document.getElementById('rcs-search-addr').value;
|
|
const kw = document.getElementById('rcs-search-kw').value;
|
|
const tid = document.getElementById('rcs-search-thread').value;
|
|
const filter = document.getElementById('rcs-msg-filter').value;
|
|
let url = '/messages?limit=200';
|
|
if (tid) url += '&thread_id=' + tid;
|
|
else if (addr) url += '&address=' + encodeURIComponent(addr);
|
|
else if (kw) url += '&keyword=' + encodeURIComponent(kw);
|
|
|
|
if (filter === 'inbox') url = '/sms-inbox';
|
|
else if (filter === 'sent') url = '/sms-sent';
|
|
else if (filter === 'drafts') url = '/drafts';
|
|
else if (filter === 'undelivered') url = '/undelivered';
|
|
|
|
const r = await rcsGet(url);
|
|
const list = document.getElementById('rcs-messages-list');
|
|
const count = document.getElementById('rcs-msg-count');
|
|
const msgs = r.messages || [];
|
|
count.textContent = msgs.length + ' messages';
|
|
list.innerHTML = msgs.map(renderMsgRow).join('');
|
|
}
|
|
|
|
async function rcsExtractMMS() {
|
|
const r = await rcsGet('/mms');
|
|
const list = document.getElementById('rcs-mms-list');
|
|
const msgs = r.messages || [];
|
|
list.innerHTML = msgs.length ? renderTable(msgs, ['_id','thread_id','date_formatted','msg_box','body','sub']) : '<p style="color:var(--text-muted)">No MMS messages</p>';
|
|
}
|
|
|
|
async function rcsExtractRCSProvider() {
|
|
const r = await rcsGet('/rcs-provider');
|
|
document.getElementById('rcs-provider-result').innerHTML = renderJSON(r);
|
|
}
|
|
|
|
async function rcsExtractRCSMessages() {
|
|
const r = await rcsGet('/rcs-messages');
|
|
document.getElementById('rcs-provider-result').innerHTML = renderJSON(r);
|
|
}
|
|
|
|
async function rcsExtractRCSParticipants() {
|
|
const r = await rcsGet('/rcs-participants');
|
|
document.getElementById('rcs-provider-result').innerHTML = renderJSON(r);
|
|
}
|
|
|
|
async function rcsEnumerateProviders() {
|
|
const r = await rcsPost('/enumerate-providers');
|
|
let h = '';
|
|
if (r.accessible && r.accessible.length) {
|
|
h += '<h4 style="color:#4caf50">Accessible (' + r.total_accessible + ')</h4>';
|
|
h += renderTable(r.accessible, ['uri','status','rows','name']);
|
|
}
|
|
if (r.blocked && r.blocked.length) {
|
|
h += '<h4 style="color:#f44336">Blocked (' + r.total_blocked + ')</h4>';
|
|
h += renderTable(r.blocked, ['uri','error']);
|
|
}
|
|
document.getElementById('rcs-providers-result').innerHTML = h;
|
|
}
|
|
|
|
async function rcsExportMessages(fmt) {
|
|
const addr = document.getElementById('rcs-search-addr').value;
|
|
const r = await rcsPost('/export', {address: addr || null, format: fmt});
|
|
if (r.ok) alert('Exported ' + r.count + ' messages to ' + r.path);
|
|
else alert('Export failed: ' + (r.error || 'unknown'));
|
|
}
|
|
|
|
// ── Database ────────────────────────────────────────────────────────
|
|
async function rcsExtractBugle() {
|
|
document.getElementById('rcs-bugle-result').innerHTML = '<p>Extracting bugle_db...</p>';
|
|
const r = await rcsPost('/extract-bugle');
|
|
document.getElementById('rcs-bugle-result').innerHTML = renderJSON(r);
|
|
}
|
|
|
|
async function rcsExtractRCSFromBugle() {
|
|
const r = await rcsPost('/extract-rcs-bugle');
|
|
document.getElementById('rcs-bugle-result').innerHTML = renderJSON(r);
|
|
}
|
|
|
|
async function rcsExtractConvosFromBugle() {
|
|
const r = await rcsPost('/extract-conversations-bugle');
|
|
document.getElementById('rcs-bugle-result').innerHTML = r.ok && r.rows
|
|
? renderTable(r.rows) : renderJSON(r);
|
|
}
|
|
|
|
async function rcsExtractEdits() {
|
|
const r = await rcsPost('/extract-edits');
|
|
document.getElementById('rcs-bugle-result').innerHTML = renderJSON(r);
|
|
}
|
|
|
|
async function rcsExtractAllBugle() {
|
|
document.getElementById('rcs-bugle-result').innerHTML = '<p>Exporting all tables...</p>';
|
|
const r = await rcsPost('/extract-all-bugle');
|
|
document.getElementById('rcs-bugle-result').innerHTML = renderJSON(r);
|
|
}
|
|
|
|
async function rcsQueryBugle() {
|
|
const sql = document.getElementById('rcs-sql-query').value;
|
|
if (!sql) return;
|
|
const r = await rcsPost('/query-bugle', {sql});
|
|
const el = document.getElementById('rcs-sql-result');
|
|
if (r.ok && r.rows && r.rows.length) {
|
|
el.innerHTML = '<p style="color:var(--text-muted)">' + r.count + ' rows</p>' + renderTable(r.rows);
|
|
} else {
|
|
el.innerHTML = renderJSON(r);
|
|
}
|
|
}
|
|
|
|
function rcsSQLPreset(val) {
|
|
const presets = {
|
|
rcs: "SELECT m.*, p.text, p.content_type FROM messages m LEFT JOIN parts p ON m._id=p.message_id WHERE m.message_protocol>=2 ORDER BY m.sent_timestamp DESC LIMIT 100",
|
|
all_msgs: "SELECT m._id, m.conversation_id, m.message_protocol, m.sent_timestamp, p.text, ppl.normalized_destination, ppl.full_name, CASE WHEN ppl.sub_id=-2 THEN 'IN' ELSE 'OUT' END AS dir FROM messages m LEFT JOIN parts p ON m._id=p.message_id LEFT JOIN conversation_participants cp ON m.conversation_id=cp.conversation_id LEFT JOIN participants ppl ON cp.participant_id=ppl._id ORDER BY m.sent_timestamp DESC LIMIT 100",
|
|
edits: "SELECT me.*, p.text AS current_text FROM message_edits me LEFT JOIN messages m ON me.latest_message_id=m._id LEFT JOIN parts p ON m._id=p.message_id ORDER BY me.edited_at_timestamp_ms DESC",
|
|
conversations: "SELECT c._id, c.name, c.snippet_text, c.sort_timestamp, c.participant_count, GROUP_CONCAT(ppl.normalized_destination,'; ') AS numbers FROM conversations c LEFT JOIN conversation_participants cp ON c._id=cp.conversation_id LEFT JOIN participants ppl ON cp.participant_id=ppl._id GROUP BY c._id ORDER BY c.sort_timestamp DESC",
|
|
attachments: "SELECT p._id, p.message_id, p.content_type, p.uri, p.text, m.message_protocol FROM parts p JOIN messages m ON p.message_id=m._id WHERE p.content_type!='text/plain' AND p.uri IS NOT NULL ORDER BY p._id DESC LIMIT 50",
|
|
stats: "SELECT message_protocol, COUNT(*) as count, MIN(sent_timestamp) as earliest, MAX(sent_timestamp) as latest FROM messages GROUP BY message_protocol",
|
|
};
|
|
if (presets[val]) document.getElementById('rcs-sql-query').value = presets[val];
|
|
}
|
|
|
|
async function rcsListExtracted() {
|
|
const r = await rcsGet('/extracted-dbs');
|
|
const el = document.getElementById('rcs-extracted-list');
|
|
if (r.extractions && r.extractions.length) {
|
|
el.innerHTML = renderTable(r.extractions, ['name','files','total_size']);
|
|
} else {
|
|
el.innerHTML = '<p style="color:var(--text-muted)">No extracted databases yet</p>';
|
|
}
|
|
}
|
|
|
|
// ── Forge ────────────────────────────────────────────────────────────
|
|
async function rcsForgeSMS() {
|
|
const tsInput = document.getElementById('forge-timestamp').value;
|
|
let ts = null;
|
|
if (tsInput) ts = new Date(tsInput).getTime();
|
|
const r = await rcsPost('/forge', {
|
|
address: document.getElementById('forge-address').value,
|
|
body: document.getElementById('forge-body').value,
|
|
type: document.getElementById('forge-type').value,
|
|
timestamp: ts,
|
|
contact_name: document.getElementById('forge-contact').value || null,
|
|
read: document.getElementById('forge-read').checked ? 1 : 0,
|
|
});
|
|
alert(r.ok ? 'SMS forged!' : 'Failed: ' + (r.error || ''));
|
|
}
|
|
|
|
async function rcsForgeRCS() {
|
|
const r = await rcsPost('/archon/forge-rcs', {
|
|
address: document.getElementById('forge-rcs-address').value,
|
|
body: document.getElementById('forge-rcs-body').value,
|
|
direction: document.getElementById('forge-rcs-direction').value,
|
|
});
|
|
alert(r.ok ? 'RCS message forged via Archon!' : 'Failed: ' + (r.error || r.result || ''));
|
|
}
|
|
|
|
async function rcsForgeConversation() {
|
|
let msgs;
|
|
try { msgs = JSON.parse(document.getElementById('forge-conv-msgs').value); }
|
|
catch(e) { alert('Invalid JSON'); return; }
|
|
const r = await rcsPost('/forge-conversation', {
|
|
address: document.getElementById('forge-conv-address').value,
|
|
messages: msgs,
|
|
});
|
|
alert(r.ok ? r.message : 'Failed: ' + (r.error || ''));
|
|
}
|
|
|
|
async function rcsImportXML() {
|
|
const xml = document.getElementById('forge-import-xml').value;
|
|
if (!xml) { alert('Paste XML first'); return; }
|
|
const r = await rcsPost('/import-xml', {xml});
|
|
document.getElementById('forge-import-result').innerHTML = renderJSON(r);
|
|
}
|
|
|
|
function rcsLoadXMLFile(input) {
|
|
if (!input.files.length) return;
|
|
const reader = new FileReader();
|
|
reader.onload = e => document.getElementById('forge-import-xml').value = e.target.result;
|
|
reader.readAsText(input.files[0]);
|
|
}
|
|
|
|
async function rcsRefreshForgeLog() {
|
|
const r = await rcsGet('/forged-log');
|
|
const list = document.getElementById('forge-log-list');
|
|
const log = r.log || [];
|
|
if (log.length) {
|
|
list.innerHTML = renderTable(log, ['time','action','address','body']);
|
|
} else {
|
|
list.innerHTML = '<p style="color:var(--text-muted)">No forged messages yet</p>';
|
|
}
|
|
}
|
|
|
|
async function rcsClearForgeLog() {
|
|
await rcsPost('/forged-log/clear');
|
|
rcsRefreshForgeLog();
|
|
}
|
|
|
|
// ── Modify ──────────────────────────────────────────────────────────
|
|
async function rcsModifyMessage() {
|
|
const id = parseInt(document.getElementById('mod-msg-id').value);
|
|
if (!id) { alert('Enter message ID'); return; }
|
|
const tsInput = document.getElementById('mod-timestamp').value;
|
|
const data = {};
|
|
const body = document.getElementById('mod-body').value;
|
|
if (body) data.body = body;
|
|
const type = document.getElementById('mod-type').value;
|
|
if (type) data.type = type;
|
|
if (tsInput) data.timestamp = new Date(tsInput).getTime();
|
|
const r = await fetch(RCS_BASE + '/message/' + id, {
|
|
method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify(data)
|
|
}).then(r => r.json());
|
|
alert(r.ok ? 'Modified!' : 'Failed: ' + (r.error || ''));
|
|
}
|
|
|
|
async function rcsChangeSender() {
|
|
const r = await rcsPost('/change-sender', {
|
|
msg_id: document.getElementById('mod-sender-id').value,
|
|
new_address: document.getElementById('mod-new-address').value,
|
|
});
|
|
alert(r.ok ? 'Sender changed!' : 'Failed: ' + (r.error || ''));
|
|
}
|
|
|
|
async function rcsShiftTimestamps() {
|
|
const r = await rcsPost('/shift-timestamps', {
|
|
address: document.getElementById('mod-shift-addr').value,
|
|
offset_minutes: parseInt(document.getElementById('mod-shift-mins').value) || 0,
|
|
});
|
|
alert(r.ok ? 'Modified ' + r.modified + ' messages' : 'Failed: ' + (r.error || ''));
|
|
}
|
|
|
|
async function rcsMarkAllRead() {
|
|
const tid = document.getElementById('mod-thread-id').value;
|
|
const r = await rcsPost('/mark-read', {thread_id: tid || null});
|
|
alert(r.ok ? r.message : 'Failed: ' + (r.error || ''));
|
|
}
|
|
|
|
async function rcsWipeThread() {
|
|
const tid = parseInt(document.getElementById('mod-thread-id').value);
|
|
if (!tid) { alert('Enter thread ID'); return; }
|
|
if (!confirm('Wipe all messages in thread ' + tid + '?')) return;
|
|
const r = await rcsPost('/wipe-thread', {thread_id: tid});
|
|
alert(r.ok ? r.message : 'Failed: ' + (r.error || ''));
|
|
}
|
|
|
|
async function rcsDeleteConversation() {
|
|
const tid = parseInt(document.getElementById('mod-thread-id').value);
|
|
if (!tid) { alert('Enter thread ID'); return; }
|
|
if (!confirm('Delete conversation ' + tid + '?')) return;
|
|
const r = await fetch(RCS_BASE + '/conversation/' + tid, {method:'DELETE'}).then(r => r.json());
|
|
alert(r.ok ? r.message : 'Failed: ' + (r.error || ''));
|
|
}
|
|
|
|
async function rcsDeleteMessage() {
|
|
const id = parseInt(document.getElementById('mod-del-id').value);
|
|
if (!id) { alert('Enter message ID'); return; }
|
|
if (!confirm('Delete message ' + id + '?')) return;
|
|
const r = await fetch(RCS_BASE + '/message/' + id, {method:'DELETE'}).then(r => r.json());
|
|
alert(r.ok ? r.message : 'Failed: ' + (r.error || ''));
|
|
}
|
|
|
|
// ── Exploit ─────────────────────────────────────────────────────────
|
|
async function rcsCVECheck() {
|
|
const r = await rcsGet('/cve-check');
|
|
const el = document.getElementById('rcs-cve-status');
|
|
if (r.vulnerable) {
|
|
el.style.border = '1px solid #f44336';
|
|
el.innerHTML = '<strong style="color:#f44336">VULNERABLE</strong> — ' + esc(r.message);
|
|
} else {
|
|
el.style.border = '1px solid #4caf50';
|
|
el.innerHTML = '<strong style="color:#4caf50">NOT VULNERABLE</strong> — ' + esc(r.message);
|
|
}
|
|
}
|
|
|
|
async function rcsCVEExploit() {
|
|
if (!confirm('Execute CVE-2024-0044 exploit? This will forge a package entry to gain app-level access.')) return;
|
|
document.getElementById('rcs-cve-result').innerHTML = '<p>Exploiting...</p>';
|
|
const r = await rcsPost('/cve-exploit', {target_package: 'com.google.android.apps.messaging'});
|
|
document.getElementById('rcs-cve-result').innerHTML = renderJSON(r);
|
|
rcsRefreshStatus();
|
|
}
|
|
|
|
async function rcsCVECleanup() {
|
|
const r = await rcsPost('/cve-cleanup');
|
|
document.getElementById('rcs-cve-result').innerHTML = renderJSON(r);
|
|
rcsRefreshStatus();
|
|
}
|
|
|
|
async function rcsSpoofTyping() {
|
|
const addr = document.getElementById('exploit-spoof-addr').value;
|
|
if (!addr) return;
|
|
const r = await rcsPost('/rcs-spoof-typing', {address: addr});
|
|
alert(r.ok ? r.message : 'Failed');
|
|
}
|
|
|
|
async function rcsSpoofReadReceipt() {
|
|
const mid = document.getElementById('exploit-spoof-msgid').value;
|
|
if (!mid) return;
|
|
const r = await rcsPost('/rcs-spoof-read', {msg_id: mid});
|
|
alert(r.ok ? r.message : 'Failed');
|
|
}
|
|
|
|
async function rcsCloneIdentity() {
|
|
const r = await rcsPost('/clone-identity');
|
|
document.getElementById('rcs-identity-result').innerHTML = renderJSON(r);
|
|
}
|
|
|
|
async function rcsExtractSignalState() {
|
|
const r = await rcsPost('/signal-state');
|
|
document.getElementById('rcs-identity-result').innerHTML = renderJSON(r);
|
|
}
|
|
|
|
async function rcsInterceptArchival() {
|
|
const r = await rcsPost('/intercept-archival');
|
|
document.getElementById('rcs-identity-result').innerHTML = renderJSON(r);
|
|
}
|
|
|
|
async function rcsShowCVEs() {
|
|
const r = await rcsGet('/cve-database');
|
|
const el = document.getElementById('rcs-cves-list');
|
|
if (r.cves) {
|
|
let h = '';
|
|
for (const [id, cve] of Object.entries(r.cves)) {
|
|
let sColor = cve.severity === 'critical' ? '#f44336' : cve.severity === 'high' ? '#ff9800' : '#ffeb3b';
|
|
h += '<div style="padding:8px;margin-bottom:6px;border:1px solid var(--border);border-radius:4px;border-left:3px solid ' + sColor + '">'
|
|
+ '<div style="display:flex;justify-content:space-between;align-items:center">'
|
|
+ '<strong>' + esc(id) + '</strong>'
|
|
+ '<span style="font-size:0.75rem;padding:2px 6px;border-radius:3px;background:' + sColor + ';color:#000">'
|
|
+ esc(cve.severity.toUpperCase()) + ' ' + cve.cvss + '</span></div>'
|
|
+ '<div style="font-size:0.85rem;margin-top:4px">' + esc(cve.desc) + '</div>'
|
|
+ '<div style="font-size:0.8rem;color:var(--text-muted);margin-top:2px">Affected: ' + esc(cve.affected) + ' | Type: ' + esc(cve.type) + '</div>'
|
|
+ '</div>';
|
|
}
|
|
el.innerHTML = h;
|
|
}
|
|
}
|
|
|
|
async function rcsGetIMSStatus() {
|
|
const r = await rcsGet('/ims-status');
|
|
document.getElementById('rcs-diag-result').textContent = r.raw || JSON.stringify(r, null, 2);
|
|
}
|
|
|
|
async function rcsGetCarrierConfig() {
|
|
const r = await rcsGet('/carrier-config');
|
|
document.getElementById('rcs-diag-result').textContent = JSON.stringify(r.rcs_config || r, null, 2);
|
|
}
|
|
|
|
async function rcsGetRCSState() {
|
|
const r = await rcsGet('/rcs-state');
|
|
document.getElementById('rcs-diag-result').textContent = JSON.stringify(r, null, 2);
|
|
}
|
|
|
|
async function rcsEnableLogging() {
|
|
const r = await rcsPost('/enable-logging');
|
|
document.getElementById('rcs-diag-result').textContent = JSON.stringify(r, null, 2);
|
|
}
|
|
|
|
async function rcsCaptureLogs() {
|
|
document.getElementById('rcs-diag-result').textContent = 'Capturing logs (10s)...';
|
|
const r = await rcsPost('/capture-logs', {duration: 10});
|
|
document.getElementById('rcs-diag-result').textContent = (r.lines || []).join('\n');
|
|
}
|
|
|
|
async function rcsPixelDiag() {
|
|
const r = await rcsGet('/pixel-diagnostics');
|
|
document.getElementById('rcs-diag-result').textContent = JSON.stringify(r, null, 2);
|
|
}
|
|
|
|
// ── Backup ──────────────────────────────────────────────────────────
|
|
async function rcsFullBackup(fmt) {
|
|
document.getElementById('rcs-backup-result').innerHTML = '<p>Creating backup...</p>';
|
|
const r = await rcsPost('/backup', {format: fmt});
|
|
document.getElementById('rcs-backup-result').innerHTML = renderJSON(r);
|
|
}
|
|
|
|
async function rcsArchonBackup() {
|
|
document.getElementById('rcs-backup-result').innerHTML = '<p>Archon backup in progress...</p>';
|
|
const r = await rcsPost('/archon/backup');
|
|
document.getElementById('rcs-backup-result').innerHTML = renderJSON(r);
|
|
}
|
|
|
|
async function rcsRestore() {
|
|
const path = document.getElementById('backup-restore-path').value;
|
|
if (!path) { alert('Enter backup path'); return; }
|
|
const r = await rcsPost('/restore', {path});
|
|
document.getElementById('rcs-restore-result').innerHTML = renderJSON(r);
|
|
}
|
|
|
|
async function rcsCloneDevice() {
|
|
if (!confirm('This will create a full backup for cloning. Continue?')) return;
|
|
const r = await rcsPost('/clone');
|
|
document.getElementById('rcs-restore-result').innerHTML = renderJSON(r);
|
|
}
|
|
|
|
async function rcsSetDefaultApp() {
|
|
const pkg = document.getElementById('backup-sms-app').value;
|
|
const r = await rcsPost('/set-default', {package: pkg});
|
|
alert(r.ok ? r.message : 'Failed: ' + (r.error || ''));
|
|
rcsRefreshStatus();
|
|
}
|
|
|
|
async function rcsListBackups() {
|
|
const r = await rcsGet('/backups');
|
|
const el = document.getElementById('rcs-backups-list');
|
|
if (r.backups && r.backups.length) {
|
|
el.innerHTML = renderTable(r.backups, ['name','size','modified']);
|
|
} else {
|
|
el.innerHTML = '<p style="color:var(--text-muted)">No backups yet</p>';
|
|
}
|
|
}
|
|
|
|
async function rcsListExports() {
|
|
const r = await rcsGet('/exports');
|
|
const el = document.getElementById('rcs-exports-list');
|
|
if (r.exports && r.exports.length) {
|
|
el.innerHTML = renderTable(r.exports, ['name','size','modified']);
|
|
} else {
|
|
el.innerHTML = '<p style="color:var(--text-muted)">No exports yet</p>';
|
|
}
|
|
}
|
|
|
|
// ── Monitor ─────────────────────────────────────────────────────────
|
|
async function rcsToggleMonitor() {
|
|
if (rcsMonitorRunning) {
|
|
await rcsPost('/monitor/stop');
|
|
rcsMonitorRunning = false;
|
|
document.getElementById('rcs-monitor-btn').textContent = 'Start Monitor';
|
|
if (rcsMonitorInterval) { clearInterval(rcsMonitorInterval); rcsMonitorInterval = null; }
|
|
} else {
|
|
await rcsPost('/monitor/start');
|
|
rcsMonitorRunning = true;
|
|
document.getElementById('rcs-monitor-btn').textContent = 'Stop Monitor';
|
|
rcsMonitorInterval = setInterval(rcsRefreshMonitor, 3000);
|
|
}
|
|
}
|
|
|
|
async function rcsRefreshMonitor() {
|
|
const r = await rcsGet('/monitor/messages');
|
|
const feed = document.getElementById('rcs-monitor-feed');
|
|
const msgs = r.messages || [];
|
|
document.getElementById('rcs-monitor-count').textContent = msgs.length + ' intercepted';
|
|
feed.innerHTML = msgs.map(m =>
|
|
'<div style="padding:4px 8px;border-bottom:1px solid var(--border)">'
|
|
+ '<span style="color:var(--text-muted);font-size:0.75rem">' + esc(m.time || '') + '</span> '
|
|
+ '<span style="color:#ff9800">[' + esc(m.type || '') + ']</span> '
|
|
+ esc(m.raw || '')
|
|
+ '</div>'
|
|
).join('');
|
|
}
|
|
|
|
async function rcsClearMonitor() {
|
|
await rcsPost('/monitor/clear');
|
|
document.getElementById('rcs-monitor-feed').innerHTML = '';
|
|
document.getElementById('rcs-monitor-count').textContent = '';
|
|
}
|
|
|
|
// ── Init ────────────────────────────────────────────────────────────
|
|
rcsRefreshStatus();
|
|
</script>
|
|
{% endblock %}
|