首页
社区
课程
招聘
[原创]某box App逆向分析(启动检测绕过+广告跳过)
发表于: 3天前 865

[原创]某box App逆向分析(启动检测绕过+广告跳过)

3天前
865

目标应用:com.secret.prettyhezi


核心目标:     

    1)启动检测绕过     

    2)广告绕过 


主要方法:    

    jadx 静态分析     

    Frida Java / native hook     请求与响应明文关联分析 


反编译工具:     jadx     IDA pro 

运行时工具:     Frida


 具体分析流程:

1. 启动阶段安全检测绕过 用已经被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

发现检测代码段,我们跟踪一下

经典的root检测手段 我们看一下函数调用链

追到了一个onCreate函数 onCreate() 是 Android 里非常核心的生命周期回调方法,意思是:某个组件第一次被创建时,系统会调用它的 onCreate(),让你做初始化工作

对逆向分析来说,它是寻找启动逻辑、检测逻辑、跳转逻辑的重要位置。  

可以发现我们之前追到的root检测方法:p0.e.b().d() 整个onCreate()还有提示“破解要小心”的字符串,说明整个逻辑可能更偏向于APP的启动检测 接下来我们仔细分析一下每个调用的函数会不会是检测函数 看到很多都调用m0(),分析一下具体作用是调用system.exit()函数

 public void m0() {
        for (int size = 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,就认为当前运行环境像模拟器。
public boolean d(Context context, c cVar) {
        if (context == null) {
            throw new IllegalArgumentException("context must not be null");
        }
        String strA = a("gsm.version.baseband");
        int i6 = (strA == null || strA.contains("1.0.0.0")) ? 1 : 0;
        String strA2 = a("ro.build.flavor");
        if (strA2 != null && (strA2.contains("vbox") || strA2.contains("sdk_gphone"))) {
            i6++;
        }
        String strA3 = a("ro.product.board");
        if (strA3 != null && (strA3.contains("android") | strA3.contains("goldfish"))) {
            i6++;
        }
        String strA4 = a("ro.board.platform");
        if (strA4 == null || strA4.contains("android")) {
            i6++;
        }
        String strA5 = a("ro.hardware");
        if (strA5 == null) {
            i6++;
        } else if (strA5.toLowerCase().contains("ttvm") || strA5.toLowerCase().contains("nox")) {
            i6 += 10;
        }
        boolean zHasSystemFeature = context.getPackageManager().hasSystemFeature("android.hardware.camera.flash");
        if (!zHasSystemFeature) {
            i6++;
        }
        String str = zHasSystemFeature ? "support CameraFlash" : "unsupport CameraFlash";
        int size = ((SensorManager) context.getSystemService("sensor")).getSensorList(-1).size();
        if (size < 7) {
            i6++;
        }
        String str2 = "sensorNum" + size;
        int iC = c(p0.b.c().a("pm list package -3"));
        if (iC < 5) {
            i6++;
        }
        String str3 = "userAppNum" + iC;
        String strA6 = p0.b.c().a("cat /proc/self/cgroup");
        if (strA6 == null) {
            i6++;
        }
        if (cVar != null) {
            StringBuffer stringBuffer = new StringBuffer("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 状态
public boolean a(Context context) {
        return (context.getApplicationInfo().flags & 2) != 0;
}
// 模拟器检测
if (p0.a.c(this)) {
            m0();
            return;
}

// 通过两种方式检测某类目标特征是否存在。
// 第一种方式是 b(a(context)),如果结果为空,则使用 d(context) 作为备用检测。
// 只要任意方式检测到结果,就返回 true,否则返回 false
public static boolean c(Context context) {
        ArrayList arrayList = new ArrayList();
        try {
            String strB = b(a(context));
            if (TextUtils.isEmpty(strB)) {
                List listD = 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。
private static String b(List list) {
        if (list.size() == 0) {
            return HttpUrl.FRAGMENT_ENCODE_SET;
        }
        String str = (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;
        }
        String str2 = Build.MODEL;
        return str2.contains("iTools") ? "iTools" : str2.contains("ChangWan") ? "畅玩" : "genymotion";
    }

    public static boolean c(Context context) {
        ArrayList arrayList = new ArrayList();
        try {
            String strB = b(a(context));
            if (TextUtils.isEmpty(strB)) {
                List listD = 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 蓝叠模拟器
public static List d(Context context) {
        Intent intent = new Intent("android.intent.action.MAIN", (Uri) null);
        intent.addCategory("android.intent.category.LAUNCHER");
        ArrayList arrayList = new ArrayList();
        List<ResolveInfo> listQueryIntentActivities = context.getPackageManager().queryIntentActivities(intent, 0);
        for (int i6 = 0; i6 < listQueryIntentActivities.size(); i6++) {
            ActivityInfo activityInfo = listQueryIntentActivities.get(i6).activityInfo;
            String str = activityInfo.packageName;
            String str2 = 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();
}

public boolean d() {
        if (c() == 0) {
            return true;
        }
        return e();
    }

private int c() {
        String strB = p0.b.c().b("ro.secure");
        return (strB != null && "0".equals(strB)) ? 0 : 1;
    }

private boolean e() {
        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 (int i6 = 0; i6 < 8; i6++) {
            if (new File(strArr[i6]).exists()) {
                return true;
            }
        }
        return false;
    }

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" );
  return false;
};

// 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");
  return false;
};

// 模拟器检测方法,返回 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");
  return false;
};

// root 检测方法,返回 false 绕过检测
var root_hook = p0_e.d.overload();
root_hook.implementation = function() {
  //var ret = root_hook.call(this);
  console.log("root 检测被调用, 返回值: false");
  return false;
};

运行发现还是白屏,切终端没有任何提示 这种情况大概率是检测没有找全,再次尝试找一下 结果发现一开始的代码 super.onCreate(bundle); 调用父类的 `onCreate()` 方法

 

追踪一下

protected void onCreate(Bundle bundle) {
    super.onCreate(bundle);
    g4.h.n(this);
    R0(i());
    f8217n.add(this);
    if (p0.e.b().f()) {
        m0();
    }
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectNetwork().build());
    Z();
}

其中有一段条件下调用m0() 发现是Xposed模块检测

if (p0.e.b().f()) {
            m0();
        }
// Xposed模块检测
public boolean f() {
        try {
            throw new Exception("gg");
        } catch (Exception e6) {
            for (StackTraceElement stackTraceElement : e6.getStackTrace()) {
                if (stackTraceElement.getClassName().contains("de.robv.android.xposed.XposedBridge")) {
                    return true;
                }
            }
            return false;
        }
    }

补上完整的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" );
        return false;
    };

    // 虚拟机检测方法,返回 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" );
        return false;
    };

    // 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");
        return false;
    };

    // 模拟器检测方法,返回 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");
        return false;
    };

    // root 检测方法,返回 false 绕过检测
    var root_hook = p0_e.d.overload();
    root_hook.implementation = function() {
        //var ret = root_hook.call(this);
        console.log("root 检测被调用, 返回值: false");
        return false;
    };
});

再次启动APP,发现APP正常启动,终端也显示正常

此APP的启动阶段检测已经绕过 ## 广告绕过 打开APP可以发现会调用广告,强制占用5秒

尝试去绕过广告 我们搜索一下“跳过”

我们依次查看

// 看起来是一个开屏广告 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 = new RelativeLayout.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(new a());
    }

// g4.d.c(yclh4J3zF, 14, -1, "5 跳过", 17);
public static TextView c(Context context, int i6, int i7, String str, int i8) {
        TextView textView = new TextView(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 void f() {
        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(new b(), 1000L);
    }

这么来看广告的时间核心代码出现了,为了以防万一看一下函数调用链

被第一个分析的广告组件的构造函数调用了,再上一层就是具体的run

public void run() {
            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 = new a();
            } else {
                if (MainApplication.f6868r.y().compareTo(strC) >= 0) {
                    if (jCurrentTimeMillis > j6 + 90000 || jCurrentTimeMillis < j6 - 90000) {
                        OuiCrGxF.this.E("你的手机时间不正确,请修正后启动", new c(), 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, new d());
                        OuiCrGxF ouiCrGxF3 = OuiCrGxF.this;
                        ouiCrGxF3.f6965q.addView(ouiCrGxF3.f6971w, new RelativeLayout.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 = "你所用的版本不是官方版本,请到官网下载并先卸载后安装 \n" + com.secret.prettyhezi.Server.g.latestApk();
                bVar = new b();
            }
            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 String f() {
        String strI = i(f10710e, HttpUrl.FRAGMENT_ENCODE_SET);
        return strI.isEmpty() ? i(f10712g, HttpUrl.FRAGMENT_ENCODE_SET) : strI;
    }
public String i(String str, String str2) {
        return this.f10716b.getString(str, str2);
    }

/*
调用 i()
 ↓
判断 f6045i 是否为 true
 ├─ 是:说明广告正在显示,返回 false
 └─ 否:继续判断

读取 g4.f.a().f()
 ↓
优先读取 f10710e 配置
 ↓
如果 f10710e 为空,再读取 f10712g 配置
 ↓
如果最终还是为空,说明没有广告配置,返回 false
 ↓
如果不为空,说明有广告配置,返回 true
*/

// 总结:
// com.secret.AD.h.i()判断当前是否可以显示广告。
// 只有在“广告没有正在显示”并且“本地存在广告配置字符串”的情况下,才返回 true

结果已经很明显了,接下来有两种方法可以绕过广告 1. 去hook广告的time,修改时间=0来绕过 2. 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");
        return false;
    };

    // 广告是否被初始化以及获取广告剩余时间,返回 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 = new RelativeLayout.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(new a());
    }
    // 被e1()调用
    public boolean g() {
        return this.f6021c == null || this.f6047g <= 0;
    }

    // 直接run
    // 执行函数,执行的f6048h在h()中被赋值runnable参数
    void h() {
        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, new d());
          OuiCrGxF ouiCrGxF3 = OuiCrGxF.this;
          ouiCrGxF3.f6965q.addView(ouiCrGxF3.f6971w, new RelativeLayout.LayoutParams(-1, -1));
                    }
// 接着追踪d()
// 调用e1()
class d implements Runnable {
            d() {
            }

            @Override // java.lang.Runnable
            public void run() {
                OuiCrGxF.this.e1();
            }
        }
// 追踪e1()
// if (hVar == null || hVar.g())调用com.secret.AD.g()
public void e1() {
        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");
        return true;
    };

    /*
    // 广告是否被初始化以及获取广告剩余时间,返回 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;
    };
    */

到这里广告已经绕过


原帖:7cfK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2&6N6i4q4#2k6g2)9J5k6h3y4G2L8g2)9J5c8X3#2W2L8X3N6*7k6g2)9J5k6s2g2Y4K9K6u0C8i4K6u0r3N6s2u0*7k6e0k6E0i4K6u0r3P5Y4N6U0k6K6V1I4y4$3y4&6k6$3S2U0L8o6y4X3M7#2)9K6c8Y4y4A6L8X3N6D9k6f1c8G2j5#2)9J5x3H3`.`. 《BeautyBox 逆向分析》


[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

最后于 1天前 被Mengz3编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回