I've got an Inseego MiFi M2000 that's currently doing something it was never intended to do. It's running my home automation setup.
The M2000 is a 5G mobile hotspot. Normally you turn it on, connect a few devices to its Wi-Fi, and use it for internet access. Mine runs an MQTT broker, a custom C daemon, a web interface, and a bunch of automation services. I also wrote an Android app to control everything.
I didn't discover some exciting new exploit to make this happen. The entry point was already known. I just wanted root access to the thing, and once I had it, there wasn't much reason to stop there.
getting into the MiFi
The M2000 runs embedded Linux, although Inseego would rather you interact with it through their web interface and leave the rest alone.
The useful entry point was its OpenVPN configuration functionality. The web interface accepts uploaded VPN profiles, and the underlying processing path can be abused to execute commands through OpenVPN's script functionality.
My m2000_rce.py script handles authentication, builds a configuration containing the command I want to execute, and submits it through the VPN interface.
The vulnerability itself isn't my research, and I'm not claiming otherwise. I was interested in what I could do with the access.
Once I had command execution, I started making the changes persistent. That meant modifying the startup environment, enabling ADB access, and adding boot hooks for my own services. I also disabled the firmware update clients because having the vendor replace my modified environment with an update would be rather inconvenient.
Some of the work was just getting the thing to behave like a normal Linux machine. The M2000 has its own ideas about networking and startup order, and those ideas don't necessarily agree with running services unrelated to being a hotspot.
I eventually had it joining my regular LAN and starting my own processes on boot.
putting Mosquitto to work
The device already had Mosquitto utilities available, which was convenient because I wanted MQTT to be the common interface for everything.
I don't particularly like making every smart device depend on a manufacturer's cloud service. If I have a light or a smart plug sitting three feet away from me, I shouldn't need somebody else's server to tell it to turn on.
The Meross plugs were one of the more annoying parts.
Pointing them at a local broker wasn't sufficient. They could establish an MQTT connection and still not actually behave as usable devices.
Turns out there's a binding process involved. The devices expect specific messages and acknowledgments before they'll accept the connection as properly established. Meross also signs its messages, so there was some protocol work involved beyond publishing ON to a topic and hoping for the best.
I ended up writing bind-plug.sh to handle the binding process and a local switch interface to send properly formed commands.
The end result is that I can control the plugs locally without asking Meross for permission every time.
I kept the vendor-specific handling on the MiFi rather than spreading that logic across every client that might want to control a switch.
the gateway got a little out of hand
Once the broker was handling plugs, I wanted a way to see everything connected to it.
I could have installed a conventional home automation stack on another computer. I already had working MQTT infrastructure, though, and the MiFi was sitting there running Linux. Adding an entire separate machine just to put a few controls on a screen seemed unnecessary.
So I wrote mqtt-gateway.
It's a C daemon compiled as a static ARMv7 musl executable. It uses the existing mosquitto_sub and mosquitto_pub programs for transport, maintains an inventory of observed MQTT topics, and exposes a small HTTP server.
The basic arrangement is:
MQTT devices
|
v
Mosquitto
|
+---- mqtt-gateway
| |
| +---- topic discovery
| +---- device state
| +---- HTTP dashboard
| +---- validated controls
|
+---- other MQTT clients
The gateway subscribes to traffic, recognizes topic patterns it understands, and groups related messages into devices.
For example, something publishing under:
device/switch/entity/state
can be associated with its corresponding command topic, provided it follows the expected convention.
I deliberately didn't make every discovered topic writable. Seeing a message on MQTT doesn't establish what commands the device supports, and randomly publishing to unfamiliar topics is a good way to make things do stupid shit.
Unknown devices can still appear in the topic inventory without the gateway pretending it knows how to control them.
The web interface grew support for WLED lighting, ESPHome devices, Govee Bluetooth sensors, weather data, Wake-on-LAN, and Matter devices.
WLED was fairly straightforward because its MQTT interface already exposes useful state and control topics. The gateway can handle brightness, colors, effects, palettes, presets, and transitions. It also fetches effect and palette names from the devices so I don't have to remember which arbitrary number corresponds to which lighting effect.
The Bluetooth sensors took a little more work.
My Govee H5075 and H5179 sensors publish advertisements through MQTT-connected BLE scanners. The temperature, humidity, and battery readings are packed into manufacturer-specific data, so the gateway has to decode that rather than expecting a nice JSON field called temperature.
It also has to deduplicate devices. If several scanners hear the same thermometer, I don't need three thermometers appearing in the dashboard.
There's support for Govee motion advertisements too.
The Matter side currently includes a Govee H7126 air purifier. Its state comes through MQTT as Matter cluster and attribute values, which the gateway converts into something a human might actually want to see. Power, fan percentage, air quality, and filter condition are useful. A screen full of cluster IDs isn't.
The purifier only exposes a categorical air-quality value through the data I'm getting. It doesn't provide numeric particulate concentration, so I'm not going to invent one.
I also added weather, a few display controls, and assorted other things that were already publishing MQTT messages around the house.
At this point the thing was becoming a reasonably useful home automation controller.
keeping the little bastard running
There's a difference between getting an ARM binary to execute once and making it survive unattended on a consumer hotspot.
The gateway needs the MiFi's LAN interface to have an address before its HTTP server can bind. The launcher waits for that rather than assuming the network is ready as soon as the boot script runs.
It also supervises the process and starts it again if it dies.
State is stored in compact TSV files. The gateway maintains a registry of up to 1024 observed topics, along with saved names and classifications. It batches writes instead of hammering the flash every time a sensor publishes something.
That matters when you're running on embedded storage that wasn't intended to serve as a general-purpose database.
I also had to deal with the web interface itself. There's no reason to render a thousand raw MQTT topics every time the page refreshes, so the routine dashboard stays relatively small and the full inventory is only rendered when requested.
A browser disconnect shouldn't kill the daemon either. It ignores SIGPIPE rather than letting an aborted HTTP connection take down the whole process.
None of this is especially glamorous. It's mostly making sure the services don't fall over because the network wasn't ready or somebody closed a browser tab.
Bloodsucker
The web dashboard worked, but I wanted a proper Android interface.
So I made Bloodsucker.
Yes, it's a Mosquitto joke. I'm aware.
Bloodsucker is a native Kotlin/Jetpack Compose app using Material 3. It connects directly to the MQTT broker and discovers devices from their messages rather than requiring me to manually define every switch, light, and sensor in the app.
It supports the WLED lights, locally bridged switches, Govee BLE sensors, weather data, Matter devices, the gateway's own switches, and Wake-on-LAN.
The important bit is that it talks to MQTT directly.
The gateway handles the ugly protocol translation where necessary. Bloodsucker doesn't need to know how Meross signs its messages or how its binding process works. It can publish a command to one of the gateway's normalized topics and let the MiFi deal with the device.
For WLED and other devices with useful native MQTT interfaces, the app can talk to those topics directly.
I wanted something that behaved like an actual smart-home app, not an MQTT debugger with some buttons bolted onto it.
Devices are discovered automatically, and the app stores aliases, rooms, favorites, and its own installation identity locally. It also rebuilds its state from retained MQTT messages when it reconnects.
That last part is useful because I don't need a constantly running Android background service just to remember whether a light was on when I last opened the app. MQTT already has a mechanism for retaining state.
Bloodsucker currently connects while the app process is alive. It's not intended to be an always-running notification or monitoring service.
The repository is GPLv3, so anybody who wants to adapt it can fork it. It's also pretty opinionated about how my house works, which is why I describe it as a reference implementation rather than pretending it's a general-purpose product.
I'm not interested in building another enormous smart-home framework.
why not just use Home Assistant?
Because I didn't want to.
That's probably not a satisfying technical explanation, but it is part of the reason.
Home Assistant can handle most of the equipment involved here, and I'm not claiming this setup replaces its capabilities. There's plenty of functionality in HA that I haven't implemented and have no intention of implementing.
I wanted the local MQTT broker to be the common interface, with devices and small services publishing useful state and accepting explicit commands.
I also wanted control over what the system actually does without maintaining another large application and its dependencies.
The MiFi already had Linux, networking, and enough room to run a small service. Writing a compact gateway around the things I actually use was a reasonable tradeoff for me.
It certainly isn't the easiest route if your objective is to turn on a light.
But I already own the hardware, and screwing with this sort of thing is half the point.
some obvious problems
This is still a rooted consumer hotspot running software it was never designed to run.
The firmware is proprietary and unsupported for this purpose. I've modified startup behavior and disabled update mechanisms. That gives me control, but it also means maintenance and security are now my problem.
The MQTT broker currently allows anonymous connections on the local network. The gateway also exposes local HTTP controls without application-level authentication.
I keep those services confined to my LAN and block cellular-facing access, but that's not the same thing as having proper authentication. If I were deploying this on an untrusted network, I'd want broker credentials, TLS, and authenticated control endpoints.
There's also the obvious issue of depending on one aging piece of hardware. If the M2000 dies, the services running on it die too.
And while the gateway's storage writes are deliberately limited, that doesn't magically turn the flash into enterprise-grade storage.
I haven't done a proper power consumption comparison against a Raspberry Pi or other small Linux machine, so I'm not claiming this is the most efficient way to run the setup.
It's the hardware I already had, and it does the job.
so now a hotspot runs my house
The funny part is that the root exploit was probably the least interesting piece of the whole project.
The more involved work was getting the various devices talking locally, dealing with their different protocols, writing a daemon that could recognize and control them, and getting the whole thing to survive reboots without intervention.
I didn't really set out to build a home automation platform on a MiFi. I got access to a Linux machine that wasn't supposed to be a Linux machine I could use, found it had enough resources to do something useful, and kept adding things.
Now there's an Android app called Bloodsucker talking to Mosquitto on a rooted 5G hotspot, which in turn controls a bunch of equipment around my house.
It's a fairly ridiculous arrangement.
I like it.