Fork of srlabs/blue-merle for analysis + back-port contributions. Sister repo to exo/blue-merle-mudi7 (the Mudi 7 port). Main is kept in sync with upstream; analysis/ branches hold our findings.
  • Shell 79.1%
  • JavaScript 10.8%
  • Python 5.5%
  • CSS 2.9%
  • HTML 1.1%
  • Other 0.6%
Find a file
ci ab12f0c44b
All checks were successful
CI / shell suites, UI check, shellcheck report, ipk (push) Successful in 6m6s
docs: research note on the canary standby incident of 2026-09-17 (timeline, gaps, serial 11 changes, rollout)
The logread timeline (standby 18:26:45, radio re-enabled by merle-power 18:27:15, 13 min
without a redial, the Auto Setup click at 18:39 that rewrote the section), what serial 10
did and did not cover, the state left on the canary, why the client units were never
exposed (idle disabled), what serial 11 changes per tool with the doc sections, the
timezone note (not merle's), and the canary-first rollout with the wake verification.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 20:45:15 +02:00
.forgejo/workflows ci: Forgejo Actions workflow (shell suites, UI check, shellcheck report, ipk artifact) 2026-09-17 08:31:42 +02:00
.github/workflows modem: attach a later SIM without the panel (connect-auto); vpn: re-assert policy rules after mwan3 restarts 2026-09-16 19:20:17 +02:00
docs docs: research note on the canary standby incident of 2026-09-17 (timeline, gaps, serial 11 changes, rollout) 2026-09-17 20:45:15 +02:00
files fleet: signed set idle.enabled / idle.action / idle.timeout, heartbeat idle_action + standby; idle_action none in the panel 2026-09-17 20:45:15 +02:00
tests fleet: signed set idle.enabled / idle.action / idle.timeout, heartbeat idle_action + standby; idle_action none in the panel 2026-09-17 20:45:15 +02:00
tools oled: early Pixel Defense brand page at boot (oled_boot, oled_logo knobs, pixel-defense bitmap, mcu_send helper), from the boot-logo work 2026-09-17 05:00:46 +02:00
.gitignore ui: Pixel Defense admin view redesigned on the ubus merle contract (FR/EN, dark/light, phone) 2026-09-16 03:13:37 +02:00
Documentation.pdf doc: Updates PDF documentation for v2.0 2023-10-19 17:57:16 +02:00
IMEI randomization.png Initial commit 2022-10-21 10:14:38 +02:00
LICENSE.md Initial commit 2022-10-21 10:14:38 +02:00
Makefile power: delivered-unit hardening: oled_pages, oled_main, lan_admin (+ maintenance window), usb_host, ethernet 2026-09-17 15:45:32 +02:00
README.md ci: README status badge (Forgejo Actions) 2026-09-17 08:39:29 +02:00

merle

CI

The merle software package enhances anonymity and reduces forensic traceability of the GL-E750 / Mudi 4G mobile wi-fi router ("Mudi router"). The portable device is explicitly marketed to privacy-interested retail users.

merle addresses the traceability drawbacks of the Mudi router by adding the following features to the Mudi router:

  1. Mobile Equipment Identity (IMEI) changer
  2. Media Access Control (MAC) address log wiper
  3. Basic Service Set Identifier (BSSID) randomization
  4. MAC Address randomization

Compatibility

This README covers the v2.0 release, which has been verified to work with GL-E750 Mudi version 4.3.8 - 4.3.12 . Refer back to the v1.0 README file for information about the first release, which works on older firmware releases.

A MCU version >= 1.0.7 is required. The MCU may be updated through the merle package installer or manually. SRLabs cannot guarantee that the project assets within this Git repository will be compatible with future firmware updates.

Installation

Online install & upgrade

The online install method requires an active Internet connection on your Mudi device to download up-to-date dependencies.

Download the prebuilt v2.0 release package and copy it onto your Mudi (e.g. via scp), preferably into the /tmp folder. Then install the package file:

opkg update
opkg install merle*.ipk

To uprade merle, download the newest merle*.ipk, copy it to your Mudi and reinstall with:

opkg install --force-reinstall merle*.ipk

Offline install

The offline install method does not need an active Internet connection on your Mudi device.

Download the prebuilt v2.0 offline release package, then execute the following commands:

## Execute the following commands on the computer connected to the Mudi via WiFi / LAN

unzip /path/to/downloaded.zip

# Copy the offline release package to your Mudi
# -O might be needed due to SSH daemon used by Mudi
scp -O -r merle_install root@192.168.8.1:/tmp

# Connect to Mudi via SSH
ssh root@192.168.8.1

## Execute the following commands inside the SSH tunnel
# Install dependencies and merle
cd /tmp/merle_install
./install.sh

Note: The offline install package bundles dependencies collected in October 2023. These dependencies could be outdated at the time of installation and might not be compatible with future Mudi firmware versions.

Battery saver (v3.2.0)

Two features specific to this fork, both documented with the analysis behind them in docs/BATTERY.md:

  • merle-idle: powers the Mudi off through the MCU when nobody has been connected for 5 minutes (no associated Wi-Fi station, no LAN link, no SSH session; never during a SIM swap; not while charging by default). Enabled by default; tune or disable in /etc/config/merle (idle section), then /etc/init.d/merle-idle restart. A fleet-enrolled unit going to standby (action=standby) sends a forced heartbeat with state=standby first, so the node never counts it as lost.
  • merle-power: battery-saver profile: 5 GHz radio off, 2.4 GHz HT20 at 10 dBm, LTE-only / PS-only modem, mwan3 health pings 5 s → 60 s, OLED daemon loop 5 s → 30 s, smsd poll 10 s → 60 s, repeater scans off. Opt-in: merle-power apply (or uci set merle.power.enabled=1 to re-apply at every boot); merle-power status / revert to inspect or undo. Drift GL's daemons cause is repaired every minute by the merle-ensure service (merle-power loop), whether or not idle power-off is enabled.

Cellular and VPN self-heal (v3.2.0, SIM day 2026-09-16)

Three behaviours added after a fresh SIM was watched on a real unit (details, evidence and knobs in docs/APN.md, docs/VPN.md and docs/FLEET.md):

  • A later SIM attaches by itself (merle-modem, /etc/init.d/merle-modem). At boot with a fresh SIM GL creates no network.modem_<bus> section: its modem_auto dials only "webbing" SIMs and the panel's Internet > Cellular "Auto Setup" click is what builds the section. merle-modem watches for "SIM present (AT+CPIN? READY) and no section" and runs the same gl_modem -B <bus> connect-auto the panel runs, bounded (3 attempts with backoff, 240 SIM reads while no SIM answers), never during the SIM-swap flow. A spent budget re-arms by itself every 6 h (merle.modem.rearm_seconds, 0 = never) and on merle-modem attach, so a SIM inserted hours after boot is still attached (before, ~4 h of probes then nothing until a reboot). merle-apn apply does the same before pinning the APN. Manual: merle-modem attach, merle-modem status (shows the budgets and the window age).
  • GL's policy rules survive mwan3 restarts (merle-vpn ensure, merle-vpn rules, the iface hotplug). mwan3 stop flushes every ip rule 1000-2999, GL's own 1099/1100/1101 included, and netifd only puts them back on a network reload; merle-vpn re-adds the missing ones (compared per pref against the uci-declared sections, never a duplicate) after any mwan3 restart, on every ensure pass and when wgclient or the modem comes up. merle-vpn check flags their absence (ip-rule-1101, ip-rules-policy).
  • Management tunnel MTU 1280 (merle-fleet enrol, watchdog). The LTE path carried about 1328 bytes and WireGuard's default 1420 wedged large SSH transfers through wgmdm; enrol writes network.wgmdm.mtu=1280 (bundle key wg_mtu), the watchdog adds it to an older enrolment.

Usage

You may initiate an IMEI update in three different ways:

  1. CLI: via SSH on the command line,
  2. Toggle: using the Mudi's physical toggle switch, or
  3. Web: via the LuCI web interface.

You can set a deterministic or randomized IMEI on the command line. Blue-merle's web and toggle interfaces always set a randomized IMEI.

CLI

Connect to the device via SSH, then execute the merle command. The command guides you through the process of changing your SIM card. We advise you to reboot the device after changing the IMEI.

Toggle

This is a two-stage process.

Flip the Mudi's hardware switch to initiate the first stage of changing your device's IMEI. Follow the instructions on the display, which will ask you to replace the SIM card at the end.

After replacing the SIM card, flip the switch again. The second stage changes the IMEI and then powers off the device. You should change location before booting again.

Note: Occasionally, commands may take longer than expected to execute on the device. This can result in the display switching off (standby) for a few seconds before displaying the expected final message (e.g. instructions to replace the SIM card). Wait for the final message to appear before pulling the switch again. If no message is displayed after a minute, the script might have exited or you might have missed the message. In this case, pull the switch to continue / restart the process.

Web

Open LuCI from System > Advanced Settings in Mudi's web interface. Find the Merle settings under the Network tab. The web interface displays the current IMEI and IMSI and provides a button ("SIM Swap...") to set a new randomized IMEI.

Shutdown the device once the process is complete. Then swap your SIM card and change location before booting again.

Building

This repository contains a CI script to auto-build the project using GitHub actions. Simply fork the repository or replicate the workflow on your local machine to build packages.

You can also setup a full OpenWRT development environment and build the merle package using:

git clone https://github.com/openwrt/openwrt
cd openwrt
git clone https://github.com/srlabs/merle package/merle
./scripts/feeds update -a && ./scripts/feeds install -a
make distclean && make clean
make menuconfig
	# Target System: Atheros ATH79
	# Subtarget Generic Device with NAND flash
	# Target Profile: GL.iNet GL-E750
	# In Utilities, select <M> for merle package
	# Save new configuration
make
make package/merle/compile

You will find the package in ./bin/packages/mips_24kc/base/

Implementation details

IMEI randomization

The Mudi router's baseband unit is a Quectel EP06-E/A Series LTE Cat 6 Mini PCIe module.

The Mudi router's IMEI can be changed by issuing Quectel LTE series-standard AT commands. The AT command to write a new IMEI to a Quectel EP06-E/A-based device is AT+EGMR.

Our IMEI randomization functionality is built around this command and implements two approaches to IMEI generation. The deterministic IMEI generation method generates a pseudo-random IMEI based on the inserted SIM's IMSI. This method will generate the same IMEI for the same IMSI, regardless of which particular merle-enabled Mudi device is used. The second approach generates a random IMEI.

SRLabs researchers verified that the Mudi router's IMEI can be changed persistently by connecting the device to a custom telco base station set-up. The changed IMEI is recorded within the new base station database entry, confirming that the IMEI change is observed both on the device- and ISP-level.

Furthermore, to ensure that there is no leakage of the old IMEI after changing the SIM card and setting a new IMEI, the Mudi router's radio is turned off in advance and an interim randomized IMEI is set. Both the command-line and hardware switch version of merle will guide you through the IMEI update process in order to minimize the risk of IMEI leaks.

Running merle will disrupt the device's connection with the ISP during the time the IMEI is changed, and by default the connection is only reestablished once the device is rebooted.

This process can be observed in Figure 1, where there is a large break in connectivity between entries 70 and 80. This break is the result of turning the radio off.

Figure 1. The router's radio is turned off and the IMEI is randomized between entries 70 and 80. The ISP cannot connect to it.

Figure 1 The router's radio is turned off and the IMEI is randomized between entries 70 and 80. The ISP cannot connect to it.

Basic Service Set Identifier (BSSID) randomization

The Mudi router BSSID is set by the hostapd process using the mac80211_prepare_vif() function in /rom/lib/netifd/wireless/mac80211.sh. The resulting BSSID is stored in /etc/config/wireless.

The implemented BSSID randomization function generates a valid unicast address value and overrides the current MAC values set within the wlan0 and wlan1 interfaces. This is done by issuing OpenWrt uci set commands targeting the macaddr fields of wireless.@wifi-iface[0] and wireless.@wifi-iface[1]. The Mudi router's wifi is then reset to implement the changes.

The BSSID randomization feature is run on boot, ensuring that a new BSSID is generated each time the device is started.

MAC address log wiping

Connecting devices' MAC addresses are stored persistently within the Mudi router at /etc/oui-tertf. On boot, merle deletes (using shred) the client database, then mounts a tmpfs filesystem at this location and restarts the services that manage the client database. This ensures the client database is only retained in RAM and not on disk while retaining the web UI functionality.

MAC Address Randomization

Blue-merle sets a randomized MAC address for the WAN interface. If you use the device in repeater mode to connect to another WiFI AP, the Mudi's MAC address will change after every boot. This might interfere with MAC filtering if enabled on the upstream WiFi AP.

Acknowledgement: merle

The Mudi device shares a name with a Hungarian dog breed typically used to guard and herd flocks of livestock. Mudi dogs are agile, fast-learners and extremely friendly.

"Merle" is one of the five coat colours recognized for the Mudi dog breed by the Federation Cynologique Internationale and is characterized by its mottled or patched appearance. The black splashes on the blueish-gray coat of the merle Mudi inspired the name of this project because of its obscuring appearance and camouflaging symbolism.