安卓逆向 -- 某box启动检测绕过+广告绕过
Mengzek7
Mengzek7
逆向有你
2026年10月23日 11:16
河南
去阅读
项目概览
- 目标应用:com.secret.prettyhezi
-
核心目标:
-
1)启动检测绕过
- 2)登录接口与用户态分析
- 3)加解密链路分析
- 4)视频详情与解锁流程分析
-
5)用户本地缓存分析
-
主要方法:
-
jadx 静态分析
- Frida Java / native hook
- 请求与响应明文关联分析
环境与工具
-
反编译工具:
-
jadx
-
IDA pro
-
运行时工具:
-
Frida
具体分析流程
启动阶段安全检测绕过
用已经被root以及安装了Magisk的实验机打开APP,发现打开一直白屏,对于正常手机来说是可以直接进入APP的,现在就要怀疑是不是有一些启动检测手段
用MT打开安装包查看一下:
没有加壳,直接用jadx打开
第一步:手动搜索root有关代码
复制代码 隐藏代码
/system/bin/su
/system/xbin/su
/sbin/su
/vendor/bin/su
/data/adb/magisk
/data/adb/modules
/system/app/Superuser.apk
发现检测代码段,我们跟踪一下
https://i.postimg.cc/DybRfjKM/3.webp
经典的root检测手段
我们看一下函数调用链
追到了一个onCreate函数
onCreate() 是 Android 里非常核心的生命周期回调方法,意思是:某个组件第一次被创建时,系统会调用它的 onCreate(),让你做初始化工作。
对逆向分析来说,它是寻找启动逻辑、检测逻辑、跳转逻辑的重要位置。 、
可以发现我们之前追到的root检测方法:p0.e.b().d()
整个onCreate()还有提示“破解要小心”的字符串,说明整个逻辑可能更偏向于APP的启动检测
接下来我们仔细分析一下每个调用的函数会不会是检测函数
看到很多都调用m0(),分析一下具体作用是调用system.exit()函数
复制代码 隐藏代码
publicvoidm0() {
for (intsize= f8217n.size() - 1; size >= 0; size--) {
((Yclh4J3zF) f8217n.get(size)).finish();
}
f8217n.clear();
System.exit(0);
}
追一下调用m0()的条件函数
复制代码 隐藏代码
// 模拟器 / 虚拟机环境检测
if (p0.d.b().d(this, null)) {
m0();
return;
}
// p0.d.b().d() 代码
//这段代码是一个模拟器 / 虚拟机环境检测函数
//1. 基带版本是否异常
//2. build flavor 是否包含 vbox / sdk_gphone
//3. product board 是否包含 android / goldfish
//4. board platform 是否异常
//5. hardware 是否包含 ttvm / nox
//6. 是否支持闪光灯
//7. 传感器数量是否太少
//8. 用户安装 App 数量是否太少
//9. /proc/self/cgroup 是否可读取
//10. 把检测信息通过 cVar 回调输出
//通过多个设备特征打分,i6 分数大于 3,就认为当前运行环境像模拟器。
publicbooleand(Context context, c cVar) {
if (context == null) {
thrownewIllegalArgumentException(\"context must not be null\");
}
StringstrA= a(\"gsm.version.baseband\");
inti6= (strA == null || strA.contains(\"1.0.0.0\")) ? 1 : 0;
StringstrA2= a(\"ro.build.flavor\");
if (strA2 != null && (strA2.contains(\"vbox\") || strA2.contains(\"sdk_gphone\"))) {
i6++;
}
StringstrA3= a(\"ro.product.board\");
if (strA3 != null && (strA3.contains(\"android\") | strA3.contains(\"goldfish\"))) {
i6++;
}
StringstrA4= a(\"ro.board.platform\");
if (strA4 == null || strA4.contains(\"android\")) {
i6++;
}
StringstrA5= a(\"ro.hardware\");
if (strA5 == null) {
i6++;
} elseif (strA5.toLowerCase().contains(\"ttvm\") || strA5.toLowerCase().contains(\"nox\")) {
i6 += 10;
}
booleanzHasSystemFeature= context.getPackageManager().hasSystemFeature(\"android.hardware.camera.flash\");
if (!zHasSystemFeature) {
i6++;
}
Stringstr= zHasSystemFeature ? \"support CameraFlash\" : \"unsupport CameraFlash\";
intsize= ((SensorManager) context.getSystemService(\"sensor\")).getSensorList(-1).size();
if (size < 7) {
i6++;
}
Stringstr2=\"sensorNum\" + size;
intiC= c(p0.b.c().a(\"pm list package -3\"));
if (iC < 5) {
i6++;
}
Stringstr3=\"userAppNum\" + iC;
StringstrA6= p0.b.c().a(\"cat /proc/self/cgroup\");
if (strA6 == null) {
i6++;
}
if (cVar != null) {
StringBufferstringBuffer=newStringBuffer(\"ceshi start|\");
stringBuffer.append(strA);
stringBuffer.append(\"|\");
stringBuffer.append(strA2);
stringBuffer.append(\"|\");
stringBuffer.append(strA3);
stringBuffer.append(\"|\");
stringBuffer.append(strA4);
stringBuffer.append(\"|\");
stringBuffer.append(strA5);
stringBuffer.append(\"|\");
stringBuffer.append(str);
stringBuffer.append(\"|\");
stringBuffer.append(str2);
stringBuffer.append(\"|\");
stringBuffer.append(str3);
stringBuffer.append(\"|\");
stringBuffer.append(strA6);
stringBuffer.append(\"|end\");
cVar.a(stringBuffer.toString());
}
return i6 > 3;
}
复制代码 隐藏代码
//debuggable 状态检测
if (p0.e.b().a(this)) {
I(\"破解要小心哦~\");
}
//判断当前 App 是否处于 debug/debuggable 状态
publicbooleana(Context context) {
return (context.getApplicationInfo().flags & 2) != 0;
}
复制代码 隐藏代码
// 模拟器检测
if (p0.a.c(this)) {
m0();
return;
}
// 通过两种方式检测某类目标特征是否存在。
// 第一种方式是 b(a(context)),如果结果为空,则使用 d(context) 作为备用检测。
// 只要任意方式检测到结果,就返回 true,否则返回 false
publicstaticbooleanc(Context context) {
ArrayListarrayList=newArrayList();
try {
StringstrB= b(a(context));
if (TextUtils.isEmpty(strB)) {
ListlistD= d(context);
if (listD.size() > 0) {
arrayList.add(listD.get(0));
}
} else {
arrayList.add(strB);
}
} catch (Exception e6) {
e6.printStackTrace();
}
return !arrayList.isEmpty();
}
// 判断当前 App 是否运行在模拟器环境中。
// 只要识别到 MuMu、蓝叠、夜神、逍遥、雷电、Genymotion 等模拟器特征,就返回 true。
privatestatic String b(List list) {
if (list.size() == 0) {
return HttpUrl.FRAGMENT_ENCODE_SET;
}
Stringstr= (String) list.get(0);
if (str.contains(\"mumu\")) {
return\"mumu\";
}
if (str.contains(\"ami\")) {
return\"AMIDuOS\";
}
if (str.contains(\"bluestacks\")) {
return\"蓝叠\";
}
if (str.contains(\"kaopu001\") || str.contains(\"tiantian\")) {
return\"天天\";
}
if (str.contains(\"kpzs\")) {
return\"靠谱助手\";
}
if (!str.contains(\"genymotion\")) {
return str.contains(\"uc\") ? \"uc\" : str.contains(\"blue\") ? \"blue\" : str.contains(\"microvirt\") ? \"逍遥\" : str.contains(\"itools\") ? \"itools\" : str.contains(\"syd\") ? \"手游岛\" : str.contains(\"bignox\") ? \"夜神\" : str.contains(\"haimawan\") ? \"海马玩\" : str.contains(\"windroy\") ? \"windroy\" : str.contains(\"flysilkworm\") ? \"雷电\" : str.contains(\"emu\") ? \"emu\" : str.contains(\"le8\") ? \"le8\" : str.contains(\"vphone\") ? \"vphone\" : str.contains(\"duoyi\") ? \"多益\" : HttpUrl.FRAGMENT_ENCODE_SET;
}
Stringstr2= Build.MODEL;
return str2.contains(\"iTools\") ? \"iTools\" : str2.contains(\"ChangWan\") ? \"畅玩\" : \"genymotion\";
}
publicstaticbooleanc(Context context) {
ArrayListarrayList=newArrayList();
try {
StringstrB= b(a(context));
if (TextUtils.isEmpty(strB)) {
ListlistD= d(context);
if (listD.size() > 0) {
arrayList.add(listD.get(0));
}
} else {
arrayList.add(strB);
}
} catch (Exception e6) {
e6.printStackTrace();
}
return !arrayList.isEmpty();
}
//d(Context context) 可以确定是一个备用模拟器检测函数,专门检测 BlueStacks 蓝叠模拟器
publicstatic List d(Context context) {
Intentintent=newIntent(\"android.intent.action.MAIN\", (Uri) null);
intent.addCategory(\"android.intent.category.LAUNCHER\");
ArrayListarrayList=newArrayList();
List<ResolveInfo> listQueryIntentActivities = context.getPackageManager().queryIntentActivities(intent, 0);
for (inti6=0; i6 < listQueryIntentActivities.size(); i6++) {
ActivityInfoactivityInfo= listQueryIntentActivities.get(i6).activityInfo;
Stringstr= activityInfo.packageName;
Stringstr2= activityInfo.name;
activityInfo.loadLabel(context.getPackageManager());
if (!TextUtils.isEmpty(str) && str.contains(\"bluestacks\")) {
arrayList.add(\"蓝叠\");
return arrayList;
}
}
return arrayList;
}
复制代码 隐藏代码
// root检测
// 比较基础的 root 检测逻辑。整体判断方式有两个:
// 1.检查系统属性 ro.secure 是否为 0;
// 2.检查常见路径下是否存在 su 文件。
// 只要任意一个条件命中,就认为设备已经 root,然后执行 m0()。
if (p0.e.b().d()) {
m0();
}
publicbooleand() {
if (c() == 0) {
returntrue;
}
return e();
}
privateintc() {
StringstrB= p0.b.c().b(\"ro.secure\");
return (strB != null && \"0\".equals(strB)) ? 0 : 1;
}
privatebooleane() {
String[] strArr = {\"/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 (inti6=0; i6 < 8; i6++) {
if (newFile(strArr[i6]).exists()) {
returntrue;
}
}
returnfalse;
}
hook点以及差不多找到了
直接frida hook一下,想永久化的可以用Xposed
复制代码 隐藏代码
// 虚拟机检测方法,返回 false 绕过检测
var p0_d = Java.use(\"p0.d\");
var xuniqi_hook = p0_d.d.overload(\"android.content.Context\", \"p0.c\");
xuniqi_hook.implementation = function(context, p0_c_instance) {
//var ret = xuniqi_hook.call(this, context, p0_c_instance );
console.log(\"虚拟机检测被调用, 返回值: false\" );
returnfalse;
};
// debug 调试检测方法,返回 false 绕过检测
var debug_hook = p0_e.a.overload(\"android.content.Context\");
debug_hook.implementation = function(context) {
//var ret = debug_hook.call(this, context);
console.log(\"debug调试检测被调用, 返回值: false\");
returnfalse;
};
// 模拟器检测方法,返回 false 绕过检测
var p0_a = Java.use(\"p0.a\");
var moniqi_hook = p0_a.c.overload(\"android.content.Context\");
moniqi_hook.implementation = function(context) {
//var ret = moniqi_hook.call(this, context);
console.log(\"模拟器检测被调用, 返回值: false\");
returnfalse;
};
// root 检测方法,返回 false 绕过检测
var root_hook = p0_e.d.overload();
root_hook.implementation = function() {
//var ret = root_hook.call(this);
console.log(\"root 检测被调用, 返回值: false\");
returnfalse;
};
运行发现还是白屏,切终端没有任何提示
这种情况大概率是检测没有找全,再次尝试找一下
结果发现一开始的代码 super.onCreate(bundle); 调用父类的 onCreate() 方法
追踪一下
复制代码 隐藏代码
protectedvoidonCreate(Bundle bundle) {
super.onCreate(bundle);
g4.h.n(this);
R0(i());
f8217n.add(this);
if (p0.e.b().f()) {
m0();
}
StrictMode.setThreadPolicy(newStrictMode.ThreadPolicy.Builder().detectNetwork().build());
Z();
}
其中有一段条件下调用m0()
发现是Xposed模块检测
复制代码 隐藏代码
if (p0.e.b().f()) {
m0();
}
// Xposed模块检测
publicbooleanf() {
try {
thrownewException(\"gg\");
} catch (Exception e6) {
for (StackTraceElement stackTraceElement : e6.getStackTrace()) {
if (stackTraceElement.getClassName().contains(\"de.robv.android.xposed.XposedBridge\")) {
returntrue;
}
}
returnfalse;
}
}
补上完整的hook代码即可
复制代码 隐藏代码
Java.perform(function() {
// =========================
// Java 层环境检测绕过
// =========================
// xposed 检测方法,返回 false 绕过检测
var xposed_hook = p0_e.f.overload();
xposed_hook.implementation = function() {
//var ret = xposed_hook.call(this);
console.log(\"Xposed 检测被调用, 返回值: false\" );
returnfalse;
};
// 虚拟机检测方法,返回 false 绕过检测
var p0_d = Java.use(\"p0.d\");
var xuniqi_hook = p0_d.d.overload(\"android.content.Context\", \"p0.c\");
xuniqi_hook.implementation = function(context, p0_c_instance) {
//var ret = xuniqi_hook.call(this, context, p0_c_instance );
console.log(\"虚拟机检测被调用, 返回值: false\" );
returnfalse;
};
// debug 调试检测方法,返回 false 绕过检测
var debug_hook = p0_e.a.overload(\"android.content.Context\");
debug_hook.implementation = function(context) {
//var ret = debug_hook.call(this, context);
console.log(\"debug调试检测被调用, 返回值: false\");
returnfalse;
};
// 模拟器检测方法,返回 false 绕过检测
var p0_a = Java.use(\"p0.a\");
var moniqi_hook = p0_a.c.overload(\"android.content.Context\");
moniqi_hook.implementation = function(context) {
//var ret = moniqi_hook.call(this, context);
console.log(\"模拟器检测被调用, 返回值: false\");
returnfalse;
};
// root 检测方法,返回 false 绕过检测
var root_hook = p0_e.d.overload();
root_hook.implementation = function() {
//var ret = root_hook.call(this);
console.log(\"root 检测被调用, 返回值: false\");
returnfalse;
};
});
再次启动APP,发现APP正常启动,终端也显示正常
此APP的启动阶段检测已经绕过
广告绕过
打开APP可以发现会调用广告,强制占用5秒
https://i.postimg.cc/MTnXn25H/8.webp
尝试去绕过广告
我们搜索一下“跳过”
发现三个调用
我们依次查看
复制代码 隐藏代码
// 看起来是一个开屏广告 View / 启动页广告组件的构造函数
// 传入一个 (5 跳过)的参数,刚好和广告时间一样
public h(Yclh4J3zF yclh4J3zF, Runnable runnable) {
super(yclh4J3zF);
this.f6047g = 6;
f6045i = true;
this.f6048h = runnable;
ArrayList arrayListA = c.a(1);
if (arrayListA.size() == 0) {
setVisibility(8);
return;
}
int iD = g4.f.a().d(\"splashADIndex\", 0);
c((g.a) arrayListA.get(iD % arrayListA.size()));
int i6 = iD + 1;
g4.f.a().o(\"splashADIndex\", i6 < arrayListA.size() * 10 ? i6 : 0);
TextView textViewC = g4.d.c(yclh4J3zF, 14, -1, \"5 跳过\", 17);
this.f6046f = textViewC;
textViewC.setBackground(g4.h.d(g4.h.b(Color.parseColor(\"#3f000000\"), 20.0f), g4.h.b(Color.parseColor(\"#6f000000\"), 20.0f)));
RelativeLayout.LayoutParams layoutParams = newRelativeLayout.LayoutParams(g4.h.r(60.0f), g4.h.r(40.0f));
layoutParams.topMargin = g4.h.r(20.0f);
layoutParams.rightMargin = g4.h.r(20.0f);
layoutParams.addRule(11, -1);
addView(this.f6046f, layoutParams);
f();
this.f6046f.setOnClickListener(newa());
}
// g4.d.c(yclh4J3zF, 14, -1, \"5 跳过\", 17);
public staticTextViewc(Context context, int i6, int i7, String str, int i8) {
TextView textView = newTextView(context);
textView.setTextSize(i6);
textView.setTextColor(i7);
textView.setText(str);
textView.setGravity(i8);
textView.setPadding(0, 0, 0, 0);
return textView;
}
复制代码 隐藏代码
// 广告的倒计时控制函数
// 控制开屏广告的 5 秒倒计时。
// 倒计时从 5 开始,前 2 秒隐藏“跳过”按钮,剩余 3 秒显示“跳过”按钮;
// 每秒更新一次文本;倒计时到 0 时调用 h() 结束广告。
// this.f6046f.setVisibility(i6 >= 4 ? 8 : 0);用来控制跳过时机
// View.GONE = 8(不可见,并且不占布局空间) ; View.VISIBLE = 0(可见)
public voidf() {
if (!((OuiCrGxF) this.f6020b).f6966r) {
this.f6047g--;
}
int i6 = this.f6047g;
if (i6 <= 0) {
if (i6 == 0) {
h();
return;
}
return;
}
this.f6046f.setVisibility(i6 >= 4 ? 8 : 0);
this.f6046f.setText(this.f6047g + \" 跳过\");
postDelayed(newb(), 1000L);
}
这么来看广告的时间核心代码出现了,为了以防万一看一下函数调用链
被第一个分析的广告组件的构造函数调用了,再上一层就是具体的run
复制代码 隐藏代码
public voidrun() {
OuiCrGxF ouiCrGxF;
String strC1;
OuiCrGxF ouiCrGxF2;
String strT0;
Yclh4J3zF.s bVar;
s sVar = (s) com.secret.prettyhezi.f.d(this.f7005a, s.class);
if (sVar.code != 200) {
OuiCrGxF.this.z(sVar.err);
OuiCrGxF.this.X0();
return;
}
MainApplication.f6868r.w(sVar.data);
String strC = g4.a.c();
long jCurrentTimeMillis = System.currentTimeMillis() / 1000;
long j6 = sVar.data.sys.systime;
int i6 = (int) (jCurrentTimeMillis - j6);
g4.f.a().o(\"keySysDiffTime\", i6);
g4.h.f10718b = i6;
if (MainApplication.f6868r.y().compareTo(strC) > 0) {
MainApplication.f6868r.n(6).d();
ouiCrGxF2 = OuiCrGxF.this;
strT0 = ouiCrGxF2.t0(R.string.UpdateHint);
bVar = newa();
} else {
if (MainApplication.f6868r.y().compareTo(strC) >= 0) {
if (jCurrentTimeMillis > j6 + 90000 || jCurrentTimeMillis < j6 - 90000) {
OuiCrGxF.this.E(\"你的手机时间不正确,请修正后启动\", newc(), false);
return;
}
g4.f.a().r();
// 广告绕过核心代码
// 广告做个判断,然后调用com.secret.AD.h进行初始化
if (com.secret.AD.h.i()) {
OuiCrGxF.this.f6971w = new com.secret.AD.h(OuiCrGxF.this, newd());
OuiCrGxF ouiCrGxF3 = OuiCrGxF.this;
ouiCrGxF3.f6965q.addView(ouiCrGxF3.f6971w, newRelativeLayout.LayoutParams(-1, -1));
}
if (MainApplication.f6868r.t() > com.secret.prettyhezi.Server.r.savedNoticeVersion) {
OuiCrGxF.this.p1();
} else {
com.secret.prettyhezi.Server.r.LoadPrevious();
}
if (MainApplication.f6868r.k().sys.page > com.secret.prettyhezi.Server.s.savedPageVersion) {
OuiCrGxF.this.q1();
} else {
com.secret.prettyhezi.Server.s.LoadPrevious();
}
MainApplication.f6868r.g(true);
OuiCrGxF.this.f6967s.clear();
if (OuiCrGxF.this.g1(1, com.secret.prettyhezi.Server.v.f7323a)) {
ouiCrGxF = OuiCrGxF.this;
strC1 = com.secret.prettyhezi.Server.v.f7323a;
} else {
ouiCrGxF = OuiCrGxF.this;
strC1 = ouiCrGxF.c1(1);
}
ouiCrGxF.U0(strC1);
return;
}
ouiCrGxF2 = OuiCrGxF.this;
strT0 = \"你所用的版本不是官方版本,请到官网下载并先卸载后安装 \
\" + com.secret.prettyhezi.Server.g.latestApk();
bVar = newb();
}
ouiCrGxF2.E(strT0, bVar, false);
}
// 广告判断函数
// 当开屏广告组件被创建时,它会把 f6045i 设置为 true
// 所以 f6045i 很可能表示:当前开屏广告是否正在显示;当前是否已经进入广告流程;是否禁止重复创建广告
public static boolean i() {
return (f6045i || g4.f.a().f().isEmpty()) ? false : true;
}
// g4.f.a().f()
// 读取某个本地配置字符串
// 优先读取 f10710e 对应的配置;
// 如果为空,再读取 f10712g 对应的备用配置
public Stringf() {
String strI = i(f10710e, HttpUrl.FRAGMENT_ENCODE_SET);
return strI.isEmpty() ? i(f10712g, HttpUrl.FRAGMENT_ENCODE_SET) : strI;
}
public Stringi(String str, String str2) {
returnthis.f10716b.getString(str, str2);
}
/*
调用 i()
↓
判断 f6045i 是否为 true
├─ 是:说明广告正在显示,返回 false
└─ 否:继续判断
读取 g4.f.a().f()
↓
优先读取 f10710e 配置
↓
如果 f10710e 为空,再读取 f10712g 配置
↓
如果最终还是为空,说明没有广告配置,返回 false
↓
如果不为空,说明有广告配置,返回 true
*/
// 总结:
// com.secret.AD.h.i()判断当前是否可以显示广告。
// 只有在“广告没有正在显示”并且“本地存在广告配置字符串”的情况下,才返回 true
结果已经很明显了,接下来有两种方法可以绕过广告
- 去hook广告的time,修改时间=0来绕过
- hook com.secret.AD.h.i()来直接跳过广告
复制代码 隐藏代码
// =========================
// 广告相关绕过
// =========================
// 跳过广告
var AD_h = Java.use(\"com.secret.AD.h\");
var ad_hook = AD_h.i.overload();
ad_hook.implementation = function() {
//var ret = ad_hook.call(this);
console.log(\"广告被调用, 返回值: false\");
returnfalse;
};
// 广告是否被初始化以及获取广告剩余时间,返回 true 绕过检测
var AD_h = Java.use(\"com.secret.AD.h\");
var init = AD_h.$init.overload(\"com.secret.prettyhezi.Yclh4J3zF\", \"java.lang.Runnable\");
init.implementation = function (ctx, r) {
console.log(\"广告被初始化\");
var ret = init.call(this, ctx, r);
try {
var fields = AD_h.class.getDeclaredFields();
/*
for (var i =0; i < fields.length; i++) {
// 获取真实fields
console.log(\"field name: \" + fields[i].getName() + \" value: \" + fields[i].get(this) + \" type: \" + fields[i].getType());
}
*/
// 得到真实fields后发现g字段是广告剩余时间,直接修改为0绕过广告
this.g.value = 0;
console.log(\"广告剩余时间:\" + this.g.value);
} catch (e) {
console.log(\"dump fields failed: \" + e);
}
return ret;
};
但是运行代码事与愿违
hook com.secret.AD.h.i()来直接跳过广告,没能hook到,应该是没走这条路
原本以为都结束了,结果这么看来还有不少路要走
我们返回广告类com.secret.AD认真分析一下广告的函数逻辑
复制代码 隐藏代码
/*
OuiCrGxF 启动逻辑
↓
com.secret.AD.h.i() 判断是否可以显示广告
↓
new com.secret.AD.h(OuiCrGxF.this, new d())
↓
h 构造函数初始化广告组件
↓
f() 启动倒计时
↓
倒计时归零后调用 h()
↓
h() 执行 this.f6048h.run()
↓
f6048h 实际上是 new d()
↓
d.run()
↓
OuiCrGxF.this.e1()
↓
e1() 判断广告是否结束 + 用户状态
↓
N0(cls) 跳转目标页面
↓
finish() 关闭启动页
*/
// 广告组件初始化函数
public h(Yclh4J3zF yclh4J3zF, Runnable runnable) {
super(yclh4J3zF);
this.f6047g = 6;
f6045i = true;
// 这里给f6048h = runnable参数
// f6048h也被当前类中的h()调用,具体去查看一下h()
this.f6048h = runnable;
ArrayList arrayListA = c.a(1);
if (arrayListA.size() == 0) {
setVisibility(8);
return;
}
int iD = g4.f.a().d(\"splashADIndex\", 0);
c((g.a) arrayListA.get(iD % arrayListA.size()));
int i6 = iD + 1;
g4.f.a().o(\"splashADIndex\", i6 < arrayListA.size() * 10 ? i6 : 0);
TextView textViewC = g4.d.c(yclh4J3zF, 14, -1, \"5 跳过\", 17);
this.f6046f = textViewC;
textViewC.setBackground(g4.h.d(g4.h.b(Color.parseColor(\"#3f000000\"), 20.0f), g4.h.b(Color.parseColor(\"#6f000000\"), 20.0f)));
RelativeLayout.LayoutParams layoutParams = newRelativeLayout.LayoutParams(g4.h.r(60.0f), g4.h.r(40.0f));
layoutParams.topMargin = g4.h.r(20.0f);
layoutParams.rightMargin = g4.h.r(20.0f);
layoutParams.addRule(11, -1);
addView(this.f6046f, layoutParams);
f();
this.f6046f.setOnClickListener(newa());
}
// 被e1()调用
public boolean g() {
returnthis.f6021c == null || this.f6047g <= 0;
}
// 直接run
// 执行函数,执行的f6048h在h()中被赋值runnable参数
voidh() {
this.f6048h.run();
}
// 调用h(Yclh4J3zF yclh4J3zF, Runnable runnable)的调用链
// 这里可以看到runnable参数=new d()
if (com.secret.AD.h.i()) {
OuiCrGxF.this.f6971w = new com.secret.AD.h(OuiCrGxF.this, newd());
OuiCrGxF ouiCrGxF3 = OuiCrGxF.this;
ouiCrGxF3.f6965q.addView(ouiCrGxF3.f6971w, newRelativeLayout.LayoutParams(-1, -1));
}
// 接着追踪d()
// 调用e1()
classd implements Runnable {
d() {
}
@Override// java.lang.Runnable
public voidrun() {
OuiCrGxF.this.e1();
}
}
// 追踪e1()
// if (hVar == null || hVar.g())调用com.secret.AD.g()
public voide1() {
Class cls;
if (this.f6966r || !this.f6972x) {
return;
}
com.secret.AD.h hVar = this.f6971w;
if (hVar == null || hVar.g()) {
a0 a0VarR = MainApplication.f6868r.r();
if (a0VarR == null) {
cls = CfTs9fWO.class;
} else {
if (!a0VarR.payment_password && a0VarR.grade != 0) {
BrmpD.T0(this);
return;
}
cls = ZIgnJ.class;
}
N0(cls);
finish();
}
}
分析到这里,可以知道判断广告核心函数是com.secret.AD.h.g()
-
f()
-
倒计时主循环
- 每秒 g--
- 更新“X 跳过”文本
-
到 0 时调用 h()
-
h()
-
真正执行跳过回调
-
g()
-
广告对象为空
-
或 倒计时 <= 0
-
返回“广告是否已经结束 / 是否可以跳过”的布尔状态
- 条件是:
hook com.secret.AD.h.g()即可跳过广告
复制代码 隐藏代码
// =========================
// 广告相关绕过
// =========================
// 跳过广告
// 检测广告是否已经播放完,返回 true 绕过检测
var ad_stop_time_hook = AD_h.g.overload();
ad_stop_time_hook.implementation = function() {
//var ret = ad_stop_time_hook.call(this);
console.log(\"检测广告是否已经播放完, 返回值: true\");
returntrue;
};
/*
// 广告是否被初始化以及获取广告剩余时间,返回 true 绕过检测
var AD_h = Java.use(\"com.secret.AD.h\");
var init = AD_h.$init.overload(\"com.secret.prettyhezi.Yclh4J3zF\", \"java.lang.Runnable\");
init.implementation = function (ctx, r) {
console.log(\"广告被初始化\");
var ret = init.call(this, ctx, r);
try {
var fields = AD_h.class.getDeclaredFields();
//for (var i =0; i < fields.length; i++) {
// 获取真实fields
//console.log(\"field name: \" + fields[i].getName() + \" value: \" + fields[i].get(this) + \" type: \" + fields[i].getType());
//}
// 得到真实fields后发现g字段是广告剩余时间,直接修改为0绕过广告
this.g.value = 0;
console.log(\"广告剩余时间:\" + this.g.value);
} catch (e) {
console.log(\"dump fields failed: \" + e);
}
return ret;
};
*/
到这里广告已经绕过
·今 日 推 荐·
| --- |
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论