Files
AXP-Resurection/scripts/009-read-swissquote-rasp.py
sunfish-adminandCursor 3f941e8451 009: Closure wave 3 deblob, handoff, and optional wave4/malloc bacons.
Ship script gates wave 4 behind AXP_DEBLOB_WAVE4, documents secrets cert path,
and adds deblob appliers plus tests; docs capture Swissquote fix and parity.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-09 17:31:01 +02:00

64 lines
2.5 KiB
Python

#!/usr/bin/env python3
"""Read only Swissquote's RASP exception classification; never dump reports.
Requires an already authorized adb shell su. Does not launch, stop, modify,
instrument, or grant root to Swissquote. Raw report data stays in memory.
APK/report/session identifiers and other application data are not emitted.
"""
import argparse
import json
import subprocess
def classifications(value):
if isinstance(value, dict):
exception = value.get("exception")
if isinstance(exception, dict):
reason = exception.get("reason", "")
if (exception.get("type") == "com.swissquote.android.FreeRaspException"
and isinstance(reason, str) and reason.startswith("freeRASP: on")):
yield reason
for child in value.values():
yield from classifications(child)
elif isinstance(value, list):
for child in value:
yield from classifications(child)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--device", required=True, choices=["LAB", "GOLDEN"])
args = parser.parse_args()
serial = {"LAB": "11231JEC207477", "GOLDEN": "11071JEC203839"}[args.device]
base = "/data/user/10/com.swissquote.android/files/.crashlytics.v3/com.swissquote.android"
def shell(command):
return subprocess.check_output(
["adb", "-s", serial, "shell", "su", "-c", "'" + command + "'"],
text=True, timeout=15,
)
results = []
for path in shell("find " + base + " -maxdepth 4 -type f").splitlines():
# Never interpolate arbitrary output into a shell command.
if not path.startswith(base + "/") or any(c not in
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/._:-" for c in path):
continue
if not any(part in path for part in ("/event", "/reports/", "/priority-reports/")):
continue
size = shell("stat -c %s " + path).strip()
if not size.isdigit() or int(size) > 4 * 1024 * 1024:
continue
try:
value = json.loads(shell("cat " + path))
except json.JSONDecodeError:
continue
results.extend(classifications(value))
print(json.dumps({"device": args.device, "profile": 10,
"rasp_exceptions": results,
"note": "Retained events only; absence is not proof of PASS."}, indent=2))
if __name__ == "__main__":
main()