Android frida 实战: 分析全民K歌的判断逻辑

本篇进入 Android frida 实战,旨在分析学习全民K歌这个 app 演唱页面的判断逻辑。

版本:8.22.38.278


此 app 为腾讯推出的面向国内的社交娱乐类应用软件,主要功能是提供用户唱歌、录制和分享自己演唱的歌曲。当非 vip 用户演唱某 vip 歌曲等功能时便会触发弹窗,阻止用户使用其功能。

我们进入演唱页面,点击切换音质,触发 vip 弹窗。

借助算法助手,找到点击事件的回调类:e51.a

查看其 smali 代码,一路跟踪 onlick 方法,可以看到 onclick 调用了 e51.e 的 V 方法,最终来到了 n0 这个方法处。

省略复杂且漫长的追堆栈过程,最终弹窗在 com.tencent.tme.record.module.vip.RecordPrivilegeAccountModule 这个类的 M0 方法中被触发,M0 的 smali 太啰嗦,我们直接反编译看逻辑:

typescript 复制代码
@UiThread
public final void M0(ye2.a aVar, boolean z, boolean z2, String str, f51.a aVar2) {
    byte[] bArr = SwordSwitches.switches1;
    if (bArr != null && ((bArr[888] >> 1) & 1) > 0) {
        if (SwordProxy.proxyMoreArgs(new Object[] { aVar, Boolean.valueOf(z), Boolean.valueOf(z2), str, aVar2 }, this,
                7106).isSupported) {
            return;
        }
    }
    TaskUtilsKt.s(new RecordPrivilegeAccountModule$showChargeVIPDialog$1(this, z2, z, aVar2, str, aVar));
}

经研究得知,上部分的 SwordProxy 是通用逻辑负责合法校验,不起业务判断作用,校验通过后,下部分无条件直接 new 一个 showChargeVIPDialog。也就是说 M0 只要被调用,就无条件触发弹窗。因此我们需要查看 M0 的上游,通过追堆栈找到上游为同类中的 m0 方法,再次反编译出来如下:

ini 复制代码
public final boolean m0(int i, boolean z, boolean z2, String str, f51.a aVar) {
    int i2 = i;
    boolean z3 = z;
    f51.a aVar2 = aVar;
    byte[] bArr = SwordSwitches.switches1;
    if (bArr != null && ((bArr[882] >> 3) & 1) > 0) {
        SwordProxyResult proxyMoreArgs = SwordProxy.proxyMoreArgs(
                new Object[] { Integer.valueOf(i), Boolean.valueOf(z), Boolean.valueOf(z2), str, aVar2 }, this, 7060);
        if (proxyMoreArgs.isSupported) {
            return ((Boolean) proxyMoreArgs.result).booleanValue();
        }
    }
    int u = (int) yu1.e.f().b().u();
    boolean E = yu1.e.f().b().E();
    String str2 = this.F;
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append("handleSuperSoundVIP level = ");
    stringBuilder.append(i);
    stringBuilder.append(" isVip = ");
    stringBuilder.append(z);
    stringBuilder.append(",userVipLevel:");
    stringBuilder.append(u);
    stringBuilder.append(",userIsSubscription:");
    stringBuilder.append(E);
    LogUtil.i(str2, stringBuilder.toString());
    str2 = "record_module#sound_quality_panel#nul";
    if (z3) {
        if (u >= i2) {
            return true;
        }
        if (E && aVar2.l) {
            this.M = true;
            return true;
        } else if (aVar2.l) {
            g5.a.e(this.e, i, aVar2.k, str2);
        } else {
            H(i, u, z2, null, aVar);
        }
    } else if (aVar2.l) {
        g5.a.e(this.e, i, aVar2.k, str2);
    } else if (u >= i2) {
        M0(null, true, z2, str, aVar);
    } else {
        H(i, u, z2, null, aVar);
    }
    return false;
}

Finally,看到了熟悉的日志字样,传入参数 z 是一个布尔类型,代表 isVip 字段。 int u = (int) yu1.e.f().b().u() 链式调用的方式得到 u,代表 userVipLevel。

代码执行时,将 z 的值赋给 z3,判断 if (z3),若用户是 vip 则走 if 下面的逻辑,若用户不是 vip 则走 else if 的逻辑,其中有一条 else if 调用到 M0(null, true, z2, str, aVar),触发弹窗。

当 isVip 是 true 的时候,接着判断 if (u >= i2) ,也就是用户的 vip 等级是否大于想要切换的音质的等级,若是则直接返回 true,否则继续判断走下面。


现在已经清晰了,若我们只想 hook 切换音质这一个功能的话,直接让这个 m0 方法返回 true 即可,但我们想找到传入 m0 的代表用户是否是 vip 的参数 z 是哪来的,以及用户的 vip 等级的链式调用最终走到了哪里返回。 或许所有 vip 功能的判断最终在底层走的都是同一个函数调用,这样我们就不需要一个一个去 hook 单功能点了。

篇幅关系,再次省略复杂且漫长的追堆栈过程:

1. isVip 判断逻辑:

isVip 是 true 还是 false 在 yu1.d 类中的 F() 方法中返回,F 方法如下:

ini 复制代码
    public boolean F() {
        byte[] bArr = SwordSwitches.switches9;
        if (bArr != null && ((bArr[102] >> 7) & 1) > 0) {
            SwordProxyResult proxyOneArg = SwordProxy.proxyOneArg(null, this, 192824);
            if (proxyOneArg.isSupported) {
                return ((Boolean) proxyOneArg.result).booleanValue();
            }
        }
        return o01.d.d(v());
    }

先调用 v() 方法,将返回值传入 o01.d 类中的 d 方法中,最终返回 true/false。(真啰嗦)

d 方法如下,当传入的参数为 2、3、5 时,返回 true:

arduino 复制代码
public static boolean d(int i) {
    if (!(3 == i || 2 == i)) {
        if (5 != i) {
            return false;
        }
    }
    return true;
}

v 方法如下,再次调用 ah.e 中的 o 方法得到要传入 d 方法中的参数:

csharp 复制代码
public int v() {
    int i = 1;
    try {
        i = ah.e.o(this.b, this.c);
    } catch (Exception e) {
        dn.e.b(e, "运行时类初始化异常");
    }
    return i;
}
// o 方法:
public static int o(long j, long j2) {
    if (2 == j2) {
        return 2;
    }
    if (1 == j) {
        return 3;
    }
    if (5 != j) {
        if (5 != j2) {
            if (3 != j) {
                if (4 != j2) {
                    return 1;
                }
            }
            return 4;
        }
    }
    return 5;
}

终于,终于,往下终于没有了,这个 o 方法就是最后一层了,它接收两个 long 参数,并返回 int 值,如果返回的是 2、3、5 则是 vip ,否则为非 vip。

梳理一下:yu1.d.v() => ah.e.o() 得到 int i ,i 传入 o01.d.d(i) 得到 isVip 为 true/false ,最终一层层返回到上层的业务逻辑。

2. userVipLevel 判断逻辑:

int u = (int) yu1.e.f().b().u(),相比之下,这个链式调用就朴素很多,没有那么多花花肠子。一层层最终来到了 yu1.d 类中的 u 方法:

csharp 复制代码
public long u() {
    return this.a;
}

返回此类中的变量 a ,类型为 long,代表 vip 等级。


总结:

明明是要学习 frida 的实战,却发现难点根本不在 hook 代码的编写,而是逆向过程,如何找到 hook 点,以及一层一层寻找调用堆栈。所以请保持敏锐并不断累积经验,这才是提升技术的要点。

附上部分 frida 代码:

ini 复制代码
Java.perform(function() {

    // 1.hook isVip true
    var hookIsVip = Java.use('ah.e');
    hookIsVip.o.implementation = function(j,j2) {
        console.log('[*] Hook userIsVip success ; class:ah.e ; method:o');
        return 5
    }
    // 2.hook vipLevel 8
    var hookVipLevel = Java.use('yu1.d');
    hookVipLevel.u.implementation = function() {
        console.log('[*] Hook vipLevel=8 success ; class:yu1.d ; method:u');
        return 8
    }
})

顺便提醒:

用户信息:com.tencent.karaoke.karaoke_db_base.cachedata.user.UserInfoCacheData

演唱分数:com.tencent.karaoke.audiobasesdk.scorer.ScoreResult

为节省大家研究时间。

相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui