feat(android): scripted MITM setup with emulator snapshot and Frida SSL unpinning

- Add scripts/android/ with setup-mitm-avd.sh (one-time) and start-mitm-session.sh (per-session)
- Move frida Dockerfile to scripts/android/; extract frida-server + SSL scripts via Docker
- Use native macOS mitmproxy app for capture (Docker NAT blocks emulator traffic)
- Add native-connect-hook.js to Frida launch — required for Bose app's native networking
- Document verified AP mode Wi-Fi provisioning endpoint (POST :8090/addWirelessProfile)
- Correct factory reset sequences for ST10/ST20 from official Bose guides
- Remove old scripts/setup-mitm-avd.sh and scripts/start-mitm-session.sh (moved to android/)
- Add session trace with lessons learned from first interactive capture run

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tobias Gesellchen
2026-05-02 18:08:39 +02:00
co-authored by Claude Sonnet 4.6
parent 86825c44af
commit cf7f3431f6
8 changed files with 1007 additions and 15 deletions
+8
View File
@@ -58,6 +58,14 @@ vendor/
ehthumbs.db
Thumbs.db
# Android MITM setup — downloaded/generated artefacts, not committed
scripts/android/bose.apk
scripts/android/frida-server
scripts/android/frida-server.xz
scripts/android/frida/
scripts/android/frida-venv/
scripts/android/captures/
# Temporary files
*.tmp
*.temp
+1
View File
@@ -12,6 +12,7 @@
* [Getting Started](guides/GETTING-STARTED.md)
* [SoundTouch Service](guides/SOUNDTOUCH-SERVICE.md)
* [Initial Device Setup](guides/DEVICE-INITIAL-SETUP.md)
* [Capture Device Pairing Traffic](guides/CAPTURE-DEVICE-PAIRING.md)
* [Device Setup Flow](DEVICE-SETUP.md)
* [MAC Address Mapping](guides/MAC-ADDRESS-MAPPING.md)
* [HTTPS Setup](guides/HTTPS-SETUP.md)
+30 -7
View File
@@ -2,6 +2,21 @@
Intercept HTTPS/WebSocket traffic from the Bose SoundTouch Android app using an Android emulator, mitmproxy, and Frida. Tested on Apple Silicon (ARM64) Mac.
## Automated Setup
The steps in this document are scripted for reproducibility:
```bash
scripts/android/setup-mitm-avd.sh # one-time: create AVD, install cert & APK, save snapshot
scripts/android/start-mitm-session.sh # per-session: restore snapshot, refresh proxy, start frida-server
```
Read on for the full manual walkthrough and the rationale behind each step.
> **Note:** The manual steps below use `/tmp/` for intermediate files and reflect the original approach. The automated scripts supersede them — use the scripts for day-to-day use and refer here only to understand how things work.
---
## Prerequisites
- Android Studio installed (for SDK tools and emulator)
@@ -9,6 +24,10 @@ Intercept HTTPS/WebSocket traffic from the Bose SoundTouch Android app using an
- mitmproxy installed (`pip install mitmproxy` or via your preferred method)
- The Bose SoundTouch APK (extracted from a real device, see below)
> **BLE limitation**: Android emulators do not expose Bluetooth hardware. The Bose app's default setup path (BLE Wi-Fi provisioning) therefore cannot be used to configure a factory-reset speaker from the emulator. Use **AP mode** instead: provision the speaker's Wi-Fi credentials via the Mac command line first (see [DEVICE-INITIAL-SETUP.md § 6](../guides/DEVICE-INITIAL-SETUP.md)), then the app can discover the already-networked speaker via mDNS/SSDP without BLE.
> **Emulator ↔ local network**: The emulator routes all traffic through the Mac's active network interface. Once the speaker is on the same LAN as the Mac, the emulator can reach it at its normal LAN IP (e.g. `192.168.1.50`) — no extra routing is needed. Use `adb shell ping 192.168.1.50` to confirm reachability.
Add Android SDK tools to your PATH (add to `~/.zshrc`):
```bash
@@ -89,7 +108,8 @@ adb -s emulator-5554 install bose.apk
```bash
# Start mitmproxy (generates CA cert on first run)
mitmweb --port 8080 --mode regular -w bose_traffic.mitm
# Use the native macOS app — Docker mitmproxy does not work (NAT blocks emulator traffic)
mitmweb --listen-port 8080 --mode regular -w bose_traffic.mitm
```
Extract the CA certificate (without private key):
@@ -214,16 +234,19 @@ grep -A3 "CERT_PEM" /tmp/config.js | head -5
Make sure mitmweb is running, then:
```bash
/tmp/frida-venv/bin/frida \
scripts/android/frida-venv/bin/frida \
-U \
-f com.bose.soundtouch \
-l /tmp/config.js \
-l /tmp/android-system-certificate-injection.js \
-l /tmp/android-proxy-override.js \
-l /tmp/android-certificate-unpinning.js \
-l /tmp/android-certificate-unpinning-fallback.js
-l scripts/android/frida/config.js \
-l scripts/android/frida/native-connect-hook.js \
-l scripts/android/frida/android/android-system-certificate-injection.js \
-l scripts/android/frida/android/android-proxy-override.js \
-l scripts/android/frida/android/android-certificate-unpinning.js \
-l scripts/android/frida/android/android-certificate-unpinning-fallback.js
```
> `native-connect-hook.js` is required — the Bose app uses native networking that bypasses Java proxy settings.
Expected output in the Frida REPL:
```
+415
View File
@@ -0,0 +1,415 @@
# Capture Device Pairing Traffic
Step-by-step runbook for factory-resetting a SoundTouch speaker, pairing it to a Bose cloud account, and capturing every cloud request via mitmproxy. Tested on Apple Silicon Mac.
**Goal:** obtain a full `.mitm` recording of the account-pairing flow (streaming.bose.com) triggered by the official Android app.
---
## Overview
```
Phase 0 Pre-flight checks
Phase 1 Factory reset speaker
Phase 2 Provision speaker Wi-Fi (AP mode, console)
Phase 3 Start mitmproxy + emulator + Frida
Phase 4 Pair speaker via Bose app (adb-driven)
Phase 5 Save & inspect recording
```
The Android emulator does not have Bluetooth, so the standard BLE setup path is unavailable. Instead:
1. Provision Wi-Fi directly over the speaker's AP web server (Phase 2).
2. Once the speaker is on the LAN, the Bose app discovers it via mDNS — no BLE needed.
---
## Phase 0 — Pre-flight
The emulator setup is fully scripted. Run once per machine:
```bash
# Place the Bose APK at scripts/android/bose.apk first (see BOSE-APP-ADB-Emulator.md § 1)
# Run mitmweb once to generate the CA: mitmweb --listen-port 8080 (Ctrl-C after it starts)
scripts/android/setup-mitm-avd.sh
```
This installs the system image, creates an AVD named `bose-mitm`, installs the
mitmproxy cert and Bose APK, and saves an emulator snapshot `mitm-ready` — so
subsequent sessions never repeat the cert/reboot cycle.
For subsequent sessions, Phase 3 below is replaced by a single command:
```bash
scripts/android/start-mitm-session.sh
```
Manual steps are only needed if you want to understand the internals; see
[BOSE-APP-ADB-Emulator.md](../analysis/BOSE-APP-ADB-Emulator.md) for the
full manual walkthrough.
---
## Phase 1 — Factory Reset Speaker
Perform the reset for your model (see
[DEVICE-INITIAL-SETUP.md § 5](DEVICE-INITIAL-SETUP.md) for full table):
| Model | Sequence |
|-----------------------------|-------------------------------------------------------------------------|
| SoundTouch 10 | Power on; hold **Preset 1** + **Vol ** ~10 s → solid amber Wi-Fi LED |
| SoundTouch 20 | Power on; hold **Preset 1** + **Vol ** ~10 s → lights blink L→R, amber |
| SoundTouch 20/30 Series III | Hold **Preset 1** + **Preset 6** ~10 s |
| SoundTouch 300 | Hold **Vol ** ~15 s until light bar blinks |
Wait until the white LED sweep / restart animation completes (~30 s). The speaker
is now in setup mode and broadcasting its own Wi-Fi AP.
---
## Phase 2 — Provision Speaker Wi-Fi (AP Mode)
### 2.1 Connect Mac to Speaker AP
```bash
# Find the SSID — use System Settings → Wi-Fi or:
# sudo wdutil info (macOS Sequoia+, airport command removed)
# Connect (replace SSID with actual value)
SPEAKER_SSID="Bose SoundTouch XXXX"
networksetup -setairportnetwork en0 "$SPEAKER_SSID"
# Confirm: speaker web UI reachable at 192.0.2.1 (client gets 192.0.2.2)
curl -s --connect-timeout 5 http://192.0.2.1/ | head -3
```
### 2.2 Push Home Wi-Fi Credentials
The setup UI at `http://192.0.2.1/` uses the SoundTouch API on **port 8090**. Push credentials directly:
```bash
HOME_SSID="MyHomeNetwork"
HOME_PASS="MyPassword"
# Optional: trigger a site survey first so the speaker finds your SSID
curl -s -X POST http://192.0.2.1:8090/performWirelessSiteSurvey \
-H 'Content-Type: text/xml' \
--data-raw '<PerformWirelessSiteSurvey timeout="5"/>'
curl -s -X POST http://192.0.2.1:8090/addWirelessProfile \
-H 'Content-Type: text/xml' \
--data-raw "<AddWirelessProfile><profile ssid=\"${HOME_SSID}\" password=\"${HOME_PASS}\" securityType=\"wpa_or_wpa2\" /></AddWirelessProfile>"
```
Expected response: `<AddWirelessProfileResponse />`
### 2.3 Reconnect Mac to Home Network
```bash
HOME_SSID="MyHomeNetwork"
HOME_PASS="MyPassword"
networksetup -setairportnetwork en0 "$HOME_SSID" "$HOME_PASS"
```
### 2.4 Wait for Speaker to Join LAN
```bash
# Poll mDNS until the speaker appears (~15-30 s)
echo "Waiting for speaker on LAN..."
until dns-sd -B _soundtouch._tcp local 2>&1 | grep -m1 "Add"; do sleep 2; done
echo "Speaker is online"
# Resolve its IP
dns-sd -L "$(dns-sd -B _soundtouch._tcp local 2>&1 | grep Add | awk '{print $7}')" \
_soundtouch._tcp local 2>&1 | grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}'
```
Or just scan for open port 8090:
```bash
# Quick scan of your /24 subnet for port 8090
SUBNET="192.168.1" # adjust to your local subnet
for i in $(seq 1 254); do
(ping -c1 -W1 ${SUBNET}.$i &>/dev/null && \
nc -z -w1 ${SUBNET}.$i 8090 2>/dev/null && \
echo "${SUBNET}.$i") &
done
wait
```
---
## Phase 3 — Start mitmproxy, Emulator, Frida
Run each block in a separate terminal tab.
### 3.1 Start mitmweb (native macOS app)
> **Note:** Docker mitmproxy does not work here — its NAT layer prevents the emulator from reaching it. Use the native macOS app instead (download: `https://downloads.mitmproxy.org/12.2.2/mitmproxy-12.2.2-macos-arm64.tar.gz`).
```bash
CAPTURE="bose-pairing-$(date +%Y%m%d-%H%M%S).mitm"
/Applications/mitmproxy.app/Contents/MacOS/mitmweb \
--web-host 0.0.0.0 --listen-port 8080 --mode regular \
--set web_password=bose \
-w "scripts/android/captures/${CAPTURE}"
# Captures → scripts/android/captures/
# Web UI → http://127.0.0.1:8081/?token=bose
```
### 3.2 Start Emulator
```bash
~/Library/Android/sdk/emulator/emulator -avd Pixel_6_API33 -writable-system &
echo "Waiting for emulator boot..."
adb wait-for-device
adb -s emulator-5554 wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done'
echo "Emulator ready"
```
Enable root and install certificate (only needed once per emulator session):
```bash
adb -s emulator-5554 root
adb -s emulator-5554 shell avbctl disable-verification
adb -s emulator-5554 reboot
adb -s emulator-5554 wait-for-device
adb -s emulator-5554 root
HASH=$(openssl x509 -inform PEM -subject_hash_old \
-in ~/.mitmproxy/mitmproxy-ca-cert.pem | head -1)
adb -s emulator-5554 push ~/.mitmproxy/mitmproxy-ca-cert.pem /data/local/tmp/mitmproxy.pem
adb -s emulator-5554 shell su 0 mkdir -p /data/misc/user/0/cacerts-added
adb -s emulator-5554 shell su 0 \
cp /data/local/tmp/mitmproxy.pem /data/misc/user/0/cacerts-added/${HASH}.0
adb -s emulator-5554 shell su 0 \
chmod 644 /data/misc/user/0/cacerts-added/${HASH}.0
```
Set proxy to Mac IP:
```bash
MAC_IP=$(ipconfig getifaddr en0)
adb -s emulator-5554 shell settings put global http_proxy "${MAC_IP}:8080"
echo "Proxy set to ${MAC_IP}:8080"
```
Confirm emulator can reach the speaker:
```bash
SPEAKER_IP=192.168.1.50 # adjust to your speaker's LAN IP
adb -s emulator-5554 shell ping -c 3 "$SPEAKER_IP"
```
### 3.3 Start frida-server
```bash
adb -s emulator-5554 push scripts/android/frida-server /data/local/tmp/frida-server
adb -s emulator-5554 shell su 0 chmod 755 /data/local/tmp/frida-server
adb -s emulator-5554 shell su 0 "nohup /data/local/tmp/frida-server > /dev/null 2>&1 &"
sleep 2
echo "frida-server running"
```
### 3.4 Configure config.js
`start-mitm-session.sh` patches `scripts/android/frida/config.js` automatically with the current Mac IP and mitmproxy cert. No manual step needed.
### 3.5 Launch App with SSL Unpinning
```bash
scripts/android/frida-venv/bin/frida \
-U \
-f com.bose.soundtouch \
-l scripts/android/frida/config.js \
-l scripts/android/frida/native-connect-hook.js \
-l scripts/android/frida/android/android-system-certificate-injection.js \
-l scripts/android/frida/android/android-proxy-override.js \
-l scripts/android/frida/android/android-certificate-unpinning.js \
-l scripts/android/frida/android/android-certificate-unpinning-fallback.js
```
> `native-connect-hook.js` is required — the Bose app uses native networking that bypasses Java proxy settings.
Expected Frida output:
```
== System certificate trust injected ==
== Proxy system configuration overridden to <IP>:8080 ==
== Proxy configuration overridden to <IP>:8080 ==
== Certificate unpinning completed ==
== Unpinning fallback auto-patcher installed ==
```
---
## Phase 4 — Pair Speaker via App
The Bose app should now be running in the emulator with all traffic going through mitmproxy.
### 4.1 Inspect UI to Find Interactive Elements
```bash
# Dump current screen
adb -s emulator-5554 shell uiautomator dump /sdcard/ui.xml
adb -s emulator-5554 pull /sdcard/ui.xml /tmp/ui.xml
# Helper: list all clickable elements with their text + bounds
grep -o 'text="[^"]*" resource-id="[^"]*" \.\.\. clickable="true"[^/]*' /tmp/ui.xml \
|| python3 -c "
import xml.etree.ElementTree as ET
tree = ET.parse('/tmp/ui.xml')
for n in tree.iter('node'):
if n.get('clickable') == 'true' and n.get('text'):
print(n.get('bounds'), n.get('resource-id'), repr(n.get('text')))
"
```
### 4.2 Navigate Setup Flow (adb)
```bash
# Take a screenshot at any point to see current state
adb -s emulator-5554 shell screencap /sdcard/screen.png
adb -s emulator-5554 pull /sdcard/screen.png /tmp/screen.png
open /tmp/screen.png
# Tap by resource-id (find IDs from ui.xml dump)
adb -s emulator-5554 shell uiautomator runtest ... # or input tap
# Tap by screen coordinates
adb -s emulator-5554 shell input tap X Y
# Type into the focused field
adb -s emulator-5554 shell input text "your@email.com"
# Press Enter / Next
adb -s emulator-5554 shell input keyevent 66
```
### 4.3 Expected Setup Steps in the App
Follow the on-screen flow; mitmproxy captures everything automatically.
1. **Sign in** — enter email + password → triggers `POST /streaming/account/login`
2. **Add speaker** — tap "Set Up a New Speaker" or equivalent
3. **App discovers speaker** via mDNS on LAN (no BLE required)
4. **Wi-Fi already configured** — app skips the Wi-Fi step since speaker is online
5. **Name speaker** — type a name → WebSocket `name` message to speaker port 8080
6. **Pairing** — app sends `setMargeAccount` WebSocket to speaker → speaker POSTs to `streaming.bose.com/{accountId}/devices`
All cloud requests (steps 1, 6) will appear in mitmweb at `http://127.0.0.1:8081`.
---
## Phase 5 — Save & Inspect Recording
```bash
# Stop mitmweb (Ctrl-C in its terminal) — file is already written continuously
# Inspect offline
mitmweb -r "$CAPTURE"
# Filter to streaming.bose.com only
mitmdump -r "$CAPTURE" --flow-filter '~u streaming.bose.com' -w bose-cloud-only.mitm
# Quick text summary
mitmdump -r "$CAPTURE" --flow-filter '~u streaming.bose.com' 2>/dev/null \
| grep -E "POST|GET" | head -30
```
---
## Cleanup
```bash
# Remove proxy from emulator
adb -s emulator-5554 shell settings delete global http_proxy
# Kill emulator
adb -s emulator-5554 emu kill
```
Frida artefacts live in `scripts/android/` (gitignored) and persist between sessions — no cleanup needed unless you want to force a fresh setup.
---
## Troubleshooting
| Symptom | Likely cause | Fix |
|-------------------------------------|--------------------------------------------------|-----------------------------------------------------------------------------------|
| `curl http://192.0.2.1` times out | Mac not on speaker AP | Re-run `networksetup -setairportnetwork`; speaker AP gateway is `192.0.2.1` |
| `addWirelessProfile` returns error | Speaker not in AP mode or wrong IP | Confirm speaker AP is active; use `http://192.0.2.1:8090/addWirelessProfile` |
| Speaker not found via mDNS | Speaker still on AP (not home LAN yet) | Wait ~30 s, retry; check router DHCP leases |
| Emulator can't ping speaker | Different subnet or emulator proxy misconfigured | `adb shell ping` the Mac IP first; check proxy setting |
| App shows "No speakers found" | App not detecting mDNS | Ensure emulator is on same `/24` as speaker; disable emulator Wi-Fi and re-enable |
| No traffic in mitmweb | Frida not running or cert mismatch | Check Frida output for `== Certificate unpinning ==`; verify issuer in config.js |
## See Also
- [BOSE-APP-ADB-Emulator.md](../analysis/BOSE-APP-ADB-Emulator.md) — full MITM + Frida setup
- [DEVICE-INITIAL-SETUP.md](DEVICE-INITIAL-SETUP.md) — factory reset sequences + AP mode detail
- [DEVICE-SETUP.md](../DEVICE-SETUP.md) — WebSocket and cloud pairing protocol reference
---
## Session Trace (2026-05-02, ST10)
Raw log of the first interactive run. To be cleaned up into the runbook above.
### Setup
- Ran `scripts/android/setup-mitm-avd.sh` — all 9 steps completed:
- Steps 15 were already done from a prior session (idempotent skips)
- Step 6: Docker image built, `frida-server` extracted to `scripts/android/frida-server`
- Steps 79: Emulator started fresh (`-no-snapshot-load`), AVB disabled, rebooted, cert + APK + frida-server installed, snapshot `mitm-ready` saved
- Emulator running at `emulator-5554`
### Factory Reset (ST10)
- Correct sequence confirmed from official Bose guide (`firmware/FirmwareUpdateGuide/`):
**Power on → hold Preset 1 + Volume for 10 s → Wi-Fi LED glows solid amber**
- Note: original docs said "Vol + Mute" — corrected in DEVICE-INITIAL-SETUP.md and this file
### Pre-reset note
- User inserted USB device containing `remote_services` before rebooting — device needs to be on home Wi-Fi before the USB config takes effect
### Wi-Fi Provisioning (AP mode)
- `airport` command not available on this macOS version (removed in recent releases)
- Connect Mac to speaker AP via **System Settings → Wi-Fi** (SSID: "Bose SoundTouch XXXX")
- Speaker AP gateway confirmed: `192.0.2.1` (client gets `192.0.2.2`), not `192.168.1.1` as previously assumed
- `/gabbo_wifi` endpoint was hallucinated — actual endpoint verified from browser network capture (`_/device-reset/wifi-setup.txt`):
- Site survey: `POST http://192.0.2.1:8090/performWirelessSiteSurvey` with `<PerformWirelessSiteSurvey timeout="5"/>`
- Add profile: `POST http://192.0.2.1:8090/addWirelessProfile` with XML body, `securityType="wpa_or_wpa2"`
- Both use the standard SoundTouch API port 8090, same as normal device operation
- Response: `<AddWirelessProfileResponse />`
- Reconnect Mac to home Wi-Fi; emulator stays running throughout (routes through Mac interface)
### Wi-Fi Provisioning Result
- `POST http://192.0.2.1:8090/addWirelessProfile` succeeded → `<AddWirelessProfileResponse />`
- Speaker joined home LAN at `192.168.x.y`
- SSH access confirmed: `ssh -oHostKeyAlgorithms=ssh-rsa root@192.168.x.y`
- Device name: `Bose SoundTouch XXXXXX`
- Network interfaces on device:
- `wlan0``192.168.x.y` (home LAN)
- `wlan1``192.0.2.1` (AP mode interface, stays up after provisioning)
- `usb0``203.0.113.1` (USB gadget — remote_services USB device inserted before reboot)
### MITM Session Start
- `start-mitm-session.sh` run: snapshot restored, proxy set, frida-server started, config.js patched
- Issues encountered and fixed:
- frida-server start command hung (`adb shell su 0 ... &`) → fixed with `nohup ... > /dev/null 2>&1 &`
- Frida SSL scripts download via `curl` from GitHub timed out → moved to Dockerfile (extracted alongside frida-server)
- Python heredoc cert injection syntax error (multiline cert in string) → fixed using env vars + single-quoted `'PYEOF'` heredoc
- `mitmweb --port` deprecated → `--listen-port`
- frida script paths missing `android/` subdirectory → fixed in session script
- Docker mitmproxy (`-p 8080:8080`) received no traffic from emulator — Docker NAT layer prevented the emulator reaching it
- **Fix: use native macOS mitmproxy app** (`/Users/gesellix/Downloads/mitmproxy.app`) — binds directly to Mac's real network interfaces, traffic flows immediately
- Android system traffic visible (connectivity checks to gstatic.com, www.google.com) — TLS failures for system processes expected since only Bose app has Frida cert injection
### Capture Working
- Full flow confirmed working with:
- Native mitmproxy app (`~/Downloads/mitmproxy.app`, v12.2.2)
- `native-connect-hook.js` added to Frida launch (required for Bose app's native networking)
- All 5 Frida scripts loaded: config.js, native-connect-hook.js, android-system-certificate-injection.js, android-proxy-override.js, android-certificate-unpinning.js, android-certificate-unpinning-fallback.js
- Bose app traffic visible in mitmweb — pairing flow captured successfully
+92 -8
View File
@@ -31,7 +31,7 @@ The classic "failover" or "alternate" setup method.
1. Connect a PC/Phone to the device's Wi-Fi.
2. Open a browser to `http://192.168.1.1`.
3. The device serves `setup.html`, which redirects to a setup wizard (`setup/index.html`).
4. Use the `gabbo_wifi` form to select a network and enter credentials.
4. Use the Wi-Fi setup form to select a network and enter credentials (calls `POST http://192.0.2.1:8090/addWirelessProfile` via the SoundTouch API — see §6.3).
---
@@ -69,12 +69,96 @@ While the `soundtouch-service` focuses on migrating existing devices, a truly "c
---
---
## 5. Factory Reset Button Sequences
A factory reset wipes Wi-Fi credentials, account pairing, and all presets, returning the device to out-of-box state. The exact sequence varies by hardware generation.
> Sequences verified against official Bose reset guides in `firmware/FirmwareUpdateGuide/`. Confirm the reset succeeded by watching the status LEDs and by verifying the Wi-Fi indicator glows solid amber (setup mode).
| Model | Factory Restore Sequence | Confirm |
|--------------------------|---------------------------------------------------------------------|------------------------------------|
| SoundTouch 10 | Power on; hold **Preset 1** + **Volume ** for 10 s | Wi-Fi indicator glows solid amber |
| SoundTouch 20 | Power on; hold **Preset 1** + **Volume ** for 10 s | Lights blink L→R, then solid amber |
| SoundTouch 20 Series III | Hold **Preset 1** + **Preset 6** simultaneously for ~10 s | White LED sweep |
| SoundTouch 30 Series III | Hold **Preset 1** + **Preset 6** simultaneously for ~10 s | White LED sweep |
| SoundTouch 300 | Hold **Volume ** until light bar blinks rapidly (~15 s) | Rapid blink → off → on |
| SoundTouch 10 (alt) | Press and hold the back recessed **Reset** pinhole for 10 s | Status LED restarts |
| SoundTouch 20 (soft) | Hold **AUX** for 15 s until display goes blank (settings preserved) | Display blanks |
After factory restore the speaker enters setup mode automatically; no power-cycle is needed.
---
## 6. AP Mode Wi-Fi Provisioning via Console
When BLE is unavailable (e.g. when using an Android emulator), use AP mode to push Wi-Fi credentials from the Mac command line.
### 6.1 Connect Mac to Speaker AP
After factory reset the speaker broadcasts an SSID like `Bose SoundTouch XXXX`. Connect the Mac to it:
```bash
# List nearby SSIDs — use System Settings → Wi-Fi (the airport command was removed in macOS Sequoia+)
# Connect (replace with actual SSID)
networksetup -setairportnetwork en0 "Bose SoundTouch XXXX"
```
The speaker's web UI gateway is at `192.0.2.1` (verified: ST10 assigns `192.0.2.2` to the client via DHCP).
```bash
# Confirm reachability
curl -sv http://192.0.2.1/ 2>&1 | head -40
```
### 6.2 Trigger Wi-Fi Site Survey (Optional)
The setup web UI at `http://192.0.2.1/` uses the SoundTouch API on **port 8090** — the same API as normal device operation. Trigger a network scan first so the speaker finds your SSID:
```bash
curl -s -X POST http://192.0.2.1:8090/performWirelessSiteSurvey \
-H 'Content-Type: text/xml' \
--data-raw '<PerformWirelessSiteSurvey timeout="5"/>'
```
### 6.3 Push Home Wi-Fi Credentials
```bash
HOME_SSID="MyHomeNetwork"
HOME_PASS="MyPassword"
curl -s -X POST http://192.0.2.1:8090/addWirelessProfile \
-H 'Content-Type: text/xml' \
--data-raw "<AddWirelessProfile><profile ssid=\"${HOME_SSID}\" password=\"${HOME_PASS}\" securityType=\"wpa_or_wpa2\" /></AddWirelessProfile>"
```
Expected response: `<?xml version="1.0" encoding="UTF-8" ?><AddWirelessProfileResponse />`
The speaker will disconnect from AP mode and join the home network within ~1530 s.
### 6.4 Reconnect Mac to Home Network
```bash
networksetup -setairportnetwork en0 "MyHomeNetwork" "MyPassword"
```
Wait ~15 s for the speaker to join the home network, then verify:
```bash
# Discover the speaker's new IP via mDNS
dns-sd -B _soundtouch._tcp local &
sleep 5 ; kill %1
```
---
## Comparison: Initial Setup vs. Migration
| Feature | Initial Setup | Migration (soundtouch-service) |
| :--- | :--- | :--- |
| **Connectivity** | BLE, AP Mode, USB, WAC | Ethernet/Wi-Fi (existing) |
| **Credentials** | Required (SSID/Pass) | Not required (uses existing) |
| **Access** | Web UI / App protocol | SSH (root) |
| **Primary File** | `setup/index.html` | `SoundTouchSdkPrivateCfg.xml` |
| **Use Case** | Out-of-the-box / Reset | Redirecting active devices |
| Feature | Initial Setup | Migration (soundtouch-service) |
|:-----------------|:-----------------------|:-------------------------------|
| **Connectivity** | BLE, AP Mode, USB, WAC | Ethernet/Wi-Fi (existing) |
| **Credentials** | Required (SSID/Pass) | Not required (uses existing) |
| **Access** | Web UI / App protocol | SSH (root) |
| **Primary File** | `setup/index.html` | `SoundTouchSdkPrivateCfg.xml` |
| **Use Case** | Out-of-the-box / Reset | Redirecting active devices |
+25
View File
@@ -0,0 +1,25 @@
FROM python:3.11-slim
ARG FRIDA_VERSION=17.9.1
ARG FRIDA_TOOLS_VERSION=14.8.1
RUN apt-get update && \
apt-get install -y android-tools-adb xz-utils curl && \
rm -rf /var/lib/apt/lists/*
RUN pip install frida==${FRIDA_VERSION} frida-tools==${FRIDA_TOOLS_VERSION} objection
RUN curl -L "https://github.com/frida/frida/releases/download/${FRIDA_VERSION}/frida-server-${FRIDA_VERSION}-android-arm64.xz" \
-o /tmp/frida-server.xz && \
xz -d /tmp/frida-server.xz && \
mv /tmp/frida-server /usr/local/bin/frida-server-android && \
chmod +x /usr/local/bin/frida-server-android
ARG FRIDA_SCRIPTS_REPO=https://github.com/httptoolkit/frida-interception-and-unpinning
ARG FRIDA_SCRIPTS_REF=main
RUN mkdir -p /usr/local/share/frida-scripts && \
curl -L "${FRIDA_SCRIPTS_REPO}/archive/refs/heads/${FRIDA_SCRIPTS_REF}.tar.gz" \
-o /tmp/frida-scripts.tar.gz && \
tar -xzf /tmp/frida-scripts.tar.gz --strip-components=1 \
-C /usr/local/share/frida-scripts && \
rm /tmp/frida-scripts.tar.gz
+279
View File
@@ -0,0 +1,279 @@
#!/usr/bin/env bash
# One-time setup: install mitmproxy, obtain the Bose APK, create the Android
# emulator AVD, and save a ready-to-use snapshot so subsequent sessions skip
# the cert/APK install cycle.
#
# Run once, then use start-mitm-session.sh for day-to-day use.
# Tested on: Apple Silicon Mac (arm64)
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
ANDROID_DIR="${REPO_ROOT}/scripts/android"
# ── Config ────────────────────────────────────────────────────────────────────
SDK="${ANDROID_HOME:-${HOME}/Library/Android/sdk}"
SDKMANAGER="${SDK}/cmdline-tools/latest/bin/sdkmanager"
AVDMANAGER="${SDK}/cmdline-tools/latest/bin/avdmanager"
EMULATOR="${SDK}/emulator/emulator"
ADB="${SDK}/platform-tools/adb"
SYSTEM_IMAGE="system-images;android-33;google_apis;arm64-v8a"
AVD_NAME="bose-mitm"
AVD_DEVICE="pixel_6"
EMULATOR_SERIAL="emulator-5554"
SNAPSHOT_NAME="mitm-ready"
BOSE_APK="${BOSE_APK:-${ANDROID_DIR}/bose.apk}"
MITM_CA="${HOME}/.mitmproxy/mitmproxy-ca-cert.pem"
# Keep in sync with scripts/android/Dockerfile ARG defaults
MITMPROXY_IMAGE="mitmproxy/mitmproxy:12.2.1"
# Native macOS app (recommended for capture sessions — Docker NAT blocks emulator traffic)
MITMPROXY_NATIVE_URL="https://downloads.mitmproxy.org/12.2.2/mitmproxy-12.2.2-macos-arm64.tar.gz"
MITMPROXY_NATIVE_APP="/Applications/mitmproxy.app"
FRIDA_VERSION="17.9.1"
FRIDA_SERVER="${ANDROID_DIR}/frida-server"
# ── Helpers ───────────────────────────────────────────────────────────────────
info() { echo "$*"; }
ok() { echo "$*"; }
warn() { echo "$*"; }
die() { echo "$*" >&2; exit 1; }
confirm() {
local prompt="$1"
local reply
read -r -p " ${prompt} [y/N] " reply
[[ "${reply}" =~ ^[Yy]$ ]]
}
wait_for_boot() {
info "Waiting for emulator to finish booting..."
"${ADB}" -s "${EMULATOR_SERIAL}" wait-for-device
until "${ADB}" -s "${EMULATOR_SERIAL}" shell getprop sys.boot_completed 2>/dev/null | grep -q "1"; do
sleep 2
done
sleep 3
ok "Boot complete"
}
# ── Step 1: Check prerequisites ───────────────────────────────────────────────
echo ""
echo "Step 1/9 Check prerequisites"
# Docker — used for frida-server build and CA cert generation
command -v docker &>/dev/null || die "Docker not found. Install Docker Desktop and try again."
docker info &>/dev/null || die "Docker daemon not running. Start Docker Desktop and try again."
ok "Docker available"
# Native mitmproxy app — required for capture sessions (Docker NAT blocks emulator traffic)
if [[ -x "${MITMPROXY_NATIVE_APP}/Contents/MacOS/mitmweb" ]]; then
ok "mitmproxy native app found at ${MITMPROXY_NATIVE_APP}"
else
warn "mitmproxy native macOS app not found at ${MITMPROXY_NATIVE_APP}"
warn "Download and install it before running capture sessions:"
warn " curl -L '${MITMPROXY_NATIVE_URL}' -o /tmp/mitmproxy.tar.gz"
warn " tar -xzf /tmp/mitmproxy.tar.gz -C /Applications"
fi
# ── Step 2: Generate mitmproxy CA cert (via Docker) ───────────────────────────
echo ""
echo "Step 2/9 Generate mitmproxy CA certificate"
mkdir -p "${HOME}/.mitmproxy"
if [[ -f "${MITM_CA}" ]]; then
ok "CA cert already present at ${MITM_CA}"
else
info "Starting mitmproxy container briefly to generate CA..."
MITM_CID=$(docker run -d \
-v "${HOME}/.mitmproxy:/home/mitmproxy/.mitmproxy" \
"${MITMPROXY_IMAGE}" \
mitmdump --listen-host 0.0.0.0 --listen-port 8080)
sleep 4
docker stop "${MITM_CID}" > /dev/null
docker rm "${MITM_CID}" > /dev/null
if [[ -f "${HOME}/.mitmproxy/mitmproxy-ca.pem" ]]; then
openssl x509 -in "${HOME}/.mitmproxy/mitmproxy-ca.pem" -out "${MITM_CA}"
ok "CA cert extracted to ${MITM_CA}"
else
die "CA generation failed — ~/.mitmproxy/mitmproxy-ca.pem not found"
fi
fi
# Verify issuer
ISSUER=$(openssl x509 -in "${MITM_CA}" -noout -issuer 2>/dev/null)
if echo "${ISSUER}" | grep -qi "mitmproxy"; then
ok "Cert issuer: ${ISSUER}"
else
warn "Unexpected cert issuer: ${ISSUER} — verify ${MITM_CA} is the mitmproxy CA"
fi
# ── Step 3: Obtain Bose APK ───────────────────────────────────────────────────
echo ""
echo "Step 3/9 Obtain Bose SoundTouch APK"
if [[ -f "${BOSE_APK}" ]]; then
ok "APK already present at ${BOSE_APK}"
else
echo ""
echo " The Bose APK is needed but not found at ${BOSE_APK}."
echo " Two options:"
echo " a) Pull from a real Android device connected via USB"
echo " b) Download from a URL you provide (e.g. from APKMirror or APKPure)"
echo ""
echo " APKMirror: https://www.apkmirror.com/apk/bose-corporation/bose-soundtouch/"
echo " APKPure: https://apkpure.com/bose-soundtouch/com.bose.soundtouch"
echo ""
PS3=" Choose: "
select METHOD in "Pull from connected device (adb)" "Download from URL" "Skip (place APK manually later)"; do
case "${REPLY}" in
1)
info "Listing connected devices..."
"${ADB}" devices
read -r -p " Enter device serial (leave blank for default): " DEVICE_SERIAL
ADB_ARGS=()
[[ -n "${DEVICE_SERIAL}" ]] && ADB_ARGS=(-s "${DEVICE_SERIAL}")
APK_PATH=$("${ADB}" "${ADB_ARGS[@]}" shell pm path com.bose.soundtouch \
| tr -d '\r' | sed 's/package://')
[[ -n "${APK_PATH}" ]] || die "Bose app not found on device. Is it installed?"
info "Pulling ${APK_PATH}..."
"${ADB}" "${ADB_ARGS[@]}" pull "${APK_PATH}" "${BOSE_APK}"
ok "APK saved to ${BOSE_APK}"
break
;;
2)
read -r -p " Paste the direct APK download URL: " APK_URL
echo ""
echo " URL: ${APK_URL}"
echo " Destination: ${BOSE_APK}"
if confirm "Download from this URL?"; then
info "Downloading..."
curl -L --progress-bar "${APK_URL}" -o "${BOSE_APK}"
ok "APK saved to ${BOSE_APK}"
else
warn "Download skipped — place APK at ${BOSE_APK} before continuing"
fi
break
;;
3)
warn "Skipped — place APK at ${BOSE_APK} and re-run this script"
break
;;
*)
echo " Please enter 1, 2, or 3"
;;
esac
done
fi
[[ -f "${BOSE_APK}" ]] || die "APK not found at ${BOSE_APK} — cannot continue"
# ── Step 4: Install system image ─────────────────────────────────────────────
echo ""
echo "Step 4/9 Install Android system image"
if "${SDKMANAGER}" --list_installed 2>/dev/null | grep -q "${SYSTEM_IMAGE}"; then
ok "Already installed: ${SYSTEM_IMAGE}"
else
info "Installing ${SYSTEM_IMAGE} ..."
"${SDKMANAGER}" "${SYSTEM_IMAGE}"
ok "Installed"
fi
# ── Step 5: Create AVD ────────────────────────────────────────────────────────
echo ""
echo "Step 5/9 Create AVD '${AVD_NAME}'"
if "${AVDMANAGER}" list avd 2>/dev/null | grep -q "Name: ${AVD_NAME}"; then
ok "AVD '${AVD_NAME}' already exists — skipping creation"
else
echo no | "${AVDMANAGER}" create avd \
-n "${AVD_NAME}" \
-k "${SYSTEM_IMAGE}" \
-d "${AVD_DEVICE}"
ok "Created AVD '${AVD_NAME}'"
fi
# ── Step 6: Build frida image and extract frida-server ────────────────────────
echo ""
echo "Step 6/9 Frida server v${FRIDA_VERSION} (via Docker)"
if [[ -f "${FRIDA_SERVER}" ]]; then
ok "Already present at ${FRIDA_SERVER}"
else
info "Building frida Docker image (FRIDA_VERSION=${FRIDA_VERSION})..."
docker build -q \
--build-arg "FRIDA_VERSION=${FRIDA_VERSION}" \
-t "bose-frida:${FRIDA_VERSION}" \
"${ANDROID_DIR}"
info "Extracting frida-server binary and SSL scripts from image..."
CONTAINER_ID=$(docker create "bose-frida:${FRIDA_VERSION}")
docker cp "${CONTAINER_ID}:/usr/local/bin/frida-server-android" "${FRIDA_SERVER}"
docker cp "${CONTAINER_ID}:/usr/local/share/frida-scripts/." "${ANDROID_DIR}/frida/"
docker rm "${CONTAINER_ID}" > /dev/null
ok "Extracted to ${FRIDA_SERVER} and ${ANDROID_DIR}/frida/"
fi
# ── Step 7: Start emulator ────────────────────────────────────────────────────
echo ""
echo "Step 7/9 Start emulator with writable system"
if "${ADB}" devices | grep -q "${EMULATOR_SERIAL}"; then
info "Emulator already running — will reuse"
else
"${EMULATOR}" -avd "${AVD_NAME}" -writable-system -no-snapshot-load &
EMULATOR_PID=$!
info "Emulator PID: ${EMULATOR_PID}"
fi
wait_for_boot
# ── Step 8: Root + cert + APK + frida-server ─────────────────────────────────
echo ""
echo "Step 8/9 Configure emulator (root, cert, APK, frida-server)"
"${ADB}" -s "${EMULATOR_SERIAL}" root
"${ADB}" -s "${EMULATOR_SERIAL}" shell avbctl disable-verification
"${ADB}" -s "${EMULATOR_SERIAL}" reboot
wait_for_boot
"${ADB}" -s "${EMULATOR_SERIAL}" root
# Install mitmproxy CA cert
HASH=$(openssl x509 -inform PEM -subject_hash_old -in "${MITM_CA}" | head -1)
"${ADB}" -s "${EMULATOR_SERIAL}" push "${MITM_CA}" /data/local/tmp/mitmproxy.pem
"${ADB}" -s "${EMULATOR_SERIAL}" shell su 0 mkdir -p /data/misc/user/0/cacerts-added
"${ADB}" -s "${EMULATOR_SERIAL}" shell su 0 \
cp /data/local/tmp/mitmproxy.pem "/data/misc/user/0/cacerts-added/${HASH}.0"
"${ADB}" -s "${EMULATOR_SERIAL}" shell su 0 \
chmod 644 "/data/misc/user/0/cacerts-added/${HASH}.0"
ok "Certificate installed (hash: ${HASH})"
# Install Bose APK
if "${ADB}" -s "${EMULATOR_SERIAL}" shell pm list packages 2>/dev/null | grep -q "com.bose.soundtouch"; then
ok "Bose app already installed"
else
"${ADB}" -s "${EMULATOR_SERIAL}" install "${BOSE_APK}"
ok "Bose app installed"
fi
# Push frida-server
"${ADB}" -s "${EMULATOR_SERIAL}" push "${FRIDA_SERVER}" /data/local/tmp/frida-server
"${ADB}" -s "${EMULATOR_SERIAL}" shell su 0 chmod 755 /data/local/tmp/frida-server
ok "frida-server pushed"
# ── Step 9: Save snapshot ─────────────────────────────────────────────────────
echo ""
echo "Step 9/9 Save snapshot '${SNAPSHOT_NAME}'"
"${ADB}" -s "${EMULATOR_SERIAL}" emu avd snapshot save "${SNAPSHOT_NAME}"
ok "Snapshot '${SNAPSHOT_NAME}' saved"
# ── Done ──────────────────────────────────────────────────────────────────────
echo ""
echo "Setup complete."
echo ""
echo " AVD name : ${AVD_NAME}"
echo " Snapshot : ${SNAPSHOT_NAME}"
echo ""
echo "Start a capture session with:"
echo " scripts/android/start-mitm-session.sh"
echo ""
echo "Shut down the emulator when done:"
echo " ${ADB} -s ${EMULATOR_SERIAL} emu kill"
+157
View File
@@ -0,0 +1,157 @@
#!/usr/bin/env bash
# Per-session startup: restore the 'mitm-ready' emulator snapshot, refresh the
# proxy IP (the Mac's LAN IP can change between sessions), start frida-server,
# and print the Frida launch command for the Bose app.
#
# Prerequisites: run setup-mitm-avd.sh once first.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
ANDROID_DIR="${REPO_ROOT}/scripts/android"
# ── Config ────────────────────────────────────────────────────────────────────
SDK="${ANDROID_HOME:-${HOME}/Library/Android/sdk}"
EMULATOR="${SDK}/emulator/emulator"
ADB="${SDK}/platform-tools/adb"
AVD_NAME="bose-mitm"
EMULATOR_SERIAL="emulator-5554"
SNAPSHOT_NAME="mitm-ready"
PROXY_PORT=8080
FRIDA_SCRIPTS_DIR="${ANDROID_DIR}/frida"
FRIDA_VENV="${ANDROID_DIR}/frida-venv"
FRIDA_SERVER="${ANDROID_DIR}/frida-server"
# Keep in sync with scripts/android/Dockerfile ARG defaults
FRIDA_VERSION="17.9.1"
FRIDA_TOOLS_VERSION="14.8.1"
MITMPROXY_IMAGE="mitmproxy/mitmproxy:12.2.1"
# Detect native mitmproxy app location
MITMPROXY_NATIVE=""
for candidate in "/Applications/mitmproxy.app" "${HOME}/Downloads/mitmproxy.app"; do
if [[ -x "${candidate}/Contents/MacOS/mitmweb" ]]; then
MITMPROXY_NATIVE="${candidate}/Contents/MacOS/mitmweb"
break
fi
done
[[ -n "${MITMPROXY_NATIVE}" ]] || die "mitmproxy native app not found. See setup-mitm-avd.sh for download instructions."
# ── Helpers ───────────────────────────────────────────────────────────────────
info() { echo "$*"; }
ok() { echo "$*"; }
die() { echo "$*" >&2; exit 1; }
wait_for_boot() {
"${ADB}" -s "${EMULATOR_SERIAL}" wait-for-device
until "${ADB}" -s "${EMULATOR_SERIAL}" shell getprop sys.boot_completed 2>/dev/null | grep -q "1"; do
sleep 2
done
sleep 2
}
# ── Mac IP ────────────────────────────────────────────────────────────────────
MAC_IP=$(ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null)
[[ -n "${MAC_IP}" ]] || die "Could not determine Mac LAN IP. Are you connected to Wi-Fi?"
ok "Mac IP: ${MAC_IP}"
# ── Start emulator from snapshot ──────────────────────────────────────────────
echo ""
echo "Step 1/4 Start emulator from snapshot '${SNAPSHOT_NAME}'"
if "${ADB}" devices | grep -q "${EMULATOR_SERIAL}"; then
ok "Emulator already running — skipping launch"
else
"${EMULATOR}" -avd "${AVD_NAME}" -writable-system \
-snapshot "${SNAPSHOT_NAME}" &
info "Emulator starting (PID $!)..."
fi
wait_for_boot
"${ADB}" -s "${EMULATOR_SERIAL}" root
ok "Emulator ready"
# ── Refresh proxy IP ──────────────────────────────────────────────────────────
echo ""
echo "Step 2/4 Set proxy to ${MAC_IP}:${PROXY_PORT}"
"${ADB}" -s "${EMULATOR_SERIAL}" shell settings put global http_proxy "${MAC_IP}:${PROXY_PORT}"
ok "Proxy configured"
# ── Start frida-server ────────────────────────────────────────────────────────
echo ""
echo "Step 3/4 Start frida-server"
[[ -f "${FRIDA_SERVER}" ]] || die "frida-server not found at ${FRIDA_SERVER}. Run setup-mitm-avd.sh first."
"${ADB}" -s "${EMULATOR_SERIAL}" shell su 0 \
'pgrep frida-server > /dev/null && echo already_running || (nohup /data/local/tmp/frida-server > /dev/null 2>&1 &)'
sleep 2
ok "frida-server running"
# ── Check SSL bypass scripts ──────────────────────────────────────────────────
echo ""
echo "Step 4/4 Ensure Frida SSL scripts are present"
[[ -f "${FRIDA_SCRIPTS_DIR}/config.js" ]] || \
die "Frida SSL scripts not found at ${FRIDA_SCRIPTS_DIR}. Run setup-mitm-avd.sh first."
# Patch config.js with current MAC IP and mitmproxy cert
CERT_PEM=$(openssl x509 -in ~/.mitmproxy/mitmproxy-ca-cert.pem)
FRIDA_SCRIPTS_DIR="${FRIDA_SCRIPTS_DIR}" MAC_IP="${MAC_IP}" \
PROXY_PORT="${PROXY_PORT}" CERT_PEM="${CERT_PEM}" \
python3 - <<'PYEOF'
import re, pathlib, os
cfg_path = pathlib.Path(os.environ["FRIDA_SCRIPTS_DIR"] + "/config.js")
cfg = cfg_path.read_text()
cfg = re.sub(r"const PROXY_HOST = '[^']*'", f"const PROXY_HOST = '{os.environ['MAC_IP']}'", cfg)
cfg = re.sub(r"const PROXY_PORT = [0-9]+", f"const PROXY_PORT = {os.environ['PROXY_PORT']}", cfg)
cfg = re.sub(r"const CERT_PEM = `[^`]*`", f"const CERT_PEM = `{os.environ['CERT_PEM']}`", cfg)
cfg_path.write_text(cfg)
print(f" ✓ config.js updated (proxy={os.environ['MAC_IP']}:{os.environ['PROXY_PORT']})")
PYEOF
# Ensure frida Python package matches server version
if [[ ! -d "${FRIDA_VENV}" ]]; then
info "Creating frida venv at ${FRIDA_VENV}..."
python3 -m venv "${FRIDA_VENV}"
"${FRIDA_VENV}/bin/pip" install -q "frida==${FRIDA_VERSION}" "frida-tools==${FRIDA_TOOLS_VERSION}"
fi
ok "Frida scripts ready at ${FRIDA_SCRIPTS_DIR}"
# ── Instructions ──────────────────────────────────────────────────────────────
CAPTURES_DIR="${ANDROID_DIR}/captures"
mkdir -p "${CAPTURES_DIR}"
cat <<INSTRUCTIONS
Session ready. In a separate terminal:
1. Start mitmweb (native macOS app):
CAPTURE="bose-pairing-\$(date +%Y%m%d-%H%M%S).mitm"
${MITMPROXY_NATIVE} \\
--web-host 0.0.0.0 --listen-port ${PROXY_PORT} --mode regular \\
--set web_password=bose \\
-w "${CAPTURES_DIR}/\${CAPTURE}"
Captures → ${CAPTURES_DIR}/
Web UI → http://127.0.0.1:8081/?token=bose
Note: Docker mitmproxy does not work — its NAT layer blocks emulator traffic.
2. Launch Bose app with SSL unpinning:
${FRIDA_VENV}/bin/frida \\
-U \\
-f com.bose.soundtouch \\
-l ${FRIDA_SCRIPTS_DIR}/config.js \\
-l ${FRIDA_SCRIPTS_DIR}/native-connect-hook.js \\
-l ${FRIDA_SCRIPTS_DIR}/android/android-system-certificate-injection.js \\
-l ${FRIDA_SCRIPTS_DIR}/android/android-proxy-override.js \\
-l ${FRIDA_SCRIPTS_DIR}/android/android-certificate-unpinning.js \\
-l ${FRIDA_SCRIPTS_DIR}/android/android-certificate-unpinning-fallback.js
3. Operate the Bose app in the emulator.
To dump the current UI for adb automation:
adb -s ${EMULATOR_SERIAL} shell uiautomator dump /sdcard/ui.xml
adb -s ${EMULATOR_SERIAL} pull /sdcard/ui.xml /tmp/ui.xml
To take a screenshot:
adb -s ${EMULATOR_SERIAL} shell screencap /sdcard/screen.png
adb -s ${EMULATOR_SERIAL} pull /sdcard/screen.png /tmp/screen.png && open /tmp/screen.png
INSTRUCTIONS