Interactive analysisOpen workspace

Inside BambooToken’s Linux implant: shell and file control over MQTT

September 21, 2026.

Bamboo Image wikipedia

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

Interesting Functions

Address Analyst-assigned name
0x4059b6 decode_embedded_config_xor
0x405ca0 generate_random_client_id
0x406303 get_host_machine_id
0x4074a6 publish_host_inventory
0x408073 execute_command_and_publish_output
0x4085c1 publish_directory_listing
0x408da4 publish_download_chunk
0x4092ef stream_requested_file
0x409491 handle_file_upload
0x40981f handle_file_delete
0x409ad0 on_shell_connect
0x409df4 dispatch_shell_json
0x40a438 on_file_connect
0x40a808 dispatch_file_json
0x40b426 dispatch_control_message
0x40b654 daemonize_double_fork
0x40b6bc on_control_connect_subscribe
0x40b945 sample_main
0x41517c mqtt_client_construct
0x4157e2 decode_and_dispatch_mqtt_message
0x415b9e connect_configured_mqtt_broker
0x415daa encode_mqtt_topic
0x4162cc decode_mqtt_topic
0x416802 xor_payload_in_place
0x416a14 xor_payload_copy
0x416d60 bytes_to_hex_string
0x417092 subscribe_encoded_topic
0x41716c publish_encoded_message
reverser.space
Loading