Bose SoundTouch Toolkit

Documentation for controlling and preserving Bose SoundTouch devices

View the Project on GitHub gesellix/Bose-SoundTouch

Bose SoundTouch Traffic Interception Runbook

Intercept HTTPS/WebSocket traffic from the Bose SoundTouch Android app using an Android emulator, mitmproxy, and Frida. Tested on Apple Silicon (ARM64) Mac.

Prerequisites

Add Android SDK tools to your PATH (add to ~/.zshrc):

export PATH=$PATH:~/Library/Android/sdk/emulator
export PATH=$PATH:~/Library/Android/sdk/platform-tools

1. Extract APK from Real Device

Connect your Android device via USB with USB debugging enabled.

adb devices
# note your device ID, e.g. "ABC123"

adb -s ABC123 shell pm path com.bose.soundtouch
# output e.g.: package:/data/app/~~xyz/com.bose.soundtouch-abc/base.apk

adb -s ABC123 pull /data/app/~~xyz/com.bose.soundtouch-abc/base.apk bose.apk

2. Create Android Emulator (ARM64, API 33)

On Apple Silicon you need an ARM64 image. Use the avdmanager and sdkmanager CLI tools.

# Install the system image
~/Library/Android/sdk/cmdline-tools/latest/bin/sdkmanager \
  "system-images;android-33;google_apis;arm64-v8a"

# Create the AVD
~/Library/Android/sdk/cmdline-tools/latest/bin/avdmanager create avd \
  -n Pixel_6_API33 \
  -k "system-images;android-33;google_apis;arm64-v8a" \
  -d "pixel_6"

Alternatively create the AVD via Android Studio Device Manager (choose “Google APIs”, arm64-v8a, API 33).


3. Start Emulator with Writable System

# List available AVDs
~/Library/Android/sdk/emulator/emulator -list-avds

# Start with writable system partition
~/Library/Android/sdk/emulator/emulator -avd Pixel_6_API33 -writable-system

Wait until the emulator has fully booted, then:

adb -s emulator-5554 root
adb -s emulator-5554 shell avbctl disable-verification
adb -s emulator-5554 reboot

# After reboot:
adb -s emulator-5554 root

4. Install Bose APK

adb -s emulator-5554 install bose.apk

5. Set Up mitmproxy

# Start mitmproxy (generates CA cert on first run)
mitmweb --port 8080 --mode regular -w bose_traffic.mitm

Extract the CA certificate (without private key):

openssl x509 -in ~/.mitmproxy/mitmproxy-ca.pem -out ~/.mitmproxy/mitmproxy-ca-cert.pem

# Verify it's the mitmproxy cert, not another cert:
openssl x509 -in ~/.mitmproxy/mitmproxy-ca-cert.pem -noout -issuer
# should show: issuer= /CN=mitmproxy/O=mitmproxy

6. Install mitmproxy CA Certificate in Emulator

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

7. Set System Proxy in Emulator

Find your Mac’s local IP:

ipconfig getifaddr en0
# e.g. 192.168.1.123

Set the proxy:

adb -s emulator-5554 shell settings put global http_proxy 192.168.1.123:8080

8. Set Up Frida (via Python venv)

python3 -m venv /tmp/frida-venv
/tmp/frida-venv/bin/pip install frida==17.9.1 frida-tools==14.8.1

Download the frida-server binary for ARM64 Android:

FRIDA_VERSION=17.9.1

curl -L "https://github.com/frida/frida/releases/download/${FRIDA_VERSION}/frida-server-${FRIDA_VERSION}-android-arm64.xz" \
  -o /tmp/frida-server.xz

unxz /tmp/frida-server.xz
mv /tmp/frida-server-${FRIDA_VERSION}-android-arm64 /tmp/frida-server

Push to emulator and start:

adb -s emulator-5554 push /tmp/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 /data/local/tmp/frida-server &

9. Download SSL Bypass Scripts

BASE=https://raw.githubusercontent.com/httptoolkit/frida-interception-and-unpinning/main

curl -L "${BASE}/config.js" -o /tmp/config.js
curl -L "${BASE}/android/android-system-certificate-injection.js" \
  -o /tmp/android-system-certificate-injection.js
curl -L "${BASE}/android/android-proxy-override.js" \
  -o /tmp/android-proxy-override.js
curl -L "${BASE}/android/android-certificate-unpinning.js" \
  -o /tmp/android-certificate-unpinning.js
curl -L "${BASE}/android/android-certificate-unpinning-fallback.js" \
  -o /tmp/android-certificate-unpinning-fallback.js

10. Configure config.js

Edit /tmp/config.js and set:

const CERT_PEM = `<contents of ~/.mitmproxy/mitmproxy-ca-cert.pem>`;

const PROXY_HOST = '192.168.1.123';  // your Mac IP
const PROXY_PORT = 8080;

Insert the full PEM content (from -----BEGIN CERTIFICATE----- to -----END CERTIFICATE-----) between the backticks.

Quick check that the right cert is in place:

# The issuer inside config.js should be mitmproxy, not SoundTouch
grep -A3 "CERT_PEM" /tmp/config.js | head -5

11. Start Interception

Make sure mitmweb is running, then:

/tmp/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

Expected output in the Frida REPL:

== System certificate trust injected ==
== Proxy system configuration overridden to 192.168.1.123:8080 ==
== Proxy configuration overridden to 192.168.1.123:8080 ==
== Certificate unpinning completed ==
== Unpinning fallback auto-patcher installed ==

Open mitmweb at http://127.0.0.1:8081 to observe traffic live.


12. Save & Replay Recordings

Traffic is saved to bose_traffic.mitm (set via -w flag in step 5).

# Replay/analyse a saved recording:
mitmweb -r bose_traffic.mitm

Cleanup

# Remove proxy setting from emulator
adb -s emulator-5554 shell settings delete global http_proxy

# Remove venv
rm -rf /tmp/frida-venv /tmp/frida-server /tmp/frida-server.xz
rm /tmp/config.js /tmp/android-*.js

# Stop emulator
adb -s emulator-5554 emu kill

Troubleshooting

Symptom Cause Fix
remount failed ARM64 emulator doesn’t support overlayfs remount Use /data/misc/user/0/cacerts-added/ method instead
TLS: Trust anchor not found Wrong certificate in config.js Check issuer: must be mitmproxy, not SoundTouch
Chain validation failed Private key included in cert Re-extract with openssl x509 -in mitmproxy-ca.pem -out mitmproxy-ca-cert.pem
frida-server: connection refused frida-server not running Re-run adb shell su 0 /data/local/tmp/frida-server &
frida and frida-server version mismatch Versions must be identical Pin both to same version (e.g. 17.9.1)
emulator: multiple AVDs error Emulator already running Kill first: adb emu kill, then restart with -writable-system

App Automation Options

For most traffic-recording purposes, manually operating the app while mitmproxy captures is sufficient. If you need to automate specific interactions (e.g. to repeatably capture the requests triggered by startup or a particular action), the following tools are available.

Starting the App

# Via app drawer: swipe up on the home screen and tap "Bose SoundTouch"

# Via adb monkey (simplest)
adb -s emulator-5554 shell monkey -p com.bose.soundtouch 1

# Via explicit intent (if the activity name is known)
adb -s emulator-5554 shell am start -n com.bose.soundtouch/.MainActivity

# Look up all activities if the name is unknown
adb -s emulator-5554 shell dumpsys package com.bose.soundtouch | grep Activity

adb — sufficient for simple cases

# Tap at screen coordinates
adb shell input tap 540 960

# Swipe
adb shell input swipe 540 1500 540 500

# Type text
adb shell input text "mytext"

# Take a screenshot
adb shell screencap /sdcard/screen.png && adb pull /sdcard/screen.png

UIAutomator2 — inspect UI elements

# Dump the current UI hierarchy to find element IDs
adb shell uiautomator dump /sdcard/ui.xml
adb pull /sdcard/ui.xml

Open ui.xml to find element resource IDs, then target them precisely in scripts.

Appium — full scripted automation

from appium import webdriver

driver = webdriver.Remote('http://localhost:4723/wd/hub', {
    'platformName': 'Android',
    'appPackage': 'com.bose.soundtouch',
    'appActivity': '.MainActivity',
})

# Find an element by resource ID and tap it
driver.find_element('id', 'com.bose.soundtouch:id/play_button').click()

Note: monkey is a stress-test tool that sends random events — use it only to launch the app, not to drive specific interactions.