app 界面
image.png (141.49 KB, 下载次数: 1)
下载附件
2024-12-3 20:57 上传
开始分析
通过前人的分析已经知道有 SSL 双向认证
将软件脱壳后使用 jadx 分析
image-1.png (163.39 KB, 下载次数: 1)
下载附件
2024-12-3 20:57 上传
搜索关键词找到 SSL Pinning 实现
translucent.png 是 客户端证书。
transparent.png 是 服务器证书。
image-2.png (17.75 KB, 下载次数: 1)
下载附件
2024-12-3 20:57 上传
证书密码是 lerist.key.2021
image-3.png (107.17 KB, 下载次数: 0)
下载附件
2024-12-3 20:58 上传
打开 Burp 添加客户端证书 (BKS 转 PKCS#12 过程请自行搜索)
image-4.png (136 KB, 下载次数: 0)
下载附件
2024-12-3 20:58 上传
打开算法助手,打开 JustTrustME+升级版
设置好代{过}{滤}理,启动软件,登录账号
API 分析
这里只分析主要 API,其他 API 就不分析了
所有信息已脱敏处理
/FakeLocation/app/getAppConfigs
获取程序配置
{
"body": {
"createTime": 1731500000000,
"disabledApps": [ // 禁用包名
"com.xxx.xxx.xxx",
],
"disabledFuncs": [ // 禁用功能
"mock_loc_strong",
"antidetect_strong",
"hideroot_strong",
"antidetect",
"diypackage",
"hideroot"
],
"disabledInfos": [
"bootads",
"donate",
"moreapps",
"recommend",
"discuss",
"about"
],
"id": 0,
"isAllowRun": 1,
"isAvailable": 1,
"notice": "",
"updateTime": 0
},
"code": 200,
"returnTime": 1731500000000, // 服务器时间
"success": true
}
/FakeLocation/user/login
登录账号
{
"body": {
"regtime": 1730000000000, // 注册时间
"proindate": 1732600000000, // 会员过期时间
"createTime": 1730000000000, // 注册时间
"loginType": "email",
"loginName": "[email protected]",
"updateTime": 0,
"type": 1,
"userId": "xxxxxxxxxxxxxxxxxxxxxxxxxx", // 用户ID
"key": "xxxxxxxxxxxxxxxx", // 加密用户信息
"token": "xxxxxxxxxxxxxxxxxx" // 用户Token
},
"code": 200,
"returnTime": 1731500000000, // 服务器时间
"success": true
}
/FakeLocation/user/get
获取用户信息
{
"body": {
"regtime": 1730000000000, // 注册时间
"proindate": 1732600000000, // 会员过期时间
"createTime": 1730000000000, // 注册时间
"loginType": "email",
"loginName": "[email protected]",
"updateTime": 0,
"type": 1,
"userId": "xxxxxxxxxxxxxxxxxxxxxxxxxx", // 用户ID
"key": "xxxxxxxxxxxxxxxx", // 加密用户信息
"token": "xxxxxxxxxxxxxxxxxx" // 用户Token
},
"code": 200,
"extras": "", // 用户配置,与 /FakeLocation/app/getAppConfigs 相同
"returnTime": 1731500000000,
"success": true
}
/FakeLocation/cell/queryNearby
获取基站信息
{
"body": [
{
"averageSignal": 0,
"cellid": 0,
"distance": 0,
"lac": 0,
"lat": 0,
"lon": 0,
"mcc": 0,
"mnc": 0,
"radio_type": "LTE",
"range": 0,
"unit": 0
}
],
"code": 200,
"returnTime": 1731500000000,
"success": true
}
接下来我们分析用户信息解密以及校验
image-5.png (104.31 KB, 下载次数: 0)
下载附件
2024-12-3 20:58 上传
image-6.png (26.01 KB, 下载次数: 0)
下载附件
2024-12-3 20:58 上传
image-7.png (48.07 KB, 下载次数: 0)
下载附件
2024-12-3 20:58 上传
可以发现,解密走的 Native 层
image-8.png (51.62 KB, 下载次数: 0)
下载附件
2024-12-3 20:58 上传
函数注册
image-9.png (77.53 KB, 下载次数: 0)
下载附件
2024-12-3 20:58 上传
过签检测
image-10.png (109.14 KB, 下载次数: 0)
下载附件
2024-12-3 20:58 上传
签名校验
解密函数分析
传入参数
[ol]
[/ol]
if ( (unsigned int)sq(a1) ) // 签名校验
{
v7 = getpid();
kill(v7, 9);
kill(0, 9);
return 0LL;
}
v8 = (*a1)->FindClass(a1, "android/util/Base64");
v9 = (*a1)->GetStaticMethodID(a1, v8, "decode", "([BI)[B");
v10 = _JNIEnv::CallStaticObjectMethod(a1, v8, v9, a3, 0LL);
if ( (*a1)->ExceptionCheck(a1) )
{
v11 = *a1;
LABEL_6:
v11->ExceptionClear(a1);
return 0LL;
}
v12 = (*a1)->NewStringUTF(a1, off_4EF8); // 载入公钥
v13 = (*a1)->FindClass(a1, "java/lang/String");
v14 = (*a1)->GetMethodID(a1, v13, "getBytes", "()[B");
v15 = _JNIEnv::CallObjectMethod(a1, v12, v14); // 获取 Bytes
v16 = _JNIEnv::CallStaticObjectMethod(a1, v8, v9, v15, 0LL); // 调用 Java 层解密 Base64
v17 = (*a1)->FindClass(a1, "java/security/spec/X509EncodedKeySpec");
v18 = (*a1)->GetMethodID(a1, v17, "", "([B)V");
v60 = v17;
v57 = (void *)v16;
v19 = _JNIEnv::NewObject(a1, v17, v18, v16);
v20 = (*a1)->FindClass(a1, "java/security/KeyFactory");
v21 = (*a1)->GetStaticMethodID(a1, v20, "getInstance", "(Ljava/lang/String;)Ljava/security/KeyFactory;");
v22 = (*a1)->NewStringUTF(a1, "RSA");
v23 = (*a1)->NewStringUTF(a1, "RSA/ECB/PKCS1Padding");
v61 = v22;
v25 = _JNIEnv::CallStaticObjectMethod(a1, v20, v21, v22, v24);
v26 = (*a1)->GetMethodID(a1, v20, "generatePublic", "(Ljava/security/spec/KeySpec;)Ljava/security/PublicKey;");
v56 = (void *)v25;
v62 = (void *)v19;
v27 = _JNIEnv::CallObjectMethod(a1, v25, v26); // 加载公钥
v28 = (*a1)->FindClass(a1, "javax/crypto/Cipher");
v29 = (*a1)->GetStaticMethodID(a1, v28, "getInstance", "(Ljava/lang/String;)Ljavax/crypto/Cipher;");
v58 = v23;
v31 = (void *)_JNIEnv::CallStaticObjectMethod(a1, v28, v29, v23, v30);
v32 = (*a1)->GetMethodID(a1, v28, "init", "(ILjava/security/Key;)V");
v59 = (void *)v27;
_JNIEnv::CallVoidMethod(a1, v31, v32, 2LL, v27);
v33 = (*a1)->GetMethodID(a1, v28, "doFinal", "([B)[B");
v34 = _JNIEnv::CallObjectMethod(a1, v31, v33); // 使用公钥解密
v35 = (*a1)->ExceptionCheck(a1);
v11 = *a1;
if ( v35 )
goto LABEL_6;
v38 = v11->FindClass(a1, "java/lang/String");
v39 = (*a1)->GetMethodID(a1, v38, "", "([B)V");
v54 = (void *)_JNIEnv::NewObject(a1, v38, v39, v34);
v55 = (void *)v34;
(*a1)->NewStringUTF(a1, "#");
v53 = (void *)v10;
v40 = (*a1)->FindClass(a1, "java/lang/String");
v41 = (*a1)->GetMethodID(a1, v40, "split", "(Ljava/lang/String;)[Ljava/lang/String;"); // 解密数据使用 # 分割
v36 = (void *)_JNIEnv::CallObjectMethod(a1, v54, v41);
v42 = (*a1)->GetArrayLength(a1, v36);
v43 = (*a1)->GetObjectArrayElement(a1, v36, (unsigned int)(v42 - 1)); // 获取分割后的数据,用户Token
v44 = (*a1)->GetStringUTFChars(a1, v43, 0LL);
v45 = (*a1)->GetStringUTFChars(a1, a4, 0LL);
v46 = strcmp(v44, v45); // 判断传入Token是否与解密Token一致
if ( v45 && !v46 && *v45 )
{
v47 = strstr(v45, "-T");
v48 = (int)v47 - (int)v45 + 1;
if ( !v47 )
v48 = 0LL;
v49 = v44;
v50 = v43;
v51 = 1000 * strtoll(&v45[v48 + 37], 0LL, 16); // 获取用户Token后6为转成时间戳
v52 = 1000 * time(0LL)
通过上面分析,用户信息是使用 RSA 公钥解密
然后判断传入 Token 和解密 Token 是否一致
以及判断 Token 内隐藏的 VIP 时间是否过期
虚拟定位内核分析
注入相关
释放资源目录下的 so 和 3DFly.lis 到 /data/fl
通过调用释放到 /data/fl/inject 将 /data/fl/libfl_app.so 和 /data/fl/libfl_init.so 注入到 system_service 和 com.android.phone 中
校验 libfl.so(3DFly.lis) 的 md5 是否为 f2fe0b7e56eb6f32d277224eb37a20e5
libfl_init.so 将 libfl.so(3DFly.lis) 注入并调用 o.O.o.O#init(Ljava/lang/Object;)[Ljava/lang/Object;
libfl_app.so 将 libfl.so(3DFly.lis) 注入并调用 o.O.o.O#hookApp(Ljava/lang/Object;)[Ljava/lang/Object;
具体如何注入的未分析
用户信息校验
image-11.png (98.71 KB, 下载次数: 0)
下载附件
2024-12-3 20:58 上传
软件过期时间 2025-09-26 08:07:28
过期时间检查
用户信息构成
[table]
[tr]
[td]Index[/td]
[td]信息[/td]
[/tr]
[tr]
[td]0