吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 26145|回复: 47
上一主题 下一主题
收起左侧

[Android 原创] InjectLog工具使用方法详解(多app实例:单机麻将、我的猪猪侠、城市飞车、元气骑士)

  [复制链接]
跳转到指定楼层
楼主
winding 发表于 2018-5-24 23:19 回帖奖励
本帖最后由 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)[149]刷屏了。游戏类的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条,如下:
[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
Buy: 0
com.snowfish.mahjong.UmengHelper.onCustomEventWithData(Unknown Source)[200]
com.snowfish.android.framework.game.AndroidRunLoop.getCurrentHandler(Unknown Source)[200]
SDK_CLASS_NAME=com/snowfish/cn/ganga/offline/basic/SFNativeAdapter,funanme=pay
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter.pay(Unknown Source)[200]
com.snowfish.cn.ganga.offline.helper.SFCommonSDKInterface.pay(Unknown Source)[200]
com.snowfish.cn.ganga.offline.a.c.a(Unknown Source)[200]
com.snowfish.cn.ganga.offline.basic.SFUtilsInterface.ip(Unknown Source)[200]
com.snowfish.cn.ganga.offline.b.h.f(Unknown Source)[200]
com.snowfish.cn.ganga.offline.basic.SFUtilsInterface.sp(Unknown Source)[200]
com.snowfish.cn.ganga.offline.b.h.b(Unknown Source)[200]
com.snowfish.cn.ganga.offline.a.d.<init>(Unknown Source)[200]
com.snowfish.cn.ganga.offline.a.d.run(Unknown Source)[1]
com.snowfish.cn.ganga.offline.a.a.b(Unknown Source)[1]
com.snowfish.cn.ganga.offline.a.a.createPayAdapter(Unknown Source)[1]
com.snowfish.cn.ganga.offline.sf.SFChannelAdapterAHelper.getId(Unknown Source)[1]
com.snowfish.cn.ganga.offline.a.c.a(Unknown Source)[1]
com.snowfish.cn.ganga.offline.a.c.a(Unknown Source)[1]
com.snowfish.cn.ganga.offline.sf.SFChannelAdapterAHelper.createPayAdapter(Unknown Source)[1]
com.snowfish.cn.ganga.offline.sf.d.pay(Unknown Source)[1]
com.snowfish.android.ahelper.APayment.pay(APayment.java)[1]
com.snowfish.a.a.l.AIdleServiceLoader.getInstance(AIdleServiceLoader.java)[1]
com.snowfish.a.a.l.AIdleServiceLoader.getService(AIdleServiceLoader.java)[1]
com.snowfish.a.a.l.h.a(ASvcLoader.java)[1]
com.snowfish.a.a.l.e.a(ABGSvcLoader.java)[1]
com.snowfish.a.a.l.h.a(ASvcLoader.java)[1]
com.snowfish.a.a.p.IAHelper.getTag(IAHelper.java)[1]
com.snowfish.a.a.p.IAHelper.getSrv(IAHelper.java)[1]
com.snowfish.a.a.p.IAHelper.getSrv(IAHelper.java)[1]
com.snowfish.cn.ganga.offline.sf.d.onPaymentCompleted(Unknown Source)[1]
com.snowfish.cn.ganga.offline.basic.SFUtilsInterface.sp(Unknown Source)[1]
com.snowfish.cn.ganga.offline.b.h.b(Unknown Source)[1]
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2.onFailed(Unknown Source)[1]
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2$2.<init>(Unknown Source)[1]
com.snowfish.mahjong.MainActivity$1.callback(Unknown Source)[1]
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2$2.run(Unknown Source)[200]
com.snowfish.android.framework.game.AndroidRunLoop.getCurrentHandler(Unknown Source)[200]
com.snowfish.a.a.s.IABGSvc$Stub.asInterface(IABGSvc.java)[1]
com.snowfish.a.a.s.b.<init>(IABGSvc.java)[1]
com.snowfish.a.a.s.b.call(IABGSvc.java)[1]
com.snowfish.a.a.s.IABGSvc$Stub.onTransact(IABGSvc.java)[190]
com.snowfish.a.a.s.a.call(ABGSvc.java)[190]
com.snowfish.a.a.s.ABGSvc.a(ABGSvc.java)[190]
com.snowfish.a.a.s.ABGSvc.a(ABGSvc.java)[190]
com.snowfish.a.a.l.e.a(ABGSvcLoader.java)[190]
com.snowfish.a.a.l.e.a(ABGSvcLoader.java)[190]
com.snowfish.a.a.l.h.a(ASvcLoader.java)[190]
com.snowfish.a.a.l.e.a(ABGSvcLoader.java)[190]
com.snowfish.a.a.l.h.a(ASvcLoader.java)[190]
com.snowfish.a.a.p.IAHelper.getTag(IAHelper.java)[190]
~~~ same Task
com.snowfish.mahjong.UmengHelper.onCustomEventWithData(Unknown Source)[200]

哦哦,科普一下。以com.snowfish.a.a.l.e.a(ABGSvcLoader.java)[190]为例,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:

[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
失败的
com.snowfish.cn.ganga.offline.sf.d.onPaymentCompleted(Unknown Source)[1]
com.snowfish.cn.ganga.offline.basic.SFUtilsInterface.sp(Unknown Source)[1]
com.snowfish.cn.ganga.offline.b.h.b(Unknown Source)[1]
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2.onFailed(Unknown Source)[1]
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2$2.<init>(Unknown Source)[1]
com.snowfish.mahjong.MainActivity$1.callback(Unknown Source)[1]
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2$2.run(Unknown Source)[200]
com.snowfish.android.framework.game.AndroidRunLoop.getCurrentHandler(Unknown Source)[200]
com.snowfish.a.a.s.IABGSvc$Stub.asInterface(IABGSvc.java)[1]
com.snowfish.a.a.s.b.<init>(IABGSvc.java)[1]
com.snowfish.a.a.s.b.call(IABGSvc.java)[1]
com.snowfish.a.a.s.IABGSvc$Stub.onTransact(IABGSvc.java)[190]
com.snowfish.a.a.s.a.call(ABGSvc.java)[190]
com.snowfish.a.a.s.ABGSvc.a(ABGSvc.java)[190]


[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
成功的
com.snowfish.cn.ganga.offline.sf.d.onPaymentCompleted(Unknown Source)[1]
com.snowfish.cn.ganga.offline.basic.SFUtilsInterface.sp(Unknown Source)[1]
com.snowfish.cn.ganga.offline.b.h.b(Unknown Source)[1]
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2.onSuccess(Unknown Source)[1]
com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2$3.<init>(Unknown Source)[1]
com.snowfish.mahjong.MainActivity$1.callback(Unknown Source)[1]
com.snowfish.a.a.s.IABGSvc$Stub.asInterface(IABGSvc.java)[1]
com.snowfish.a.a.s.b.<init>(IABGSvc.java)[1]
com.snowfish.a.a.s.b.call(IABGSvc.java)[1]
com.snowfish.a.a.s.IABGSvc$Stub.onTransact(IABGSvc.java)[221]
com.snowfish.a.a.s.a.call(ABGSvc.java)[221]
com.snowfish.a.a.s.ABGSvc.a(ABGSvc.java)[221]


其中一个com.snowfish.cn.ganga.offline.basic.SFNativeAdapter$2$2.run(Unknown Source)[200]的方法比较可疑,看一下代码,主要就是调用了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信息):
[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
[bodyEncrypt:1070] -----
[bodyEncrypt:1075] -----
[bodyEncrypt:1079] -----
[bodyEncrypt:1070] -----
[bodyEncrypt:1075] -----
[bodyEncrypt:1079] -----
com.soulgame.sms.pay.MiGuPay$2.onResult(MiGuPay.java)[1]
com.soulgame.sms.SMSSdk$3.onFail(SMSSdk.java)[1]
com.soul.sdk.plugin.pay.PayProxy$4.onFail(PayProxy.java)[1]
com.soul.sdk.plugin.pay.PayParams.getPayPluginType(PayParams.java)[1]
com.soulgame.SoulSdk$4$1.onFail(SoulSdk.java)[1]
com.soulgame.sgsdkproject.sgtool.SGLog.w(SGLog.java)[1]
com.soulgame.sgsdkproject.sgtool.SGLog.w(SGLog.java)[1]
com.soulgame.SoulSdk.sendNotifyUnity(SoulSdk.java)[1]
com.soul.sdk.plugin.pay.PayParams.getPrice(PayParams.java)[1]
com.soul.sdk.plugin.pay.PayParams.getProductId(PayParams.java)[1]
com.soul.sdk.plugin.pay.PayParams.getPayType(PayParams.java)[1]
com.soulgame.SoulSdk.showPayTip(SoulSdk.java)[1]
com.soulgame.sgsdkproject.sgtool.ToastUtil.showShort(ToastUtil.java)[1]
com.soulgame.sgsdkproject.sgtool.ToastUtil.show(ToastUtil.java)[1]
com.soulgame.analytics.SGAgent.onPayEndEvent(SGAgent.java)[1]
com.soulgame.analytics.manager.CommonEventControl.getInstance(CommonEventControl.java)[1]
com.soulgame.analytics.manager.CommonEventControl.payEndEvent(CommonEventControl.java)[1]
com.soulgame.analytics.model.EventData.<init>(EventData.java)[1]
com.soulgame.analytics.model.EventData.setValue(EventData.java)[1]
com.soulgame.analytics.model.EventData.setProperties(EventData.java)[1]
com.soulgame.analytics.SGAgent.onEvent(SGAgent.java)[1]
com.soulgame.analytics.manager.HandleAction.onEvent(HandleAction.java)[1]
com.soulgame.analytics.model.EventData.getName(EventData.java)[1]
com.soulgame.analytics.model.EventData.updateTime(EventData.java)[1]
com.soulgame.analytics.manager.AppTools.getAppContext(AppTools.java)[1]
com.soulgame.analytics.manager.AppTools.getTimeInterval(AppTools.java)[1]
com.soulgame.analytics.utils.SharedUtil.getLong(SharedUtil.java)[1]
com.soulgame.analytics.manager.AppTools.getAuit(AppTools.java)[1]
(共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,只找到一处定义:
[Asm] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
.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:
[Asm] 纯文本查看 复制代码
1
2
3
4
5
6
com.soulgame.sms.pay.MiGuPay$2.onResult(MiGuPay.java)[1]
com.soulgame.sms.SMSSdk$3.onSuccess(SMSSdk.java)[1]
com.soul.sdk.plugin.pay.PayProxy$4.onSuccess(PayProxy.java)[1]
com.soulgame.SoulSdk$4$1.onSuccess(SoulSdk.java)[1]
com.soul.sdk.utils.SGDebug.print_d(SGDebug.java)[1]
>>> 返回到游戏端 :购买道具:[003] 成功!

同时试一试微信、和包、Mo9等其他支付方式,以及购买VIP,都成功了。那么我们的推断是正确的。

6.其实我们还有两点没有细致考虑。直接点取消的时候,onFail(int paramInt, String paramString, PayParams paramPayParams)的第二个、第三个参数是否是null。如果是null,要出错的;3个onFail是如何启动的。更细致的分析,是插入LOG(用androidkiller自带的就可以),把这两个参数打印出来,看看有没有值,是什么值;搞明白3个onFail究竟如何启动,会对程序运行逻辑有更清晰和准确的认识。

(未完,下楼继续)

免费评分

参与人数 10吾爱币 +12 热心值 +10 收起 理由
onihot + 1 + 1 感谢分享教程!
ihavebeenno + 1 + 1 我也看过charlessimonyi大神的文章,但就是不会怎么实际操作。谢谢楼主了
lwwjust + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
冥界3大法王 + 4 + 1 这么好的贴子不加精华,真是看走眼了~谢谢,我实验成功了!
不伦不类 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
玩世不攻 + 1 + 1 热心回复!
我住你家隔壁 + 1 + 1 谢谢@Thanks!
xwzj20170829 + 1 + 1 谢谢@Thanks!
bxm001 + 1 + 1 谢谢@Thanks!
chenjingyes + 1 + 1 谢谢@Thanks!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
 楼主| winding 发表于 2018-5-24 23:20 |楼主
本帖最后由 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脚本。找到这句代码:
[Asm] 纯文本查看 复制代码
1
if len(method_section[ii].strip())!= 0 and len(method_section[ii+1].strip()) == 0:

这句的意思是如果一行不为空且下一行为空,目的就是找到第一个空行,改成如下
[Asm] 纯文本查看 复制代码
1
if len(method_section[ii].strip())!= 0 and len(method_section[ii+1].strip()) == 0 and method_section[ii+2].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:
[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
com.iapppay.b.c.a(Unknown Source)[152]
com.iapppay.sdk.main.SdkMainBegsession.onPayResult(Unknown Source)[1]
com.iapppay.b.c.f(Unknown Source)[152]
com.iapppay.b.h.c(Unknown Source)[152]
com.chillyroomsdk.iapppay.MainActivity$4$1.onPayResult(MainActivity.java)[1]
com.iapppay.b.h.a(Unknown Source)[152]
com.chillyroomsdk.sdkbridge.pay.BasePayAgent.onPayCancel(BasePayAgent.java)[1]
com.chillyroomsdk.sdkbridge.order.OrderManager.onOrderCancel(OrderManager.java)[1]
use orig AudioTrack! mIsTimed=0 transferType=0 flags=4
com.chillyroomsdk.sdkbridge.order.HistoryOrderManager.getInstance(HistoryOrderManager.java)[1]
com.iapppay.e.a.a.a(Unknown Source)[152]
com.chillyroomsdk.sdkbridge.pay.BasePayAgent.onPayFail(BasePayAgent.java)[1]
requestCode:2,signvalue:,resultInfo:取消支付


com.chillyroomsdk.iapppay.MainActivity$4$1.onPayResult 简单看下JAVA伪代码,成功和失败参数差不多,成功的多了一个productId。关键的resultInfo参数没有往下传递,那么我们改switch 和IF语句吧。



回编测试,开LOG,提示支付成功,但马上校验订单,接着校验失败,没有购买成功,关LOG。还需要处理掉校验的逻辑。看LOG(LOG太多刷屏不好分析,我又把google等一些集成包里的调用去掉了):


[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
com.iapppay.ui.a.a.a(Unknown Source)[1]
com.iapppay.sdk.main.SdkMainBegsession.onPayResult(Unknown Source)[1]
com.chillyroomsdk.iapppay.MainActivity$4$1.onPayResult(MainActivity.java)[1]
com.iapppay.sdk.main.IAppPayOrderUtils.checkPayResult(Unknown Source)[1]
java.lang.Exception: denglibo Toast callstack! strTip=支付成功
com.chillyroomsdk.sdkbridge.pay.BasePayAgent.onPaySuccess(BasePayAgent.java)[1]
com.chillyroomsdk.sdkbridge.BasePlayerActivity.checkOrder(BasePlayerActivity.java)[1]
com.chillyroomsdk.sdkbridge.BasePlayerActivity$28.<init>(BasePlayerActivity.java)[1]
com.chillyroomsdk.sdkbridge.BasePlayerActivity$28.run(BasePlayerActivity.java)[1]
com.chillyroomsdk.sdkbridge.BasePlayerActivity.getOrderAgent(BasePlayerActivity.java)[1]
com.chillyroomsdk.iapppay.MainActivity$3.checkOrder(MainActivity.java)[1]
java.lang.Exception: denglibo show AlertDialog! title=Soul Knight
com.chillyroomsdk.iapppay.IAppPayOrderChecker.StartTaskOnMainThread(IAppPayOrderChecker.java)[1]
com.chillyroomsdk.iapppay.IAppPayOrderChecker$1.<init>(IAppPayOrderChecker.java)[1]
com.chillyroomsdk.sdkbridge.pay.record.PayRecordManager.sdkPurchaseSuccess(PayRecordManager.java)[1]
requestCode:2,signvalue:,resultInfo:取消支付
com.chillyroomsdk.iapppay.IAppPayOrderChecker$1.run(IAppPayOrderChecker.java)[269]
com.chillyroomsdk.iapppay.IAppPayOrderChecker$1$1.<init>(IAppPayOrderChecker.java)[269]
com.chillyroomsdk.iapppay.IAppPayOrderChecker$1$1.run(IAppPayOrderChecker.java)[1]
com.chillyroomsdk.iapppay.IAppPayOrderChecker.<init>(IAppPayOrderChecker.java)[1]
com.chillyroomsdk.iapppay.IAppPayOrderChecker.doInBackground(IAppPayOrderChecker.java)[173]


看到走了支付成功的一些方法,但校验没通过,所以金币没到手。先看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-24 23:19 |楼主
本帖最后由 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多行:

[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
com.morgoo.helper.MyProxy.isMethodDeclaredThrowable(MyProxy.java)[126]
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)[133]
[bodyEncrypt:1070] -----
[bodyEncrypt:1075] -----
[bodyEncrypt:1079] -----
com.morgoo.helper.MyProxy.isMethodDeclaredThrowable(MyProxy.java)[164]
com.morgoo.helper.MyProxy.isMethodDeclaredThrowable(MyProxy.java)[158]
[bodyEncrypt:1070] -----
[bodyEncrypt:1075] -----
[bodyEncrypt:1079] -----
com.ck.sdk.AndGameSDK$1.onResult(AndGameSDK.java)[1]
com.ck.sdk.utils.LogUtil.iT(LogUtil.java)[1]
com.ck.sdk.adapter.CKSDKAdapter.onCKPayFail(CKSDKAdapter.java)[1]
com.ck.sdk.CKSDK.getInstance(CKSDK.java)[1]
com.ck.sdk.CKSDK.isOnlineGame(CKSDK.java)[1]
com.ck.sdk.CKSDK.getInstance(CKSDK.java)[1]
com.ck.sdk.CKSDK.getContext(CKSDK.java)[1]
com.ck.sdk.utils.files.SPUtil.getInt(SPUtil.java)[1]
com.ck.sdk.PayParams.getPaySdk(PayParams.java)[1]
com.ck.sdk.database.CkEventTool.setPayFail(CkEventTool.java)[1]
com.ck.sdk.database.CkEventTool.getBaseEventBean(CkEventTool.java)[1]
com.ck.sdk.bean.CkEventBean.<init>(CkEventBean.java)[1]
com.ck.sdk.database.CkEventTool.setBaseEventData(CkEventTool.java)[1]
com.ck.sdk.utils.DeviceInfo.getImei(DeviceInfo.java)[1]
com.ck.sdk.utils.DeviceInfo.getIccid(DeviceInfo.java)[1]
com.ck.sdk.utils.DeviceInfo.getAndroid_id(DeviceInfo.java)[1]
com.morgoo.helper.MyProxy.isMethodDeclaredThrowable(MyProxy.java)[161]
com.ck.sdk.CKSDK.getInstance(CKSDK.java)[1]
com.ck.sdk.CKSDK.getCKAppID(CKSDK.java)[1]
com.ck.sdk.SDKParams.contains(SDKParams.java)[1]
com.ck.sdk.SDKParams.getInt(SDKParams.java)[1]
com.ck.sdk.SDKParams.getString(SDKParams.java)[1]
(中间略)
com.feamber.isp.SmsIAPListener.onResult(SmsIAPListener.java)[1]
pay falied code 11msg pay cancel
com.racergame.cityracing3d.GameActivity.dismissProgressDialog(GameActivity.java)[1]
com.ck.sdk.utils.net.SubmitExtraDataUtil.submitOrSaveData(SubmitExtraDataUtil.java)[1]
com.ck.sdk.CKSDK.getInstance(CKSDK.java)[1]
com.ck.sdk.CKSDK.isOnlineGame(CKSDK.java)[1]
com.ck.sdk.CKSDK.getInstance(CKSDK.java)[1]
com.ck.sdk.CKSDK.getContext(CKSDK.java)[1]
com.ck.sdk.utils.files.SPUtil.getInt(SPUtil.java)[1]
com.ck.sdk.plugin.CKAppEvents.getInstance(CKAppEvents.java)[1]
com.ck.sdk.plugin.CKAppEvents.payFail(CKAppEvents.java)[1]
com.ck.sdk.plugin.CKAppEvents.isNullPlugin(CKAppEvents.java)[1]
com.ck.sdk.utils.LogUtil.iT(LogUtil.java)[1]
com.ck.sdk.AndGameSDK.dealPayResult(AndGameSDK.java)[1]
Skipped 82 frames! The application may be doing too much work on its main thread.
(其余略)


简单分析,推断com.ck.sdk.AndGameSDK$1.onResult(AndGameSDK.java)[1]是关键方法,com.ck.sdk.adapter.CKSDKAdapter.onCKPayFail(CKSDKAdapter.java)[1]和com.ck.sdk.database.CkEventTool.setPayFail(CkEventTool.java)[1]是失败的分支逻辑。我们还看到,顺着LOG再往下找,还有com.feamber.isp.SmsIAPListener.onResult(SmsIAPListener.java)[1]等方法,也可以作为破解点尝试。我们选择最早的那个。
  
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的时机就要早一些了)。
[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
不修改:
(前略)
com.ck.sdk.plugin.CKPay$1.run(CKPay.java)[1]
com.ck.sdk.CKSDK.getInstance(CKSDK.java)[1]
com.ck.sdk.CKSDK.getContext(CKSDK.java)[1]
com.ck.sdk.utils.files.SPUtil.getInt(SPUtil.java)[1]
com.ck.sdk.plugin.CKPay.checkHaveUpdate(CKPay.java)[1]
com.ck.sdk.CKSDK.getInstance(CKSDK.java)[1]
com.ck.sdk.CKSDK.getContext(CKSDK.java)[1]
com.ck.sdk.utils.files.SPUtil.getString(SPUtil.java)[1]
(后略)


[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
照楼主修改后:
(前略)
com.ck.sdk.plugin.CKPay$1.run(CKPay.java)[1]
com.ck.sdk.plugin.CKPay.checkPayResultTest(CKPay.java)[1]
com.ck.sdk.CKSDK.getInstance(CKSDK.java)[1]
com.ck.sdk.CKSDK.getContext(CKSDK.java)[1]
com.ck.sdk.PayParams.getProductId(PayParams.java)[1]
com.ck.sdk.PayParams.getPrice(PayParams.java)[1]
com.ck.sdk.PayParams.getProductName(PayParams.java)[1]
(后略)


大体对比一下,可以看到,修改后主要的不同是checkPayResultTest运行了(对于一些复杂的app,插件多、逻辑复杂,只好用人工简单判断的方式;对于简单app,可以把修改前后的LOG分别存档为txt文件,然后使用beyond compare比对)。这个方法可疑,看一下这个方法,根据里面的字符判断这个就是那个支付调试提示框。看一下run方法的smali文件

[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
.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修改是无用的,起作用的只有第一个。另一问也是同理,略。

(未完,下楼继续)

点评

这篇文章写得太好了,配个录像该给精华!加2000CB!  发表于 2018-12-15 00:01
4#
小草莓种植员 发表于 2018-5-24 23:39
5#
chenjingyes 发表于 2018-5-25 00:25
这很不错哦,用这个方法可以很快找到破解点
6#
wisly 发表于 2018-5-25 00:35
收藏学习了,感谢!
7#
yssun 发表于 2018-5-25 09:17
666,
收藏学习了
8#
hpy1994 发表于 2018-5-25 10:09
收藏学习了
头像被屏蔽
9#
w5645060 发表于 2018-5-25 10:30
提示: 作者被禁止或删除 内容自动屏蔽
10#
 楼主| winding 发表于 2018-5-25 12:34 |楼主
w5645060 发表于 2018-5-25 10:30
InjectLog有时候会出错,楼主遇到过没有,比如中文。

能不能提供个样本。

injectlog本身不涉及中文,如果因为中文出错,可能是使用的apktool版本的问题。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-5-31 04:25

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表