A cross-platform tool to stream live system audio from your Linux, Windows, or macOS machine directly to a Sony Bravia TV over the local network — by exploiting its unauthenticated UPnP/DLNA control interface.
This repository accompanies the technical write-up: How I Hacked my Living Room: Unauthenticated Control of a Sony Bravia TV via UPnP/DLNA.
Many modern smart TVs (including Sony Bravia models) expose UPnP (Universal Plug and Play) and DLNA (Digital Living Network Alliance) endpoints on the local network. Because these protocols are designed for frictionless media sharing, they often do not implement any authentication or permission checks for devices on the same subnet.
By crafting raw SOAP XML requests, we can:
- Forcibly alter the TV volume (overriding manual controls).
- Set the media source (
AVTransportURI) to an arbitrary HTTP stream URL. - Trigger media playback (
Playcommand).
All of this happens without any visual prompt, pairing PIN, or permission dialog appearing on the TV screen.
tvstreamer/
├── tv-speaker.py ⭐ Cross-platform controller (Linux / Windows / macOS)
├── audio_server.py DLNA-compatible streaming server (auto-detects OS audio backend)
├── tv-speaker.sh Original bash controller (Linux legacy)
└── LICENSE
flowchart LR
subgraph sender ["Your Machine (Sender)"]
A["System Audio\n(PulseAudio / WASAPI / AVFoundation)"] -->|"Loopback Capture"| B("FFmpeg Encoder")
B -->|"MP3 Stream"| C["Flask Server (:8000)\naudio_server.py"]
D["tv-speaker.py"] -.->|"1. Launch"| C
end
subgraph receiver ["Sony Bravia TV (Receiver)"]
E["UPnP / DLNA Endpoint\n(:2870)"]
F["Media Player"]
end
D -->|"2. SOAP: SetVolume"| E
D -->|"3. SOAP: SetAVTransportURI"| E
D -->|"4. SOAP: Play"| E
E -.->|"Triggers Playback"| F
F -->|"5. HTTP GET /stream.mp3"| C
A lightweight Flask HTTP server that:
- Auto-detects the OS and selects the correct ffmpeg audio backend:
Platform ffmpeg backend Audio Source Linux -f pulsePulseAudio / PipeWire monitor (auto-detected via pactl)Windows -f dshowStereo Mix / virtual loopback device (auto-scanned) macOS -f avfoundationVirtual audio device e.g. BlackHole (index configurable) - Encodes captured loopback audio on-the-fly to MP3 (192kbps, 44.1kHz stereo).
- Streams continuously at
/stream.mp3with proper DLNA compliance headers.
tv-speaker.py ⭐ (recommended)
A fully dynamic cross-platform Python controller:
- 🔍 SSDP auto-discovery — scans the LAN for Sony Bravia / UPnP MediaRenderers, no IP config needed.
- 🌐 Auto-detects local machine IP for the stream URL.
- 🖥️ Interactive picker when multiple devices are found on the network.
- 🎨 Colourful ASCII banner and rich terminal output.
- 🛑 Graceful Ctrl+C — sends a UPnP
Stopto the TV before quitting. - Zero extra deps beyond Flask — all SOAP via stdlib
urllib.
tv-speaker.sh (Linux legacy)
The original bash script. Requires curl and hardcoded IP variables. Kept for reference.
| Dependency | Linux | Windows | macOS |
|---|---|---|---|
| Python 3.8+ | ✅ | ✅ | ✅ |
| Flask | ✅ | ✅ | ✅ |
| FFmpeg | ✅ | ✅ | ✅ |
| PulseAudio / PipeWire | ✅ | ❌ | ❌ |
| Stereo Mix (loopback) | ❌ | ✅ | ❌ |
| BlackHole / Soundflower | ❌ | ❌ | ✅ |
pip install flask🐧 Linux — FFmpeg install
sudo apt install ffmpeg # Debian / Ubuntu
sudo pacman -S ffmpeg # Arch Linux
sudo dnf install ffmpeg # Fedora🪟 Windows — FFmpeg + Stereo Mix setup
- Download FFmpeg from https://ffmpeg.org/download.html and add it to your
PATH. - Enable Stereo Mix (Windows system audio loopback):
- Right-click the speaker icon → Sounds → Recording tab
- Right-click in empty space → Show Disabled Devices
- Right-click Stereo Mix → Enable
- If your soundcard doesn't have Stereo Mix, install VB-Audio Virtual Cable as a loopback alternative.
Note:
tv-speaker.pywill auto-scan and prefer loopback devices (Stereo Mix, Wave Out Mix, etc.).
If it picks the wrong device, setAUDIO_DEVICEmanually (see below).
🍎 macOS — Virtual audio device setup
- Install BlackHole:
brew install blackhole-2ch
- Open Audio MIDI Setup → create a Multi-Output Device with both your speakers and BlackHole.
- Set system output to the Multi-Output Device.
- BlackHole becomes the loopback capture source for ffmpeg.
# Auto-discover TV and start streaming
python tv-speaker.py
# Scan for TVs on your network, then exit
python tv-speaker.py --scan-only
# Skip SSDP discovery — specify everything manually
python tv-speaker.py --tv-ip 192.168.1.50 --host-ip 192.168.1.10 --volume 75
# All options
python tv-speaker.py --helpCLI flags:
| Flag | Description | Default |
|---|---|---|
--tv-ip <ip> |
TV IP — skips SSDP scan | auto-discover |
--host-ip <ip> |
Local machine IP for stream URL | auto-detect |
--port <port> |
Audio server port | 8000 |
--volume <0-100> |
TV volume to set | 80 |
--monitor <name> |
Linux: PulseAudio/PipeWire monitor sink to capture | interactive picker |
--scan-only |
Print found TVs and exit | — |
--list-monitors |
Linux: print all available monitor sinks and exit | — |
Override audio device if auto-detection picks the wrong source:
# Linux — list all available monitor sinks
python tv-speaker.py --list-monitors
# Linux — pin a specific monitor sink (skip the interactive picker)
python tv-speaker.py --monitor "alsa_output.pci-0000_00_1f.3.HiFi__Speaker__sink.monitor"
# macOS — set avfoundation device index
export AUDIO_DEVICE=1
# Windows (PowerShell) — set dshow device name
$env:AUDIO_DEVICE = "Stereo Mix (Realtek HD Audio)"
python tv-speaker.pyFind your Linux monitor sinks manually:
pactl list short sources | grep monitorThe interactive picker (shown when multiple exist) shows the friendly name alongside the sink name so you can easily identify Speakers, HDMI, USB audio, etc.
# Edit TV_IP, LAP_IP, MONITOR inside the script first, then:
chmod +x tv-speaker.sh
./tv-speaker.shBoth tv-speaker.py and tv-speaker.sh send raw SOAP XML over HTTP to the TV's UPnP endpoints at port 2870.
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:SetVolume xmlns:u="urn:schemas-upnp-org:service:RenderingControl:1">
<InstanceID>0</InstanceID>
<Channel>Master</Channel>
<DesiredVolume>80</DesiredVolume>
</u:SetVolume>
</s:Body>
</s:Envelope><?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:SetAVTransportURI xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
<InstanceID>0</InstanceID>
<CurrentURI>http://<HOST_IP>:8000/stream.mp3</CurrentURI>
<CurrentURIMetaData></CurrentURIMetaData>
</u:SetAVTransportURI>
</s:Body>
</s:Envelope><?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:Play xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
<InstanceID>0</InstanceID>
<Speed>1</Speed>
</u:Play>
</s:Body>
</s:Envelope>If you own a Sony Bravia and want to prevent this kind of unauthenticated control:
- Enable IP Control authentication —
Settings → Network → Home Network → IP Control- Set Authentication to Normal or Pre-Shared Key (not None).
- Disable Simple IP Control if unused.
- Network segmentation — Put smart TVs and IoT devices on a dedicated VLAN or guest network, isolated from your primary LAN.
- Disable UPnP on your router — Prevents UPnP port mappings and reduces attack surface from devices on the LAN.
This repository is licensed under the MIT License. Feel free to use, modify, and distribute.
Made by 6cloudguy — check out the full technical write-up on how the exploit was discovered:
How I Hacked my Living Room: Unauthenticated Control of a Sony Bravia TV via UPnP/DLNA