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
| Artifact | Package | Size | Role |
|---|---|---|---|
cmxubPB37M.apk | com.wpit.dooo.pkcxr | 3.4 MB, MOX/DPTV branding | BTMOB outer APK, static + dynamic analysis |
MXxEEHVGctOa.apk | com.hnllwbjt.gvwcv.rehwiovt | 10,598,557 bytes, BCDV label with invisible Unicode between BC and DV | Second BTMOB specimen, static analysis |
BCTV.apk | net.file.clock.plus | 12,748,128 bytes | Outer 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
- Original BTMOB sample.
cmxubPB37M.apk, presented as MOX/DPTV, yielded an extractedb.apkpayload and captured communication with65[.]20[.]85[.]237. The original download URL is unspecified in the retained evidence. - Second BTMOB specimen. The collector supplied
MXxEEHVGctOa.apkfrominducrious[.]top. It displayed BCDV branding and configured139[.]84[.]137[.]223. Static analysis established the BTMOB-aligned payload. - BCTV acquisition.
BCTV.apkwas reported downloaded fromfemotale[.]top. Its recovered installer projectpdocwm8matched the suppliedalphaleaf[.]org/p/pdocwm8/dlURL, and installer telemetry referencedplainlookout[.]org. - BCTV control channel. Recovered code and the recorded session selected
chaocctv[.]com. The session logged incoming Socket.IO command actions, and the public/panelfrontend contained matching action names plus a Build link to@xzziyuanbot. - Builder interaction. The researcher configured the builder bot, which generated
89b49e08.apkascom.easy.flashplanwith Play Store branding. This build is labeled research-generated throughout. - Downloader handoff. The builder referred the researcher to
@v2mszybot. That service producedPlay Store.apkunder projectpnqdwbl. The recovered DEX matches the first bot output byte-for-byte. - 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.
| Connection | Evidence | Assessment |
|---|---|---|
| BCTV installer to alphaleaf distribution | pdocwm8 occurs in both the recovered installer configuration and the supplied download URL | Exact project-identifier match |
| BCTV installer to bot distribution cluster | plainlookout[.]org shares IP, registration day, registrar and nameserver pair with the 17 bot-issued domains | Strong infrastructure correlation |
| chaocctv panel to builder bot | Saved panel JavaScript links directly to @xzziyuanbot | Direct service link |
| Builder APK to downloader APK | Decrypted downloader payload DEX is byte-identical to classes.dex in 89b49e08.apk | Confirmed payload continuity |
| BCDV distribution to BCTV distribution | inducrious[.]top and femotale[.]top share NameSilo and the carl/nena Cloudflare nameserver pair | DNS-administration lead |
| BTMOB to BCTV display destination | famelack[.]com appears as a configured display-page destination in both | Shared 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:
- Delivery. A streaming-themed outer APK: MOX/DPTV or BCDV branding.
- Unpack installer. Static extraction recovers the installer DEX plus HTML, artwork and the secondary
b.apk. - Install inner APK. An update-style user journey: VPN and install-source prompts, and Android records a second package.
- Load RAT payload. The secondary loader decrypts the DEX: a byte transform for MOX, authenticated AES-GCM for BCDV.
- Initialize state. Services start and saved configuration loads.
- Bootstrap C2. An HTTP HEAD/POST exchange returns the connection fields.
- Enroll over WebSocket. Device metadata and wallpaper leave the device; server options arrive.
- Maintain reporting. A background status loop continues reporting, recovering the socket when needed.
- 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.
| Observation | Analysis |
|---|---|
| Update page opens offline | The app renders bundled HTML; displayed ratings are page content |
| VPN consent and key icon | Android reached VPN setup; no Play Protect evasion was measured |
| Outer app recorded as installer | Package records tie the inner installation to the outer app |
| Emulator warning | Initialization exits, but foreground services remain |
| Services remain after Home | The 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:
| Direction | Message category | Count |
|---|---|---|
| App to server | join | 1 |
| App to server | Device information: add | 1 |
| App to server | Status: png | 38 |
| Server to app | connected | 1 |
| Server to app | Options: optns | 39 |
| Server to app | Status request: spng | 1 |
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:
| Field | Actual role | Interpretation trap |
|---|---|---|
type=add | Enrollment/device report | Not just an empty heartbeat |
wallpap | Base64 PNG wallpaper in add | Not a captured screen stream |
type=png | Application status JSON | Not a PNG file or protocol ping |
keylogs | Available cache inventory | Not actual keylog contents |
adm | Status field, value 0 | Not proof of granted device administrator |
conk | Connection-envelope value | Not 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 finding | Evidence level | Limit |
|---|---|---|
| Injected-page data retrieval | Executable code path | No real credential theft observed |
| Clipboard response, type clip | Executable code path | No captured clipboard theft established |
| Keystroke and notification options | Incoming configuration observed | Capture success unverified |
| 143 local UI/text records | Recovered local acquisition | Not proven real passwords or uploaded records |
| Microphone-to-voc data path | Code supports WAV construction and encoding | No 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.
| Field | Recorded value |
|---|---|
| Network | TRON, USDT-TRC20 |
| Requested amount | 300.030 USDT |
| Account entitlement | One APK slot, one remaining, expiring 30 days |
| Payment status at capture | Not 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 projectpnqdwbl. - 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[.]comshares the registrar and nameserver pair and resolves to103[.]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[.]comnow 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:
| Rule | Match criteria | Interpretation |
|---|---|---|
| Exact specimen | Full-file SHA-256 equals a listed digest | Identifies the exact known malicious file; repacking changes the hash |
| BTMOB_CLIENT_JOIN | itype Slr_client, BT-MOB identifier prefix, subc join | Protocol-specific client enrollment |
| BTMOB_CLIENT_ADD/PNG | Same client markers plus nested type add or png | Device-report or status message |
| CANDIDATE_SERVER_OPTNS/SPNG | type com, nested pdata.msg optns or spng | Correlate 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
| Indicator | Type and role | Recommended use |
|---|---|---|
65[.]20[.]85[.]237 | Original observed C2, TCP 80 and 8080 | Block and investigate historical connections |
139[.]84[.]137[.]223 | BCDV configured C2 | Block and hunt with specimen context |
/yaarsa/private/yarsap_80541.php | Bootstrap HTTP path | Correlate with the form token and WebSocket flow |
www[.]chaocctv[.]com/adv.php | Connection bootstrap | Correlate with Socket.IO /io1771 activity |
85[.]137[.]51[.]237 | Bot-issued distribution cluster | Hunt for related campaign infrastructure |
com.wpit.dooo.pkcxr to com.meaghddz.jvva | MOX outer to inner packages | Endpoint and package inventory |
com.hnllwbjt.gvwcv.rehwiovt to com.srlq.btvt | BCDV outer to inner packages | Endpoint and package inventory |
net.file.clock.plus to com.editor.sync.zone | BCTV outer to inner packages | Endpoint and package inventory |
304f5802a2660698772ba9357e75edc9e7d21da3594163cf242fffaf5e69632c | MOX outer APK SHA-256 | Exact sample match |
d73a845e6b7ad7676b53687127d98ec91999e1778801d651dada43e27787aa0a | BCTV outer APK SHA-256 | Exact 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.