Skip to content

docs(reports): Kaiten/STD timeline follow-up and API review addendum - #27

Draft
rdmmf wants to merge 19 commits into
mainfrom
worktree-mirai7-followup
Draft

docs(reports): Kaiten/STD timeline follow-up and API review addendum#27
rdmmf wants to merge 19 commits into
mainfrom
worktree-mirai7-followup

Conversation

@rdmmf

@rdmmf rdmmf commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Follow-up analysis requested on doc/reports/mirai7_diff_7b70_vs_botnet.md, driven entirely through the BSimVis API.

New reports

  • doc/reports/mirai7_kaiten_timeline.md — the analysis
  • doc/reports/api_pivoting_review_followup.md — API usability addendum

Findings

  • The eight 2026-02-09 *net samples carry an identical 59-symbol malware set across 8 architectures — one cross-compiled build. The family is two generations (2025-04, 2026-02), not a gradient.
  • The same-ISA A vs net diff (both MIPS:LE) puts 36 of 37 malware functions at similarity 1.000; only processCmd changed (967 → 2153 features). This corrects the earlier report — its sockprintf +316 %, main +78 % and print +32 % deltas were SuperH4 decompilation artefacts.
  • The command table has 29 entries when read off MIPS; SuperH4 recovers 13. Five game-title commands (CSGO, GTAV, TF2, R6-DROP, R6-LAG) were previously invisible.
  • DNSw is a flood handler, dispatched as DNS <target> <port> <secs> — not DNS-based C2 resolution.
  • C2 lives in a .data char * array (commServer), default port 6982, confirmed unreachable through the API.

API issues raised

Issue Impact
avg_features on matched diff rows is max(), not a mean hides growth on the rows that changed
functions_metadata uses name, search endpoints use function_name silent None, reads as "diff has no names"
Matched rows can pair differently-named functions unflagged fabricates renames (SendSTD_HEXSendHOME1 at 1.000)
Score penalises containment an ancestor scores 0.485, an unrelated fork 0.625
diff?table= carries no score extra round trip
No data-section / DAT_ symbol read the family's top IOC is unreachable

./scripts/wt-test.sh — 278/278 passed.

🤖 Generated with Claude Code

rdmmf and others added 13 commits July 28, 2026 11:22
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Corrects the single-family conclusion: the collection also contains a
Kaiten/STD lineage (the *net samples), found by enumerating the symbols
actually present instead of probing for expected Mirai names.

- §7 Kaiten/STD family: attack API, IRC C2, cross-family libc false friends
- §8 static-libc mass: 82-99% of functions per sample, and why raw
  similarity scores are mostly a toolchain measurement
- Appendix A: all 50 leaf clusters, Appendix B: the 29 unclustered files
- API review: doc gaps 4.11-4.14 (no name enumeration, libc-dominated
  scores, silent noise drop, member/files envelope split)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… exclude it

- family report §2.1: 26 of 50 leaf clusters are UPX stubs; table of every
  filter attempted and how each one fails
- API review §4.15: bin_sim/search file-tag filters silently ignore any tag
  containing a colon (rsplit(":",1) in _file_tag_union), reproduced with a
  colon-free control tag; one-line fix suggested
- API review §4.16: unknown query params silently ignored (no 400)
- API review §4.17: min_funcs is max(a,b), so stub-vs-real pairs survive

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d samples

The 82-99% figures come from a regex allowlist over function names, not from
clustering. Records the limits (extrapolated from symbolised samples, allowlist
makes malware share a lower bound) and the cluster-based method that would
measure it properly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ation

Symbol-name patterns over the symbolised samples give a cheap library seed set;
propagating the tag across each seed's cluster reaches the stripped siblings the
regex cannot see. Four-step pipeline in family report 8.3, API-side note in the
review.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each section now opens with the API calls that produced it, the trap that
wasted time, and the insight worth carrying to the next collection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Took all 50 leaves mechanically; the tree is strictly binary so that is the
maximally-split view. Parents carry the campaign-level groupings (node 197 =
whole boatnet set at 0.875, node 268 = whole iran group at 0.748). Adds a
cohesion-cut recipe and the caveat that too low a threshold merges families.

Corrects Appendix B: only 2 files are truly unclustered, not 29 - the other 27
sit in internal nodes and were an artefact of the leaf-only selection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Same Kaiten/STD code base ten months apart: A's malware function set is a
strict subset of B's (28 shared, 19 added). Documents why the 0.107 score
understates it (cross-ISA + libc volume) and uses name-set Jaccard instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds Step 8 (two-file diff via table=&limit=0, and the symbol-set shortcut when
both sides keep symbols) plus three gaps: unique_to_a/b never flag that a name
appears on both sides (86 cases here, 24 provably identical), diff scores are
not comparable across ISAs or libc builds, and there is no set operation
between two files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follow-up analysis on the mirai7 7b70/botnet version diff, using the API:

- The eight 2026-02-09 *net samples share an identical 59-symbol malware
  set across 8 architectures: one cross-compiled build, not a timeline.
  The family reduces to two generations, 2025-04 and 2026-02.
- The same-ISA A-vs-net diff (both MIPS:LE) shows 36 of 37 malware
  functions matching at similarity 1.000; only processCmd changed. This
  corrects the earlier report's "modified in place" section, whose
  sockprintf/main/print deltas were SuperH4 decompilation artefacts.
- The command table is 29 entries read off MIPS, not the 13 recovered
  from SuperH4.
- DNSw is a DNS flood handler dispatched from processCmd, not DNS-based
  C2 resolution as hypothesised.
- The C2 host is a .data char* array (commServer) with default port 6982;
  confirmed unreachable through the API.

The API addendum records new findings: avg_features on matched diff rows
is max() rather than a mean, functions_metadata uses `name` where the
search endpoints use `function_name`, matched rows can pair differently
named functions without flagging it, the score penalises containment, and
there is no way to read a data section or resolve a DAT_ symbol.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mermaid sankey in the timeline report: the 37 functions carried from the
2025-04 build bucketed by role, the 22 that first appear in 2026-02, and
the 12 the 2026-03 fork lifted. Bucket membership is tabulated below the
diagram so the flow widths are checkable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mermaid's sankey-beta lexer rejects non-ASCII characters in node names,
including inside double-quoted fields, so the middot separators made the
diagram fail to parse. Verified with mermaid-cli 11: the diagram now
renders with all 13 nodes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rdmmf

rdmmf commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator Author

The reports on this branch are the background for #28 — a proposal to turn the timeline built by hand here into a family evolution view in BSimVis.

Reports referenced there:

The rendered diagram (doc/reports/img/kaiten_sankey.png) is embedded in the issue from this branch, so the branch needs to stay alive for the image to resolve.

The Kaiten method (symbol-set diff) does not transfer to the Mirai side:
only 4 of 164 files kept symbols, and those four use four different
naming conventions for the same modules (attack_tcp_syn / flood_syndata
/ udpplain_thread). The analysis therefore runs on similarity clusters,
with symbol names used only as labels.

100 symbols -> 101 clusters -> 54 capabilities across 80 reachable files:

- The shared core is RNG, config table and checksums, present in 61-96 %
  of reachable samples across the whole five-month window.
- xor_init and table_init share a cluster: a rename, not a new
  capability.
- mipsel's flood_* and nova.mipsel's *_thread cluster only with
  themselves, so those forks rewrote the attack layer rather than
  renaming it.
- Clusters resolve modules, not individual attack methods: one cluster
  holds ten different attack_* variants that share a packet loop.

Includes a sankey of capability first-appearance waves by role, the
per-role reach table, an explicit list of what the timeline is not, and
the API gaps that made it a 380-call job.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rdmmf

rdmmf commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator Author

Added the same analysis for the Mirai side of the collection: doc/reports/mirai7_mirai_timeline.md.

The Kaiten method did not transfer. Only 4 of 164 Mirai files kept symbols, and those four use four different names for the same modules (attack_tcp_syn / attack_tcp_syn_aisuru / flood_syndata / udpplain_thread), so a symbol-set diff compares vocabularies rather than code. The analysis runs on similarity clusters instead, with symbol names used only as labels: 100 symbols → 101 clusters → 54 capabilities, reaching 80 otherwise-stripped samples.

Mirai capability waves

What the clusters showed that names could not:

  • xor_init and table_init share a cluster — a rename, not a new capability.
  • mipsel's flood_* and nova.mipsel's *_thread cluster only with themselves — those forks rewrote the attack layer.
  • The shared core (RNG, config table, checksums) is carried by 61–96 % of reachable samples; the attack layer and C2 resolver are fork-specific (15–20 %).
  • Limit worth knowing: clusters resolve modules, not individual attack methods — one cluster holds ten attack_* variants that share a packet loop.

doc/reports/api_pivoting_review_followup.md gains a §4 with the API gaps this pass exposed — chiefly that finding those 4 symbolised files cost 164 calls and ~6 MB, that the clusters map is keyed by cluster_id while the drill-down wants cluster_uuid, and that clusters carry no first_seen, so the timeline is a client-side join.

./scripts/wt-test.sh — 278/278 passed.

rdmmf and others added 5 commits July 28, 2026 15:42
One function per role, seven of the eight taken from a single binary
(nuclear.arm7); the exploit scanner exists only in mipsel, which is why
it sits at 1 % reach.

Two C2 addresses surfaced that the collection's cc_ip metadata does not
record: raw.flameblox.com with fallback 87.120.191.32:5221
(nuclear.arm7), and 94.26.106.197 (arm7). The endianness reading of both
constants is flagged as needing confirmation against a big-endian
sibling.

The exploit scanner is CVE-2017-17215 (Huawei HG532 UPnP), identified
from the hard-coded digest credentials and port 37215 in the SOAP
payload.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three reports on collection mirai_unpacked_and_renamed3, mirroring the
mirai7 set: family report, Kaiten timeline, Mirai timeline.

The collection is mirai7 with upx -d applied and the packed MD5 kept in
the filename, which makes the two joinable. Across the 55 matched pairs
the same binaries go from 231 to 12,446 functions and 1,619 recovered
symbol names, so most of what the mirai7 reports could not read is now
readable.

Main findings:
- Vortax, a new Go family (8 samples, package vortax_server, C2
  5.175.221.69:9111), undetected by ClamAV and absent from mirai7
- Kaiten's two newest builds drop 21 of the 59 functions its 2026-02
  peak had, contradicting the mirai7 "nothing is ever dropped" result
- chernobyl.mips is a Kaiten/Mirai hybrid: Kaiten attacks, Mirai
  config-table, killer and string runtime
- symbolised Mirai seeds 4 -> 13, six of them recovered by unpacking,
  including an Aisuru merge visible in a single file

Analysis is read-only through the REST API. The libc-similarity trap and
the cross-ISA blind spot from the mirai7 reports both reproduce; neither
is fixed by unpacking.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ction

Third per-family companion to the unpacked3 family report, replacing an
earlier draft that restated the family report and left its central claims
unverified.

Vortax: the 2026-03-26 build is a strict symbol subset of the 2026-04-05
build (4364 of 4372, 8 symbols added = StartDNS + StartGREIP and their
wrappers); full C2 command grammar recovered from main.main; C2 confirmed
on x86-64 only, checked rather than assumed on both MIPS:BE samples.

xnxn: config is AES-128-CBC with the key in .rodata, decrypted on three
architectures across both build dates -- feather-daddy.duckdns.org:54128
to itzmeyourbro.duckdns.org:54128, token unchanged. The rebuild changes
exactly one function of 257, and 10 of 11 architecture pairs still score
1.000 in bin_sim. Attribution: 79 of 257 functions share clusters with
the p* Mirai campaign and none of them is bot code, so the 0.28 Jaccard
is a shared build toolchain, not lineage; the ClamAV Mirai label on all
22 files is wrong.

Family report section 6 and conclusions updated to match; its deliberate
"unattributed" verdict is restored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Vortax sankey had no split -- two sources into one sink, nothing
dropped -- so it encoded no flow. Replaced with a short note on why the
Kaiten sankey works and this one would not.

The build-history timeline is now a gantt on a real date axis. The
spacing is the finding: xnxn ships twice, 39 days apart, 11 arches each;
Vortax ships four times inside 11 days. A category-per-column timeline
flattened that away.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rtax Go

No binary cluster holds exactly the 8 Vortax files. Cluster 262 holds 7 of
them plus mipsle, and excludes the x86-64 sample. Applying the section 2.5
test to mipsle: 260 functions shared with Vortax MIPS64, all Go stdlib and
runtime, zero of the 29 vortax_server/main symbols. mipsle is a stripped Go
binary that is not Vortax, so cluster 262 is named "vortax x7 + mipsle
(Go runtime cut)" rather than "vortax".

Also corrects the family report: mipsle's isolation is not an unshared libc,
it has no libc -- it is Go, and the only other Go in the collection is
Vortax. New section 1.3 records that the 8 files are 2 code versions and
about 5 distinct binaries (one same-build pair at 1.000, three ARM
sub-targets differing only in unnamed functions).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant