Most online transactions require a two-step authentication, and the One-Time-Password (OTP) sent by SMS is often one of those two steps. The purpose of an OTP is to prevent fraud by confirming that the person making the transaction and the credit card owner are one and the same. To do so, a temporary code is automatically sent by SMS to the phone number associated with the bank account used.
Once the OTP SMS is received, the user types it in the transaction interface and he is only then able to finalize his purchase. But is the mobile device (tablet or smartphone) used to send and receive an SMS innocuous? Regrettably, not very. What seemed to be like a strong authentication process when it was first introduced is nowadays easily bypassed by mobile apps.
Our team identified two kinds of mobile applications using the OTP interception technique: the legitimate ones and the malicious ones. While a safe app will intercept a SMS OTP to facilitate transactions and make them fast, a malicious app will intercept it in order to commit banking fraud.
Different Purposes, Same Mechanism
The same permissions are required
The more permission an app requires, the more suspicious it looks. However, an OTP interception only requires two permissions to be executed, and one of them (Internet access) is a very common one. As a consequence, apps featuring OTP interception for a malicious purpose do not appear as suspicious at first sight.
- Permission to intercept SMS: 'android.permission.RECEIVE_SMS'
- Permission to send the content, using internet or an SMS: 'android.permission.INTERNET' or 'android.permission.SEND_SMS'
A weak spot for applications stores
The Google Play and the Apple App Store perform first-level checks to prevent malwares from entering their stores. However, the lack of depth of these security scans often lets malicious apps to pass through thanks to the system limits. It is even truer when the first visible layers of an app, the permissions and functions, are not giving any indication about its true nature.
An OTP interception mechanism can be used by both legitimate apps and malwares. As a result, it has the capacity to successfully pass stores’ standard security checks.
Case study
App name: Postbank Finanzassistent (An impostor app that mimics the official German PostBank’s one)
sha1: 32f85c91e5a1437e93128203c27cd8eeb8bbea19
This app intercepts SMS OTP in two steps:
1 – Interception of SMS with the implementation of an android.content.BroadcastReceiver (org.slempo.service.MessageReceiver)
2 – SMS content sending via (Lorg/slempo/service/MainService) and more precisely using a java.lang.Runnable (org.slempo.service.a.a$1)
1 - Interception via BroadcastReceiver
Required permission: 'android.permission.RECEIVE_SMS'
A Broadcast Receiver (MessageReceiver) is used to intercept incoming SMS with the 'onReceive' method.
Name of the Broadcast Receiver :
.class public Lorg/slempo/service/MessageReceiver;
.super Landroid/content/BroadcastReceiver;
Method :
.method public onReceive(Landroid/content/Context;Landroid/content/Intent;)V
Description :
(*) This method calls a(Landroid/content/Intent;)Ljava/util/Map to access the SMS
invoke-static {p2}, Lorg/slempo/service/MessageReceiver;->a(Landroid/content/Intent;)Ljava/util/Map;
(*) then calls the method b(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V which formats the information into JSON.
invoke-static {p1, v1, v0}, Lorg/slempo/service/a/f;->b(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V
(-) method that calls the constructor <init>(Ljava/lang/String;Lorg/slempo/service/a/a$a;Landroid/content/Context;)V which saves the information in the field: Lorg/slempo/service/a/a;->a:Ljava/lang/String;
invoke-direct {v0, v1, v2, p0}, Lorg/slempo/service/a/a;-><init>(Ljava/lang/String;Lorg/slempo/service/a/a$a;Landroid/content/Context;)V
2 - Data sending
Service name:
.class public Lorg/slempo/service/MainService;
.super Landroid/app/Service;
This method instantiate the runnable MainService$1:
.method public onCreate()V
(*) creation of Lorg/slempo/service/MainService$1
new-instance v1, Lorg/slempo/service/MainService$1;
invoke-direct {v1, p0},
Lorg/slempo/service/MainService$1;-><init>(Lorg/slempo/service/MainService;)V
Runnable Name:
.class Lorg/slempo/service/MainService$1
.implements Ljava/lang/Runnable;
Method Name:
.method public run()V
The call is made in the method run()
(*) invoke-static {v0}, Lorg/slempo/service/a/f;->a(Landroid/content/Context;)V
(-) Lorg/slempo/service/a/a;->a()V that runs Runnable Lorg/slempo/service/a/a$1
Runnable Name:
.class Lorg/slempo/service/a/a$1;
.implements Ljava/lang/Runnable;
Method Name:
.method public run()V
(*) Sending via http
Permission: 'android.permission.INTERNET'
invoke-static {v0, v2, v3, v4},
Lorg/slempo/service/a/a;->a(Lorg/slempo/service/a/a;Landroid/content/Context;Ljava/lang/String;Ljava
/lang/String;)Lorg/apache/http/HttpResponse;
(*) Invisible SMS sending
Permission: 'android.permission.SEND_SMS'
invoke-static {v0, v2},
Lorg/slempo/service/a/h;->a(Ljava/lang/String;Ljava/lang/String;)Z
Discover Pradeo Security solution suite:
- Mobile Threat Defense
- In-App Protection
- Mobile Application Security Testing
- Secure Private Store
- Mobile Threat Intelligence