Malware
Geost: Anatomy of the Android Trojan Targeting Russia
We dug deeper into the behavior of Geost, a trojan targetting Russian banks, by reverse engineering a sample of the malware. It has several layers of obfuscation, encryption, reflection that made it more difficult to reverse engineer.
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
SHA 256 | Detection Name |
92394e82d9cf5de5cb9c7ac072e774496bd1c7e2944683837d30b188804c1810 | AndroidOS_Fobus.AXM |