This is the fourth post on one question: where do residential proxy exits actually come from?

The SYN fingerprints said the pool isn’t the crowd of real devices it’s sold as, and left off at “whichever networks the recruitment malware found most fertile.” The cracked driving game was the first attempt at naming that recruitment, and ended with an encrypted payload I couldn’t open. MediaGet was the second, and that one closed: a torrent client handing every geo it answers a proxy bundle, disclosed in the west and unlabelled everywhere else. At the end of it I wrote that the cracked game’s payload was “still sitting on the bench, still silent, still unproven,” and that the hidden channel was the one I still owed an answer on.

This is the third attempt, and the answer is no.

Not for the same sample — that one is still on the bench. But for a payload of exactly the same shape, injected by the same kind of operator into the kind of APK that fills these pools: a hidden, obfuscated, dynamically-loaded blob that looks like proxyware until you read it. It’s a cheat framework with Chinese analytics bolted on. There is no proxy relay in it, and no command-and-control server of its own, which I did not expect.

The negative result is the least interesting thing here. The statistical half of the investigation took two days and two corrections and still ended in a number I can’t interpret. Everything that survived came from an emulator and a ZIP listing.

Where it started

Same place as always. A purchased residential proxy, run through public BitTorrent attribution, and a download history that was wall-to-wall Android mod APKs. I have written before about why that’s a map rather than a confession, and I stand by that. But this time I had a specific sample in hand, pulled off a crawler: a 933 MB Minecraft build called com-mod-minecraft-mod-apk-menu-god-modeunlocked-1-26-10-26_972601026.apk.

The first sample was clean, and that’s a real result

Repacked Minecraft Bedrock 1.26.10.26, resigned with a throwaway key:

Issuer/Subject: L=indna, C=9821
Not Before:     2024-12-06      Not After: 2049-11-30

C=9821 is not a country code. This is a keytool run by somebody who mashed the keyboard through the prompts.

The injection was three files and one hook. Two Java classes, one native library, and a single call site:

// MainActivity.java:1469
Menu.Start(this);

com.an1menu.Menu draws a floating overlay; libNotlib.so reads /proc/self/maps, finds libminecraftpe.so, calls mprotect and patches bytes. A game trainer, textbook. The toggle list comes from assets/not.json, and there is exactly one entry:

{"patch1": {"en": "Immortality", "ru": "Бессмертие"}}

libNotlib.so imports no network symbols: no socket, no connect, no getaddrinfo. It has no way to reach the network. Every manifest component — 11 activities, 6 services, 5 receivers, 3 providers — is stock Mojang, Firebase, GMS, Braze, Xbox or androidx. Nothing added.

Piracy plus a cheat, no malware. The rest of this post is about a build of the same title and same versionCode that is not clean, and the difference between them is the whole method.

The swarm was 100% one client

While the APK was decompiling I looked at who was seeding it, and the peer list was strange in a way I’d never seen: every single peer reported the same client.

37.8.138.123     -HT3334-
37.66.21.23      HyperTorrent 3.4.0
37.130.17.21     HyperTorrent 3.3.1
38.25.51.6       -HT3400-
...
60 connected, not one qBittorrent, uTorrent, Transmission or libtorrent

The -HT####- entries aren’t a different client — that’s the raw peer_id where the inspector’s lookup table didn’t have a friendly name. HT + 3400 is 3.4.0.0.

Real swarms are never homogeneous. This one was, at 100%, across four version numbers.

I initially chased “HyperTorrent” as a product, found an Android torrent client by that name on the usual download-mirror sites, and spent an hour on the wrong thing. The version numbers were what gave it away. The peers reported 3.2.6, 3.3.1, 3.3.3 and 3.4.0 — and HappyMod’s current version is 3.4.0. The older ones line up too.

HappyMod ships a BitTorrent engine:

lib/arm64-v8a/libjlibtorrent-1.2.15.2.so      7.3 MB
btdownload.services.EngineService
btdownload.services.EngineBroadcastReceiver   + RECEIVE_BOOT_COMPLETED
btdownload.services.statistics.WorkService

And hyper-torrent.com turns out to be their own tracker domain, which I only found later, inside their torrent files:

http://tracker.hyper-torrent.com:8001/announce
udp://tra-pk-khi.hyper-torrent.com:8011/announce     Karachi
http://tra-ph-mnl.hyper-torrent.com:8007/announce    Manila
http://tra-th-bkk.hyper-torrent.com:8019/announce    Bangkok
udp://tra-sa-jnb.best61.com:8002/announce            Johannesburg
http://tra-ved-br.best82.com:8000/announce           Brazil
http://tra-ac-ae.apktorrents.com:8001/announce       UAE

Four domains, geo-coded subdomains, a distributed tracker fleet — that’s infrastructure somebody maintains.

So the “third-party torrent client” I’d been chasing was the mod store itself. There is no separate client in this story.

What HappyMod actually does to you

I put it on a rooted emulator and pulled three things from its catalogue to have samples to work on. Ninety minutes later, du -sh on its data directory:

3.7 GB.

Three downloads, and 3.7 GB of disk. The libtorrent session explains the gap:

Poppy Playtime   downloaded   667,796,385   uploaded      161,288   1252/1252 pieces
Minecraft        downloaded   968,965,900   uploaded   33,100,881    894/894
Car Parking      downloaded 1,840,293,968   uploaded    8,565,219    871/871
                             ─────────────              ──────────
                             3.48 GB down                41.8 MB already served
paused: 0    auto_managed: 1

I asked for those three files. What I did not ask for is everything after completed_time: paused: 0, auto_managed: 1, and 41.8 MB already pushed back out to strangers. There is no seeding ratio, no stop-when-done, and no prompt. And btdownload.services.EngineBroadcastReceiver is registered for RECEIVE_BOOT_COMPLETED, so a reboot brings the engine back up and the seeding resumes.

Worth being exact about this, because I got it wrong on the first pass and the timestamps caught me. Two of the three were added 41 seconds apart, the third an hour later — that’s a person tapping things, not an app helping itself. HappyMod does not download what you didn’t ask for. It just never stops serving what you did.

That is still the flywheel, and it is enough of one. Every download becomes an indefinite upload, which is why the front-page items carry thousand-peer swarms, why my crawler kept hitting the same handful of APKs, and why a rented residential IP shows a torrent history full of mod APKs. The donor device has HappyMod on it, and somebody there once tapped download.

A few other things fell out of the same box:

<network-security-config>
  <base-config cleartextTrafficPermitted="true"/>
</network-security-config>

Global cleartext, in an app that holds REQUEST_INSTALL_PACKAGES. Their config confirms it’s used — d_no_cdn_ip is a hardcoded http://195.154.26.56:82 fallback on Scaleway for when the CDN is unreachable.

And /sdcard/Download/HappyMod/device_id.png, 36 bytes, not a PNG:

23cfea93-3e70-4fe5-b56e-898869f5f525

A persistent identifier in public storage, disguised by extension, surviving uninstall and readable by any app with storage access.

The APK itself is packed with 360 Jiagu (com.stub.StubApp plus four libjiagu*.so in assets), which is why VirusTotal shows 1/68 with ESET calling it Android/Packed.Jiagu.D Potentially Unsafe. That’s a packer detection, not a malware detection — and on a packed sample, 1/68 exonerates nothing, because the static engines can’t see inside either.

The number that wouldn’t die

This is the part that cost me two days.

I pulled the swarm peers and ran them through IP-reputation data. The first sample: 49% flagged as residential proxy exits. Second swarm: 63%. Then I pulled 499 unique peers straight out of the emulator’s own libtorrent session — peers HappyMod itself was talking to — and got 72.8%, with a median percent_days_seen of 70% and 134 nodes sitting at 100%.

My working assumption was that a normal swarm sits in the low single digits. I never measured that, which in hindsight is the same gap as the missing control arm. I read it as the finding, and wrote it up as the finding twice.

It wasn’t, and finding out why took two corrections — the second of which I didn’t find myself.

The control I built backwards

Before writing any of it up I built what I thought was a proper control. For a sample of proxy-flagged peers I picked a random other address in the same /24 — an address I had never observed in any swarm — and looked it up.

Peers:              72.8% proxy
Random neighbours:  73.3% proxy

Identical. I concluded the flag was a property of the address range rather than the host, declared the line dead, and said so in strong terms.

That control was broken. Look at what it samples:

prox = [x for x in peers if x.proxy]     # ← only already-flagged peers
sample = random.sample(prox, 60)
neighbours = [random_neighbour(ip) for ip in sample]

I drew neighbours only from /24s that were already known to contain a proxy. That conditions the control on the outcome, and of course it comes back near the peer rate. I killed a live hypothesis with a test that could not have found anything.

The correct control takes a neighbour for every peer, regardless of that peer’s own status. Pooled across six such experiments:

minecraft 893MB      33/42 = 79%   29/42 = 69%   +10
gta sa up-mod        33/45 = 73%   27/45 = 60%   +13
minecraft 937.5MB    24/45 = 53%   20/45 = 44%    +9
minecraft 932.6MB    34/45 = 76%   26/45 = 58%   +18
truckers 613MB       35/41 = 85%   28/41 = 68%   +17
toca boca 868MB      14/19 = 74%   11/19 = 58%   +16
────────────────────────────────────────────────────
pooled              173/237 = 73%  141/237 = 59%  +14

z = 3.11    p = 0.0019    95% CI: +5.0 .. +22.0 pp

Six swarms, all six in the same direction. Peers really are more likely to be flagged than random hosts in their own neighbourhoods.

And even that measures the wrong thing

I was ready to publish that number. Then the obvious objection landed, and it wasn’t mine.

To appear in my sample at all, a peer has to be powered on, connected, and running a background service right now. Residential proxy networks recruit and use exits by exactly that criterion — they want always-on devices with stable connections. Both populations are filtered by the same hidden variable.

And what did I compare them against? A random address in the same /24, which might be a switched-off laptop or an unassigned DHCP lease. I put “definitely online” next to “maybe anything.” A +14 point excess is entirely consistent with that, with no proxyware anywhere in the picture.

So p = 0.0019 is real, and it isn’t measuring what I wanted it to.

The test that would separate them needs both arms to be live torrent peers — take a swarm from an ordinary torrent, run the same matched control, and compare the two excesses. If a Linux ISO swarm also runs +14 over its neighbours, the effect is “device is switched on” and nothing more. That arm is still missing.

Where that leaves the number, stated as plainly as I can: the correlation is real and I measured it carefully. The causality is not there, and I don’t know which way it will go when someone runs the control I’m missing. I’ve been wrong about this specific number twice already, in both directions, which is a decent argument for not making a third prediction.

Meanwhile, the boring method

While the statistics were eating two days, I noticed something in the attribution data that cost nothing:

com-mod-minecraft-mod-apk-menu-god-modeunlocked-1-26-10-26_972601026.apk   889.40 MiB
com-mod-minecraft-mod-apk-menu-god-modeunlocked1-26-10-26-972601026.apk    893.48 MiB
com-mod-minecraft-mod-apk-menu-god-modeunlocked-1-26-10-26-972601026.apk   894.09 MiB

Same title, same versionCode 972601026, three different sizes, and the names differ only in punctuation.

I had the first one. I pulled the second, and the ZIP listing diff was five lines:

+ assets/dd.json
+ assets/hs
+ assets/pg
+ assets/info
+ classes6.dex

Nothing removed. And the existing code was modified too — classes.dex grew by 14,540 bytes, AndroidManifest.xml by 512.

What’s in it

assets/dd.json is 91 bytes and explains the whole design:

{"dd": [{"host": "hs"}, {"plugin": "pg"}]}

hs and pg are complete APKs, stored as plain files under names with no extension. assets/info is a 24-character token.

The loader is in the patched classes.dex, in a package called com.dd_load, and it is not obfuscated at all:

// com.dd_load, jadx output, trimmed
InputStream is = context.getAssets().open(pluginName);

// 2. extract the matching native libs
String arch = is64Bit() ? "lib/arm64-v8a/" : "lib/armeabi-v7a/";

// 3. load it as a separate APK
new DexClassLoader(file.getAbsolutePath(), dexDir, libDir, getClass().getClassLoader());

// 4. hand over control
loadClass("com.ddmx.staticcheathost.CheatManager")
    .getMethod("getInstance").invoke(null)
    .init(hostContext);              // at startup
    .onResume(activity, "appName");  // on every resume

is64Bit() goes through reflection on dalvik.system.VMRuntime. Everything else is a stock plugin-loading framework.

hscom.ddmx.staticcheathost, 3.58 MB:

lib/arm64-v8a/libumeng-spy.so           463 KB
lib/arm64-v8a/libcrashlytics-main.so    3.2 MB
permissions: INTERNET, CHANGE_NETWORK_STATE, storage, AD_ID
package names: oOOo00oO, IIllIIll, lIllIlIllIlIllI, _0x_a1, ﺎﻠﺎﻠﺎﻠﺎﻠ, لاإلاإلا

One of those library names is a lie. libcrashlytics-main.so is not Crashlytics — Firebase’s NDK libraries are 200–500 KB and named differently, and this one exports:

Java_android_app_settings_ActivitySettings_GL_Generate
Java_android_app_settings_ActivitySettings_GL_OnSingleTapConfirmed
Java_android_app_settings_ActivitySettings_SDK_Init / OnPause / OnResume / OnTouchEvent
Java_android_app_settings_ActivitySettings_encode / decode / sendEvent

An OpenGL overlay renderer with Crypto++ linked in, in a class tree named to look like Android system settings.

libumeng-spy.so I initially read as bespoke spyware because of the file name. It isn’t — it exports Java_com_umeng_umzid_Spy_*, and com.umeng.umzid.Spy is a real class in Alibaba’s Umeng SDK. That one’s genuine, which doesn’t make it good: Umeng is Chinese analytics that collects OAID and attempts IMEI.

pgcom.hydrozoa.emp, 761 KB, libdobby.so plus libAK.so, libAKCompat.so, libmod.so. Dobby is a well-known inline hooking framework. This is the actual cheat.

Decrypting the strings

hs has its strings encrypted, which is where I expected to hit the same wall as last time. It took one grep:

$ grep -rhoE '[A-Za-z_$][A-Za-z0-9_$]*\.[A-Za-z_$][A-Za-z0-9_$]*\("' sources/ | sort | uniq -c | sort -rn
10742 lIllIIlI.lIllIIlI("
    6 sb.append("

Ten thousand calls to one two-argument method. the method is built to break decompilers — it contains byteArray[-1], a loop with length3 = "".length() + 1, and a Math.random() in the key derivation, all of which are anti-decompilation traps rather than real code. But the call sites give it away:

lIllIIlI.lIllIIlI("jJVp1pyhjmnY2H7AyO+VasA=\n", "rPgcpeiB4AY=\n")

Two base64 blobs. Decode both, XOR the first against the second repeating. That call site comes out as the string below, which is what told me the routine was right:

ct  = base64.b64decode(a)
key = base64.b64decode(b)
out = bytes(ct[i] ^ key[i % len(key)] for i in range(len(ct)))

5,156 unique strings came out. Every URL among them:

ulogs.umeng.com          cnlogs.umeng.com/ext_event      ucc.umeng.com/v2/inn/fetch
utoken.umeng.com         aspect-upush.umeng.com          resolve.umeng.com
pslog.umeng.com          alogus.umeng.com                ulogs.umengcloud.com

Filter out Umeng, Google, Android and standard schema URLs and nothing remains. Not one third-party domain in the entire payload.

The “sensitive” keyword hits are okhttp’s own library strings — Proxy-Authorization, HTTP_PROXY_AUTH (407), exhausted proxy configurations. Stock HTTP client code, not a relay. There is no DexClassLoader inside hs, no reference to dd.json, hs or pg; the loader lives entirely in the host APK. HiddenApiBypass is present, which is a real open-source library for reaching restricted Android APIs.

So: a cheat framework with covert Chinese telemetry, camouflaged library names, five thousand encrypted strings, and no C2 of its own.

That is the answer to the question I’ve been carrying since May. For this sample, the hidden payload is ad-and-analytics-monetised cheat middleware, built to resist the kind of analysis I was doing. It is not proxyware.

The structural risk doesn’t go away, though. the delivery mechanism is a dynamic loader reading unencrypted APKs out of assets/. The same loader in tomorrow’s build will pick up whatever hs is placed next to it, the outer APK will look identical, and no app update is needed. What sits in that slot today is analytics, and I have no way to know what sits there next month.

Doing it again on purpose

If the pair-diff worked once by luck, it should work again on demand. I pulled every same-title-different-size pair I could find. Seven APKs, one script — note that it only looks for Kit A’s asset names, so a hit here means this exact kit, not “injected”:

unzip -l "$f" | grep -cE 'assets/(dd\.json|hs|pg|info)$'

One hit:

up-mod-minecraft-dream-it-build-it-...-972600201-972600201.apk   888.44 MiB   ★
up-mod-minecraft-dream-it-build-it-...-972600201_972600201.apk   884.36 MiB   clean

Different title. Same injection. And the payload is byte-identical:

dd.json   4fc633e9b0dfb2e5   91 B         identical
hs        5c35a8b698b51a42   3,580,915 B  identical
pg        18bfbf572832f35a   761,504 B    identical
info      d1bb18c5… vs 13600a47…          different

Only info changes:

iEzQlKvNdFjOzHnUqUvMyAyS      (god mode build)
gFfIfVmBhLwDuNqXpBoLnImO      (dream it build it)

Twenty-four characters, alternating case. That’s a per-build identifier — the thing CheatManager reports to say which repack it’s running in. So it’s a pipeline: one fixed kit stamped onto title after title, with a fresh key each time.

Note also that classes6.dex was not present in the second one — that was multidex numbering in the first sample, not a marker. The reliable signature is the four asset files.

Two for two on the pair method, in about twenty minutes of work.

A taxonomy fell out

Eleven APKs in, the operators separate, and the signing key name is the pivot:

Kit A — ddmx (key JTWJEST, L=indna, C=9821, Dec 2024)

assets/dd.json + hs + pg + info, payload byte-identical across titles
base mod: com.an1menu.Menu + libNotlib.so
seen in: Minecraft god mode (893.5 MiB), Minecraft Dream it Build it (888.4 MiB)

Kit B — speedremoveads / magicmodapk (keys SIGNOVER, SIGN10_K)

*.speedremoveads.RMApplication + embedded Flutter UI + com.joke.speedmenu
assets/htm.txt (plaintext JSON) + assets/acf (encrypted, 22 KB)
POST -> mmk.magicmodapk.com/api/mmu.php
seen in: GTA SA x2, Truckers of Europe 3

Kit B’s config is not even hidden:

{"source":"magic","toast":0,"update_dialog":1,"openscreen":0,
 "menu_speed":0,"menu":1,"adremove":1,"unlimited_resources":0}

Its strings use the same two-argument XOR scheme, and decrypting them gives a promo widget: it POSTs packageName, versionName, versionCode and a dialog version, receives three promo slots with URLs, icon and colours, and renders them in a floating menu with VISIT US and Join Telegram buttons. No device identifiers.

The interesting bit is that speedremoveads shows up under two different signing keys, so it’s a service that multiple repackers use rather than one crew. So the signing key tells you who did the repack, which is a separate question from which toolkit they used.

The truckers pair shows it directly: two builds of the same version, 30 KB apart, one signed JTWJEST and clean, one signed SIGNOVER carrying Kit B.

The seeders are not part of the crowd

One thing the IP data did produce cleanly. Every torrent I pulled off the device was created by the same client build:

CREATED BY: Transmission/3.00 (bb6b5a062e)
92.204.40.68    velia.net  DE-VELIANET1     proxy=false, 0 networks
92.204.53.212   velia.net                   proxy=false
92.204.54.21    velia.net                   proxy=false
92.204.53.56    velia.net                   proxy=false
148.72.144.76   velia.net  VIG-97           proxy=false
148.72.158.43   velia.net  VIG-97           proxy=false
51.15.23.143    Online.net / Scaleway NL    proxy=false

Dedicated servers at one German host plus one box at Scaleway, the same machines appearing across Minecraft, GTA and Car Parking swarms, none of them in any proxy network. That’s the seeding fleet, separate from the residential noise around it. It also works as a negative control for the reputation data, which declines to flag any of them.

What my own SYN data already said

In March I fingerprinted 319,706 residential proxy exits by their TCP handshakes. 98% Linux, and — the figure that matters here — two near-identical stock kernel defaults covering more than 70% of the pool between them, MSS 1460, window scale 12 or 6.

That is not the shape a population of recruited Android phones makes. Phones run Linux kernels too, so they land inside that 98%, but they do not handshake in lockstep: different vendors, different kernel versions, different carrier MTUs, a mess of window scales. Half a pool sharing one fingerprint on plain Ethernet is a pool of boxes. And the 6.9% sitting at MSS 1412 is tunnel overhead — wholesale gateways re-exporting somebody else’s traffic, which is a different kind of not-a-phone.

I read that post at the time as being about the pool being fake. Re-reading it against this one, it was also telling me where not to look. If mod-APK recruitment were filling these pools at any scale, the SYN distribution would have to carry a much larger and much messier Android signature than it does.

Which makes this week’s null less surprising than it felt at the time. My own composition data had already relegated the Android channel to a minority tributary in March, and I still spent two days measuring a fourteen-point correlation inside it.

The honest ledger

Proven, from direct evidence on the device:

HappyMod seeds everything it downloads, indefinitely and without asking — three requested files, 41.8 MB served back out to strangers, paused: 0 after completion and a boot receiver so it resumes after restart. It runs a global tracker fleet under four domains. It writes a persistent identifier to public storage disguised as a PNG. It permits global cleartext HTTP while holding REQUEST_INSTALL_PACKAGES, with a hardcoded plaintext fallback IP.

Not proven, and I claimed it before checking: that it downloads anything you didn’t request. The resume timestamps say otherwise and I have corrected the section above.

Proven, from the samples:

At least two builds carry a hidden dynamic loader whose payload is byte-identical between titles and keyed per build. The payload is a cheat framework with Umeng telemetry and camouflaged component names, and it has no command-and-control infrastructure of its own. Two separate repacking kits are in circulation, distinguishable by signing key and manifest entry point.

Not proven:

That any of this recruits proxy exits. The +14 point excess is a measurement without a causal arm behind it, and the arm it needs is listed under “Still open”. Nor does a clean sample today constrain tomorrow’s assets/hs.

Where the statistics went wrong

Build the control before the hypothesis gets interesting. It costs three minutes. I ran mine on day two, after I’d already invested in the answer, and then built it in the shape that confirmed what I expected.

A negative control is not the same as a matched control. Sampling neighbours of already-flagged hosts conditions on the outcome, and I did it anyway.

File names are not identity. Name, version and versionCode are all attacker-controlled and were all identical across the three builds. Size and hash were the only fields that told them apart.

And the cheap methods won. Every hour spent on IP statistics produced either nothing or a retraction. Everything that survived came from putting the app on an emulator and looking at the disk, or from unzip -l on two files whose names differ by a hyphen.

Indicators

Kit A (ddmx) — the four asset files are the signature; classes6.dex is not:

assets/dd.json    91 B      {"dd":[{"host":"hs"},{"plugin":"pg"}]}
assets/hs         3,580,915 B   sha256 5c35a8b6…  com.ddmx.staticcheathost
assets/pg         761,504 B     sha256 18bfbf57…  com.hydrozoa.emp
assets/info       24 B          per-build id, alternating case

Inside hs:

lib/*/libumeng-spy.so           genuine Umeng UMZID
lib/*/libcrashlytics-main.so    NOT Crashlytics  GL overlay + Crypto++
class tree                      android.app.settings.ActivitySettings
loader                          com.dd_load, com.ddmx.staticcheathost.CheatManager

Kit B (speedremoveads):

android:name="com.??.speedremoveads.RMApplication"    prefix varies (com.hm, com.mma)
assets/htm.txt        {"source":"magic",}
assets/acf            encrypted, ~22 KB
POST                  https://mmk.magicmodapk.com/api/mmu.php
                      packageName, versionName, versionCode, dialogVersion
promo target          https://t.me/topfreeapks
UI class              com.joke.speedmenu.widget.SpeedMenu.SpeedMenuLayout

HappyMod 3.4.0 infrastructure, from shared_prefs/launch_doman.xml:

https://app.happymodapp.com          config
https://om-server.btc42.com/init     ad config (POST only, Spring Boot)
https://logt.apkomega.com            BT telemetry
https://log1.apkomega.com
https://log.360modbaixar.com
https://ads.appbundledownload.com
http://195.154.26.56:82              cleartext fallback, Scaleway

Trackers:

tracker.hyper-torrent.com    apktorrents.com    best82.com    best61.com
geo-coded subdomains: tra-<region>-<city>.<domain>:80xx/announce

Seeding fleet — Transmission/3.00 (bb6b5a062e), all proxy=false:

92.204.40.68  92.204.53.212  92.204.54.21  92.204.53.56    velia.net (DE)
148.72.144.76  148.72.158.43                               velia.net (VIG-97)
51.15.23.143                                               Online.net / Scaleway NL

Samples:

39aefb7ff70b188c0484da37183cbc5e6d6b176a829ed27b5e1fda1bb86c3804   936,886,778   ddmx
e4b09ee783e69f524b35ce16d16567788d5afb71d314b37ad0d2333a682cec47   932,606,173   clean
7165a2b3fed886c1a301fc4f55bae8cecf8210dd179d2a54bc4f7ad941f82ae7    21,021,090   HappyMod 3.4.0

Signing keys as a pivot:

JTWJEST    L=indna, C=9821                          2024-12-06
SIGNOVER   C=ef, ST=ds, L=rtte, O=ck, OU=ywh, CN=kd 2025-06-27
SIGNOVER   C=usa, ST=str, L=msk, O=O, OU=u, CN=usa  2025-07-07
SIGN10_K   C=ds, ST=ds, L=sd, O=ds, OU=ds, CN=ds    2024-12-11

Still open

The live-peer control arm, which is the only thing that turns the +14 into evidence or into an artifact. assets/acf, Kit B’s 22 KB encrypted config, which should fall to the same decryptor. And the original sample from May, still sitting on the bench, still silent — though I now have a much better idea of what to expect when it opens.

Method note: all sample analysis is static, on a rooted emulator with no personal data on it. Reputation lookups are one read-only API call per address. I report ISP/ASN aggregates and sample hashes; individual addresses appear only where they are infrastructure rather than subscribers. Nothing here touched a host that didn’t offer the file.