分析
GestureUnlock密码界面,isError(密码)做验证回调
DexClassLoader加载了一个外置dex来判断和解密文本
不像是正常的东西,getStaticMethod
方法中还有对dex的修复
Class[] uClassArray = new Class[]{Context.class,String.class,int[].class};
Object[] objArray = new Object[]{this,p0,this.getResources().getIntArray(R$array.A_offset)};// this,密码,数据
isValidate(Context p0,String p1,int[] p2)
C.getStaticMethod(p0, p2, "com.zj.wuaipojie2024_2.A", "d", uClassArray).invoke(null, objArray)
private static Method getStaticMethod(Context p0,int[] p1,String p2,String p3,Class[] p4){
String str = null;
try{
File uFile = C.fix(C.read(p0), p1[0], p1[1], p1[2], p0);//修复dex
File dir = p0.getDir("fixed", 0);
uFile.delete();
new File(dir, uFile.getName()).delete();
return new DexClassLoader(uFile.getAbsolutePath(), dir.getAbsolutePath(), str, p0.getClass().getClassLoader()).loadClass(p2).getDeclaredMethod(p3, p4);
}catch(java.lang.Exception e6){
e6.printStackTrace();
return str;
}
}
private static ByteBuffer read(Context p0){
ByteBuffer uByteBuffer = null;
try{
File uFile = new File(p0.getDir("data", 0), "decode.dex");
if (!uFile.exists()) {
return uByteBuffer;
}
FileInputStream uFileInputSt = new FileInputStream(uFile);
byte[] uobyteArray = new byte[uFileInputSt.available()];
uFileInputSt.read(uobyteArray);
uFileInputSt.close();
return ByteBuffer.wrap(uobyteArray);
}catch(java.lang.Exception e0){
return e0;
}
}
看来还是得看解密后的dex
题解
1. 分析进入的checkPassword函数
hook checkPassword
,看看入参是啥
setImmediate(function () {
Java.perform(function () {
var targetClass = decodeURIComponent("com.zj.wuaipojie2024%5f2.MainActivity");
var methodName = "checkPassword";
var gclass = Java.use(targetClass);
gclass[methodName].overload("java.lang.String").implementation = function (arg0) {
console.log("checkPassword(java.lang.String)" + "\n\targ0 = " + arg0);
var i = this[methodName](arg0);
console.log("return " + i);
return i;
};
});
});
checkPassword(java.lang.String)
arg0 = 012345678
return false
# 即九宫格
# 0 1 2
# 3 4 5
# 6 7 8
此处应有暴力解!!!!!
2. 分析解密函数getStaticMethod
重新审视getStaticMethod
方法,发现fix仅与传入的 int[] p2有关
即this.getResources().getIntArray(R$array.A_offset)
int[] p2 = [0,3,7908]
要不,修好的文件直接拦截删除?
但是!!!! 这dex载不进去啊
索性直接JEB导出gradle项目,自己解看看了
3. 手动构建解密
先MT修复dex,使用JEB打开,导出com.zj.wuaipojie2024_2.*
的java
新建一个Android项目,导入java文件,如下
运行,发现decode.dex
不存在
apk搜索,发现只有com.zj.wuaipojie2024_2.C#read
有使用,盲猜就是这个assets/classes.dex
改名,放到对应的路径/data/data/包名/app_data/decode.dex
删除getStaticMethod
方法中的删除操作
运行!!!
2.dex
成功导出!!!
已知,isValidate
调用com.zj.wuaipojie2024_2.A.d(Context,密码)
// 2.dex com.zj.wuaipojie2024_2.A.d
public static String d(Context context, String str) {
MainActivity.sSS(str);//frida检测
String signInfo = Utils.getSignInfo(context);//签名校验
if (signInfo == null || !signInfo.equals("fe4f4cec5de8e8cf2fca60a4e61f67bcd3036117")) {
return "";
}
StringBuffer stringBuffer = new StringBuffer();
int i = 0;
while (stringBuffer.length() < 9 && i < 40) {
int i2 = i + 1;
String substring = "0485312670fb07047ebd2f19b91e1c5f".substring(i, i2);
if (!stringBuffer.toString().contains(substring)) {
stringBuffer.append(substring);
}
i = i2;
}
//stringBuffer.toString().toUpperCase() 锁屏密码 048531267
return !str.equals(stringBuffer.toString().toUpperCase()) ? "" : "唉!哪有什么亿载沉睡的玄天帝,不过是一位被诅咒束缚的旧日之尊,在灯枯之际挣扎的南柯一梦罢了。有缘人,这份机缘就赠予你了。坐标在B.d";
}
//感觉这个B.d还是不对,可能还得解密
public static String d(String str) {
return "?" + str + "?";
}
这时候就得灵机一动了,这玩意卡了我好几天,在查看dex时发现有如下字样
说明B.d
方法肯定是没解出来,fix是根据int[3]数组进行修复的,这想起了另一个数组B_offset = [1,1,8108],解密得
public static String d(String str) {
return "机缘是{" + Utils.md5(Utils.getSha1("password+你的uid".getBytes())) + "}";
}
安卓代码
package com.ql.test;
import static com.zj.wuaipojie2024_2.C.isValidate;
import android.os.Bundle;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
import com.zj.wuaipojie2024_2.Utils;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.button);
button.setOnClickListener(v -> test());
System.out.println(test2("048531267"));
System.out.println(test3("048531267", "838695"));
}
public void test() {
try {
int[] A_offset = new int[3];
A_offset[0] = 0;
A_offset[1] = 3;
A_offset[2] = 7908;
isValidate(this, "123456", A_offset);
A_offset[0] = 1;
A_offset[1] = 1;
A_offset[2] = 8108;
isValidate(this, "123456", A_offset);
} catch (Exception e) {
e.printStackTrace();
}
}
public String test2(String str) {
StringBuffer stringBuffer = new StringBuffer();
int i = 0;
while (stringBuffer.length() < 9 && i < 40) {
int i2 = i + 1;
String substring = "0485312670fb07047ebd2f19b91e1c5f".substring(i, i2);
if (!stringBuffer.toString().contains(substring)) {
stringBuffer.append(substring);
}
i = i2;
}
System.out.println("锁屏密码:" + stringBuffer.toString().toUpperCase());
return !str.equals(stringBuffer.toString().toUpperCase()) ? "" : "唉!哪有什么亿载沉睡的玄天帝,不过是一位被诅咒束缚的旧日之尊,在灯枯之际挣扎的南柯一梦罢了。有缘人,这份机缘就赠予你了。坐标在B.d";
}
public String test3(String password, String uid) {
try {
return "机缘是{" + Utils.md5(Utils.getSha1((password + uid).getBytes())) + "}";
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
}
花絮
sSS方法是个frida检测,有简单的/proc/self/maps
检测
密码的样子