【木马分析】解密使用谷歌云消息服务的Android锁屏恶意软件

阅读量137847

|

发布时间 : 2017-01-20 14:02:27

x
译文声明

本文是翻译文章,文章来源:fortinet.com

原文地址:http://blog.fortinet.com/2017/01/16/android-locker-malware-uses-google-cloud-messaging-service

译文仅供参考,具体内容表达以及含义原文为准。

http://p9.qhimg.com/t010c44df2c22f921d6.jpg

翻译:shinpachi8

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


前言

上个月,我们发现了一个新的Android锁屏恶意软件,它会启动勒索软件,在设备上显示锁定屏幕,并勒索用户提交他们的银行卡信息以解锁设备。 这个勒索软件变种的有趣的变化是,它利用谷歌云消息(GCM)平台,推送通知服务,发送消息到注册的客户端,作为其C2基础设施的一部分。 它还在受感染设备和C2服务器之间的通信中使用AES加密。 在这个博客中,我们对这个恶意软件做了详细的分析。


恶意软件快速浏览

用户启动受感染的应用程序后,它会请求设备管理员权限,如下所示。

http://p7.qhimg.com/t0162108c9cece47b65.png

一旦用户授予管理员权限, 恶意软件的显示以下屏幕截图。

http://p4.qhimg.com/t015fd935d578ed0ba6.png

锁定屏幕如下所示。它要求用户的赎金高达545000Rub (约9000美元)用于解锁设备。

http://p9.qhimg.com/t01829b8fa650ec5bc2.png


恶意软件如何工作

以下是有关此恶意软件变体如何工作的详细技术分析。以下是恶意软件开始启动时使用的关键代码段。

http://p5.qhimg.com/t017e3a6dcad5ddcab9.png

接下来,我们分析了这三个关键类。

1.Jfldnam Class

这是用于GCM注册的服务类。关键代码段如下所示。

http://p6.qhimg.com/t01d63387dd1dbd1df8.png

通过我们的分析,类“Yzawsu”是类“GCMRegistrar”。您可以参考https://chromium.googlesource.com/android_tools/+/master/sdk/extras/google/gcm/gcm-client/src/com/google/android/gcm/GCMRegistrar.java 

它用于GCM注册,v8.efjmaqtnlsph()返回sender_id。

http://p3.qhimg.com/t01409bfb6c0443921c.png

AndroidManifest文件中的GCM广播接收器声明如下所示。

http://p9.qhimg.com/t012312f0dd65921ea4.png

在AndroidManifest文件中有三个服务声明。

http://p5.qhimg.com/t01154ddab00683c1b2.png

类kbin.zqn.smv.Ewhtolr是GCM服务类。以下是Ewhtolr类的代码片段

http://p9.qhimg.com/t018171741c0af26f42.png

在子类Hkpvqnb中,以下代码用于处理与GCM相关的intent的操作。 如果操作等于“com.google.android.c2dm.intent.REGISTRATION”,则表示GCM注册已成功。 恶意软件处理来自GCM服务器的响应。

http://p3.qhimg.com/t01e11c7278427dbbdf.png

函数xmrenoslft如下所示。它将registration_id存储在本地存储中。

http://p0.qhimg.com/t01b62883f047e05201.png

registration_id存储在com.google.android.gcm.xml中。

http://p4.qhimg.com/t011310e5df4511d226.png

在GCM注册成功后,恶意软件将RegId发送到C2服务器。

http://p3.qhimg.com/t0146526dbf919fdfe4.png

http://p5.qhimg.com/t01931b02bff9a8e5ae.png

从上面的图中,我们可以看到恶意软件使用AES加密存储reg_id的json数据,然后将加密的数据发送到其C2服务器。 这里,我们修改了原来的加密的类名和函数名,以便于理解。 捕获的流量如下所示。

http://p7.qhimg.com/t014adb048b0bd51ec4.png

http请求正文中的原始json数据如下所示。

http://p9.qhimg.com/t01a8d0d494b19dbf58.png

http响应中的解密数据如下所示。

http://p2.qhimg.com/t013217d4d05ccf2f6e.png

2.locker类

用来获得设备的管理员权限

3.Omnpivk 类

这用于显示要求用户提交其信用卡信息以解锁设备的锁屏信息。 Omnpivk类的代码段如下所示。

http://p9.qhimg.com/t01bb9f645d21c6db36.png

http://p1.qhimg.com/t01d56af76a1a27d2f0.png

锁定屏幕从资产文件夹加载。它看起来像这样。

http://p7.qhimg.com/t01b9700439f6451da3.png

以下是Google翻译翻译的英文版本。

http://p9.qhimg.com/t0165aff88215d0e35f.png

一旦设备被此恶意软件锁定,锁定屏幕将覆盖在系统窗口的顶部。 在提供银行卡信息之前,用户无法在设备上执行任何操作。 一旦用户输入其信用卡信息,恶意软件将其发送到C2服务器。 捕获的流量如下所示。

http://p6.qhimg.com/t01e018f451b0f8711d.png

HTTP post请求和响应中的数据都使用AES加密。解密后的请求的主体数据如下所示。

http://p2.qhimg.com/t017bed8d978b8eaf79.png

解密的响应如下所示。

http://p1.qhimg.com/t01936d3d0493fa257e.png

总而言之,下图说明了恶意软件的工作原理,以及从恶意软件执行的C2服务器发送命令的过程。

http://p9.qhimg.com/t01df0fd1ff5c6d0c39.png


C2服务器通过GCM发送命令

C2服务器首先通过HTTP或XMPP向GCM服务器发送命令,然后GCM服务器将命令推送到受感染的设备。

在上面的部分中,我们显示GCM服务类Ewhtolr用于与GCM服务器通信。 一旦它从GCM服务器收到命令,它调用类Auepniow中的函数cibuwlvohd。 类Auepniow用于处理从C2服务器传递的命令。 这些命令也使用AES加密。

命令列表如下所示。

message_delivered:消息已成功传递到C2服务器。

gcm_register_ok:gcm注册成功。

add_msg_ok:添加一些新手机和msg发送短信。

register_ok:更新http代理列表和用于在本地数据库中发送SMS的模式。

UPDATE_PATTERNS:将更新的信息,包括imei,国家,运营商,电话号码,模型等发送到C2服务器。

URL:更新c2服务器,然后将设备信息发送到新的C2服务器。

STOP:停止GCM服务。

START:启动GCM服务。

UNBLOCK:解除阻塞设备。

MESSAGE:将短信发送到自定义电话。

RESTART:重新启动GCM服务。

PAGE:从URL请求新页面并将响应发送到C2服务器。

CONTACTS:添加新的联系人电话和发送短信测试给他们。

CHANGE_GCM_ID:改变新的sender_id并用新的寄存器GCM注册GCM。

LOCKER_UPDATE:尝试得到一个消息到locker。

LOCKER_BLOCK:启动设备管理锁。

LOCKER_UNBLOCK:释放设备管理锁。

ALLCONTACTS:获取所有联系人号码,并将联系人列表发送到C2服务器。

ALLMSG:获取所有SMS消息并发送到C2服务器。

LOCKER:接收新的锁屏屏幕网页并在设备上显示叠加层。

NEWMSG:在SMS收件箱中添加新消息。

ONLINE:将设备的当前状态发送到C2服务器,包括管理员,锁定,群组,网络类型的状态。

UPDATE:更新新的恶意软件版本或其他恶意应用程序,并将其安装在设备上。

CONTACTS_PRO:获取有效的联系人电话号码,并将其发送到C2服务器。

其复杂的设计包含20多个命令。我们选择命令“UPDATE”来执行进一步的分析。 以下是一个关键的代码片段。

http://p8.qhimg.com/t01f6727c9c932b5128.png

http://p0.qhimg.com/t013a4e8436b2b7f42d.png

类uijevngswhml的定义如下所示。

http://p4.qhimg.com/t014c93bba2fa89ca0c.png

http://p3.qhimg.com/t01e3c91772ab7efec7.png

我们可以看到恶意软件启动http请求以获取更新的apk,并将其存储在/ sdcard / Download文件夹中。 然后在设备上安装apk。 apk可能是恶意软件或其他恶意应用程序的新版本。 我们一直在监控它。


C2服务器和代理列表

恶意软件不会以纯文本格式编码C2服务器的URL。相反,它使用AES加密C2服务器的url。以下是与C2服务器相关的关键代码。

http://p9.qhimg.com/t0117456fd415ac5f01.png

未加密的网址如下所示。 

hxxp://streamout.space

但真正的C2服务器是“streamout.space”的子域。以下代码用于生成实际的C2服务器进行通信。

http://p6.qhimg.com/t01e9d6bba911ebcf34.png

它生成“streamout.space”的动态子域。以下是我们找到的域的部分列表。

stkru[.]streamout[.]space
jfyds[.]streamout[.]space
dgywz[.]streamout[.]space
moazn[.]streamout[.]space
wjrxf[.]streamout[.]space
ykarbm[.]streamout[.]space
ucgeh[.]streamout[.]space

同时,我们还发现一些代理IP被恶意软件硬编码。以下是一段代码片段。

http://p9.qhimg.com/t01888463b369952a21.png

解密的数据如下所示。

 ["193.124.44.118:7777","194.58.100.175:7777","37.140.198.185:7777"]

代理列表也通过命令“register_ok”更新。似乎恶意软件不使用代理列表与其C2服务器通信,但它很可能使用它来与未来变体中的C2服务器通信 。 我们将继续监控此恶意软件系列。 流量如下所示。

http://p4.qhimg.com/t0151c00e8de5bb82a9.png

http post请求中的解密数据如下所示。

http://p2.qhimg.com/t017f23d2d930ccb3b3.png

http响应中的解密数据如下所示。

http://p1.qhimg.com/t01aeec825869801462.png


本地 SQLite 数据库

恶意软件使用SQLite数据库来存储一些关键信息。以下代码用于在数据库中创建两个表。

http://p0.qhimg.com/t0105eaf887aafc44e4.png

我们从设备导出数据库文件,并用SQLite Expert Professional打开它。

http://p5.qhimg.com/t013196b9294bd001df.png

然后我们使用附录中的解密程序解密数据库中的这些字段,如下所示。

http://p3.qhimg.com/t01927c7defd9d1a2b0.png


流量

下面显示了其他解密的流量。

http://p9.qhimg.com/t01a66b3d79f0f57d87.png

设备接收命令“消息”,并向特定电话号码发送SMS消息。

http://p0.qhimg.com/t0176536b9d1d63ea91.png


解决办法

此示例会被Fortinet AntiVirus签名的Android / Locker.FK!tr 检测到。 我们还向Google报告了此恶意软件使用的GCM ID。


结论

GCM是一个有用的服务,旨在将通知从合法软件开发商推送到客户端。 它也是一把双刃剑,因为这个恶意软件表明攻击者也可以使用它作为其C2基础设施的一部分。 GCM服务似乎被这些Android恶意软件作者滥用作为其C&C方法的一部分。 我们将继续监控此恶意软件系列的未来活动,以及其他使用GCM的家族。


附录

SHA256: 286cbb181204e3c67151766d3c4d969c13ef10350c57ebd71e8bb02423d15609

解密代码

package com.example.kailu.myapplication;
/**
* Created by kailu on 12/2/2016.
*/
import android.util.Base64;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Dcpoxqeg {
    static char[] HEX_CHARS;
    private Cipher cipher;
    public String ezkqxsihndnr;
    public String fbxhvatwdljk;
    private IvParameterSpec ivspec;
    private SecretKeySpec keyspec;
    static {
        Dcpoxqeg.HEX_CHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    }
    public Dcpoxqeg() {
        super();
        this.fbxhvatwdljk = "q4s6d8tg5x2y8k2l";
        this.ezkqxsihndnr = "12k8y2x5gt8d6s4q";
        this.ivspec = new IvParameterSpec(this.fbxhvatwdljk.getBytes());
        this.keyspec = new SecretKeySpec(this.ezkqxsihndnr.getBytes(), "AES");
        try {
            this.cipher = Cipher.getInstance("AES/CBC/NoPadding");
            return;
        }
        catch(NoSuchPaddingException v0) {
        }
        catch(NoSuchAlgorithmException v0_1) {
            ((GeneralSecurityException) v0_1).printStackTrace();
        }
    }
    public static String bytesToHex(byte[] arg5) {
        char[] v0 = new char[arg5.length * 2];
        int v1;
        for(v1 = 0; v1 < arg5.length; ++v1) {
            v0[v1 * 2] = Dcpoxqeg.HEX_CHARS[(arg5[v1] & 240) >>> 4];
            v0[v1 * 2 + 1] = Dcpoxqeg.HEX_CHARS[arg5[v1] & 15];
        }
        return new String(v0);
    }
    public static byte[] hexToBytes(String arg5) {
        byte[] v0 = null;
        if(arg5 != null && arg5.length() >= 2) {
            int v2 = arg5.length() / 2;
            v0 = new byte[v2];
            int v1;
            for(v1 = 0; v1 < v2; ++v1) {
                v0[v1] = ((byte)Integer.parseInt(arg5.substring(v1 * 2, v1 * 2 + 2), 16));
            }
        }
        return v0;
    }
    private static String padString(String arg6) {
        int v1 = 16 - arg6.length() % 16;
        int v0;
        for(v0 = 0; v0 < v1; ++v0) {
            arg6 = arg6 + 'u0000';
        }
        return arg6;
    }
    public byte[] uebmdofgszp(String arg8) throws Exception {
        if(arg8 != null && arg8.length() != 0) {
            try {
                arg8 = Base64.encodeToString(arg8.getBytes(), 0);
                this.cipher.init(1, this.keyspec, this.ivspec);
                return this.cipher.doFinal(Dcpoxqeg.padString(arg8).getBytes());
            }
            catch(Exception v1) {
                throw new Exception("[uebmdofgszp] " + v1.getMessage());
            }
        }
        throw new Exception("Empty string");
    }
    public byte[] yrvietanugbdowl(String arg10) throws Exception {
        byte[] v0;
        if(arg10 != null && arg10.length() != 0) {
            try {
                this.cipher.init(2, this.keyspec, this.ivspec);
                v0 = Base64.decode(this.cipher.doFinal(Dcpoxqeg.hexToBytes(arg10)), 0);
                if(v0.length > 0) {
                    int v4 = 0;
                    int v2;
                    for(v2 = v0.length - 1; v2 >= 0; --v2) {
                        if(v0[v2] == 0) {
                            ++v4;
                        }
                    }
                    if(v4 <= 0) {
                        return v0;
                    }
                    byte[] v3 = new byte[v0.length - v4];
                    System.arraycopy(v0, 0, v3, 0, v0.length - v4);
                    v0 = v3;
                }
            }
            catch(Exception v1) {
                throw new Exception("[yrvietanugbdowl] " + v1.getMessage());
            }
            return v0;
        }
        throw new Exception("Empty string");
    }
}
package com.example.kailu.myapplication;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Dcpoxqeg test = new Dcpoxqeg();
        try {
decryptdata(test, ”aca8ff261bbe67b29e0b3e56e41148ec47952d87d097f02d901b7478c5185bc6”);
   } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public void decryptdata(Dcpoxqeg test, String data){
        byte[] ret = new byte[0];
        try {
            ret = test.yrvietanugbdowl(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.d("TEST", "original data = " + new String(ret));
    }
}
本文翻译自fortinet.com 原文链接。如若转载请注明出处。
分享到:微信
+10赞
收藏
shinpachi8
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66