翻译:shinpachi8
预估稿费:200RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
前言
上个月,我们发现了一个新的Android锁屏恶意软件,它会启动勒索软件,在设备上显示锁定屏幕,并勒索用户提交他们的银行卡信息以解锁设备。 这个勒索软件变种的有趣的变化是,它利用谷歌云消息(GCM)平台,推送通知服务,发送消息到注册的客户端,作为其C2基础设施的一部分。 它还在受感染设备和C2服务器之间的通信中使用AES加密。 在这个博客中,我们对这个恶意软件做了详细的分析。
恶意软件快速浏览
用户启动受感染的应用程序后,它会请求设备管理员权限,如下所示。
一旦用户授予管理员权限, 恶意软件的显示以下屏幕截图。
锁定屏幕如下所示。它要求用户的赎金高达545000Rub (约9000美元)用于解锁设备。
恶意软件如何工作
以下是有关此恶意软件变体如何工作的详细技术分析。以下是恶意软件开始启动时使用的关键代码段。
接下来,我们分析了这三个关键类。
1.Jfldnam Class
这是用于GCM注册的服务类。关键代码段如下所示。
通过我们的分析,类“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。
AndroidManifest文件中的GCM广播接收器声明如下所示。
在AndroidManifest文件中有三个服务声明。
类kbin.zqn.smv.Ewhtolr是GCM服务类。以下是Ewhtolr类的代码片段
在子类Hkpvqnb中,以下代码用于处理与GCM相关的intent的操作。 如果操作等于“com.google.android.c2dm.intent.REGISTRATION”,则表示GCM注册已成功。 恶意软件处理来自GCM服务器的响应。
函数xmrenoslft如下所示。它将registration_id存储在本地存储中。
registration_id存储在com.google.android.gcm.xml中。
在GCM注册成功后,恶意软件将RegId发送到C2服务器。
从上面的图中,我们可以看到恶意软件使用AES加密存储reg_id的json数据,然后将加密的数据发送到其C2服务器。 这里,我们修改了原来的加密的类名和函数名,以便于理解。 捕获的流量如下所示。
http请求正文中的原始json数据如下所示。
http响应中的解密数据如下所示。
2.locker类
用来获得设备的管理员权限
3.Omnpivk 类
这用于显示要求用户提交其信用卡信息以解锁设备的锁屏信息。 Omnpivk类的代码段如下所示。
锁定屏幕从资产文件夹加载。它看起来像这样。
以下是Google翻译翻译的英文版本。
一旦设备被此恶意软件锁定,锁定屏幕将覆盖在系统窗口的顶部。 在提供银行卡信息之前,用户无法在设备上执行任何操作。 一旦用户输入其信用卡信息,恶意软件将其发送到C2服务器。 捕获的流量如下所示。
HTTP post请求和响应中的数据都使用AES加密。解密后的请求的主体数据如下所示。
解密的响应如下所示。
总而言之,下图说明了恶意软件的工作原理,以及从恶意软件执行的C2服务器发送命令的过程。
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”来执行进一步的分析。 以下是一个关键的代码片段。
类uijevngswhml的定义如下所示。
我们可以看到恶意软件启动http请求以获取更新的apk,并将其存储在/ sdcard / Download文件夹中。 然后在设备上安装apk。 apk可能是恶意软件或其他恶意应用程序的新版本。 我们一直在监控它。
C2服务器和代理列表
恶意软件不会以纯文本格式编码C2服务器的URL。相反,它使用AES加密C2服务器的url。以下是与C2服务器相关的关键代码。
未加密的网址如下所示。
hxxp://streamout.space
但真正的C2服务器是“streamout.space”的子域。以下代码用于生成实际的C2服务器进行通信。
它生成“streamout.space”的动态子域。以下是我们找到的域的部分列表。
stkru[.]streamout[.]space
jfyds[.]streamout[.]space
dgywz[.]streamout[.]space
moazn[.]streamout[.]space
wjrxf[.]streamout[.]space
ykarbm[.]streamout[.]space
ucgeh[.]streamout[.]space
同时,我们还发现一些代理IP被恶意软件硬编码。以下是一段代码片段。
解密的数据如下所示。
["193.124.44.118:7777","194.58.100.175:7777","37.140.198.185:7777"]
代理列表也通过命令“register_ok”更新。似乎恶意软件不使用代理列表与其C2服务器通信,但它很可能使用它来与未来变体中的C2服务器通信 。 我们将继续监控此恶意软件系列。 流量如下所示。
http post请求中的解密数据如下所示。
http响应中的解密数据如下所示。
本地 SQLite 数据库
恶意软件使用SQLite数据库来存储一些关键信息。以下代码用于在数据库中创建两个表。
我们从设备导出数据库文件,并用SQLite Expert Professional打开它。
然后我们使用附录中的解密程序解密数据库中的这些字段,如下所示。
流量
下面显示了其他解密的流量。
设备接收命令“消息”,并向特定电话号码发送SMS消息。
解决办法
此示例会被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));
}
}

评论