硕士生
小佬
 
- 金币
- 7002
- 好评
- 162
- 信誉
- 270
  

|
本帖最后由 JiGuro 于 2025-7-20 20:08 编辑
今天在 Google Play 里发现了一款应用,名字叫 Kuku TV,还是三哥写的,可惜评分只有0.0

下载下来之后,一点进去就直接给我弹了两个窗 :

好嘛,不就是谷歌服务校验吗?今天我就要手撕它。
第一个弹出的黑色窗口是软件自带的,第二个弹出的白色窗口则是谷歌官方的。两个窗口均用于谷歌服务较验。今天主要分析的是第二个谷歌官方窗口,此窗口在多款 Google Play 软件中均有发现。
贴心地翻译一下 :
出错了
在打开应用程序之前,请检查您的设备上启用了Google Play,并且您使用的是最新版本。如果问题仍然存在,尝试重新安装应用程序。
关闭
相信各位平时在使用一些外网软件时,由于手机上未装谷歌服务框架,或框架版本过老,一定也遇到过类似的情况。按照网上的教程折腾很久都搞不好。最恶心人的是,软件有的时候明明不需要用到任何谷歌框架,但是他偏要给你搞一个验证
目前在网络上,还未发现任何相关的逆向教程。现在我就要通过逆向的方法,带着大家一步步去除该弹窗。
看前提醒:本帖子内容可能有亿点长,很多干货,请准备好花生瓜子,耐心阅读。

事先声明,本人手机上未装谷歌服务套件。由于本人技术能力有限,请各位大佬多多体谅!
老样子,先提取安装包。发现是 apks 包,这倒也不意外,外网很多软件其实都是 apks 格式。我们使用 MT 的转 apk 功能将 apks 格式转换成 apk 格式。

什么鬼?居然有谷歌加固,难道我还要脱壳?
网上查询得知,谷歌加固并不会加密 dex,且不会对 hook 做限制。
这方法不就来了吗?我们直接用我们的模块大法。

打开老色逼,启用模块 SimpleHook ,在其模块作用域里勾选 Kuku TV ,并进入模块拓展页面,添加 Kuku TV ,设置如下:

我们把复制来的热修复路径粘贴到 MT 管理器的路径栏里,定位到目录,并把原包中的所有 dex 文件复制到该路径中。当我们启动软件时,SimpleHook 就会自动帮我们修复 dex ,使我们的修改生效。这样,我们就能成功绕过签名校验,从而解决 dex 修改的问题。

那么,现在的问题就变成了怎么修改。
这时,我们就要用到军哥写的神级模块——算法助手。

像之前的 SimpleHook 一样配置好模块作用域,打开模块,添加 Kuku TV ,设置如下:


设置好后,我们直接戳右上角的运行按钮,打开 Kuku TV ,在所有窗口都弹出后,返回算法助手,在日志页面就可以看到从 Kuku TV hook 到的信息。

我们进入 Kuku TV 的日志,可以发现获取到了非常多的 log 记录。不用急,我们先定位到 Google 官方的这条弹窗,点进去发现其调用堆栈为:
at hDuncLjwovQIoC.QX.bAQF.JungeSpoxedBridge$LegacyApiSupport.handleAfter(Unknown Source:33)
at org.lsposed.lspd.impl.LSPosedBridge$NativeHooker.callback(Unknown Source:279)
at LSPHooker_.show(Unknown Source:8)
at android.app.AlertDialog$Builder.show(AlertDialog.java:1131)
at com.pairip.licensecheck.LicenseActivity.lambda$showErrorDialog$0(LicenseActivity.java:99)
at com.pairip.licensecheck.LicenseActivity.$r8$lambda$N5_Pzpb-eSKmOONXn3Kn0QvMbys(Unknown Source:0)
at com.pairip.licensecheck.LicenseActivity$$ExternalSyntheticLambda1.run(D8$$SyntheticClass:0)
at android.app.Activity.runOnUiThread(Activity.java:7297)
at com.pairip.licensecheck.LicenseActivity.showErrorDialog(LicenseActivity.java:87)
at com.pairip.licensecheck.LicenseActivity.onStart(LicenseActivity.java:41)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1455)
at android.app.Activity.performStart(Activity.java:8273)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3786)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2286)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:210)
at android.os.Looper.loop(Looper.java:299)
at android.app.ActivityThread.main(ActivityThread.java:8292)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:556)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1045)
我们忽略所有系统底层和模块本身的堆栈,不难发现弹窗方法就是位于 com.pairip.licensecheck.LicenseActivity 类的 lambda$showErrorDialog$0 方法。
我们直接使用 MT 打开之前解压出来的 dex ,定位到方法:

分析代码后,我们发现,这个方法只是用于构建窗体及文本,并使其弹出。这时可能会有同学说,可以直接清空该方法。但这里是行不通的,如果对该方法进行修改,软件可能会直接闪退,且弹窗背后的 Activity 依然能够正常弹出。
按照正常逆向思路,我们应该查找调用处。但是我替你们试过了,如果直接查找,需要查很多层,最终还是失败。
分析到这里,很多人应该要放弃了吧?
我们不妨回到算法助手的日志页面,再次进行分析。
这时就要向大家介绍一种新方法,即——时间溯源法(名字没听过?因为是我编的 )
如果要弹出谷歌服务异常窗口,那么软件必然要检测是否谷歌服务存在。这种检测机制相当于让软件与系统的谷歌服务握手,如果握手成功,则返回成功的消息;如果失败,返回失败的消息,这样窗口才能正常弹出。这条失败的消息就是我们要找的"源"。
众所周知,log 是按时间来记录的,越下面的 log 越新。那我们就直接从之前定位到的弹窗记录开始,向上按时间顺序寻找:

很快,我们就能看到这几条 log :

其实,之前试错的经历也并不是没用的。我们从之前得知的弹窗中可以猜想出," license "这个单词其实指的就是 Google Play 的一项服务。再看 log 信息,发现有三条 log 传递了" Connecting to the licensing "这个参数,结合上面翻译一下就是正在连接谷歌服务。下面的三条则是" License check fail ",即检验失败。我们推测,这个弹窗实际上是一个错误弹窗。
综上,我们可以推导出如下的大致运行流程:

怎么样,是不是有种豁然开朗的感觉?
那么现在的重点就转移到抛出错误的方法,只要能够找到这个方法,并把它干掉,那么问题就全解决了。
我们可以在算法助手日志页面右上角找到搜索按钮,搜索" Log.e "(全写为 Log.error ,即错误日志),这样就可以找到所有抛出的异常。

可以发现,结果只有四个。

前三个都与找不到谷歌框架的错误无关,第四个就是解题的关键。

此日志传递的参数1为" LicenseClient ",即连接服务,我们重点看参数2:
Error while checking license: com.pairip.licensecheck.LicenseCheckException: Could not bind with the licensing service.
at com.pairip.licensecheck.LicenseClient.connectToLicensingService(LicenseClient.java:115)
at com.pairip.licensecheck.LicenseClient.$r8$lambda$tTRuJInP7s484yRu-m6AsnoI1z4(Unknown Source:0)
at com.pairip.licensecheck.LicenseClient$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:210)
at android.os.Looper.loop(Looper.java:299)
at android.app.ActivityThread.main(ActivityThread.java:8292)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:556)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1045)
翻译过来大致是:
在验证谷歌服务时发生错误:com.pairip.licensecheck.LicenseCheckException:无法与谷歌服务建立连接。
我们通过MT管理器定位到这个类,并转成Java :

发现 LicenseCheckException 是一个自定义异常类,代码非常简单,用于处理错误消息。
从上述log可知,调用这个异常处理器的是 com.pairip.licensecheck.LicenseClient.connectToLicensingService 这个方法,也就是说,这就是那个抛出错误的方法。

现在的推理的过程其实已经结束了,但为了验证我们的猜想,我们再来分析一下这个抛出错误的方法。
- .method private connectToLicensingService()V
- .registers 4
- .line 100
- const-string v0, "LicenseClient"
- const-string v1, "Connecting to the licensing service..."
- invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
- .line 101
- new-instance v0, Landroid/content/Intent;
- const-string v1, "com.android.vending.licensing.ILicensingService"
- invoke-direct {v0, v1}, Landroid/content/Intent;-><init>(Ljava/lang/String;)V
- const-string v2, "com.android.vending"
- .line 103
- invoke-virtual {v0, v2}, Landroid/content/Intent;->setPackage(Ljava/lang/String;)Landroid/content/Intent;
- move-result-object v0
- .line 104
- invoke-virtual {v0, v1}, Landroid/content/Intent;->setAction(Ljava/lang/String;)Landroid/content/Intent;
- move-result-object v0
- .line 107
- :try_start_18
- iget-object v1, p0, Lcom/pairip/licensecheck/LicenseClient;->context:Landroid/content/Context;
- const/4 v2, 0x1
- .line 108
- invoke-virtual {v1, v0, p0, v2}, Landroid/content/Context;->bindService(Landroid/content/Intent;Landroid/content/ServiceConnection;I)Z
- move-result v0
- :try_end_1f
- .catch Ljava/lang/SecurityException; {:try_start_18 .. :try_end_1f} :catch_2c
- if-nez v0, :cond_2b
- .line 115
- new-instance v0, Lcom/pairip/licensecheck/LicenseCheckException;
- const-string v1, "Could not bind with the licensing service."
- invoke-direct {v0, v1}, Lcom/pairip/licensecheck/LicenseCheckException;-><init>(Ljava/lang/String;)V
- invoke-direct {p0, v0}, Lcom/pairip/licensecheck/LicenseClient;->retryOrThrow(Lcom/pairip/licensecheck/LicenseCheckException;)V
- :cond_2b
- return-void
- :catch_2c
- move-exception v0
- .line 110
- new-instance v1, Lcom/pairip/licensecheck/LicenseCheckException;
- const-string v2, "Not allowed to bind with the licensing service."
- invoke-direct {v1, v2, v0}, Lcom/pairip/licensecheck/LicenseCheckException;-><init>(Ljava/lang/String;Ljava/lang/Throwable;)V
- invoke-direct {p0, v1}, Lcom/pairip/licensecheck/LicenseClient;->retryOrThrow(Lcom/pairip/licensecheck/LicenseCheckException;)V
- return-void
- .end method
复制代码
这个方法唯一目的就是向 Google Play 的 License 服务发起一次 bind,用于检测用户手机是否安装 Google Play 服务,返回值(是否 bind 成功)放在 v0 里,如果系统抛出 SecurityException 会被 :catch_2c 接住。接着,new-instance 在 Java 堆里创建并初始化了一个 LicenseCheckException 对象。SecurityException 被包装成 LicenseCheckException 后,交给 retryOrThrow 决定后续命运。
这与我们的猜想完全吻合,错误信息也是由这个方法提供的。至此,分析过程完全结束。

所以,我们只需要清空这个方法,谷歌服务验证就不复存在了!!!
总结一下,此方法推导过程虽然历经曲折,最后的方案却十分简单,只需要清空位于 com.pairip.licensecheck.LicenseClient类的connectToLicensingService方法即可(不知道哪个 sb 闲的蛋疼琢磨了一天 )。理论上来说,这个方法适用于所有具有 Something went wrong 弹窗的 APP(本人已测试ChatGPT、Suno,均有效) ,以后再遇到这种情况,就可以直接照搬这个方案。
至于软件自带的弹窗,应该不是很难。这个问题就交给各位来解决,就作为一项作业吧!要求是:用算法助手进行分析,使软件在启动和运行时没有相关弹窗弹出,且功能正常(除谷歌登录等功能外,但这软件好像不支持中国手机号)。
这是完全修改后的结果:

如果心情好,可能会再出分析思路。所有的工具和原包都放在下面了,感谢大家的耐心阅读,也欢迎各位在评论区分享自己的见解。
如果有大佬有不同意见,或发了作业的标准答案,我会择优置顶
下载链接
—————————————————————————
码字不易,点赞可有?   |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
-
查看全部评分
总评分:好评 +11
金币 +9
|