← All writing

Threat Intelligence / September 30, 2026

Cover Story: An Android C2 Masked by an APK Distribution Network 

BTMOB and BCTV: payload recovery, command traffic, Telegram build services and the infrastructure connecting delivery to device control.

Three malicious Android applications reached us wearing the costume of ordinary streaming apps. Two belong to the BTMOB surveillance lineage, presented as MOX/DPTV and BCDV. The third, BCTV, arrived through the same criminal supply chain it advertises from. All three conceal a remote-access payload behind a fake “app update” journey, enroll over encrypted channels, and are wired for credential collection.

Verdict: cmxubPB37M.apk, MXxEEHVGctOa.apk and BCTV.apk are malicious Android applications carrying BTMOB-aligned remote-access and surveillance payloads, and should be handled as malware.

This post summarizes the full investigation: the recovered payloads, the command traffic we captured, the Telegram build services that assemble these APKs, and the infrastructure that ties delivery to device control.

The specimens at a glance

ArtifactPackageSizeRole
cmxubPB37M.apkcom.wpit.dooo.pkcxr3.4 MB, MOX/DPTV brandingBTMOB outer APK, static + dynamic analysis
MXxEEHVGctOa.apkcom.hnllwbjt.gvwcv.rehwiovt10,598,557 bytes, BCDV label with invisible Unicode between BC and DVSecond BTMOB specimen, static analysis
BCTV.apknet.file.clock.plus12,748,128 bytesOuter BCTV wrapper with native loader

Each outer APK hides a concealed secondary package, and each secondary package hides an encrypted DEX. The MOX/DPTV inner payload runs as com.meaghddz.jvva; BCDV’s secondary package is com.srlq.btvt; BCTV’s is com.editor.sync.zone with application com.tools.boostersync.App. Outer and inner packaging can both show related branding, which makes the second installation less obvious to a user.

Exact hashes for all four known files are in the detection section below.

How the investigation developed

  1. Original BTMOB sample. cmxubPB37M.apk, presented as MOX/DPTV, yielded an extracted b.apk payload and captured communication with 65[.]20[.]85[.]237. The original download URL is unspecified in the retained evidence.
  2. Second BTMOB specimen. The collector supplied MXxEEHVGctOa.apk from inducrious[.]top. It displayed BCDV branding and configured 139[.]84[.]137[.]223. Static analysis established the BTMOB-aligned payload.
  3. BCTV acquisition. BCTV.apk was reported downloaded from femotale[.]top. Its recovered installer project pdocwm8 matched the supplied alphaleaf[.]org/p/pdocwm8/dl URL, and installer telemetry referenced plainlookout[.]org.
  4. BCTV control channel. Recovered code and the recorded session selected chaocctv[.]com. The session logged incoming Socket.IO command actions, and the public /panel frontend contained matching action names plus a Build link to @xzziyuanbot.
  5. Builder interaction. The researcher configured the builder bot, which generated 89b49e08.apk as com.easy.flashplan with Play Store branding. This build is labeled research-generated throughout.
  6. Downloader handoff. The builder referred the researcher to @v2mszybot. That service produced Play Store.apk under project pnqdwbl. The recovered DEX matches the first bot output byte-for-byte.
  7. Infrastructure correlation. Registration and DNS linked all 17 bot-issued distribution domains and BCTV’s telemetry domain to one address, 85[.]137[.]51[.]237, a common registration date and the same nameserver pair.

One chain, two control protocols

The delivery connections are strong, but the payloads themselves speak different protocols. BTMOB bootstraps through /yaarsa/private/yarsap_80541.php and exchanges its own WebSocket messages. BCTV uses /adv.php, Socket.IO events and a separate binary channel. Same criminal service layer, different control planes.

ConnectionEvidenceAssessment
BCTV installer to alphaleaf distributionpdocwm8 occurs in both the recovered installer configuration and the supplied download URLExact project-identifier match
BCTV installer to bot distribution clusterplainlookout[.]org shares IP, registration day, registrar and nameserver pair with the 17 bot-issued domainsStrong infrastructure correlation
chaocctv panel to builder botSaved panel JavaScript links directly to @xzziyuanbotDirect service link
Builder APK to downloader APKDecrypted downloader payload DEX is byte-identical to classes.dex in 89b49e08.apkConfirmed payload continuity
BCDV distribution to BCTV distributioninducrious[.]top and femotale[.]top share NameSilo and the carl/nena Cloudflare nameserver pairDNS-administration lead
BTMOB to BCTV display destinationfamelack[.]com appears as a configured display-page destination in bothShared presentation resource; ownership requires separate evidence

The full lifecycle, delivery to collection

The outer app presents an update page and installs a second APK. That package loads the RAT, contacts its server and starts reporting. The chain:

  1. Delivery. A streaming-themed outer APK: MOX/DPTV or BCDV branding.
  2. Unpack installer. Static extraction recovers the installer DEX plus HTML, artwork and the secondary b.apk.
  3. Install inner APK. An update-style user journey: VPN and install-source prompts, and Android records a second package.
  4. Load RAT payload. The secondary loader decrypts the DEX: a byte transform for MOX, authenticated AES-GCM for BCDV.
  5. Initialize state. Services start and saved configuration loads.
  6. Bootstrap C2. An HTTP HEAD/POST exchange returns the connection fields.
  7. Enroll over WebSocket. Device metadata and wallpaper leave the device; server options arrive.
  8. Maintain reporting. A background status loop continues reporting, recovering the socket when needed.
  9. Collect and retrieve. UI events, injected-page submissions, clipboard and microphone paths feed local stores, and a separate handler returns stored content to the C2 on request.

Installer behavior, VPN scope and emulator detection

The original application opens com.shell.a.MainActivity and displays a bundled installing.html update page, even without Internet access. Its ratings, review count and dates are text in that local page, not evidence of a genuine Play Store listing.

Pressing Update brings up Android VPN consent and installation permissions. The VPN implementation routes selected Google services and Play Store packages into the VPN, and we found no forwarding code, suggesting this traffic is dropped. The captures show the consent flow but do not confirm a Play Protect bypass. Android package records do tie the inner APK installation to the outer app.

The payload also carries an emulator check that reads Android build properties. On the test device the check produced a warning and exited initialization, yet foreground services remained present. Launching another entry point left the detector intact: the warning interrupted one activity while other components kept running.

ObservationAnalysis
Update page opens offlineThe app renders bundled HTML; displayed ratings are page content
VPN consent and key iconAndroid reached VPN setup; no Play Protect evasion was measured
Outer app recorded as installerPackage records tie the inner installation to the outer app
Emulator warningInitialization exits, but foreground services remain
Services remain after HomeThe app continues running in the background during the captures

Two containers, two decoders

The two BTMOB specimens use different protection layers around the same family behavior, and that distinction matters for anyone writing recovery tooling.

The original outer decoder skips a 24-byte prefix, reads a big-endian encoded-region length, and transforms exactly that declared region before GZIP decompression. Applying a decompressor directly to the original asset is expected to fail. The recovered transform:

int x = input.readUnsignedByte();
x = ((x >>> 6) | (x << 2)) & 255;
int key = (((index * 48 + 21) + (index >>> 7)) ^ 229) & 255;
x = (x ^ key) & 255;
x = (x + 68) & 255;
x = (x * 181) & 255;
x = (x * 151) & 255;
x = (x - 166) & 255;
x = (x ^ 110) & 255;
x = (x + 152) & 255;

For the first three input bytes the outputs are 31, 139 and 8: the GZIP signature 1f 8b 08. After decompression, the first bytes are a DEX 037 header. Validation then compares the whole recovered installer buffer against the preserved extraction, not just its magic bytes.

BCDV is different. Its inner container begins after a 63-byte prefix and uses AES/GCM/NoPadding with a 128-bit tag, a key reconstructed by XORing two 32-byte shares, and additional authenticated data that binds the payload index:

BCDV framing, big-endian integers:
  prefix[63] | count:u32
  for index in payloads:
    nonce[12] | ciphertext_length:u32 | ciphertext_with_tag[length]

Key = share_1 XOR share_2
AAD = hex(dd6d21398c443a3ca0bd0aedf5a8e8e5) || uint32_be(index)
DEX = GZIP(AES_GCM_Decrypt(key, nonce, AAD, ciphertext_with_tag))

A successful GCM verification is evidence that the extracted record matches the recovered construction; it does not authenticate the malware publisher. Two specimens being “same family” must not imply “same packer.”

Recovered staging and configuration

The BCDV sample’s inner encrypted asset yields its DEX through authenticated AES-GCM handling, and 3,265 XOR-obscured string expressions decoded cleanly. The recovered configuration constants:

Cipher:      AES/CBC/PKCS5Padding
KDF:         PBKDF2WithHmacSHA1
Iterations:  65536
Key length:  128 bits
Password:    4814780584699673
Salt, UTF-8: 2894356330652558
IV, UTF-8:   2230209522049090

And the ciphertext-to-value mappings, defanged:

uL9kuiuUuqRKGx9xXHCCIQ==            -> 65[.]20[.]85[.]237
t6sh+arL30BhWpyVqnpBqg==            -> 139[.]84[.]137[.]223
ZlbrGBSDz6h0b5GlZQz2GoCDRXhJ9HlwSvQhXXqnt0g= -> hxxps://famelack[.]com/

The trailing less-than sign is a configuration delimiter, not part of an address. These values identify configured destinations at extraction time; they do not establish present-day reachability. The famelack[.]com value is a configured display-page destination, not established as a command server, and the site’s own FAQ states it does not release an app or APK. The malware’s use of that page is treated separately from the site operator’s distribution.

HTTP bootstrap and the WebSocket channel

The bootstrap request is form-encoded and, despite the name, user_email carries an opaque configured token rather than an email address:

POST /yaarsa/private/yarsap_80541.php
Host: 65[.]20[.]85[.]237
Content-Type: application/x-www-form-urlencoded

&user_email=CMwDA4h3PS5982CwIJu%2F2J7IUPgOQtZ5ZXezJoeQzcE%3D

The response prefix Conf: introduces the connection fields idf, cip, sk and ad. The capture then contains two distinct upgraded connections in the first live window: one probe-like socket that exchanges close frames immediately, and one application socket that carries four outbound and five inbound text frames. Conflating the two would make a healthy enrollment look like an instant disconnect.

The observed protocol sequence: HTTP bootstrap, probe upgrade with a reciprocal close, application upgrade, a join envelope, connected/optns/spng replies, a device report including wallpaper, then repeated status reports and option messages.

What actually crossed the socket

The original sample was observed for 601 seconds on 14 September 2026. The final reconstruction contains 81 WebSocket text messages, all belonging to the MOX lineage:

DirectionMessage categoryCount
App to serverjoin1
App to serverDevice information: add1
App to serverStatus: png38
Server to appconnected1
Server to appOptions: optns39
Server to appStatus request: spng1

Every incoming option message shared the same normalized values, with keystrokes and notifications enabled:

keystrokes = 1      Activities = 0    visitedapps = 0
notifications = 1   visitedlinks = 0  livenotify = 0
livescreen = 0      autoj = 0

Status intervals averaged 15.672 seconds, and all 21 checkpoints retained the same PID and two foreground services. No main-socket reconnect occurred during the window. The data dictionary matters here, because the field names are traps:

FieldActual roleInterpretation trap
type=addEnrollment/device reportNot just an empty heartbeat
wallpapBase64 PNG wallpaper in addNot a captured screen stream
type=pngApplication status JSONNot a PNG file or protocol ping
keylogsAvailable cache inventoryNot actual keylog contents
admStatus field, value 0Not proof of granted device administrator
conkConnection-envelope valueNot a proven unlock password

The distinction between index and content is central to this specimen. A matching local cache later yielded 143 UI/text records during offline analysis, which strengthens the evidence for local collection. It still does not retroactively insert those records into a network frame that contained only a filename. Exfiltration claims must follow the actual value through the send path or decode it from the capture.

The lifecycle windows measured Home navigation, sustained background reporting, a screen-off transition and stale-socket recovery. Measured continuity: same PID, foreground services and main socket at repeated checkpoints, including messages after Home and during short screen-off. Not measured: reboot recovery, force-stop resistance, hour-long uptime and prolonged Doze. These observations establish sustained execution during the windows, not persistence across reboot.

Collection and retrieval boundaries

The injected-page chain is the concrete basis for the malicious assessment. Incoming fields identify an injection target and provide page data; a JavaScript bridge accepts submitted content, stores it by application, and a separate handler can return those records to the communication layer:

Incoming injection fields: isinjct / jctid / filedata
  -> extract target-specific content
  -> accessibility target and file checks
  -> launch injected-page activity
  -> JavaScript returnResult(submitted_string)
  -> per-target storage in AppDataPrefs/appData
  -> retrieval handler builds type ject with jdata entries
Capability or findingEvidence levelLimit
Injected-page data retrievalExecutable code pathNo real credential theft observed
Clipboard response, type clipExecutable code pathNo captured clipboard theft established
Keystroke and notification optionsIncoming configuration observedCapture success unverified
143 local UI/text recordsRecovered local acquisitionNot proven real passwords or uploaded records
Microphone-to-voc data pathCode supports WAV construction and encodingNo observed microphone command or audio exfiltration

The microphone path is a good example of an honest boundary. The handlers prepend a 44-byte RIFF/WAVE PCM header with caller-supplied rate, channels and bit depth, which establishes WAV PCM as the intended representation. It remains code evidence: the investigation resolved the format and did not recover private conversations. The same applies to the miner controller, which constructs a path to libxmrig.so and explicitly handles the missing file: no native library was present in either APK inventory, and no mining traffic was established.

The Telegram build service

The public BCTV panel links its Build menu to @xzziyuanbot, an APK creation bot with a six-step wizard: backend domain prefix, panel APK ID, display name, icon, disguise homepage URL and version. Suggested identities include Chrome, System Update, Play Store and Samsung Health. The builder then refers customers to the downloader service @v2mszybot, which wraps the payload a second time, creates mirrored landing pages and tracks installation statistics. In our recorded session, the downloader’s recovered DEX matched the builder’s output byte-for-byte, proving code continuity through the paid wrapper.

The service is a paid subscription: 300 USDT over TRC20 for one APK slot for 30 days, invoiced through the bot with an exact decimal amount per customer for amount-based reconciliation. Telegram messages advertise persistence features, 114-language coverage and antivirus evasion; static analysis confirms the added wrapper and an unchanged recovered payload, with the performance uplift remaining an advertising claim.

FieldRecorded value
NetworkTRON, USDT-TRC20
Requested amount300.030 USDT
Account entitlementOne APK slot, one remaining, expiring 30 days
Payment status at captureNot yet detected

The project console and management backends were reachable at check time; the checked download URLs returned 404/410. These are historical supporting observations and make no claim about present-day reachability.

Infrastructure correlation

Registration and DNS collection covered 27 domains, five additional hostnames and 21 IPs. The strongest findings:

  • High-confidence distribution cluster. All 17 bot-issued landing, backup and statistics domains resolve to 85[.]137[.]51[.]237, were registered on 2026-08-17, list the same registrar, and share the alex/jamie Cloudflare nameserver pair and the bot-issued project pnqdwbl.
  • The plainlookout link. plainlookout[.]org, recovered from BCTV’s installer telemetry, has the same IP, registration day, registrar and nameserver pair as that cluster. This materially strengthens the connection between the independently acquired BCTV installer and the bot-issued distribution infrastructure.
  • The chaocctv link. chaocctv[.]com shares the registrar and nameserver pair and resolves to 103[.]106[.]229[.]69. Its panel’s saved JavaScript directly links to the builder bot, an application-level link stronger than any nameserver overlap.
  • OEM service movement. www[.]whh6666[.]com now resolves to a different address than the one recorded in the earlier session, while both allocations share the same /24 neighborhood and network label. Hosting overlap and a changed endpoint, not a proven common owner.
  • The realsadtv dead end. The domain selected by the researcher during APK generation returned NXDOMAIN; historical session findings remain tied to their specimens and collection dates.

Earlier-sample comparison endpoints from published analyses: 78[.]135[.]93[.]123 and 195[.]160[.]221[.]203.

Detection engineering

The strongest hunting logic combines specimen identity, package topology and protocol semantics. The validated detector identifies four exact APKs and classifies decoded BTMOB protocol messages:

RuleMatch criteriaInterpretation
Exact specimenFull-file SHA-256 equals a listed digestIdentifies the exact known malicious file; repacking changes the hash
BTMOB_CLIENT_JOINitype Slr_client, BT-MOB identifier prefix, subc joinProtocol-specific client enrollment
BTMOB_CLIENT_ADD/PNGSame client markers plus nested type add or pngDevice-report or status message
CANDIDATE_SERVER_OPTNS/SPNGtype com, nested pdata.msg optns or spngCorrelate with the client session; not a standalone verdict
KNOWN_SHA256 = {
    'd73a845e6b7ad7676b53687127d98ec91999e1778801d651dada43e27787aa0a': 'BCTV outer APK',
    '304f5802a2660698772ba9357e75edc9e7d21da3594163cf242fffaf5e69632c': 'MOX outer APK',
    '3fb7b91b3c0c49395b36291cff019c6e79036b563e22c42cf7ee3e4ac46fae09': 'MOX embedded APK',
    '9962c0a2d069419163ca003dee3ea831b257f4ddd6ea4863a50cae1de2e4da89': 'BCDV outer APK',
}

def classify_frame(frame):
    msg = as_object(frame)
    identifier = msg.get('idf')
    if (msg.get('itype') == 'Slr_client'
            and isinstance(identifier, str)
            and identifier.startswith('BT-MOB')):
        if msg.get('subc') == 'join':
            return 'BTMOB_CLIENT_JOIN'
        inner = as_object(msg.get('msg'))
        if msg.get('subc') == 'msg' and inner.get('type') in ('add', 'png'):
            return 'BTMOB_CLIENT_' + inner['type'].upper()
    if msg.get('type') == 'com':
        command = as_object(msg.get('pdata')).get('msg')
        if command in ('optns', 'spng'):
            return 'CANDIDATE_SERVER_' + command.upper()
    return None

Validation matched all four preserved APKs and eight messages in the saved BTMOB session. Seven malformed or unrelated message cases, an unrelated file and a one-byte-modified BCTV sample returned no match. Missing a hash or protocol match is not a benign verdict; server-command candidates require endpoint correlation before attribution.

Priority IOCs

IndicatorType and roleRecommended use
65[.]20[.]85[.]237Original observed C2, TCP 80 and 8080Block and investigate historical connections
139[.]84[.]137[.]223BCDV configured C2Block and hunt with specimen context
/yaarsa/private/yarsap_80541.phpBootstrap HTTP pathCorrelate with the form token and WebSocket flow
www[.]chaocctv[.]com/adv.phpConnection bootstrapCorrelate with Socket.IO /io1771 activity
85[.]137[.]51[.]237Bot-issued distribution clusterHunt for related campaign infrastructure
com.wpit.dooo.pkcxr to com.meaghddz.jvvaMOX outer to inner packagesEndpoint and package inventory
com.hnllwbjt.gvwcv.rehwiovt to com.srlq.btvtBCDV outer to inner packagesEndpoint and package inventory
net.file.clock.plus to com.editor.sync.zoneBCTV outer to inner packagesEndpoint and package inventory
304f5802a2660698772ba9357e75edc9e7d21da3594163cf242fffaf5e69632cMOX outer APK SHA-256Exact sample match
d73a845e6b7ad7676b53687127d98ec91999e1778801d651dada43e27787aa0aBCTV outer APK SHA-256Exact sample match

Closing

The streaming-app costume is the least interesting layer of this chain. What makes BTMOB and BCTV effective is everything behind it: layered encrypted containers, a quiet enrollment protocol, an operator economy that sells packaging as a subscription, and infrastructure registered in coordinated batches. The specimens we analyzed communicate on different control protocols despite shared delivery plumbing, so detections should combine file identity, package topology and protocol semantics rather than trusting any single indicator.

Quarantine matching APKs, identify both outer and inner package installations, isolate affected devices from the recovered C2 destinations, preserve application state and network evidence, and investigate data exposure using the decoded messages and acquired stores. The full report, including the complete indicator appendix and preserved evidence hashes, is available on request.

VirusTotal references for the analyzed specimens are published under their SHA-256 digests: MOX outer APK, MOX embedded APK, BCDV outer APK and BCTV outer APK.