Reversing AISURU: From Two Stripped ARM Binaries to a Live C2 Sensor
This experiment was different from my earlier posts. Those started with a claim I already knew how to test; this one started with two stripped ARM binaries and almost nothing else.
I didn’t know AISURU, didn’t know the actors, didn’t know the protocol. But in short, AISURU is a Mirai-derived IoT botnet that spreads by brute-forcing Telnet on embedded Linux devices: routers, IP cameras, modems, the usual. The whole thing rested on one borrowed question from the NoName work: could I connect to a live C2 and watch its target list go by, without ever running the malware? I had no idea whether the answer was yes.
So this was far more AI-led. I started with basic questions and worked outward from the answers, with plenty of trial and error along the way: different prompts, wrong turns, routines mislabelled and re-checked. Most of what fell out ended up in one Python script that joins the C2 and prints the commands it hands down.
Note that this post runs deeper into the technical weeds than the last post, and that is the point. I wanted to show how far the models have come at chewing through genuinely hard problems, so I have left the detail in rather than smoothing it over. Most of what follows is lifted straight from the work log, in the order it happened.
The samples
The two ARMv7 ELF binaries were:
| Filename | Role | Size | SHA-256 |
|---|---|---|---|
aisuru_8087_xd.armv7l |
Stage-1 loader / dropper | 74 KB | ff454f64c8e6a9808c182eade0291759dd1e987ef78e3e4f78dab8f812ccd404 |
aisuru_armv7.bin |
Full bot payload | 242 KB | 118eb9e81e70d1ba9f2702037b244bea154100ef52685f0b499fa7b8fa375e90 |
Both are statically linked, static-PIE, stripped ARM EABI5. The loader fetches the bot at runtime; the bot scans, propagates, and runs DDoS attacks on command. The goal I set myself was concrete: get to the point where I could read the live command channel without running either file. This is how that went, in order.
Phase 1: Triage
I drove triage with the binary-analysis skill, a five-phase static workflow
(identification, static analysis, security-feature check, behavioral heuristics,
synthesis) that never executes the target. The first phase is just stock
binutils:
file aisuru_armv7.bin # ELF 32-bit LSB, ARM EABI5, statically linked, stripped
sha256sum *.armv7l *.bin
readelf -S aisuru_armv7.bin # section layout
strings -n 6 aisuru_8087_xd.armv7l | wc -l
For the tedious parts the skill ships a helper, binary_analyzer.py, that does
per-section entropy, the security-feature audit, and a MITRE-tagged heuristic
scan:
SKILL=.claude/skills/binary-analysis/scripts/binary_analyzer.py
python3 $SKILL --entropy aisuru_8087_xd.armv7l # per-section Shannon entropy
python3 $SKILL --checksec aisuru_8087_xd.armv7l # NX / PIE / canary / RELRO / FORTIFY
python3 $SKILL --heuristics aisuru_armv7.bin \
--strings strings.txt -o heuristics.json # persistence / c2 / anti-analysis ...
--checksec returns the usual stripped-IoT-malware profile on both: NX and PIE
on, no stack canary, no RELRO. Size-optimised builds, minimal hardening.
--heuristics on the bot lights up persistence, c2_communication, and
anti_analysis before I had decompiled a single function: useful, but not yet
evidence.
The two samples split immediately on string count:
- The bot gives ~1040 strings: a 500+ entry Telnet credential dictionary,
25+ device-banner regexes (
BusyBox on \S+ \wogin,GAPM-\d\d\d\d \wogin, …), busybox self-propagation one-liners, persistence drop paths (/var/tmp/,/dev/shm/,/data/, …). - The loader gives 16. In a 74 KB binary, that is the tell. The capability is encrypted or packed.
Entropy confirms which:
| Section | Loader | Bot |
|---|---|---|
.text |
6.14 | 6.14 |
.rodata |
6.949 | 6.39 |
.data |
3.24 (52 B) | 1.95 (66 KB) |
The loader’s .rodata sits at 6.949, right under the ~7.0 packed threshold, and
its .data is 52 bytes. Something in .rodata is encrypted. Two strings
survive in both binaries and tie them to one build lineage:
expand 3te k, a mangled ChaCha20 constant (expand 32-byte k)PJbiNbbeasddDfsc, a 16-byte key
Those two are the whole thread. Everything else unspools from them.
Phase 2: Decompilation
I decompiled both binaries twice, to cross-check:
| Tool | Output | Addressing |
|---|---|---|
IDA Pro 9.3 (idalib + Hex-Rays hexarm) |
*.ida.dec/ |
IDA addr == ELF file offset (static-PIE) |
| Binary Ninja headless (HLIL) | *.dec/ |
BN addr = IDA addr + 0x10000 (PIE rebase) |
Both runs were driven by dedicated skills (/decompile-idapro,
/decompile-binaryninja; ref .claude/skills) that run each tool headless and
emit one .c file per non-thunk function.
For this target IDA’s ARM output was the cleaner of the two: it resolves syscall
numbers, names ARM intrinsics (__ROR4__, __mcr, __dmb), and infers better
types. The loader’s watchdog loop is the sharpest example:
// IDA: sub_F9F4 (syscall numbers resolved: 248 = futex, 1 = exit)
void __noreturn sub_F9F4(...) {
for ( i = 248; ; i = 1 )
linux_eabi_syscall(i, a1, ...);
}
// Binary Ninja: same code, semantics collapsed
void sub_1f9f4() {
while (true) syscall();
}
That futex/exit alternation, plus a forked child, is a self-respawning
watchdog: the parent re-execs the loader whenever the child dies. It keeps the
infection resident across crashes.
One caution I’ll repeat because it bit me later: the first decompiler pass
mislabelled several functions: it called sub_18888 a ChaCha20 block when it is
__udivsi3. Verify a suspected crypto routine against what it actually does.
Don’t trust an auto-generated label.
Phase 3: Two ciphers, not one
The loader carries two independent ciphers, and conflating them is the classic AISURU trap:
| Cipher | Key | Constant | Purpose |
|---|---|---|---|
| ChaCha20 (standard, 20-round) | 32-byte session key | expand 32-byte k |
C2 network traffic |
| RC4-variant + 32-bit LFSR | PJbiNbbeasddDfsc |
n/a | Config string table + C2 key-wrapping |
PJbiNbbeasddDfsc is not the ChaCha20 key. It keys a Mirai-style locked
string table, the reason the loader has almost no strings.
Breaking the config cipher
Read off the IDA decompilation, the string-table cipher is RC4’s i/j walk
plus a 32-bit LFSR (feedback polynomial 0xD800A4), a non-standard S-box init,
and a final output whitening of rol8(v,3) ^ (v >> 4). It is symmetric, so one
routine both locks and unlocks. Ported to Python (decrypt_config.py):
KEY = b"PJbiNbbeasddDfsc"
def keystream(length):
"""sub_43F4 PRGA: produce `length` keystream bytes."""
S = ksa() # 256-byte S-box, custom init
i = j = c = 0
lfsr = 1
out = []
for _ in range(length):
i = (i + 1) & 0xFF
si = S[i]
lfsr = ((lfsr << 1) | (lfsr >> 31)) & 0xFFFFFFFF # ROL32 by 1
j = (j + si) & 0xFF
c = (c + S[(j + i) & 0xFF]) & 0xFF
S[i], S[j] = S[j], S[i]
if lfsr & 1:
lfsr ^= 0xD800A4 # LFSR feedback
inner = (S[(c + j) & 0xFF] + S[(j + i) & 0xFF]) & 0xFF
v = (S[inner] ^ (lfsr & 0xFF)) & 0xFF
out.append((((v << 3) | (v >> 5)) ^ (v >> 4)) & 0xFF) # rol8(v,3)^(v>>4)
return out
def decrypt(blob):
ks = keystream(len(blob))
return bytes(b ^ ks[i] for i, b in enumerate(blob))
Each unlock restarts the state (i = j = c = 0, lfsr = 1), so the keystream
is fixed and depends only on the hard-coded key. Point it at the loader’s config
blob and all 38 populated entries fall out:
$ .venv/bin/python3 decrypt_config.py
idx len decrypted
0 29 System upgrade in progress... (argv[0] masquerade)
1 17 stun.l.google.com (public-IP discovery)
2 11 hardload.su (C2 domain)
3 15 rep.hardload.su (C2 reporting subdomain)
4 60 telnetd,udhcpc,inetd,ntpclient,... (process kill-list)
13 36 VMware,VirtualBox,KVM,Microsoft,QEMU (VM/sandbox evasion)
14 32 tcpdump,wireshark,tshark,dumpcap (anti-analysis)
21 24 /proc/self/oom_score_adj (OOM self-protection)
31 38 /tmp/,/var/tmp/,/root/,/dev/shm/,... (persistence drop paths)
...
That table answers the question triage left open: the loader’s C2 is the domain
hardload.su, with a rep. check-in subdomain, resolved at runtime, which
is why no IP was ever visible statically. It also lays out the anti-analysis
posture in plaintext: hypervisor-name detection, packet-capture-tool detection,
OOM-killer self-protection (oom_score_adj = -1000). Nothing subtle once you
have the key.
A DNS covert channel
hardload.su publishes one TXT record: k4MwZw==. It is not a verification
token. It is an obfuscated fallback C2 address:
- base64-decode →
93 83 30 67 - XOR with
0xCAFEBABE→59 7d 8a d9 - read as dotted-quad IPv4 →
89.125.138.217
$ .venv/bin/python3 decode_txt.py
hardload.su TXT "k4MwZw==" -> 89.125.138.217
The apex hides behind Cloudflare, but this TXT-encoded fallback points straight at a real host, and the operator can rotate it by editing one DNS record, no recompile. That host is where the sensor eventually connects.
Phase 4: The C2 protocol
With the bot decompiled, the command channel was next. Transport is TCP port 8001. Every message is three segments:
header 8 bytes
body <bodylen> bytes random padding
payload <payloadlen> actual content (may be empty)
The 8-byte header:
| Off | Size | Field |
|---|---|---|
| 0 | 1 | message type |
| 1 | 1 | bodylen |
| 2 | 2 | payloadlen (big-endian) |
| 4 | 4 | MAC (big-endian) |
Once the session is keyed, header and payload are ChaCha20-encrypted (the random body padding is not). The handshake is a five-state machine:
| State | Event | Action | Encrypted? |
|---|---|---|---|
| 0 | connect | send type 0 (registration, empty) | no |
| 1 | recv type 1 | RC4-unwrap ChaCha20 key+nonce, install, send type 2 | no |
| 2 | recv type 2 | send type 3 (telemetry) | yes |
| 3 | recv type 3 | store a dword | yes |
| 4 | recv type 4/5/6 | dispatch command (steady state) | yes |
And the command vocabulary:
| Type | Direction | Meaning |
|---|---|---|
| 0 | bot→C2 | registration |
| 1 | C2→bot | session-key provisioning |
| 2 | both | handshake ack |
| 3 | bot→C2 | telemetry / sysinfo |
| 4 | C2→bot | keepalive (bot echoes it) |
| 5 | C2→bot | kill / self-update |
| 6 | C2→bot | attack command |
Here is the design mistake that makes everything else possible: the bot ships no static ChaCha20 key. The C2 provisions a per-session key in the type-1 message, wrapped with the same RC4-variant cipher I had already broken. Recover that one message and the whole session is readable. The type-1 payload is four length-prefixed fields:
def parse_type1(payload):
"""Type-1 key provisioning: RC4-unwrap key + nonce, verify each MAC."""
o = 0
klen = int.from_bytes(payload[o:o+4], "big"); o += 4
key_enc = payload[o:o+klen]; o += klen
keymac = int.from_bytes(payload[o:o+4], "big"); o += 4
nlen = int.from_bytes(payload[o:o+4], "big"); o += 4
nonce_enc = payload[o:o+nlen]; o += nlen
noncemac = int.from_bytes(payload[o:o+4], "big"); o += 4
key = rc4_unwrap(key_enc) # decrypt_config.decrypt, key PJbiNbbeasddDfsc
nonce = rc4_unwrap(nonce_enc)
return {
"key": key, "nonce": nonce,
"key_ok": len(key) == 32 and c2_mac(key) == keymac,
"nonce_ok": len(nonce) == 12 and c2_mac(nonce) == noncemac,
}
The MAC, by emulation
Every message also carries a 4-byte MAC: a custom 64-bit MurmurHash-flavoured mixing hash over the plaintext payload. It is inlined into several bot functions and never appears as a clean routine, so hand-porting it meant risking a subtle bug in the 64-bit carry arithmetic: the kind that passes nine test vectors and fails the tenth.
So I didn’t port it. The region is pure ALU work (no syscalls, no library calls), so I run the bot’s own MAC code under Unicorn. Nothing from the malware actually runs; about 120 arithmetic instructions execute in an isolated VM with scratch memory mapped around them.
# c2_mac.py: emulate .text 0xBCE4..0xBE98 of aisuru_armv7.bin
_MAC_START, _MAC_END = 0xBCE4, 0xBE9C
def c2_mac(payload: bytes) -> int:
"""Return the 32-bit C2 MAC of `payload` by emulating the bot's code."""
mu = Uc(UC_ARCH_ARM, UC_MODE_ARM)
mu.mem_map(0, _IMG_SIZE)
mu.mem_write(0, _CODE) # the bot binary, mapped at its file offset
mu.mem_map(_STACK, 0x10000)
mu.mem_map(_BUF, max(0x1000, (len(payload) + 0xFFF) & ~0xFFF))
mu.mem_write(_BUF, payload)
mu.reg_write(UC_ARM_REG_SP, _STACK + 0x8000)
mu.reg_write(UC_ARM_REG_R5, len(payload)) # payload length
mu.reg_write(UC_ARM_REG_R6, _BUF) # payload pointer
mu.emu_start(_MAC_START, _MAC_END, count=2_000_000)
return mu.reg_read(UC_ARM_REG_R3) & 0xFFFFFFFF
This is exact by construction (it is the malware’s algorithm) and it checks
out against 12 ground-truth vectors in the module’s self-test. One quirk drops
out of the emulation: mac(b"") is 0xaf70ac84, but the protocol writes MAC =
0 for an empty payload. Knowing that special case is exactly what lets the
sensor’s empty registration and ack messages pass verification later. Small
detail, load-bearing.
Phase 5: Threat intelligence: mapping the campaign
Reversing one binary tells you what that binary does. It doesn’t tell you how
big the campaign is or where it lives. For that I leaned on two platforms:
REDS (RationalEdge, sample and code intelligence) for the family, and
Censys for the C2 infrastructure. Both pivots start from artefacts the
reverse already produced: the hashes, the expand 3te k marker, and the C2 IP
89.125.138.217.
Sample intelligence: REDS
Driven by the reds-malware-platform skill (a redscli wrapper). Both samples
went in for decompilation and code-similarity analysis, and the marker string
from Phase 1 turned out to be a precision IOC:
redscli strings samples --string 'expand 3te k' # -> exactly 2 samples: our loader + bot
redscli search -q 'ipv4="15.204.230.147"' # -> 30 samples on the old cluster C2
| Pivot | Result |
|---|---|
expand 3te k string |
2 samples (ours), the mangled ChaCha20 constant is unique to this May-2026 build |
C2 IP 15.204.230.147 |
30 samples, 10 known cluster members + 20 new |
C2 IP 220.158.234.23 |
2 samples, a PowerPC bot and an x86-64 XMRig cryptojacker |
The 30 samples on the primary C2 span at least two architectures (the
original cluster was ARM; a new one I examined is x86-64) and more than one
payload: one new x86-64 sample is an SSH-scanning botnet rather than our
Telnet ARM bot, and the 220.158.234.23 pivot drags in an XMRig miner. So
that IP is shared, cross-payload infrastructure, not a clean AISURU C2.
I’ll be blunt about the limits of this. The 20 new samples are linked by
shared C2 IP alone, not the code similarity that defined the original
cluster, and none of them carry the expand 3te k marker: they are an earlier,
un-obfuscated generation, not this build. The interesting part isn’t the count,
it’s the arc: a plaintext single-binary bot in January became a two-stage loader
with an encrypted config by May. The operator hardened.
Infrastructure intelligence: Censys
I pivoted the C2 addresses in Censys through the censys-platform skill (a
cscli wrapper). Host lookups of all four:
cscli host get 89.125.138.217 --pretty
| C2 IP | Role | ASN / network | Live services |
|---|---|---|---|
89.125.138.217 |
TXT-fallback C2 (the sensor’s target) | AS26383 Baxet Group | 22 (OpenSSH 8.9p1), 8080 (Go net/http) |
212.192.12.168 |
rep.hardload.su check-in |
AS26383 Baxet Group | 22 only |
15.204.230.147 |
Jan-2026 cluster C2 | AS16276 OVH | none (rotated/sinkholed) |
220.158.234.23 |
cross-payload IOC | AS38623 Viettel Cambodia | none (residential) |
Both active C2 hosts sit in the same ASN, AS26383, Baxet Group, despite
different GeoIP countries. They have distinct SSH host keys, so two separate
VMs, but an identical HASSH and banner: both stock Ubuntu 22.04, one
provisioning template. Don’t over-read it: n=2, and the ASN announces 11,700+
hosts. This is where these two C2s live, not “the AISURU network”.
The 8001 listener never shows up in Censys, and it would be easy to claim it is
invisible by design. It isn’t. Censys scanned both hosts days later and does
record open-but-bannerless ports (the host lists 111/udp with the empty-string
hash), so an open 8001 would appear too. Its absence means 8001 was closed or
filtered at scan time: the listener is intermittent, or firewalled to bot IPs.
It was up when the sensor hit it on 5/21, down ten days later. That’s also why
the operator bothers with a rotatable DNS fallback.
Every generic attempt to enumerate sibling C2s failed. I’m listing them so nobody repeats the work:
| Pivot | Hits | Verdict |
|---|---|---|
| Baxet hosts on port 8001 | 47 | the ones I checked run nginx; 8001 is not AISURU |
Go-404 8080 banner hash |
3.4M globally | stock Go net/http, pure noise |
| stock Ubuntu SSH banner | 11,709 in Baxet | noise |
The C2 has no Censys-fingerprintable surface. Track this layer by ASN and DNS,
not service scans. One structural result did fall out, though: the hardload.su
booter-panel web cluster, found separately via a shared SSL OK\n body
hash, is hosted across AWS, Oracle, Alibaba, DigitalOcean and PFcloud, and
none of it is on Baxet. The customer-facing panel and the bot C2 share a
brand, not a provider.
The combined picture
Put the two together and AISURU is a multi-architecture, multi-payload operation
(ARM and x86-64; DDoS bots and cryptominers) that tightened its operational
security between January and May 2026, runs its live check-in on Baxet
(AS26383) behind a rotatable DNS-fronted fallback, and keeps its booter panels
on mainstream clouds well away from the C2. The expand 3te k marker pins my
two samples to the newest, obfuscated generation, and hands defenders an
exact-match hunt string for it.
Phase 6: A passive sensor
Now the pieces compose. c2_monitor.py breaks the key wrap with
decrypt_config.decrypt, computes MACs with c2_mac, and frames messages with
a stock ChaCha20. The session driver is a straight transcription of the state
machine:
def session(self):
self.sock = socket.create_connection((self.ip, self.port), timeout=30)
# state 0 -> 1 : registration (plaintext, empty payload)
self._send(0)
# state 1 -> 2 : receive key provisioning, RC4-unwrap the session key
msg = self._recv()
info = parse_type1(msg["payload"])
self.key, self.nonce = info["key"], info["nonce"]
log("session key provisioned", key=self.key.hex(), nonce=self.nonce.hex(),
msg_mac_ok=msg["mac_ok"])
# state 2 -> 3 : ack, then telemetry (TLV reversed from sub_C310)
self._send(2); self._recv()
self._send(3, self.telemetry); self._recv()
# state 4 : steady state, observe and log, never execute
log("ENROLLED - monitoring command channel")
while True:
self.handle(self._recv())
handle() is deliberately inert. Type-6 attack orders get parsed, logged, and
enriched with the victim’s network owner and abuse contact, and then nothing.
Type-5 kill/update is logged and ignored. The only thing I send back is the
keepalive echo, because that is what keeps the sensor enrolled:
def handle(self, msg):
t, pl = msg["type"], msg["payload"]
if t == 4:
self._send(4) # keepalive echo (passive)
log("keepalive (type 4)")
elif t == 5:
log("KILL/UPDATE command (type 5) - observed, NOT executed")
elif t == 6:
attack = parse_attack_command(pl)
log("attack command (type 6)", attack=attack, mac_ok=msg["mac_ok"])
notify_victims(attack) # RDAP/whois abuse-contact enrichment
The whole stack self-tests offline, with no network and no malware in sight:
.venv/bin/python3 c2_monitor.py --selftest # 7 crypto/framing/parser checks
.venv/bin/python3 c2_mac.py # MAC oracle: 12 ground-truth vectors
Connecting to the live C2
Because the protocol is fully reversed and the key wrap is broken, the sensor
can do something a packet capture can’t: join the real botnet’s command channel
as a passive node and decrypt every order as it arrives. It connects outbound,
completes the handshake, RC4-unwraps the per-session ChaCha20 key the C2 hands
it, verifies each MAC, and reads the plaintext of every command, writing each
line to c2_events.jsonl and executing none of them.
On 2026-05-21 I ran it against the live C2 for about 1h40m, from isolated infrastructure. The handshake log:
{"ts":"2026-05-21T11:43:20Z","msg":"connecting","c2":"89.125.138.217:8001"}
{"ts":"2026-05-21T11:43:20Z","msg":"sent registration (type 0)"}
{"ts":"2026-05-21T11:43:20Z","msg":"session key provisioned",
"key":"c2d7847b5ddc40b895a14377cca7dd9067a4046d4028e3081835ae2bca354fd0",
"nonce":"029e5cb17d8f17e3c2a0c276","msg_mac_ok":true}
{"ts":"2026-05-21T11:43:20Z","msg":"handshake step 2","got_type":2}
{"ts":"2026-05-21T11:43:21Z","msg":"sent telemetry (type 3)","bytes":28}
{"ts":"2026-05-21T11:43:21Z","msg":"ENROLLED - monitoring command channel"}
The session key the C2 provisioned, c2d7847b...354fd0, is exactly what
PJbiNbbeasddDfsc unwrapped, and the MAC verified. Enrolled. Seconds later the
first attack order arrived, fully decrypted:
{
"ts": "2026-05-21T11:43:27Z",
"msg": "attack command (type 6)",
"attack": {
"flags": 1252071,
"vector": 0,
"duration_s": 60,
"targets": ["79.153.152.58/32"],
"options": [
{ "key": 0, "data": "0043" },
{ "key": 2, "data": "0578" }
]
},
"mac_ok": true
}
Over 69 minutes the sensor logged 36 DDoS commands against 10 distinct
victims, every one MAC-verified, plus 202 keepalives. The commands were
uniform: 60-second, single-/32 attacks on vector 0; option key0 tracked a
destination port, key2 was a constant 0x0578 (1400, probably packet size).
The victim list, decoded straight from the log:
| Hits | Target | Port | Network owner (country) |
|---|---|---|---|
| 14 | 210.2.84.8 |
53 | QTSC-VN (VN) |
| 5 | 79.153.224.235 |
67 | Telefónica de España (ES) |
| 4 | 191.95.135.26 |
67 | Colombia Móvil / Tigo (CO) |
| 4 | 178.237.233.230 |
67 | MásOrange (ES) |
| 3 | 139.74.17.179 |
67 | Valmet Oyj / Elisa (FI) |
| 2 | 178.237.236.52 |
67 | MásOrange (ES) |
| 1 | 79.153.152.58 |
67 | Telefónica de España (ES) |
| 1 | 190.14.243.226 |
67 | Media Commerce Partners (CO) |
| 1 | 102.208.130.111 |
80 | Ericsson NAT pool (CI) |
| 1 | 83.172.96.61 |
62962 | CITYNET / Lidnet (SE) |
For each target, notify_victims() resolved the RIR abuse contact over RDAP
(with a whois fallback) and wrote it back into the same log. That turns a
decrypted command stream into per-victim threat intel you can actually send
somewhere. 89.125.138.217 is a confirmed-active AISURU C2 issuing live DDoS
orders at a global victim set, and c2_events.jsonl is a timestamped,
attributable record of its targeting, produced without running the malware or
joining a single attack.
Takeaways
- Two strings carried the whole thing.
expand 3te kandPJbiNbbeasddDfsc, surviving in both binaries, gave me the cipher lineage and the key that unwraps everything. Triage earns its keep. - Separate the ciphers. AISURU’s config cipher (RC4-variant + LFSR) and its C2 cipher (ChaCha20) are independent. The shared key wraps the session key, not the traffic. Mix them up and nothing decrypts.
- Emulate what you can’t cleanly port. Running the bot’s own MAC code under Unicorn killed a whole class of transcription bugs, and stayed safe: the region is pure arithmetic in a sandboxed VM.
- A per-session key wrapped with a recoverable static key is a gift. It means a passive sensor needs no pre-shared secret to read the live channel. The operator handed me the keys, one message at a time.
- Threat-intel pivots turn one reverse into a campaign view, and tell you where not to look. REDS scaled the marker and a cluster IP into a multi-arch, multi-payload picture; Censys placed both live C2s in one ASN and showed the panel and C2 layers are hosted apart, while making clear that port 8001 and stock banners are noise, not leads.
The IOCs worth keeping: domains hardload.su / rep.hardload.su; the check-in
host 212.192.12.168 and the TXT-record fallback 89.125.138.217, both on
AS26383 Baxet Group; and expand 3te k as an exact-match hunt string for
the May-2026 build.
One last word, since this is where I started. The NoName-style idea, join a live C2 and watch its target list without running the malware, held up: I went from two stripped binaries I knew nothing about to a passive sensor logging real DDoS orders, and most of the path there was AI-led. Basic questions, answers, the occasional wrong turn, then a script. AI-led reversing and threat intel works, and it is really powerful, not as a replacement for knowing what the bytes do, but as a way to get there far faster than I could alone.
#Malware-Analysis #Reverse Engineering #Iot #Mirai #Botnet #Arm #C2 #Threat-Intelligence #Censys