InjectLog工具使用方法详解(多app实例:单机麻将、我的猪猪侠、城市飞车、元气骑士)
本帖最后由 winding 于 2018-6-5 08:41 编辑前言
这篇帖子是介绍injectlog工具的使用。起由是在一篇帖子中使用了该工具,有坛友跟帖询问使用方法,所以单独开这个帖子,结合一些实例,做一个系统的介绍。
小白工具,大神请无视。
1.来源。
这个工具是charlessimonyi大神在一篇博文中设计的。原文:https://blog.csdn.net/charlessimonyi/article/details/52027563
我在一篇帖子里使用,该贴里就使用方法也贴了不少图,跟帖里有不少关于这个工具的讨论。原贴:点这里
昨夜星辰2012在关于androidkiller的一篇详细介绍和整合包里,也包括了这个工具。原贴:点这里
上边贴文里都有工具的下载地址。这里再附一个最新优化过版本的地址:链接:https://pan.baidu.com/s/1kKACob6C3do38DgiXZbDBA 密码:2z5d
就@w5645060 反馈,在带有汉字的样本中运行出错,经检查是编码问题产生的BUG,下载链接中已修正。具体修改内容见跟帖。
2.组成以及运行。
包括InjectLog.smali、smalihook.py和InjectLog.bat,一共3个文件。其中InjectLog.smali是需要放到逆向工程里的smali文件,smalihook.py负责在所有smali文件中插入调用,InjectLog.bat是实现一键操作的批处理。前两个文件是核心,是charlessimonyi大神的作品,我作了一点优化;后边的批处理是我弄的,代码很丑狗尾续貂,但胜在便于操作。
使用时,上述3个文件放到andriodkiller根目录,InjectLog.bat可添加为andriodkiller自定义工具。
电脑中安装python3.0以上版本,并设置环境变量。
3.优缺点。
这个工具属于LOG分析方法。LOG方法是入门必学的,但很多新人都不是很重视,这个工具可以算是LOG分析的一个重磅工具吧,也可以说是小白神器,感谢charlessimonyi大神的分享。
作用简单说就是把JAVA层运行过的所有方法名(包括包名类名),按照运行先后顺序在LOG中输出出来。
它可以做到:把所有执行过的方法打印出来,所有没打印出来的方法,我们能确定它没执行过!
所以,这个工具虽然是静态方法,但一定程度上可以达到动态分析的效果,特别是对分析app运作流程(如内购)有奇效。
已知和需要注意的问题,A.如果app对系统LOG做过手脚关闭输出的话,需要想办法解开B.极少数的方法名在LOG中打印不出来C.这个工具是JAVA层的,只对smali起作用D.方法名高度混淆的不好使E.多dex的没有弄,还不支持,不是injectlog不能用于多dex,而是我不会写多dex的批处理,有懂的可以自己优化一下。
实战介绍
选取几个坛友已经发过破解教程的app,借助inject工具来分析破解。样本坛友原贴里有,我不提供了。。请注意,因为是介绍这个工具,所以我基本上是使用Injectlog来独立完成任务,没有用特征定位等其他分析方法,但其实这个工具有些时候结合其他方法更有效。
(一)《单机麻将》内购(最简单的例子)
论坛原贴:https://www.52pojie.cn/thread-676953-1-5.html(原贴说app有毒,这里只展示工具,没留意毒的事,大家留意吧) 先拿个5M的小游戏试试手。跑起来发现购买的时候会卡在检查更新。。。得先把检查更新去掉了。
0.把发送短信权限删掉(多余,纯粹习惯)。运行injectlog。查看LOG,发现com.snowfish.android.framework.game.view.MainGLSurfaceView$1.onDrawFrame(UnknownSource)刷屏了。游戏类的app都有这个情况。找到这个onDrawFrame,把这个方法内的“invoke-static {},Lcom/hook/testsmali/InjectLog;->PrintFunc()V”语句删掉。
1.开LOG,点购买,出现提示,关LOG。不提示“检查更新”,改成“付费异常”了,没有打开支付界面,也没有失败的提示。也难怪,貌似只有短信一种付费方式。那么,app在支付时可能有检查权限的动作。我们过会再处理它,先整理一下LOG。 截取到的LOG有103条(开关时机的问题,每次可能有浮动),先大体看一下,多数LOG的TAG是injectlog,这是我们插入的,有些不是的,是app自带的或者模拟器的(我们没有指定TAG)。复制到notepad++中,把umeng的都删掉(使用正则表达式,将com.umeng.*替换为空),友盟是一个统计插件,与软件无关;使用编辑/行操作/移除空行;前11条都是类似onTouchEvent这样处理点击事件的,都删掉。还剩52条,如下:
Buy: 0
com.snowfish.mahjong.UmengHelper.onCustomEventWithData(Unknown Source)
com.snowfish.android.framework.game.AndroidRunLoop.getCurrentHandler(Unknown Source)
SDK_CLASS_NAME=com/snowfish/cn/ganga/offline/basic/SFNativeAdapter,funanme=pay
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter.pay(Unknown Source)
com.snowfish.cn.ganga.offline.helper.SFCommonSDKInterface.pay(Unknown Source)
com.snowfish.cn.ganga.offline.a.c.a(Unknown Source)
com.snowfish.cn.ganga.offline.basic.SFUtilsInterface.ip(Unknown Source)
com.snowfish.cn.ganga.offline.b.h.f(Unknown Source)
com.snowfish.cn.ganga.offline.basic.SFUtilsInterface.sp(Unknown Source)
com.snowfish.cn.ganga.offline.b.h.b(Unknown Source)
com.snowfish.cn.ganga.offline.a.d.<init>(Unknown Source)
com.snowfish.cn.ganga.offline.a.d.run(Unknown Source)
com.snowfish.cn.ganga.offline.a.a.b(Unknown Source)
com.snowfish.cn.ganga.offline.a.a.createPayAdapter(Unknown Source)
com.snowfish.cn.ganga.offline.sf.SFChannelAdapterAHelper.getId(Unknown Source)
com.snowfish.cn.ganga.offline.a.c.a(Unknown Source)
com.snowfish.cn.ganga.offline.a.c.a(Unknown Source)
com.snowfish.cn.ganga.offline.sf.SFChannelAdapterAHelper.createPayAdapter(Unknown Source)
com.snowfish.cn.ganga.offline.sf.d.pay(Unknown Source)
com.snowfish.android.ahelper.APayment.pay(APayment.java)
com.snowfish.a.a.l.AIdleServiceLoader.getInstance(AIdleServiceLoader.java)
com.snowfish.a.a.l.AIdleServiceLoader.getService(AIdleServiceLoader.java)
com.snowfish.a.a.l.h.a(ASvcLoader.java)
com.snowfish.a.a.l.e.a(ABGSvcLoader.java)
com.snowfish.a.a.l.h.a(ASvcLoader.java)
com.snowfish.a.a.p.IAHelper.getTag(IAHelper.java)
com.snowfish.a.a.p.IAHelper.getSrv(IAHelper.java)
com.snowfish.a.a.p.IAHelper.getSrv(IAHelper.java)
com.snowfish.cn.ganga.offline.sf.d.onPaymentCompleted(Unknown Source)
com.snowfish.cn.ganga.offline.basic.SFUtilsInterface.sp(Unknown Source)
com.snowfish.cn.ganga.offline.b.h.b(Unknown Source)
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2.onFailed(Unknown Source)
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2$2.<init>(Unknown Source)
com.snowfish.mahjong.MainActivity$1.callback(Unknown Source)
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2$2.run(Unknown Source)
com.snowfish.android.framework.game.AndroidRunLoop.getCurrentHandler(Unknown Source)
com.snowfish.a.a.s.IABGSvc$Stub.asInterface(IABGSvc.java)
com.snowfish.a.a.s.b.<init>(IABGSvc.java)
com.snowfish.a.a.s.b.call(IABGSvc.java)
com.snowfish.a.a.s.IABGSvc$Stub.onTransact(IABGSvc.java)
com.snowfish.a.a.s.a.call(ABGSvc.java)
com.snowfish.a.a.s.ABGSvc.a(ABGSvc.java)
com.snowfish.a.a.s.ABGSvc.a(ABGSvc.java)
com.snowfish.a.a.l.e.a(ABGSvcLoader.java)
com.snowfish.a.a.l.e.a(ABGSvcLoader.java)
com.snowfish.a.a.l.h.a(ASvcLoader.java)
com.snowfish.a.a.l.e.a(ABGSvcLoader.java)
com.snowfish.a.a.l.h.a(ASvcLoader.java)
com.snowfish.a.a.p.IAHelper.getTag(IAHelper.java)
~~~ same Task
com.snowfish.mahjong.UmengHelper.onCustomEventWithData(Unknown Source)
哦哦,科普一下。以com.snowfish.a.a.l.e.a(ABGSvcLoader.java)为例,com.snowfish.a.a.l.e是文件名,对应com/snowfish/a/a/l/e.smali文件,最后的那个a是方法名。
2.分析。先看看有没有发送短信权限的检查,搜索send_sms没有找到;搜索sms找到4个结果:
这4个方法,都没有在LOG中出现,说明都没有运行过。那么是我们多虑了,可能是通过回调判断的,没有去检查权限。直接改内购好了。
考验眼力的时候到了。第5条LOGfunanme=pay,启动支付流程,下边SFNativeAdapter.pay、SFCommonSDKInterface.pay、createPayAdapter等等一系列的动作,都是在生成订单,第30条com.snowfish.cn.ganga.offline.sf.d.onPaymentCompleted像极了处理回调结果,而com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2.onFailed应该就是失败逻辑。那么修改的关键点在com.snowfish.cn.ganga.offline.sf.d.onPaymentCompleted。
好简单。。。都没有传参数,小软件就是好,简洁明了。那直接swtich大法吧,都改成成功的switch_0。测试一下,购买成功了。发觉没,injectlog要依赖对付方法名的判断,所以没有混淆的特别好使,虽然现在混淆挺多,但好像内购方面的方法名很少有混淆的。
到这里修改就结束了,但真正想研究逆向的坛友应该是有疑惑的:那个“支付异常”的提示到底是怎么回事?如何会触发这个异常,改成成功为什么就没有了?我们对比一下失败和成功的LOG:
失败的
com.snowfish.cn.ganga.offline.sf.d.onPaymentCompleted(Unknown Source)
com.snowfish.cn.ganga.offline.basic.SFUtilsInterface.sp(Unknown Source)
com.snowfish.cn.ganga.offline.b.h.b(Unknown Source)
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2.onFailed(Unknown Source)
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2$2.<init>(Unknown Source)
com.snowfish.mahjong.MainActivity$1.callback(Unknown Source)
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2$2.run(Unknown Source)
com.snowfish.android.framework.game.AndroidRunLoop.getCurrentHandler(Unknown Source)
com.snowfish.a.a.s.IABGSvc$Stub.asInterface(IABGSvc.java)
com.snowfish.a.a.s.b.<init>(IABGSvc.java)
com.snowfish.a.a.s.b.call(IABGSvc.java)
com.snowfish.a.a.s.IABGSvc$Stub.onTransact(IABGSvc.java)
com.snowfish.a.a.s.a.call(ABGSvc.java)
com.snowfish.a.a.s.ABGSvc.a(ABGSvc.java)
成功的
com.snowfish.cn.ganga.offline.sf.d.onPaymentCompleted(Unknown Source)
com.snowfish.cn.ganga.offline.basic.SFUtilsInterface.sp(Unknown Source)
com.snowfish.cn.ganga.offline.b.h.b(Unknown Source)
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2.onSuccess(Unknown Source)
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2$3.<init>(Unknown Source)
com.snowfish.mahjong.MainActivity$1.callback(Unknown Source)
com.snowfish.a.a.s.IABGSvc$Stub.asInterface(IABGSvc.java)
com.snowfish.a.a.s.b.<init>(IABGSvc.java)
com.snowfish.a.a.s.b.call(IABGSvc.java)
com.snowfish.a.a.s.IABGSvc$Stub.onTransact(IABGSvc.java)
com.snowfish.a.a.s.a.call(ABGSvc.java)
com.snowfish.a.a.s.ABGSvc.a(ABGSvc.java)
其中一个com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2$2.run(Unknown Source)的方法比较可疑,看一下代码,主要就是调用了SFNativeAdapter中的onFailed方法,这是一个native方法。把调用注释掉,运行测试。
不弹支付异常了,改卡住了。说明我们的方向是对的。因为native层不是本工具涉及的内容,就不再往下分析了,这是只是演示一下injectlog还可以分析其他的流程,不止内购。
3.善后。带着无数LOG发布的apk,对运行速度影响还是比较明显的。建议确定修改点后,新开一个逆向工程,再修改,然后打包;如果修改的地方比较多,也可以使用notepad++,在工程smali目录下,使用文件中替换,把所有的invoke-static {}, Lcom/hook/testsmali/InjectLog;->PrintFunc()V替换为空。
(二) 《我的猪猪侠》内购(典型的例子)
论坛原贴:https://www.52pojie.cn/thread-724986-1-1.html
支付方式和原贴不一样,有微信、支付宝、和包、Mo9一共4种支付方式。不知道是插件热更新了,还是软件会检测设备以选择支付渠道。
0.运行injectlog工具。
1.先试试支付宝
会有“支付取消”的原生提示框,那么根据这个找应该也好找的,这里是介绍injectlog,我们用injectlog的方式来弄。
就在上图这个界面(不用点“确认支付”往下走了),开始LOG,点“X”取消,弹出“支付取消”提示框后,停止LOG。
2.发现太多unity3d的LOG刷屏了。用notepad++,文件中替换,“invoke-static {}, Lcom/hook/testsmali/InjectLog;->PrintFunc()V”替换成 空 ,目标文件夹.. smali\com\unity3d(unity3d的smali文件夹)。替换掉语句就是injectlog调用语句,说白了就是unity3d下的smali文件都不加LOG了。一共换掉354个。
3.重做,得到LOG,复制到notepad++中,将明显无用的替换掉(灵活运用正则,如update.*表示以update开头的行),剩下65条(其实也可以设置TAG只显示injectlog的,不设置是防止漏掉程序本身带的重要LOG信息):
pkg:com.soulgame.ggbond , class:com.soulgame.bear.LaunchActivity
updateFocusedWindowLocked, not mm
updateFocusedWindowLocked, focusApp Inform:START:我的猪猪侠:com.soulgame.ggbond:com.soulgame.bear.LaunchActivity
Send cmp info to player :START:我的猪猪侠:com.soulgame.ggbond:com.soulgame.bear.LaunchActivity
-----
-----
-----
-----
-----
-----
com.soulgame.sms.pay.MiGuPay$2.onResult(MiGuPay.java)
com.soulgame.sms.SMSSdk$3.onFail(SMSSdk.java)
com.soul.sdk.plugin.pay.PayProxy$4.onFail(PayProxy.java)
com.soul.sdk.plugin.pay.PayParams.getPayPluginType(PayParams.java)
com.soulgame.SoulSdk$4$1.onFail(SoulSdk.java)
com.soulgame.sgsdkproject.sgtool.SGLog.w(SGLog.java)
com.soulgame.sgsdkproject.sgtool.SGLog.w(SGLog.java)
com.soulgame.SoulSdk.sendNotifyUnity(SoulSdk.java)
com.soul.sdk.plugin.pay.PayParams.getPrice(PayParams.java)
com.soul.sdk.plugin.pay.PayParams.getProductId(PayParams.java)
com.soul.sdk.plugin.pay.PayParams.getPayType(PayParams.java)
com.soulgame.SoulSdk.showPayTip(SoulSdk.java)
com.soulgame.sgsdkproject.sgtool.ToastUtil.showShort(ToastUtil.java)
com.soulgame.sgsdkproject.sgtool.ToastUtil.show(ToastUtil.java)
com.soulgame.analytics.SGAgent.onPayEndEvent(SGAgent.java)
com.soulgame.analytics.manager.CommonEventControl.getInstance(CommonEventControl.java)
com.soulgame.analytics.manager.CommonEventControl.payEndEvent(CommonEventControl.java)
com.soulgame.analytics.model.EventData.<init>(EventData.java)
com.soulgame.analytics.model.EventData.setValue(EventData.java)
com.soulgame.analytics.model.EventData.setProperties(EventData.java)
com.soulgame.analytics.SGAgent.onEvent(SGAgent.java)
com.soulgame.analytics.manager.HandleAction.onEvent(HandleAction.java)
com.soulgame.analytics.model.EventData.getName(EventData.java)
com.soulgame.analytics.model.EventData.updateTime(EventData.java)
com.soulgame.analytics.manager.AppTools.getAppContext(AppTools.java)
com.soulgame.analytics.manager.AppTools.getTimeInterval(AppTools.java)
com.soulgame.analytics.utils.SharedUtil.getLong(SharedUtil.java)
com.soulgame.analytics.manager.AppTools.getAuit(AppTools.java)
(共65条,其余略)
大体浏览下这个65条LOG。这个比较好找,可以看到,很明显开头几个就是了(经验和猜测)。com.soulgame.sms.pay.MiGuPay$2.onResult是结果处理,com.soulgame.sms.SMSSdk$3.onFail、com.soul.sdk.plugin.pay.PayProxy$4.onFail和com.soulgame.SoulSdk$4$1.onFail是支付失败走的路径。查看伪JAVA代码分析逻辑:
在上图的确有onFail和onsuccess,但没有找到三个不同onFail的具体调用,而是调用this.val$pPayCallBack.onFail,val$pPayCallBack的定义是:.field final syntheticval$pPayCallBack:Lcom/soul/sdk/plugin/pay/IPayCallBack;那么很有可能,是通过接口的方式,从com.soulgame.sms.pay.MiGuPay$2.onResult启动com.soulgame.sms.SMSSdk$3.onFail、com.soul.sdk.plugin.pay.PayProxy$4.onFail和com.soulgame.SoulSdk$4$1.onFail三个失败函数。这种情况,说明这个onresult是总的处理枢纽,改了这里,其他微信、和包、Mo9等3种支付方式就同时破解了。
4.我们再做一下分析,看如何修改。我们去找一下三个onfail函数,发现参数都是一致的:onFail(int paramInt, String paramString, PayParams paramPayParams)同时对应的onsuccess函数,与onfail参数也是一致的:onSuccess(int paramInt, String paramString, PayParams paramPayParams)那么我们大胆判断,关键就是调用onFail或者onSuccess,以及第一个参数是301还是其他了。搜一下301,只找到一处定义:.field public static final CODE_PAY_FAIL:I = 0x12e
.field public static final CODE_PAY_NOSMS_PAYTYPE:I = 0x132
.field public static final CODE_PAY_NOSNOW_PAYTYPE:I = 0x133
.field public static final CODE_PAY_PARAM_ERROR:I = 0x130
.field public static final CODE_PAY_SUCCESS:I = 0x12d
.field public static final CODE_PAY_TIMEOUT:I = 0x131
.field public static final CODE_UNINIT:I = 0x69
.field public static final PAY_CHECK_TIMEOUT:I = 0xfa0
那么八九不离十了。动手修改com.soulgame.sms.pay.MiGuPay$2.onResult。不贴代码了,成功的代码在:cond_1段,确保走到这里、不走失败的逻辑分支就好了。我是在:pswitch_1、:pswitch_0、switch默认执行代码段的第一行,都加上goto :cond_1。大家随意。
5.测试。成功了,主要LOG:
com.soulgame.sms.pay.MiGuPay$2.onResult(MiGuPay.java)
com.soulgame.sms.SMSSdk$3.onSuccess(SMSSdk.java)
com.soul.sdk.plugin.pay.PayProxy$4.onSuccess(PayProxy.java)
com.soulgame.SoulSdk$4$1.onSuccess(SoulSdk.java)
com.soul.sdk.utils.SGDebug.print_d(SGDebug.java)
>>> 返回到游戏端 :购买道具: 成功!
同时试一试微信、和包、Mo9等其他支付方式,以及购买VIP,都成功了。那么我们的推断是正确的。
6.其实我们还有两点没有细致考虑。直接点取消的时候,onFail(int paramInt, String paramString, PayParams paramPayParams)的第二个、第三个参数是否是null。如果是null,要出错的;3个onFail是如何启动的。更细致的分析,是插入LOG(用androidkiller自带的就可以),把这两个参数打印出来,看看有没有值,是什么值;搞明白3个onFail究竟如何启动,会对程序运行逻辑有更清晰和准确的认识。
(未完,下楼继续) 本帖最后由 winding 于 2018-5-25 00:03 编辑
(四)《元气骑士》1.7.6签名验证及内购(实际使用中出错的处理)
论坛原贴:https://www.52pojie.cn/thread-735060-1-2.html
0.运行INJECTLOG工具。回编,出错了。
处理下这个错误。根据错误提示,打开com/google/android/gms/internal/zzckc.smali,找到第673行(两个数字,前者行号,后者权当列号吧)
发现是我们插入的invoke-static {}, Lcom/hook/testsmali/InjectLog;->PrintFunc()V语句,插入到声明里,导致语法出错了。我们的python脚本的判断逻辑是,如果方法中存在.prologue,就插入到它的下一行;如果不存在.prologue(很多app做了处理,没有这个),就插入到方法中的第一个空行(往往方法声明一块,中间空一行,然后代码块)。多数情况下是可行的,如果有多个.annotation块则会出错,有多个.annotation块的时候,有时两个.annotation块之间也会有空行,这样就出错了。我不懂python,没法弄更复杂的逻辑,有懂的可以完善一下。
解决方法三个。一是把com/google/android/gms/internal/zzckc.smali中的调用语句,都剪切复制到方法内代码块的第一行,手工挪到它该去的地方。如图
但你需要改好多地方。
第二个方法,索性把com/google/android/gms按照unity3d的方法处理,所有gms下的调用语句都删掉,反正这是google的包,一般关键点不在这里,没啥影响。我们用第二种方法,我怕一个个改麻烦。替换掉5500处。回编,还有错误,干脆把google下的都去掉。也能解决问题。但是这种情况下,你要知道,gms或者google文件夹下的smali文件运行与否,LOG中就看不出来了,你得明白这一点。
第三种方法,微调smalihook.py脚本。找到这句代码:
if len(method_section.strip())!= 0 and len(method_section.strip()) == 0:
这句的意思是如果一行不为空且下一行为空,目的就是找到第一个空行,改成如下
if len(method_section.strip())!= 0 and len(method_section.strip()) == 0 and method_section.find(".annotation") == -1:
意思是如果一行不为空且下一行为空且下下行不包含.annotation字符,意思就是找到下行不包含.annotation的第一个空行。
根据不同的情况,得能够对工具进行微调,它才能发挥好作用。实际上,injectlog出错大概只会有类似的这一种情况;其他使用Injectlog出错的,大体是操作方法的问题,或者是apk本身对输出log进行了自定义设置,不是injectlog工具的问题了。
重新反编一个工程,运行injectlog,回编,不报错了。
检查一下,再打开com/google/android/gms/internal/zzckc.smali同样的位置
它已经插入到该插的地方了。
1.处理签名验证。(先说明本例中injectlog不能解决签名验证的问题)
不得不说原贴楼主处理签名验证的操作很风骚,奈何没看明白如何确定的关键点,以及如何确定直接返回空会不会出问题的。
我们按我们的思路走。搜索signatures,要有s。涉及10处smali,涉及具体方法十几个。
开LOG,运行app,报错(提示“请到正规渠道下载。。。。。”),关LOG。LOG很长,就不贴了。到得到的LOG里搜索,确定以上十几个方法是否运行过(其实有些一看就不是),发现只有4个方法运行过(这里不准确,因为方法名都混淆过了,有同名但不同参数的方法,LOG里没法区分):
com.google.android.gms.internal.zzclq.zzaf
com.google.android.gms.internal.zzclq.zzag
com.everyplay.Everyplay.c.e.d
com.everyplay.Everyplay.c.e.b
那么其余的方法基本就排除了,因为没运行过。还有一个情况,com.everyplay.Everyplay.c.e.d与com.everyplay.Everyplay.c.e.b最早出现的时机,在提示“请到正规渠道下载”之后,说明不是真正起作用的方法。还剩两个方法,都是gms集成包下的方法,根据经验也不像。
进到死胡同了,那么这个签名验证,可能不在JAVA层。返回原贴作者的思路,关注com/chillyroom/unityextend/UnityExtendActivity;-> GetKeyHash()Ljava/lang/String;。这个方法没有在LOG中打印,说明应该没有运行过;但按照原贴作者的方法修改又起作用。第一次遇到这种情况,运行过但打印不出来,猜测可能是u3d的so或者脚本调用的原因(在java层没有对这个方法的调用,只能在so层)。
我们新开一个逆向工程(防止干扰,个人习惯,大家随意),用andriodkiller自带的log工具,尝试把GetKeyHash()的返回值打印出来。把killer复制到逆向工程相应的位置,在GetKeyHash()中插入调用:
回编,运行,发现同样打印不出来。
让它返回空,再试一下:
签名验证能过,说明返回空的修改的确起作用,但LOG的确没打印出来。看一下LOG,发现UnityExtendActivity这个类报错了。
那么原贴作者的修改,实际上相当于把这个UnityExtendActivity.smali废掉了。我们验证一下,直接把UnityExtendActivity.smali删除,回编运行,成功了,签名验证过掉了。
到这里还是没有分析明白真正的原理,想继续深入的童鞋可以去so层找答案。使用Il2CppDumper,解开libil2cpp.so和global-metadata.dat,得到script.py和dump.cs,结合着libil2cpp.so分析吧。Injectlog是java层的,话题跑太远了(其实我也不懂so怕讲错),不再继续了。
2.内购。微信、银联、qq钱包3种方式。在下图界面开LOG,点确认,稍等一会,关LOG。
简单贴下部分LOG:
略
com.iapppay.b.c.a(Unknown Source)
com.iapppay.sdk.main.SdkMainBegsession.onPayResult(Unknown Source)
com.iapppay.b.c.f(Unknown Source)
com.iapppay.b.h.c(Unknown Source)
com.chillyroomsdk.iapppay.MainActivity$4$1.onPayResult(MainActivity.java)
com.iapppay.b.h.a(Unknown Source)
com.chillyroomsdk.sdkbridge.pay.BasePayAgent.onPayCancel(BasePayAgent.java)
com.chillyroomsdk.sdkbridge.order.OrderManager.onOrderCancel(OrderManager.java)
use orig AudioTrack! mIsTimed=0 transferType=0 flags=4
com.chillyroomsdk.sdkbridge.order.HistoryOrderManager.getInstance(HistoryOrderManager.java)
略
com.iapppay.e.a.a.a(Unknown Source)
com.chillyroomsdk.sdkbridge.pay.BasePayAgent.onPayFail(BasePayAgent.java)
略
requestCode:2,signvalue:,resultInfo:取消支付
略
com.chillyroomsdk.iapppay.MainActivity$4$1.onPayResult 简单看下JAVA伪代码,成功和失败参数差不多,成功的多了一个productId。关键的resultInfo参数没有往下传递,那么我们改switch 和IF语句吧。
回编测试,开LOG,提示支付成功,但马上校验订单,接着校验失败,没有购买成功,关LOG。还需要处理掉校验的逻辑。看LOG(LOG太多刷屏不好分析,我又把google等一些集成包里的调用去掉了):
略
com.iapppay.ui.a.a.a(Unknown Source)
com.iapppay.sdk.main.SdkMainBegsession.onPayResult(Unknown Source)
com.chillyroomsdk.iapppay.MainActivity$4$1.onPayResult(MainActivity.java)
略
com.iapppay.sdk.main.IAppPayOrderUtils.checkPayResult(Unknown Source)
略
java.lang.Exception: denglibo Toast callstack! strTip=支付成功
com.chillyroomsdk.sdkbridge.pay.BasePayAgent.onPaySuccess(BasePayAgent.java)
com.chillyroomsdk.sdkbridge.BasePlayerActivity.checkOrder(BasePlayerActivity.java)
com.chillyroomsdk.sdkbridge.BasePlayerActivity$28.<init>(BasePlayerActivity.java)
com.chillyroomsdk.sdkbridge.BasePlayerActivity$28.run(BasePlayerActivity.java)
com.chillyroomsdk.sdkbridge.BasePlayerActivity.getOrderAgent(BasePlayerActivity.java)
com.chillyroomsdk.iapppay.MainActivity$3.checkOrder(MainActivity.java)
略
java.lang.Exception: denglibo show AlertDialog! title=Soul Knight
com.chillyroomsdk.iapppay.IAppPayOrderChecker.StartTaskOnMainThread(IAppPayOrderChecker.java)
com.chillyroomsdk.iapppay.IAppPayOrderChecker$1.<init>(IAppPayOrderChecker.java)
com.chillyroomsdk.sdkbridge.pay.record.PayRecordManager.sdkPurchaseSuccess(PayRecordManager.java)
requestCode:2,signvalue:,resultInfo:取消支付
略
com.chillyroomsdk.iapppay.IAppPayOrderChecker$1.run(IAppPayOrderChecker.java)
com.chillyroomsdk.iapppay.IAppPayOrderChecker$1$1.<init>(IAppPayOrderChecker.java)
com.chillyroomsdk.iapppay.IAppPayOrderChecker$1$1.run(IAppPayOrderChecker.java)
com.chillyroomsdk.iapppay.IAppPayOrderChecker.<init>(IAppPayOrderChecker.java)
com.chillyroomsdk.iapppay.IAppPayOrderChecker.doInBackground(IAppPayOrderChecker.java)
略
看到走了支付成功的一些方法,但校验没通过,所以金币没到手。先看onPaySuccess,发现没有实际发放物品的代码,继续往后捋。关注带order的方法或类,有3个checkOrder的方法,浏览一下,发现只是启动检查,没有实际的判定语句;发现有个类IappPayOrderChecker,貌似是个专门处理校验订单的类,进行了大量操作。这个可疑,仔细看一下。
从方法启动顺序看,先是从StartTaskOnMainThread启动,然后PayRecordManager保存订单,然后在doInBackground中,向http://ipay.iapppay.com:9999/payapi/queryresult发送json请求,并返回json数据,最后在onPostExecute中进行返回结果的检查(校验时的那些提示都在这个方法里)。
其中onPostExecute并没有运行的LOG,这是个桥接方法,又一种特殊的不能打印运行日志的方法。
好在我们已经锁定它了。这个时候我们就可以直接修改了,但原贴作者提到onPostExecute里逻辑很复杂,看了下也的确很复杂,我们看下doInBackground返回值什么样的,方便修改。这时可以抓包,我们不抓了,还是用LOG的方法,使用ak自带的Log工具把它打印出来:
运行结果(我把ak带的工具的默认tag改了,默认应该是androidkiller-string)
看到各种信息都比较全,没有null的情况,关键信息就是result字段,结果是2。
直接到onPostExecute方法里,找到取result值的地方:
Result的值不等于0则跳转到cond_3,而cond_3是校验失败的代码段。那么我们判断,真实支付的情况下, Result应该是0。我们直接把这里的v3赋值2,让它与2比较。回编,测试,成功了。为什么这么确定别的地方不用改?因为返回信息很全,可能的判定失败的逻辑只有这一个点,程序一定会走到这里的。好了,结束了。
(本帖结束了。长文好麻烦,再也不写长文了。明天再检查有没有粘贴出错吧)
本帖最后由 winding 于 2018-5-25 15:22 编辑
(三)《城市飞车》内购(没啥代表性,纯粹因为是老相识)
论坛https://www.52pojie.cn/thread-710786-1-1.html
偶然看到这个帖子,想起来去年刚来坛子的时候,跟着教我哥们学逆向系列,有一个课后作业就是类似的游戏,折腾好几天也没弄出来,现在再看太简单了,身为小白的我还是有进步的。就拿它做示例吧。
额,跟(一)同样,都是咪咕的,没啥代表性。。。算了,算搭上的吧。
0.运行INJECTLOG工具。
1.看到有太多com.feamber.util.GameView$Renderer.onDrawFrame的LOG刷屏,把这个方法中的那句invoke-static {}, Lcom/hook/testsmali/InjectLog;->PrintFunc()V删掉,不打印这个方法了。
还有com.morgoo.droidplugin包下的一大堆,晃眼。。。百度了一下是实现插件功能的,把这个包下所有的LOG调用都去掉,方法同(一)
2.已经有经验了,到图示这一步,开LOG,点X,停止LOG。
把得到的LOG简单处理一下,把明显无关的用notepad++的正则替换去掉,有240多行:
com.morgoo.helper.MyProxy.isMethodDeclaredThrowable(MyProxy.java)
pkg:com.racergame.cityracing3d , class:com.racergame.cityracing3d.GameActivity
updateFocusedWindowLocked, not mm
updateFocusedWindowLocked, focusApp Inform:START:城市飞车:com.racergame.cityracing3d:com.racergame.cityracing3d.GameActivity
Send cmp info to player :START:城市飞车:com.racergame.cityracing3d:com.racergame.cityracing3d.GameActivity
com.morgoo.helper.MyProxy.isMethodDeclaredThrowable(MyProxy.java)
-----
-----
-----
com.morgoo.helper.MyProxy.isMethodDeclaredThrowable(MyProxy.java)
com.morgoo.helper.MyProxy.isMethodDeclaredThrowable(MyProxy.java)
-----
-----
-----
com.ck.sdk.AndGameSDK$1.onResult(AndGameSDK.java)
com.ck.sdk.utils.LogUtil.iT(LogUtil.java)
com.ck.sdk.adapter.CKSDKAdapter.onCKPayFail(CKSDKAdapter.java)
com.ck.sdk.CKSDK.getInstance(CKSDK.java)
com.ck.sdk.CKSDK.isOnlineGame(CKSDK.java)
com.ck.sdk.CKSDK.getInstance(CKSDK.java)
com.ck.sdk.CKSDK.getContext(CKSDK.java)
com.ck.sdk.utils.files.SPUtil.getInt(SPUtil.java)
com.ck.sdk.PayParams.getPaySdk(PayParams.java)
com.ck.sdk.database.CkEventTool.setPayFail(CkEventTool.java)
com.ck.sdk.database.CkEventTool.getBaseEventBean(CkEventTool.java)
com.ck.sdk.bean.CkEventBean.<init>(CkEventBean.java)
com.ck.sdk.database.CkEventTool.setBaseEventData(CkEventTool.java)
com.ck.sdk.utils.DeviceInfo.getImei(DeviceInfo.java)
com.ck.sdk.utils.DeviceInfo.getIccid(DeviceInfo.java)
com.ck.sdk.utils.DeviceInfo.getAndroid_id(DeviceInfo.java)
com.morgoo.helper.MyProxy.isMethodDeclaredThrowable(MyProxy.java)
com.ck.sdk.CKSDK.getInstance(CKSDK.java)
com.ck.sdk.CKSDK.getCKAppID(CKSDK.java)
com.ck.sdk.SDKParams.contains(SDKParams.java)
com.ck.sdk.SDKParams.getInt(SDKParams.java)
com.ck.sdk.SDKParams.getString(SDKParams.java)
(中间略)
com.feamber.isp.SmsIAPListener.onResult(SmsIAPListener.java)
pay falied code 11msg pay cancel
com.racergame.cityracing3d.GameActivity.dismissProgressDialog(GameActivity.java)
com.ck.sdk.utils.net.SubmitExtraDataUtil.submitOrSaveData(SubmitExtraDataUtil.java)
com.ck.sdk.CKSDK.getInstance(CKSDK.java)
com.ck.sdk.CKSDK.isOnlineGame(CKSDK.java)
com.ck.sdk.CKSDK.getInstance(CKSDK.java)
com.ck.sdk.CKSDK.getContext(CKSDK.java)
com.ck.sdk.utils.files.SPUtil.getInt(SPUtil.java)
com.ck.sdk.plugin.CKAppEvents.getInstance(CKAppEvents.java)
com.ck.sdk.plugin.CKAppEvents.payFail(CKAppEvents.java)
com.ck.sdk.plugin.CKAppEvents.isNullPlugin(CKAppEvents.java)
com.ck.sdk.utils.LogUtil.iT(LogUtil.java)
com.ck.sdk.AndGameSDK.dealPayResult(AndGameSDK.java)
Skipped 82 frames! The application may be doing too much work on its main thread.
(其余略)
简单分析,推断com.ck.sdk.AndGameSDK$1.onResult(AndGameSDK.java)是关键方法,com.ck.sdk.adapter.CKSDKAdapter.onCKPayFail(CKSDKAdapter.java)和com.ck.sdk.database.CkEventTool.setPayFail(CkEventTool.java)是失败的分支逻辑。我们还看到,顺着LOG再往下找,还有com.feamber.isp.SmsIAPListener.onResult(SmsIAPListener.java)等方法,也可以作为破解点尝试。我们选择最早的那个。
3.分析com.ck.sdk.AndGameSDK$1.onResult的JAVA代码。
因为是内部类,有的方法名显示不全。直接看到com.ck.sdk.AndGameSDK中看onResult(JAVA代码界面,内部类的所有方法同时在主类中也有,一样的,有时显示略有不同)
看到有onCKPayFail方法了,查看接口
有接口,而且父类就是CKSDKAdapter,那么支付逻辑是从com.ck.sdk.AndGameSDK$1.onResult走到com.ck.sdk.adapter.CKSDKAdapter.onCKPayFail没错了。 老办法,修改com.ck.sdk.AndGameSDK$1.onResult,让他走到成功的cond_0段就可以了。我加了3个goto,你也可以修改switch和if,随意。测试成功。
Ps:原贴里楼主提出了两个疑问:“为什么删除请求太频繁前面的if判断能达到内购破解的目的,请求频繁这个提示为什么需要删除一些权限后才会出现?”楼主的破解方式,实际上是将程序原带的调试功能打开了。我们照着楼主的思路修改com.ck.sdk.plugin.CKPay$1.run,看下LOG的不同(这里开启LOG的时机就要早一些了)。
不修改:
(前略)
com.ck.sdk.plugin.CKPay$1.run(CKPay.java)
com.ck.sdk.CKSDK.getInstance(CKSDK.java)
com.ck.sdk.CKSDK.getContext(CKSDK.java)
com.ck.sdk.utils.files.SPUtil.getInt(SPUtil.java)
com.ck.sdk.plugin.CKPay.checkHaveUpdate(CKPay.java)
com.ck.sdk.CKSDK.getInstance(CKSDK.java)
com.ck.sdk.CKSDK.getContext(CKSDK.java)
com.ck.sdk.utils.files.SPUtil.getString(SPUtil.java)
(后略)
照楼主修改后:
(前略)
com.ck.sdk.plugin.CKPay$1.run(CKPay.java)
com.ck.sdk.plugin.CKPay.checkPayResultTest(CKPay.java)
com.ck.sdk.CKSDK.getInstance(CKSDK.java)
com.ck.sdk.CKSDK.getContext(CKSDK.java)
com.ck.sdk.PayParams.getProductId(PayParams.java)
com.ck.sdk.PayParams.getPrice(PayParams.java)
com.ck.sdk.PayParams.getProductName(PayParams.java)
(后略)
大体对比一下,可以看到,修改后主要的不同是checkPayResultTest运行了(对于一些复杂的app,插件多、逻辑复杂,只好用人工简单判断的方式;对于简单app,可以把修改前后的LOG分别存档为txt文件,然后使用beyond compare比对)。这个方法可疑,看一下这个方法,根据里面的字符判断这个就是那个支付调试提示框。看一下run方法的smali文件
.method public run()V
.locals 7
.prologue
invoke-static {}, Lcom/hook/testsmali/InjectLog;->PrintFunc()V
const/4 v6, 0x0
.line 92
iget-object v1, p0, Lcom/ck/sdk/plugin/CKPay$1;->this$0:Lcom/ck/sdk/plugin/CKPay;
invoke-static {v1}, Lcom/ck/sdk/plugin/CKPay;->access$0(Lcom/ck/sdk/plugin/CKPay;)Lcom/ck/sdk/IPay;
move-result-object v1
#if-nez v1, :cond_1 ------关建行
.line 93
iget-object v1, p0, Lcom/ck/sdk/plugin/CKPay$1;->this$0:Lcom/ck/sdk/plugin/CKPay;
iget-object v2, p0, Lcom/ck/sdk/plugin/CKPay$1;->val$tempData:Lcom/ck/sdk/PayParams;
invoke-static {v1, v2}, Lcom/ck/sdk/plugin/CKPay;->access$1(Lcom/ck/sdk/plugin/CKPay;Lcom/ck/sdk/PayParams;)V
.line 125
:cond_0
:goto_0
return-void
(后略)
如果不注释if-nez v1, :cond_1这行的话,是要跳过return-void的,v1是取Lcom/ck/sdk/plugin/CKPay;->payPlugin:Lcom/ck/sdk/IPay;的实例,一定是不等于0的(非0即真,真非0),所以这里一定是跳过的。注释掉后,.line 93的3行代码就得以执行,其中access$1就是调用启动了checkPayResultTest方法。程序启动checkPayResultTest后,运行到return-void就退出了,所以后边几个if修改是无用的,起作用的只有第一个。另一问也是同理,略。
(未完,下楼继续) 谢谢楼主 这很不错哦,用这个方法可以很快找到破解点:lol 收藏学习了,感谢! 666,
收藏学习了 收藏学习了 w5645060 发表于 2018-5-25 10:30
InjectLog有时候会出错,楼主遇到过没有,比如中文。
能不能提供个样本。
injectlog本身不涉及中文,如果因为中文出错,可能是使用的apktool版本的问题。