The Android banking trojan Geost was first revealed in a research by Sebastian García, Maria Jose Erquiaga and Anna Shirokova from the Stratosphere Laboratory. They detected the trojan by monitoring HtBot malicious proxy network. The botnet targets Russian banks, with the victim count at over 800,000 users at the time the study was published in Virus Bulletin last year.

The research disclosed the types of information that Geost (detected by Trend Micro as AndroidOS_Fobus.AXM) steals from victims, as well as the activities of the group behind the botnet, including operational tactics and internal communication between masters and botnet coders.

Building upon this interesting finding, we decided to dig deeper into the behavior of Geost by reverse engineering a sample of the malware. The trojan employed several layers of obfuscation, encryption, reflection, and injection of non-functional code segments that made it more difficult to reverse engineer. To study the code and analyze the algorithms, we had to create Python scripts to decrypt strings first.

Initial Analysis

Geost hides in malicious apps that are distributed via unofficial web pages with randomly generated server hostnames. The victims usually encounter these as they look for apps that are not available on Google Play, or when they don’t have access to the app store. They then find a link to that application on some obscure web server, download the app, then launch it on their phones. The app will then request for permissions that, when the victims allow, enables malware infection.

The Geost sample we analyzed resided in the malicious app named “установка” in Russian, which means “setting” in English. The app showed a version of the Google Play logo as its own icon, which did not appear on the phone screen after launch.

Figure 1. Application icon of the malicious app установка

When the app was launched, it requested device administrator privileges. This was unusual since legitimate apps don’t often ask for this, as it basically gives an app complete rights over a device.

Important permissions that the user might unknowingly allow include those for accessing SMS messages, including confirmation messages from banking apps. These messages allow the malware to harvest the victims’ names, balances, and other bank account details. With just a few clicks, attackers can then transfer money from the bank accounts of unaware victims.

Figure 2: Screen that requests device admin permission

Figure 3: Application permissions requested

After confirming necessary permissions, the visible part of the app will close and the app icon disappears, making victims think that the app was deleted. The sample device did not show any alarming signs of malicious activity at first, but the malware is working in the background and the attackers just gained access to the device, allowing them to monitor sent and received messages, including SMS confirmation messages from banking apps.

To maintain persistence across reboots, it registers for BOOT_COMPLETED and QUICKBOOT_POWERON broadcasts.

Figure 4: Registering services to boot broadcasts (some codes were obfuscated)

Stage One

Like many malware types, Geost’s run-time life is split into stages. The first stage is small and simple, which will then download and/or decrypt and run the next stage, which is more complex.

The Geost sample’s APK housed compiled Java code in classes.dex file. It also contained AndroidManifest.xml and resource files, which are usual contents of APK files. It also had a “.cache” file with a size of 125k.

To decompile the extracted classes.dex file, several Java decompilers, namely dex2jar, jadx, jd-core/jd-gui and Ghidra, were all used, as no single decompiler was able to decompile all the Smali code.

Figure 5: Decompiled Java source code

At first glance, the decompiled code seemed to be partially encoded in a series of strings; however, character frequency analysis showed random character usage.

Further analysis revealed that the malware contained additional pieces of code that have no impact on the app’s behavior except to slow down its execution. It made reverse engineering more difficult because the malware split useful code into parts and frequently changed execution paths. Which branch was taken was usually dependent on some variable with an unknown value. The same is applied with “switch”, “if”, and “try/catch” command blocks. Functions without meaningful code were inserted to make overall understanding of the malware actions harder.

Figure 6: Example of code with case switch

The non-functional code segments were gradually removed and the first decryption algorithm used was identified. All strings in stage one were encrypted through RC4, using an algorithm that was split into several functions to avoid indication that it used RC4. After this, the next step was to find the key for RC4 decryption.

Figure 7: Decompiled Java source, which is part of the RC4 algorithm

Figure 8: Part of cleaned up RC4 code

Figure 9: RC4 key

RC4 is a stream cipher, with an internal state that changes with every decrypted symbol. To decrypt several encrypted strings, usually the decryption must be performed in the very same order the encryption used. Fortunately, this was not the case with the sample. The code authors simplified RC4 without keeping internal state between decryptions, as the RC4 encryption code always copied state array S[].

Figure 10: RC4 encryption always copied state array S[]

Afterwards, the search for common code libraries began. Android.support.v4 libraries and ReflectASM Java Reflection libraries were found.

Figure 11: Code with encrypted strings

Figure 12: Code with strings after decryption and symbol deobfuscation

At this point, the stage one code became understandable: It uses reflection code to hide interesting classes and methods from curious eyes. Basically, the first stage decrypted the second stage file with the same RC4 algorithm and key.

Figure 13: Example of reflection method invocation

The aforementioned “.cache” file is renamed to .localsinfotimestamp1494987116 and saved after decryption as ydxwlab.jar, from which the .dex file is loaded and launched.

Figure 14: Decrypting and saving second stage

Code authors inserted a false flag, HttpURLConnection and its URL, which seemed to connect to the Command and Control (C&C) server. But this http open connection is never executed.

Figure 15: False flag

Stage one loads a class from the second stage, which the researcher named “MaliciousClass”.

Figure 16: Launching the second stage

Stage Two

Looking at the classes.dex, it’s clear that obfuscation and encryption were used again in stage two. But this time, the symbol names were partially replaced by strings 1-2 characters long instead of the previous 6-12 character strings. Also, the string encryption algorithm is modified, making it different from the algorithm used in the previous stage. Different tools were used. Additionally, parameters of the decryption algorithm were modified separately for each class.

All Java decompilers had problems decompiling the decryption algorithm due to goto command jumping into the if block. Only Jeb decompiler handled this construction well.

Figure 17: Smali code of decryption algorithm

Figure 18: Java code of decryption algorithm

Each class decryption method contained different parameter orders and different constants; writing the Python decryption script was made more difficult. It meant either the decryption script must detect the algorithm setup from the Smali code and adapt itself, or the parameters must be manually set up within the script before decryption for each class.

Figure 19: Example of an encrypted string

After string decryption, libraries used could be detected. These include:

AES encryption engine

Base64 encoding

Emulator detector

File download service

IExtendedNetworkService

USSD api library

Zip4jUtil

Initialization phase

The aforementioned MaliciousClass invoked from the first stage serves as an envelope for the instantiated class the researcher named “Context.”

Figure 20: Context Class

The Context class launches the EmulatorDetector service first. It then starts two other services: AdminService and LPService, followed by the main application Intent.

Figure 21: Main initialization routine

Emulator Detector

The emulator detector checks for signs that it’s running in an emulated environment. The sample detected the existence of Nox, Andy, Geny, Bluestacks and Qemu Android emulators.

Figure 22: Emulated enviroment traces

AdminService

This service is responsible for granting admin permission to the application. This is a critical part since it enables access to sensitive data and can launch privileged actions.

Figure 23: Critical part of AdminService

LPService

This service was responsible for keeping the application running and connected to the C&C server. It used WakeLock and WifiLock acquire() calls to reach this state. A side effect to this is high battery drain, which most victims usually ignore.

Figure 24: Locking to CPU and WiFi resources

LPService then creates LPServiceRunnable Thread, which wakes up every five seconds and is responsible for monitoring and relaunching these services:

MainService

AdminService

SmsKitkatService

This service also collects information about running processes and tasks. It also periodically starts WebViewActivity, which can open browser window to arbitrary URLs or launch malicious code. WebViewActivity code was not implemented in this sample.

MainService

The MainService first hooks to AlarmManager for time scheduling tasks, then registers two broadcast receivers, MainServiceReceiver1 and MainServiceReceiver2. At the end of the initialization phase, it will launch MainServiceRunnable Thread. When the sample executes overloaded onDestroy() method, it restarts the MainService again.

Figure 25: Overloaded onDestroy to restart MainService

An important method of MainService is processApiResponse(), which processes commands formatted as JSON string received from C&C server.

Figure 26: Processing C&C server commands

ClearService

This service invokes the ClearServiceRunnable thread, which takes care of locking/unlocking commands (blocking/unblocking user activity) so the botnet operator can perform remote tasks without user intervention. The ClearService also relaunches itself if there is an attempt to terminate it.

Figure 27: ClearService class

Figure 28: ClearServiceRunnable

SmsKitkatService

This service was prepared to replace the standard SMS messaging application with a different one written by the attackers. In this version, it used a default one.

Figure 29: Code for replacement of default SMS application

Commands

The list of commands that this malware recognized can be seen in the table and screenshot below (organized by the order they were defined in the code):

Commands Description #conversations Collects the address, body, date, and type columns from all SMS messages from content://sms/conversations/, content://sms/inbox and content://sms/sent, and sends to the C&C server #contacts Collects a list of all contacts from content://com.android.contacts/data/phones and sends to the C&C server #calls Collects all calls performed from content://call_log/calls and sends to the C&C server #apps Collects list of installed package names and labels and sends to C&C server #bhist This command is ignored in this sample #interval {set:number} Sets time period for fetching C&C server commands #intercept Sets the phone numbers from which to intercept SMS (“all” or a list of numbers) #send id:, to:, body: Sends SMS #ussd {to:address, tel:number} Calls a number via USSD framework #send_contacts Sends SMS to all contacts in phonebook #server Sets scheduled time to run #check_apps {path:uri_to_server} Sends a list of running apps to C&C server, downloads archive.zip file from path defined in parameter as error.zip, and unzip it. Zip archive has password “encryptedz1p”. Default server name is hxxp://fwg23tt23qwef.ru/ #send_mass {messages: {to:address, body:text}, delay:ms} Sends multiple SMS messages to different addresses, with a delay between sends #lock Starts RLA service from ClearServiceRunnable, which intercepts events from key press AKEYCODE_HOME, AKEYCODE_CAMERA, and AKEYCODE_FOCUS. It also intercepts onBackPressed() Activity method, mutes ringer, clears all SMS notifications, stops itself, and makes the phone unresponsive #unlock Disables actions listed under #lock command and unlocks phone by stopping ClearServiceRunnable #makecall {number:tel_number} Calls a number using standard android.intent.action.CALL API #openurl {filesDir=j:url} Opens a webpage URL #hooksms {number:tel_number} Hooks to a number – it forwards all incoming SMS messages to a number in the parameter #selfdelete Sets task time to unparsable string value, which stops its self-scheduling tasks

Figure 30: List of C&C SERVER commands

ApiRequest, ApiResponse, ApiInterfaceImpl

The ApiRequest, ApiResponse, and ApiInterfaceImpl classes enable communication with the C&C server. In the connection parameters initialization, the value of replaceWithRandomStr variable was set to true by default and is not changed within the code.

Figure 31: Building C&C server connection string

Figure 32: Connection parameters initialization

An algorithm was used to generate a random string for the C&C server URL. The API connection was then initialized, and the hostname of the C&C server was set up.

Figure 33: Building random string for the C&C server URL

Figure 34: API connection initialization

Figure 35: Setting up C&C server hostname

An example of C&C server API usage was shown as the C&C server command “#contacts“ was implemented. Finally, parameters for commands are appended as JSON format and converted to string.

Figure 36: Example of C&C server API calling

Best Practices and Trend Micro Solutions

In its 2020 Security Predictions, Trend Micro predicted the continued proliferation of mobile malware families, such as Geost, that target online banking and payment systems. Mobile users should safeguard themselves as they navigate the treacherous mobile landscape by following best practices for securing mobile devices. One such step is to avoid downloading apps outside official app stores.

Unfortunately, threat actors also find ways to spread malicious apps via legitimate app stores. Along with the continued campaigns of these stores to remove compromised apps, users can also avoid such apps by carefully inspecting app reviews and other information before downloading.

App users should scrutinize the permissions requested by an installed app before allowing them. Afterwards, users should watch out for changes in their devices, such as the decreased performance or battery life, which may indicate a malware infection. In this case, users should delete the newly installed app immediately. Users should also conduct regular audits to remove unused apps.

For additional defense against mobile threats, users can install a multilayered mobile security solution such as Trend Micro™ Mobile Security to protect devices from malicious applications and other mobile threats.

Indicator of Compromise