Inside BambooToken’s Linux implant: shell and file control over MQTT
September 21, 2026.
![]()
A small configuration blob is enough to explain how this Linux backdoor finds its controller. Decode it, follow the MQTT callbacks, and the program resolves into three useful pieces: a host inventory collector, a shell command worker, and a file manager. MQTT topics connect those pieces, while a repeating XOR key obscures their messages.
The sample also has a direct connection to published threat intelligence. Black Lotus Labs lists its exact SHA-256 under BambooToken Linux samples in the campaign’s published IOC collection. That supports the family identification beyond a similar filename or shared protocol. Identifying the operator requires separate evidence.
The sample was analyzed using the Reverser Space workspace. This walkthrough follows the application code in the Reverser Space session.
The sample at a glance
| Property | Value |
|---|---|
| SHA-256 | bf0681e43f51e98fe7ec24bf73a43facc66b0feb325a3a835f8f01b2861b926e |
| SHA-1 | bed2b7acb36e01bf82e8a82e3e163e5cce9b395a |
| MD5 | b4cff5f5c6088d54df434d628d2e8e57 |
| File size | 2,404,112 bytes |
| Format | Stripped, statically linked Linux x86-64 ELF; C++ |
| GNU build ID | 65dae98336d48a1926031c3b90d6995c25da6cab |
| Entry / application main | 0x405890 / 0x40b945 |
| Embedded broker | live-hk[.]c2iznja[.]com |
| Default connection port | TCP 2883 |
| Fixed group topic | {534E19D5-434B-4cad-A0C2-8D75E0B2FBFC} |
From 59 bytes to a control channel
At startup, main passes a 59-byte blob at 0x647160 file offset 0x247160 to decode_embedded_config_xor at 0x4059b6. The final byte, 0xaf, supplies the XOR key for the preceding 58 bytes. Decoding produces two newline-separated values:
live-hk.c2iznja.com
{534E19D5-434B-4cad-A0C2-8D75E0B2FBFC}
The first value supplies the broker hostname. The second supplies a shared group topic. Separately, generate_random_client_id at 0x405ca0 creates a fresh UUID-shaped identifier for the running process. This distinction matters: the group is embedded in the sample, while the individual client topic can change on a later launch.
The connection wrapper at 0x415b9e falls back to the configured broker and TCP port 2883 when given a null hostname and zero port. Before entering the event loop, the program registers an MQTT last-will message on logical topic OFFLINE, containing op: "offline" and the client ID in gid. Connection failures and event-loop errors lead to a 15-second wait before another attempt.
There is also a background mode. Although the option string is ipv, only -i has a substantive branch in the inspected parser. It reaches daemonize_double_fork at 0x40b654: fork, exit the parent, create a session, fork again, change directory to /, and set the umask to zero. This explains how the process detaches. No boot-time installation mechanism was established.
Topics divide the work
The main client subscribes to the fixed group, its own client ID, and three additional topics ending in /Plugin, /removePlugin, and /LUA. The control dispatcher at 0x40b426 reveals which messages have implemented handlers:
Decoded MQTT message
|
+-- Fixed group topic + ONLINE
| -> Collect and publish host inventory
|
+-- Client-ID topic + SHELL
| -> Start shell worker and its MQTT client
|
+-- Client-ID topic + FILEEX
-> Start file worker and its MQTT client
The shell worker subscribes to <id>/Shell and publishes replies on <id>/Shell/Resp. The file worker follows the same arrangement with <id>/File and <id>/File/Resp. The broker routes messages between these subscriptions and the controller.
The plugin and Lua topic names are useful leads, but the inspected control dispatcher does not implement corresponding execution paths. Subscribing to a suggestive topic is insufficient evidence that a module loader or Lua interpreter is functional in this sample.
What the XOR layer actually hides
The configuration key is separate from the key used on the wire. The MQTT client constructor at 0x41517c installs this 37-byte string:
+0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
Payloads are XORed with this repeating key and transmitted as binary bytes. Topics take another step: each slash-separated segment is XORed from key index zero, then converted to lowercase hexadecimal. The slash separators survive, and a final + wildcard is preserved.
This produces recognizable values even though the logical words disappear:
| Logical topic or suffix | Computed wire representation |
|---|---|
ONLINE |
647e7d7b7d71 |
OFFLINE |
6476777e7a7a70 |
/Shell |
/7858545e5f |
/File |
/6d595d57 |
/Resp |
/79554242 |
On receipt, the bridge at 0x4157e2 decodes both the topic and payload before passing the message to the application callback. That explains why the command handlers compare readable strings even though the publish and subscribe wrappers encode them.
These topic values were computed from the binary; they are not observations from a packet capture collected here. The inspected connection setup contains no observed TLS configuration call. The XOR transformation itself provides no meaningful confidentiality once the embedded key is known.
A shell prompt backed by individual commands
The shell worker opens with a sh> response. Its dispatcher at 0x409df4 accepts JSON with op: "command", takes the command from the command field, and starts execution in a thread.
The execution routine at 0x408073 reaches popen(command, "r"). Following the statically linked implementation through 0x51ce00 and 0x51c950 establishes the /bin/sh -c execution path. Output is read using 1,024-byte line buffers and returned with the command text and another sh> prompt. An op: "stop" message disconnects the shell worker.
The prompt makes this look like a terminal, but each request launches an individual shell command. The inspected implementation does not provide a persistent interactive PTY. Its pipe captures standard output; standard error appears only when the submitted command redirects it into that stream.
File management, including a distinctive typo
The file worker initially advertises a disk array containing .. Its dispatcher at 0x40a808 recognizes dir, download, upload, delete, stop, and exit.
Directory enumeration at 0x4085c1 produces a response with op: "list", the path, a thread identifier, and an array named flies. That spelling is present in the protocol. Each entry carries name, type, size, and time fields, making the typo a useful discriminator when examining decoded traffic.
Downloads move files from the victim to the controller in 128-KiB chunks. Each chunk combines a JSON header with raw file bytes:
JSON metadata: op, tid, path, size, offset
NUL byte
Binary file data
The combined buffer then passes through the payload XOR layer. Upload handling reverses the arrangement, obtains the bytes after the NUL separator, and writes them at the requested path and offset. No automatic execution of an uploaded file was established. Deletion at 0x40981f checks file type and removes regular files; its directory branch returns without deleting the directory.
Several file handlers adapt Windows-style paths by removing a leading character and normalizing backslashes. Directory timestamps also use a conversion to the Windows FILETIME epoch and 100-nanosecond units. These details suggest compatibility with a Windows-oriented management protocol, an inference from the code rather than evidence of authorship.
The host profile contains both measurements and placeholders
An ONLINE request on the group topic reaches publish_host_inventory at 0x4074a6. The response includes the username from USER, hostname, OS release, machine identifier, interface MAC address, local IPv4 address, CPU model, process ID, and thread ID.
Some field names overstate what the code collects. The path value comes from /proc/self/comm, making it a process name rather than a complete executable path. Product, SerialNumber, macname, and arch receive the literal string linux. The values of mem and ver are constants, zero and two. The wire field ver: 2 should be recorded as observed in the binary; it need not match a researcher’s version label.
The machine identifier has its own fallback chain. At 0x406303, the program first tries /etc/machine-id, then /sys/class/dmi/id/product_uuid. A helper at 0x40619b can read or attempt to store a generated identifier at /var/lib/myapp/machine-id. Successful creation of the parent directory was not established. This path serves identity tracking in the inspected code; it does not start the implant after a reboot.
Historical infrastructure adds context
The embedded hostname also appears in Black Lotus Labs’ IOC collection, paired with 202.144.192[.]149. The publisher lists TCP 2883 among that host’s MQTT ports and gives an infrastructure observation window of December 7, 2025, through September 14, 2026. These are historical reporting dates, not proof that this sample ran throughout the interval or that the address remains active. Versioned infrastructure indicators.
A public Hybrid Analysis report for the same SHA-256, submitted on December 6, 2025, records resolution of the embedded hostname to 202.144.192[.]149 and a connection to port 2883. This provides third-party runtime corroboration of the destination recovered statically. It does not demonstrate a complete operator command session, and it is separate from the analysis performed here. Exact-sample sandbox report.
The distinction between hostname and address is useful for incident response: the hostname is embedded configuration; the IP is a historically reported resolution. Neither a current DNS answer nor present broker availability was checked.
Turning the findings into hunts
The strongest network leads combine protocol and application behavior. Look for MQTT connections to the configured hostname or historical address, especially on TCP 2883, and inspect topic fields for the encoded values above. Port 2883 alone is weak evidence; MQTT framing plus matching topics and decoded message structure is much more specific.
Where payload capture is available, decode the repeating XOR layer before inspecting JSON. File transfers need special treatment because the NUL byte separates metadata from binary content. A parser that assumes every decrypted payload is entirely JSON will lose the file-transfer portion.
On the host, correlate an unfamiliar ELF making those connections with child /bin/sh -c processes, machine-identity reads, and subsequent file access. The fallback /var/lib/myapp/machine-id path can help connect events, but the file alone does not prove compromise. The decoded flies key and encoded response-topic suffix provide additional pivots.
These are hunting hypotheses derived from implemented behavior, not tested production detection rules. The following ATT&CK mappings describe capabilities rather than confirmed actions on a victim:
| Implemented behavior | ATT&CK mapping |
|---|---|
| MQTT publish/subscribe command channel | T1071.005 : Publish/Subscribe Protocols |
Commands passed to /bin/sh -c |
T1059.004 : Unix Shell |
| Host inventory collection | T1082 : System Information Discovery |
| Directory enumeration | T1083 File and Directory Discovery |
| Victim files returned over the control channel | T1041 : Exfiltration Over C2 Channel |
