
Just the other day, Group-IB
reported on the activity of the mobile Android Trojan Gustuff. He works exclusively in international markets, attacking customers of the 100 largest foreign banks, users of mobile 32 crypto wallet, as well as major e-commerce resources. But the developer Gustuff - Russian-speaking cybercriminal under the nickname Bestoffer. More recently, he praised his Trojan as "a serious product for people with knowledge and experience."
Group-IB Malicious Code Analysis Specialist
Ivan Pisarev in his research describes in detail how Gustuff works and what is its danger.
')
Who hunts Gustuff
Gustuff is a new generation of malware with fully automated features. According to the developer, the Trojan has become a new improved version of the AndyBot malware, which since November 2017 has been attacking Android phones and stealing money through phishing web forms disguised as mobile applications by well-known international banks and payment systems. Bestoffer reported that the rental price of Gustuff Bot was $ 800 per month.
An analysis of the Gustuff sample showed that the Trojan potentially targets customers using mobile applications of major banks, such as Bank of America, Bank of Scotland, JPMorgan, Wells Fargo, Capital One, TD Bank, PNC Bank, and Bitcoin Wallet , Cryptopay, Coinbase and others.
Originally created as a classic banking trojan, in the current version of Gustuff has significantly expanded the list of potential objects to attack. In addition to Android applications of banks, fintech companies and cryptoservices, Gustuff is aimed at users of marketplace applications, online stores, payment systems and instant messengers. In particular, PayPal, Western Union, eBay, Walmart, Skype, WhatsApp, Gett Taxi, Revolut and others.
Entry point: based on mass infection
Gustuff is characterized by a “classic” vector of penetration of Android-based smartphones via SMS distribution with links to APK. When an Android device is infected with a trojan by a server command, Gustuff can further spread to the contact database of the infected phone or to the server database. The functionality of Gustuff is designed for mass infection and maximum capitalization of the business of its operators - it has a unique “auto-fill” function in legitimate mobile banking applications and cryptographic cards, which allows you to speed up and scale the theft of money.
The study of the Trojan showed that the avtozaliva function was implemented in it using the Accessibility Service, a service for people with disabilities. Gustuff is not the first trojan that successfully bypasses protection from interacting with window elements of other applications using this Android service. However, the use of the Accessibility Service in combination with the auto-fill is still quite a rare phenomenon.
After downloading to the phone, the victim Gustuff, using the Accessibility Service, gets the opportunity to interact with the elements of windows of other applications (banking, cryptocurrency, as well as applications for online shopping, messaging, etc.), performing the necessary actions for attackers. For example, at the command of a server, a Trojan can click on buttons and change the values ​​of text fields in banking applications. Using the Accessibility Service allows the trojan to bypass the security mechanisms used by banks to counter past-generation mobile Trojans, as well as changes in security policies implemented by Google in new versions of the Android OS. So, Gustuff "can" disable the protection of Google Protect: according to the author, this function works in 70% of cases.

Gustuff can also show fake push notifications with icons of legitimate mobile apps. The user clicks on the PUSH notification and sees a phishing window downloaded from the server, where he himself enters the requested data of the bank card or cryptograph. In another Gustuff scenario, an application is opened, on whose behalf a PUSH notification was displayed. In this case, a malicious program at the server’s command through the Accessibility Service may fill out the form fields of a banking application for a fraudulent transaction.
The functionality of Gustuff also includes sending information about an infected device to the server, the ability to read / send SMS messages, send USSD requests, launch SOCKS5 Proxy, follow the link, send files (including photo scans of documents, screenshots, photos) to the server Reset device to factory settings.
Malware Analysis
Before installing a malicious Android OS application, a user is shown a window containing a list of the rights requested by Gustuff:
Installation of the application will occur only after obtaining the consent of the user. After launching the application, the trojan will show the user a window:
After that, delete its icon.
According to the author, Gustuff is packaged by FTT. After starting, the application periodically accesses the CnC server in order to receive commands. In several files examined by us, the IP address
88.99.171 [.] 105 was used as the control server (hereinafter we will denote as
<% CnC%> ).
After starting the program starts sending messages to the server
http: // <% CnC%> /api/v1/get.php .
The response is expected JSON in the following format:
{ "results" : "OK", "command":{ "id": "<%id%>", "command":"<%command%>", "timestamp":"<%Server Timestamp%>", "params":{ <%Command parameters as JSON%> }, }, }
Each time you access the app, it sends information about the infected device. The message format is shown below. It should be noted that the fields
full ,
extra ,
apps and
permission are optional and will be sent only in the case of a request command from CnC.
{ "info": { "info": { "cell":<%Sim operator name%>, "country":<%Country ISO%>, "imei":<%IMEI%>, "number":<%Phone number%>, "line1Number":<%Phone number%>, "advertisementId":<%ID%> }, "state": { "admin":<%Has admin rights%>, "source":<%String%>, "needPermissions":<%Application needs permissions%>, "accesByName":<%Boolean%>, "accesByService":<%Boolean%>, "safetyNet":<%String%>, "defaultSmsApp":<%Default Sms Application%>, "isDefaultSmsApp":<%Current application is Default Sms Application%>, "dateTime":<%Current date time%>, "batteryLevel":<%Battery level%> }, "socks": { "id":<%Proxy module ID%>, "enabled":<%Is enabled%>, "active":<%Is active%> }, "version": { "versionName":<%Package Version Name%>, "versionCode":<%Package Version Code%>, "lastUpdateTime":<%Package Last Update Time%>, "tag":<%Tag, default value: "TAG"%>, "targetSdkVersion":<%Target Sdk Version%>, "buildConfigTimestamp":1541309066721 }, }, "full": { "model":<%Device Model%>, "localeCountry":<%Country%>, "localeLang":<%Locale language%>, "accounts":<%JSON array, contains from "name" and "type" of accounts%>, "lockType":<%Type of lockscreen password%> }, "extra": { "serial":<%Build serial number%>, "board":<%Build Board%>, "brand":<%Build Brand%>, "user":<%Build User%>, "device":<%Build Device%>, "display":<%Build Display%>, "id":<%Build ID%>, "manufacturer":<%Build manufacturer%>, "model":<%Build model%>, "product":<%Build product%>, "tags":<%Build tags%>, "type":<%Build type%>, "imei":<%imei%>, "imsi":<%imsi%>, "line1number":<%phonenumber%>, "iccid":<%Sim serial number%>, "mcc":<%Mobile country code of operator%>, "mnc":<%Mobile network codeof operator%>, "cellid":<%GSM-data%>, "lac":<%GSM-data%>, "androidid":<%Android Id%>, "ssid":<%Wi-Fi SSID%> }, "apps":{<%List of installed applications%>}, "permission":<%List of granted permissions%> }
Configuration Storage
Gustuff stores important information for work in a preference file. The file name, as well as the names of the parameters in it, is the result of the calculation of the MD5 sum from line
15413090667214.6.1 <% name%> , where
<% name%> is the original name-value. Python interpretation of the name generation function:
nameGenerator(input): output = md5("15413090667214.6.1" + input)
In the following we will denote as
nameGenerator (input) .
Thus, the name of the first file:
nameGenerator ("API_SERVER_LIST") , it contains values ​​with the following names:
Variable name | Value |
---|
nameGenerator ("API_SERVER_LIST") | Contains a list of CnC addresses as an array. |
nameGenerator ("API_SERVER_URL") | Contains the cnc address. |
nameGenerator ("SMS_UPLOAD") | The default flag is set. If the flag is set, sends SMS messages to CnC. |
nameGenerator ("SMS_ROOT_NUMBER") | The phone number to which SMS messages received by the infected device will be sent. The default is null. |
nameGenerator ("SMS_ROOT_NUMBER_RESEND") | The default flag is cleared. If set - when an infected device receives an SMS, it will be sent to the root number. |
nameGenerator ("DEFAULT_APP_SMS") | The default flag is cleared. If this flag is set, the application will process incoming SMS messages. |
nameGenerator ("DEFAULT_ADMIN") | The default flag is cleared. If the flag is set, the application has administrator rights. |
nameGenerator ("DEFAULT_ACCESSIBILITY") | The default flag is cleared. If the flag is set, a service is running that uses the Accessibility Service. |
nameGenerator ("APPS_CONFIG") | The JSON object contains a list of actions that must be performed when an Accessibility event is triggered associated with a specific application. |
nameGenerator ("APPS_INSTALLED") | Stores the list of applications installed on the device. |
nameGenerator ("IS_FIST_RUN") | The flag at the first start is cleared. |
nameGenerator ("UNIQUE_ID") | Contains a unique identifier. Generated when the bot is first launched. |
Command processing module from the server
The application stores the addresses of CnC servers as an array of Base85-encoded strings. The list of CnC servers can be changed when a corresponding command is received, in which case the addresses will be stored in a preference file.
In response to the request, the server sends the command to the application. It is worth noting that the commands and parameters are presented in JSON-format. The application can handle the following commands:
Team | Description |
---|
forwardstart | Start sending SMS messages received by the infected device to the CnC server. |
forwardStop | Stop sending SMS messages received by the infected device to the CnC server. |
ussdRun | Run USSD request. The number to which the USSD request is to be made is in the “number” JSON field. |
sendSms | Send one SMS-message (if necessary, the message is “split” into parts). As a parameter, the command takes a JSON object that contains the “to” fields — the destination number and “body” —the message body. |
sendSmsAb | Send SMS messages (if necessary, the message is “split up” into parts) to everyone from the contact list of the infected device. The interval between sending messages is 10 seconds. The body of the message is in the JSON-field "body" |
sendSmsMass | Send SMS messages (if necessary, the message is “split up” into parts) to the contacts specified in the command parameters. The interval between sending messages is 10 seconds. As a parameter, the command accepts a JSON array (the “sms” field), the elements of which contain the “to” fields — the destination number and the “body” - message body. |
changeServer | This command can take a value with the key “url” as a parameter - then the bot will change the nameGenerator (“SERVER_URL”) value, or “array” - then the bot will write the array into the nameGenerator (“API_SERVER_LIST”). Thus, the application changes the address of the CnC servers. |
adminNumber | The command is designed to work with root number. The command accepts a JSON object with the following parameters: “number” - change nameGenerator (“ROOT_NUMBER”) to the received value, “resend” - change nameGenerator (“SMS_ROOT_NUMBER_RESEND”), “sendId” - send to nameGenerator (“ROOT_NUMBER”) uniqueID. |
updateInfo | Send information about the infected device to the server. |
wipeData | The command is intended to delete user data. Depending on which name the application was launched, there is either a complete erasure of data with a device reboot (primary user), or the removal of only user data (secondary user). |
socksStart | Run the proxy module. The operation of the module is described in a separate section. |
socksStop | Stop the proxy module. |
openLink | Follow the link. The link is in the JSON parameter by the key "url". To open the link, use "android.intent.action.VIEW". |
uploadAllSms | Send all received SMS messages to the server. |
uploadAllPhotos | Send images to the URL from an infected device. URL comes as a parameter. |
uploadFile | Send a file from an infected device to a URL. URL comes as a parameter. |
uploadPhoneNumbers | Send phone numbers from the contact list to the server. If the JSON-object value with the key “ab” comes as a parameter, the application receives a list of contacts from the phone book. If the JSON object with the “sms” key comes as a parameter, the application reads the list of contacts from senders of SMS messages. |
changeArchive | The application loads the file from the address that comes in as a parameter using the key “url”. The downloaded file is saved with the name "archive.zip". After this, the application unzips the file, using the password for the archive “b5jXh37gxgHBrZhQ4j3D”, if necessary. Unzipped files are saved to the [external storage] / hgps directory. In this directory, the application stores web-fakes (described below). |
actions | The command is designed to work with the Action Service, which is described in a separate section. |
test | Doing nothing. |
download | The command is intended for downloading a file from a remote server and saving it in the “Downloads” directory. The URL and the file name come as a parameter, the fields in the JSON object parameter, respectively: “url” and “fileName”. |
remove | Removes a file from the “Downloads” directory. The file name comes in a JSON parameter with the key "fileName". The default file name is “tmp.apk”. |
notification | Show notification with description and header text, defined by the managing server. |
Command format
notification :
{ "results" : "OK", "command":{ "id": <%id%>, "command":"notification", "timestamp":<%Server Timestamp%>, "params":{ "openApp":<%Open original app or not%>, "array":[ {"title":<%Title text%>, "desc":<%Description text%>, "app":<%Application name%>} ] }, }, }
The notification created by the examined file looks identical to the notifications created by the application specified in the
app field. If the
openApp field
value is True, when the notification is opened, the application specified in the
app field is launched. If the
openApp field
value is False, then:
- opens a phishing window, the contents of which is loaded from the directory <% external storage%> / hgps / <% filename%>
- opens a phishing window, the contents of which is downloaded from the server <% url%>? id = <% Bot id%> & app = <% Application name%>
- opens a phishing window disguised as a Google Play Card, with the ability to enter data cards.
The result of the execution of any command is sent by the application to
<% CnC%> \ set_state.php as a JSON object of the following format:
{ "command": { "command":<%command%>, "id":<%command_id%>, "state":<%command_state%> } "id":<%bot_id%> }
ActionsServiceThe list of commands that the application handles is
action . When a command is received, the command processing module accesses this service in order to execute the extended command. As a parameter, the service accepts a JSON object. The service can execute the following commands:
1. PARAMS_ACTION - when receiving such a command, the service first of all receives the value from the JSON parameter by the Type key, it can be the following:
- serviceInfo - the subcommand gets the value from the JSON parameter by the includeNotImportant key. If the flag is True, the application sets the FLAG_ISOLATED_PROCESS flag to the service using the Accessibility Service. Thus, the service will be launched in a separate process.
- root — get and send to the server information about the window that is now in focus. An application obtains information using the AccessibilityNodeInfo class.
- admin - request administrator rights.
- delay - suspend the ActionsService for the number of milliseconds specified in the parameter by the “data” key.
- windows - send a list of windows visible to the user.
- install - install the application on the infected device. The name of the package archive is in the key "fileName". The archive itself is located in the Downloads directory.
- global - the subcommand is designed to make the transition from the current window:
- on the Quick Settings menu
- backwards
- home
- to notifications
- to the window of recently opened applications
- launch - launch an application. The name of the application comes as a parameter by the data key.
- sounds - change sound mode to silence.
- unlock - turns on the backlight of the screen and keyboard at full brightness. The application performs this action using WakeLock, specifies the string [Application lable]: INFO
- permissionOverlay - the function is not implemented (the response to the execution of the command is {“message”: “Not support”} or {“message”: “low sdk”})
- gesture - the function is not implemented (response to the execution of the command - {“message”: “Not support”} or {“message”: “Low API”})
- permissions - this command is required to request rights for the application. However, the query function is not implemented, so the command does not make sense. The list of requested rights comes as a JSON array with the key "permissions". Standard list:
- android.permission.READ_PHONE_STATE
- android.permission.READ_CONTACTS
- android.permission.CALL_PHONE
- android.permission.RECEIVE_SMS
- android.permission.SEND_SMS
- android.permission.READ_SMS
- android.permission.READ_EXTERNAL_STORAGE
- android.permission.WRITE_EXTERNAL_STORAGE
- open - display a phishing window. Depending on the parameter coming from the server, the application may display the following phishing windows:
- Show a phishing window whose contents are registered in a file in the <% external directory%> / hgps / <% param_filename%> directory . The result of the user's interaction with the window will be sent to <% CnC%> / records.php
- Show a phishing window, the contents of which are preloaded from the address <% url_param%>? Id = <% bot_id%> & app = <% packagename%> . The result of the user's interaction with the window will be sent to <% CnC%> / records.php
- Show a phishing window disguised as a Google Play Card.
- interactive - the command is designed to interact with elements of windows of other applications using the AcessibilityService. For interaction in the program a special service is implemented. The application under study can interact with the windows:
- Active at the moment. In this case, the parameter contains the id or text (name) of the object with which you want to interact.
- Visible to the user at the time of execution of the command. The application selects windows by id.
Having obtained AccessibilityNodeInfo objects for window elements of interest, depending on the parameters, the application can perform the following actions:
- focus - set the focus on the object.
- click - click on the object.
- actionId - perform an action by ID.
- setText - change the text of the object. The text can be changed in two ways: perform ACTION_SET_TEXT action (if the Android version of the infected device is younger or is equal to LOLLIPOP ), or by placing a string in the clipboard and pasting it into an object (for older versions). This command can be used to change the data in the banking application.
2. PARAMS_ACTIONS - the same as
PARAMS_ACTION , only comes JSON-array of commands.
It seems that many will be interested in what the function of interaction with elements of the window of another application looks like. This is how this functionality is implemented in Gustuff:
boolean interactiveAction(List aiList, JSONObject action, JsonObject res) { int count = action.optInt("repeat", 1); Iterator aiListIterator = ((Iterable)aiList).iterator(); int count = 0; while(aiListIterator.hasNext()) { Object ani = aiListIterator.next(); if(1 <= count) { int index; for(index = 1; true; ++index) { if(action.has("focus")) { if(((AccessibilityNodeInfo)ani).performAction(1)) { ++count; } } else if(action.has("click")) { if(((AccessibilityNodeInfo)ani).performAction(16)) { ++count; } } else if(action.has("actionId")) { if(((AccessibilityNodeInfo)ani).performAction(action.optInt("actionId"))) { ++count; } } else if(action.has("setText")) { customHeader ch = CustomAccessibilityService.a; Context context = this.getApplicationContext(); String text = action.optString("setText"); if(performSetTextAction(ch, context, ((AccessibilityNodeInfo)ani), text)) { ++count; } } if(index == count) { break; } } } ((AccessibilityNodeInfo)ani).recycle(); } res.addPropertyNumber("res", Integer.valueOf(count)); }
Text replacement function:
boolean performSetTextAction(Context context, AccessibilityNodeInfo ani, String text) { boolean result; if(Build$VERSION.SDK_INT >= 21) { Bundle b = new Bundle(); b.putCharSequence("ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE", ((CharSequence)text)); result = ani.performAction(0x200000, b);
Thus, if the managing server is properly configured, Gustuff is able to fill in the text fields in the banking application and click on the buttons necessary for the transaction. The trojan does not even need to be authorized in the application - it is enough to send a command to demonstrate the PUSH notifications and then open the previously installed banking application. The user himself will be authorized, after which Gustuff will be able to make avtozaliv.
SMS Processing Module
The application sets an event handler to receive an SMS message from an infected device. The investigated application can receive commands from the operator that come in the body of an SMS message. Commands come in the format:
7! 5 = <% Base64 encoded command%>The application searches for all incoming SMS messages line
7! 5 = , when a line is detected - decodes a line from Base64 at offset 4 and executes the command. Commands are similar to commands with CnC. The result of the performance is sent to the same number from which the command was received. Answer format:
7 * 5 = <% Base64 encode of "result_code command"%>Optionally, the application can send all received messages to the Root number. To do this, the Root number must be specified in the preference file and the message redirect flag must be set. The SMS message is sent to the number of the attacker in the format:
<% From number%> - <% Time format: dd / MM / yyyy HH: mm: ss%> <% SMS body%>Also, the application can optionally send messages to CnC. The SMS message is sent to the server in JSON format:
{ "id":<%BotID%>, "sms": { "text":<%SMS body%>, "number":<%From number%>, "date":<%Timestamp%> } }
If the
nameGenerator flag is
set (“DEFAULT_APP_SMS”) , the application stops processing the SMS message and clears the list of incoming messages.
Proxy module
In the application under study, there is a Backconnect Proxy module (hereinafter referred to as Proxy module), which has a separate class that includes static fields with configuration. Configuration data is stored in the open sample:
All actions performed by the proxy module are logged into files. To do this, the application in External Storage creates a directory called “logs” (the ProxyConfigClass.logsDir field in the configuration class) in which the log files are stored. Logging occurs in files with the names:
- main.txt - the operation of the class called CommandServer is logged to this file. Further, the str logging into this file will be denoted as mainLog (str).
- session - <% id%>. txt - the log data associated with a particular proxy session is saved to this file. In the future, logging the str string to this file will be referred to as sessionLog (str).
- server.txt - all data written to the above files is logged to this file.
Log data format:
<% Date%> [Thread [<% thread id%>], id []]: log-string
Exceptions that occur during the operation of the Proxy module are also logged to a file. For this, the application generates a JSON format object:
{ "uncaughtException":<%short description of throwable%> "thread":<%thread%> "message":<%detail message of throwable%> "trace": //Stack trace info [ { "ClassName": "FileName": "LineNumber": "MethodName": }, { "ClassName": "FileName": "LineNumber": "MethodName": } ] }
Then it converts it into a string representation and logs it.
The launch of the Proxy-module is carried out after the arrival of the appropriate command. When a command is received to start the proxy module, the application starts a service called
MainService , which is responsible for managing the operation of the proxy module — starting and stopping it.
Stages of service start:
1. Starts the timer, which is triggered once a minute and checks the activity of the proxy module. If the module is not active, it starts it.
Also, when the
android.net..nn.CONNECTIVITY_CHANGE event is triggered, a Proxy module is launched.
2. The application creates a wake-lock with the
PARTIAL_WAKE_LOCK parameter and captures it. Thus, it does not allow the device's CPU to go to sleep.
3. Runs the processing class of commands of the Proxy module, pre-logging the mainLog line
("start server") and
Server :: start () host [<% proxy_cnc%>], commandPort [<% command_port%>], proxyPort [<% proxy_port%>]where
proxy_cnc, command_port and proxy_port are the parameters obtained from the proxy server configuration.
The command processing class is called
CommandConnection . Immediately after starting it performs the following actions:
4. Connects to
ProxyConfigClass.host :
ProxyConfigClass.commandPort and sends there data about the infected device in JSON format:
{ "id":<%id%>, "imei":<%imei%>, "imsi":<%imsi%>, "model":<%model%>, "manufacturer":<%manufacturer%>, "androidVersion":<%androidVersion%>, "country":<%country%>, "partnerId":<%partnerId%>, "packageName":<%packageName%>, "networkType":<%networkType%>, "hasGsmSupport":<%hasGsmSupport%>, "simReady":<%simReady%>, "simCountry":<%simCountry%>, "networkOperator":<%networkOperator%>, "simOperator":<%simOperator%>, "version":<%version%> }
Where:
- id - identifier, trying to get a value from the Shared Preference file with the name "x" with the "id" field. If this value could not be obtained, it generates a new one. , Proxy- ̆ , ̆ Bot ID.
- imei — IMEI ̆. — .
- imsi — International Mobile Subscriber Identity ̆. — .
- model — The end-user-visible name for the end product.
- manufacturer — The manufacturer of the product/hardware (Build.MANUFACTURER).
- androidVersion — "<%release_version%> (<%os_version%>),<%sdk_version%>"
- country — ̆.
- partnerId – .
- packageName – package name.
- networkType — (: «WIFI», «MOBILE»). null.
- hasGsmSupport – true – GSM, false.
- simReady – SIM-.
- simCountry — ISO- ( ̆ -).
- networkOperator — . — .
- simOperator — The Service Provider Name (SPN). — .
- version — -, ̆ «1.6».
5. . :
- 0 offset – command
- 1 offset – sessionId
- 2 offset – length
- 4 offset — data
:
mainLog(«Header { sessionId<%id%>], type[<%command%>], length[<%length%>] }»):
Name | Command | Data | Description |
---|
connectionId | 0 | Connection ID | |
SLEEP | 3 | Time | Proxy- |
PING_PONG | four | - | PONG- |
PONG- 4 :
0x04000000 .
connectionId ( )
CommandConnection ProxyConnection .
- : ProxyConnection end . ProxyConnection ProxyConfigClass.host : ProxyConfigClass.proxyPort JSON-:
{ "id":<%connectionId%> }
SOCKS5-, , . ̆
end . :
̆
̆ CnC- SSL. JSON-. :
- http://<%CnC%>/api/v1/set_state.php — .
- http://<%CnC%>/api/v1/get.php — .
- http://<%CnC%>/api/v1/load_sms.php — SMS-̆ ̆.
- http://<%CnC%>/api/v1/load_ab.php — ̆.
- http://<%CnC%>/api/v1/aevents.php – , preference-̆.
- http://<%CnC%>/api/v1/set_card.php — , -, Google Play Market.
- http://<%CnC%>/api/v1/logs.php – -.
- http://<%CnC%>/api/v1/records.php – , .
- http://<%CnC%>/api/v1/set_error.php – ̆ .
Recommendations
, , .
, . , , .
– - , , -, , , , , .
:
- Android - , Google Play;
- ;
- Android;
- ;
- ;
- , SMS-.
, Group-IB.