吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 30883|回复: 97
收起左侧

[Android 原创] 小白的《宾果消消消》某定制版逆向笔记

[复制链接]
winding 发表于 2018-3-17 16:24
本帖最后由 winding 于 2018-3-17 17:58 编辑


这是来论坛的第2篇实践笔记主题帖,记录学习过程中的一些零散想法。有感身为小白,学习过程中经常遇到困惑的经历,尽量把自己遇到的问题以及尝试的思路办法说详细一些,所以可能啰嗦琐碎,不成体系。涉及的内容也主要是smali的静态分析,有一点log的内容,初始入门级别,没有技术含量,写出来一为自己记录,二与众多小白共勉。作为小白,涉及的一些内容猜测居多,有的可能说得不对,请自行甄别查证;如有牛牛路过欢迎斧正。

这次的样本是《宾果消消消》OPPO定制版V5.2.0版本。为毛用定制版?第一次下载下来是这个版本,就它了,没挑剔;也想通过与原版的对比多琢磨点东西。论坛上有很多《宾果消消消》逆向成品,倒还没有贴过程的,就把这次学习的过程贴出来了。

相关链接 链接:https://pan.baidu.com/s/1O5iq93pS60qyZRiljLJlmQ 密码:j145   只放了原版样本,成品没放,其实只改了com.nearme.game.sdk.a$8.handleMessage一处,自己改吧;injectlog工具放进去了,原作者不是偶。

一、初识
先扔模拟器和andriodkiller里跑一圈,掌握基本信息。

样本未加壳,有混淆;cocos2工程,lib文件夹下有libBugly.so和libcocos2dlua.so文件,未发现大量lua脚本;assets文件夹下内容丰富,其中assets\china和assets\res下有大量未加密的图片和.json脚本文件,感觉可以自己diy一个版本了,assets\nearme下有oppo_game_service_200604.so文件。so文件就3个,jar文件没有(用windows的文件夹搜索后缀名)。

好像第一次有开屏广告,基本不出现;有内购,微信和支付宝两个渠道;app自带一些LOG信息。

隐藏了一个活动com.mfp.jelly.oppo.wxapi.WXPayEntryActivity,灰色的,不知道藏哪里了。

01.jpg

这里就遇到了小白经常疑惑的第一个问题。疑惑一:灰色的、打不开的那些活动,哪里去了?我也很疑惑,下文讨论。




二、动手

(一)去广告

这个版本的开屏广告很好说话,基本不出现。。。都没有逆向的必要。但既然有,还是看一看,大体说下。

程序入口是OppoAdSplashActivity,看名字就在这里了。尝试过一些app,总的来说,去除开屏广告很简单的,基本都在application类或者入口类启动。换位思考,程序猿设计时考虑的都是自己的程序主体,开屏广告大多是在程序主体完工后,后期加上的,与程序主体衔接多数不紧密。对于一些小型的程序,甚至直接把程序入口从原来的入口改成程序主界面即可,不影响程序运转;大型程序设计比较严密,会在入口类初始化主界面的一些东西,或者加了检测,就需要分析修改了。

02.jpg 03.jpg 04.jpg

用《当前activity》这个小程序观察启动过程,发现还经过了不少跳转才到真正的主界面jellyactivity。大型软件需要初始化的东西多,就不尝试直接改入口了,肯定是失败的,从OppoAdSplashActivity开始分析java代码。

因为插入的这些开屏广告,无论如何,肯定都有启动主界面和关闭自身的动作,分析的时候关注startActivity和finish这两个函数(以及类似函数)以及oncreate函数,很简单就能梳理出基本流程。如果不行,再回过头去找application类。

05.jpg
06.jpg


通过startActivity(启动其他活动的)和finish(结束自身的)两个函数定位,可以大体看出样本的启动流程,OppoAdSplashActivity.oncreate>3处startActivity>AppSplashActivity.oncreate>JellyBaseSplashActivity.oncreate>JellyBaseSplashActivity.startGameActivity启动主界面。其中AppSplashActivity.oncreate中是通过调用父类super。。。的方式启动basesplash的。

关键流程是这样,细节的就可以逐一确定了,这里就不详细分析了。修改的话,老办法修改跳转;或者直接添加对startActivity和finish两个函数的调用,如可以直接把OppoAdSplashActivity中next方法中startActivity和finish的smali语句,直接复制到OppoAdSplashActivity的oncreate方法里。需要注意的问题就是插入代码的位置,该做的初始化还得让它做了。因为样本老是不出广告,就不试验贴图了。






(二)破解内购

因为看到有支付宝渠道,就从这里入手。
尝试1:上来三板斧,9000,0x2328,onbilling等关键字定位。

支付宝最典型特征找到一处,在Lcom/microfun/onesdk/purchase/c;中

07.jpg

改成这样试试
[Asm] 纯文本查看 复制代码
    .sparse-switch
        0x1771 -> :sswitch_0
        0x1f40 -> :sswitch_0
        0x2328 -> :sswitch_0


试验无果,失败了。这里遇到小白经常疑惑的第二个问题。疑惑二:为什么照着教程做的修改不好用?为什么同样的方法,有的时候好用有的时候不好用?

为什么会失败呢?就这次尝试而言,我们并没有分析具体逻辑。看一下关键的java代码:
[Java] 纯文本查看 复制代码
        l();
        localPurchaseResult.setState(PurchaseState.Success);
        continue;
        localPurchaseResult.setState(PurchaseState.Purchasing);
        continue;
        localPurchaseResult.setState(PurchaseState.Cancel);


可以看到,成功失败和取消3种情况,只是设定了PurchaseResult的支付状态字段,没有任何增加物品的实际操作;成功里还额外执行了 l(),简单追一下代码

[Java] 纯文本查看 复制代码
  protected void l()
  {
    a("", "CNY", this.m);
  }

  protected void a(String paramString1, String paramString2, String paramString3)
  {
    JSONObject localJSONObject = new JSONObject();
    try
    {
      localJSONObject.put("userid", this.n);
      localJSONObject.put("level", this.o);
      localJSONObject.put("branch", this.p);
      localJSONObject.put("currency", paramString2);
      localJSONObject.put("revenue", paramString3);
      localJSONObject.put("amount", 1);
      localJSONObject.put("order_id", this.j);
      localJSONObject.put("platform_order_id", paramString1);
      localJSONObject.put("purchase_channel", this.g.getChannel());
      localJSONObject.put("itemid", this.h);
      BIManager.getInstance().recordPurchaseLog(localJSONObject);
      return;
    }
    catch (JSONException paramString1)
    {
      for (;;)
      {
        paramString1.printStackTrace();
      }
    }


看到是构建json对象,然后形成交易记录。那么我们修改的这个地方,并不是最终增加物品的位置,只是中间一个环节,还不一定是必经的环节。

疑惑二解答(讨论):
修改失败有几种可能性。
(1)是不是阻断了程序的原有逻辑。本例来说,只修改了state一个值,我们知道支付宝接口信息传递中还有状态码,还有状态字符串,我们只修改了state,如果支付失败的状态码和状态字符串都照常传递下去,那么的确有可能失败。解决的办法就是顺着本例中锁定的位置,继续分析上下文联系,比如可以发现与Lcom/microfun/onesdk/purchase/c;同目录下还有PurchaseState、PurchaseResult、PurchaseListener等等文件,可以看一下有没有状态码状态字符串,一并改了。这是第一个想到的,真这样做的话也是最累的,因为还有其他可能。
(2)程序是不是只有一种支付逻辑。对于小程序来说,一个渠道一般只有一种逻辑;但大型程序很可能不止一种。原因,一个是加了混淆,加了好多无用的支付代码和逻辑,实际起作用的只有一条路径;另一个是程序正常开发中,难免集成好多sdk,里面都有某渠道(如这里是支付宝)的支付逻辑。本例是oppo定制的,那么程序原来肯定有一种支付逻辑,还有可能加入oppo定制的逻辑,一些统计用的sdk如U盟也同时集成支付逻辑。所以我们需要先判断,我们修改的地方,是不是程序真正执行的那条逻辑,如果不是,累死也白搭。
(3)支付渠道有没有搞错。这个如果搞错了,撞墙吧。。。

对于本例如何解决呢?我们倒着分析,(3)没问题,看(2)。查一下LOG。

08.jpg

看到有一个o_a[133]::支付回调游戏SDK | GC201803170930493800100190000 ,是判断完支付结果回调,后面GC。。。。应该是订单号。这条LOG前(图片上部)、后(图片下部)各有一条LOG,里面regcode=1004,regmsg=支付失败。想到两点,一是我们修改的state不是那么关键,不是关键信息点,二是我们锁定的支付逻辑,很大可能并不是执行的这一条,我们用的特征码是9000。

那么我们需要确定真正执行的支付逻辑。第一个想到的是特征码1004搜索,估计很多,不去试了;第二个想到的是,有好多类似o_a::等的输出LOG,通过LOG关键字不就可以确定关键位置了么?搜一下LOG的位置,没找到。

又一个疑惑冒出来了。疑惑三:为什么看到有LOG输出,在工程里搜不到呢?

尝试2:疑惑先放着,先解决眼前的问题。怎么确定真正的逻辑呢?这么大的游戏,找了找各种关键字一大堆,一点点分析得累死,决定用最流氓、最一劳永逸的办法,把程序所有执行的方法用LOG打印出来!
具体办法借用的这篇帖子里的http://blog.csdn.net/charlessimonyi/article/details/52027563Android应用逆向——分析反编译代码之大神器 作者:charlessimonyi大体思路是插入一个smali文件,用python遍历所有的smali文件,插入对它的调用,把所有调用它、也就是执行过的方法名打印出来。具体思路请看原贴,实践操作里有点坑,文后介绍。

操作中,我们的目标是把所有执行过的方法打印出来,所有没打印出来的方法,我们能确定它没执行过

09.jpg 10.jpg

执行一圈遛一遛。。。

11.jpg

找到上次发现的那个回调的地方,上图显示的回调前的信息,我们从回调处往下分析

[Asm] 纯文本查看 复制代码
o_a[133]::支付回调游戏SDK | GC201803171120128420100140000
onEventInTime error is 0
onEventInTime error is 0
notifyGameSdkPayResult[235]::notify game
com.nearme.plugin.framework.LogUtils.log(LogUtils.java)[1]
com.nearme.plugin.framework.LogUtils.log(LogUtils.java)[1]
com.nearme.game.sdk.common.model.biz.ReportParam.<init>(ReportParam.java)[1]
com.nearme.game.sdk.a$8.handleMessage(GCInternal.java)[1]
com.nearme.game.sdk.common.util.LongSparseArray.get(LongSparseArray.java)[1]
com.nearme.game.sdk.common.util.LongSparseArray.get(LongSparseArray.java)[1]
com.nearme.game.sdk.common.util.LongSparseArray$ContainerHelpers.binarySearch(LongSparseArray.java)[1]
com.nearme.game.sdk.GCInternalImpl$3.onFailure(GCInternalImpl.java)[1]
com.microfun.onesdk.purchase.q$1.onFailure(Unknown Source)[1]


很容易锁定com.nearme.game.sdk.a$8.handleMessage就是处理支付组件返回信息的最初关键点。这里是JAVA层接收支付组件返回信息的最初连接点,处理这里的话之后再复杂的逻辑也不用分析,不用去管它了。(为什么这么断定,因为所有在smali文件夹下的smali文件我们都插入了LOG,而回调LOG的TAG不是我们的TAG:injectlog,所以这里就是支付结果返回smali文件夹的最初入口)如果支付失败,分别调用com.nearme.game.sdk.GCInternalImpl$3.onFailure和com.microfun.onesdk.purchase.q$1.onFailure两个支付失败的方法。看a$8.handleMessage的内容

12.jpg

看到有ApiResult;和ApiCallback;两个接口,这个是消息处理类,是一定有的。显眼的一个判断语句,1001与ApiResult实例resultCode的比较。到ApiResult中查一下,

13.jpg

1001代表成功。那么逻辑就很清楚了,如果ApiResult实例的resultCode不等于1001,支付失败,执行paramMessage.onFailure(localApiResult.resultMsg, localApiResult.resultCode);传达失败消息,2个参数是resultMsg和resultCode。如果等于,执行paramMessage.onSuccess(localApiResult.resultMsg);传递成功消息,参数resultMsg。看一下com.nearme.game.sdk.GCInternalImpl$3.onFailure和com.microfun.onesdk.purchase.q$1.onFailure,的确同时也有onsuccess方法,参数也对,o了。

那么动手修改smali
找到关键代码

[Asm] 纯文本查看 复制代码
    .line 442
    const/16 v2, 0x3e9
iget v3, v0, Lcom/nearme/game/sdk/common/model/ApiResult;->resultCode:I
if-ne v2, v3, :cond_3
.line 443
    iget-object v2, v0, Lcom/nearme/game/sdk/common/model/ApiResult;->resultMsg:Ljava/lang/String;
invoke-interface {v1, v2}, Lcom/nearme/game/sdk/callback/ApiCallback;->onSuccess(Ljava/lang/String;)V


这段代码之前还有一大段是判断信息有效性的,之前的LOG里我们看到已经传入信息了,所以前面这段不用考虑,有效性是没问题的。动手改的话,一个要改if-ne v2, v3, :cond_3,直接删掉就可以,另一个要给V2赋值。这里V2的值必须改,因为在前面的LOG里我们看到retmsg=支付取消(前边贴图里有),直接把这个值赋值给onSuccess肯定得出毛病。

resultMsg的类型是Ljava/lang/String;是字符对象,onSuccess(Ljava/lang/String;)的参数也是字符对象。怎么改这个参数偶还卡了一阵子,经人提示,琢磨了一阵子,琢磨出怎么改了,改成下面的样子:

[Asm] 纯文本查看 复制代码
    .line 442
iget v3, v0, Lcom/nearme/game/sdk/common/model/ApiResult;->resultCode:I
new-instance v2, Ljava/lang/StringBuilder;
const-string v4, "\u652f\u4ed8\u6210\u529f"
invoke-direct {v2, v4}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
.line 443
    #iget-object v2, v0, Lcom/nearme/game/sdk/common/model/ApiResult;->resultMsg:Ljava/lang/String;
invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v2
invoke-interface {v1, v2}, Lcom/nearme/game/sdk/callback/ApiCallback;->onSuccess(Ljava/lang/String;)V


iget v3, v0, Lcom/nearme/game/sdk/common/model/ApiResult;->resultCode:I这句留着,没仔细看别的地方用不用这个值,取不到的话怕出错;v2不用给他赋值比较了,用这个寄存器新建一个字符串实例new-instance v2, Ljava/lang/StringBuilder;,增加一个v4寄存器,赋值为字符串“支付成功”,对应支付失败猜的,然后把字符串实例初始化,然后调用toString()方法返回Ljava/lang/String;的类型,最后传给onSuccess
我们增加了一个寄存器V4,所以别忘了改方法的声明
[Asm] 纯文本查看 复制代码
    .locals 4  改成 
   .locals 5


跑一圈测试一下

14.jpg

成功了,嘻嘻,出名游戏也不是这么可怕么,嘿嘿。看成功的LOG

15.jpg

果然走向成功了。其实试一下,微信支付也同时破解了,那么我们就明白了,为什么传递参数不是CODE,而是“支付成功”字符串,为什么要同时传递给两个onSuccess或是两个onFailure,大概。。。吧

另外,无论成功还是失败的LOG里,都没有发现一开始定位的那个b方法,所以我们改它没用。

(三)存档问题的尝试

本来到这里就结束了,结果找朋友真机测试悲剧了。因为发现这个游戏卸载掉重装的时候,不绑定账号,也会提示已经存在游戏数据,是否恢复(包括关卡、金砖等等),就放心干了。朋友的已经玩到700多关了,拿来测试了一下,结果有个检测手机权限的提示被我否了,结果。。。破解是测试成功了,但是700多关的记录没了,哭死。

没办法,硬着头皮试试能不能改存档吧,寄希望于关卡信息不敏感,没加密。

以下是分析的过程。(1)卸载掉重装提示有数据,说明是网络存档或者本地游戏文件夹以外的地方有记录的存档,游戏卸掉了,存档还在,各种折腾,没找到;(2)拔掉网线,郁闷,不能恢复数据,看来是网络存档,可是抓包实在没发现啥线索,只有一丁点加密数据有可疑,但是不懂加密,无果;(3)尝试利用LOG锁定游戏第一次启动时恢复数据提示的位置,无奈这个时候程序需要复制各类文件,操作太多,COCOS又不懂,放弃;(4)尝试修改现有存档,位置在/data/data/cmo.mfp.jelly.oppo/files下面(咋发现的?逐一找的,db、xml、dat以及其他能用文本文件打开的,都不放过),在断网的情况下进行游戏,会产生bi_cache_play.dat、bi_cache_tutorial.dat、cache.dat以及其他几个dat文件。前两个是记录断网时玩过的关卡记录,包括关卡号、分数等信息。联网后部分文件被删除。尝试修改,不成功,考虑是cache.dat和其他几个dat文件里还有相应记录,但是这几个文件乱码,不知道是什么格式的,放弃了。cache.dat文件的乱码,看着跟AndroidManifest.xml文件很像,无奈AndroidManifest.xml格式不懂。
最后分析,应该是以手机iemi为标识,在服务器存档。

如果有牛牛路过,能指点一下破解存档,感激不尽。

最后实在没办法,想起来assets\res下有很多脚本,改关卡吧。assets\res\Level文件夹下都是各关卡的配置文件,其中map001.json到map3110.json是普通地图,还有其他特殊地图。一开始就看到了,是明文的,随便打开一个:
(好像超长了,二楼继续)

免费评分

参与人数 15威望 +1 吾爱币 +23 热心值 +15 收起 理由
我就是王八蛋 + 1 + 1 我很赞同!
幽客楚楠 + 1 + 1 谢谢@Thanks!
daemon224 + 1 + 1 好文!
大傻瓜 + 1 热心回复!
qtfreet00 + 1 + 9 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
mogoyu + 1 + 1 谢谢@Thanks!
认定666 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
就是这么帅 + 1 + 1 谢谢@Thanks!
pro713 + 1 + 1 用心讨论,共获提升!
tail88 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
审判者压缩 + 1 + 1 热心回复!
夏雨微凉 + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
王娜娜 + 1 + 1 谢谢@Thanks!
xwzj20170829 + 1 + 1 谢谢@Thanks!
williamxia + 1 + 1 用心讨论,共获提升!

查看全部评分

本帖被以下淘专辑推荐:

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

linuxprobe 发表于 2018-3-18 11:01
笔记写的很多,没耐心看,况且也不懂编程,看起来很无趣。
 楼主| winding 发表于 2018-4-2 16:06
昨夜星辰2012 发表于 2018-4-2 13:54
能详细介绍下打印LOG的代码具体怎样使用吗?

1.具体实现的原理,可以看偶贴出来的那个大神的博客原贴。

2.实际使用,偶已经做了简化,做成了一个批处理,应该是比较简单了。
具体步骤:
a.安装python3及以上版本,并添加系统变量,具体操作请百度。检验:在cmd输入“python”,可以成功显示python版本进入python命令行。
b.把偶提供的InjectLog.bat、InjectLog.smali、smalihook.py三个文件,放到andriodkiller根目录,把InjectLog.bat设置成andriodkiller自定义工具。
完成。使用的时候,在andriodkiller自定义工具启动工具,按提示操作即可。

免费评分

参与人数 1吾爱币 +2 热心值 +1 收起 理由
昨夜星辰2012 + 2 + 1 谢谢大神的热心回复!

查看全部评分

 楼主| winding 发表于 2018-3-17 16:25
本帖最后由 winding 于 2018-3-17 17:08 编辑

(接一楼)

随便打开一个看看

[JavaScript] 纯文本查看 复制代码
{
"version":27,
    "tile":[["11","11","11","0","0","0","11","11","11"],
            ["11","0","0","0","0","0","0","0","11"],
            ["11","0","0","0","0","0","0","0","11"],
            ["0","0","0","11","0","11","0","0","0"],
            ["0","0","0","0","0","0","0","0","0"],
            ["0","0","0","11","0","11","0","0","0"],
            ["11","0","0","0","0","0","0","0","11"],
            ["11","0","0","0","0","0","0","0","11"],
            ["11","11","11","0","0","0","11","11","11"]],
    "items":[["0","0","0","0","0","0","0","0","0"],
             ["0","0","0","0","0","0","0","0","0"],
             ["0","0","0","0","0","0","0","0","0"],
             ["0","0","0","0","22","0","0","0","0"],
             ["0","0","0","22","23","22","0","0","0"],
             ["0","0","0","0","22","0","0","0","0"],
             ["0","0","0","0","0","0","0","0","0"],
             ["0","0","0","0","0","0","0","0","0"],
             ["0","0","0","0","0","0","0","0","0"]],
        "clay":[["0","0","0","2","2","2","0","0","0"],
             ["0","2","2","2","2","2","2","2","0"],
             ["0","2","2","2","2","2","2","2","0"],
             ["2","2","2","0","2","0","2","2","2"],
             ["2","2","2","2","2","2","2","2","2"],
             ["2","2","2","0","2","0","2","2","2"],
             ["0","2","2","2","2","2","2","2","0"],
             ["0","2","2","2","2","2","2","2","0"],
             ["0","0","0","2","2","2","0","0","0"]],                 
    "stepLimit":"26",
    "targets":[{"type":"3","code":"1","num":"57"}],
    "ruleDefinitions":
    {
        "Initialization":[{"code":"1","weight":"10"},{"code":"501","weight":"5"},{"code":"4","weight":"10"},
                          {"code":"5","weight":"10"},{"code":"6","weight":"10"}],
        "Rule1":[{"code":"1","weight":"10"}],
                "Rule2":[{"code":"1","weight":"10"},{"code":"2","weight":"10"},{"code":"3","weight":"10"},
                 {"code":"5","weight":"10"},{"code":"6","weight":"10"}],
        "SweetTimeRule":[{"code":"2","weight":"10"},{"code":"3","weight":"10"},{"code":"1","weight":"10"},
                 {"code":"4","weight":"10"},{"code":"5","weight":"10"},{"code":"6","weight":"10"}]    
    },
    "spawnTiles":
    [
    {"x":"0","y":"3","rule":"Initialization"},{"x":"1","y":"1","rule":"Initialization"},{"x":"2","y":"1","rule":"Initialization"},
    {"x":"3","y":"0","rule":"Initialization"},{"x":"4","y":"0","rule":"Initialization"},{"x":"5","y":"0","rule":"Initialization"},
    {"x":"6","y":"1","rule":"Initialization"},{"x":"7","y":"1","rule":"Initialization"},{"x":"8","y":"3","rule":"Initialization"}
    ],
    "playMode":"0",
    "hitPoints":"30",
        "starScoresNew":["29720","40407","43880"],
        "restStepScoreNew":"3000"
}


"playMode"是游戏模式,0的代表普通消除关卡,"targets":[{"type":"3","code":"1","num":"57"}],这个是目标,正则替换就好了,保留一种类型,数量num=1,或者直接num=0。playMode=1是打到女巫模式,"witchHitPoints":"120",是女巫血量(这时targets表示会对女巫产生打击的种类和点数),改成0。好了,打开即通关,感觉偶已经无敌了。。。。吼吼吼。。。。朋友还需要把前700关逐一点开一遍。。。咳咳咳

(四)账号绑定

游戏里有绑定微信的功能,点击提示只有oppo游戏中心版本可以绑定,可能有检测。试验的时候,先绑定正版,卸载正版再绑定逆向版,竟然成功了,顿时没有逆向的欲望了。BABY,拿着破解版去朋友圈争锋吧,你是无敌的,嘿嘿嘿。

三、扩展

(一)未回答的疑惑


疑惑一的解答(讨论):androidkiller反编出来活动灰色点不开,几种情况。(1)加壳了,先检查有无壳;(2)多个dex,灰色点不开的到第二个第三个smali文件夹里找找;(3)在jar包里,曾经碰到过;(4)在伪装的jar、so文件里,后缀名不一定准确,有时候故意改了;(5)其他地方,本例中的在哪里偶也没找到,或者用文件管理器到模拟器安装目录下找找?

疑惑三的解答(讨论):有时候app保留的LOG,都在一些包里,跟疑惑二类似。本例中,类似o_a等的LOG输出,有时跟nearme牵扯着,可搜不到,nearme包下也没有。想起来assets有个nearme文件夹,下边有个oppo_game_service_200604.so。拖到ida里,竟然直接弹出这个

16.jpg

分明是个apk文件,改文件名,拖进andiodkiller里

17.jpg

果然在这里。那么,这里也可以搞事情了,嘿嘿,我就不测试了。

(二)关于injectlog的使用

强调一下偶是借用,这是别人的成果,原贴在一楼。偶主要和同是小白的坛友们说一下一些坑和改进。

A.首先,需要自己编译和反编,得到InjectLog.smali。原贴没提供,这个是偶反编的。
[Asm] 纯文本查看 复制代码
.class public Lcom/hook/testsmali/InjectLog;
.super Ljava/lang/Object;
.source "InjectLog.java"
# direct methods
.method public constructor <init>()V
    .locals 0
.prologue
    .line 3
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method public static PrintFunc()V
    .locals 6

    .prologue
    .line 7
    invoke-static {}, Ljava/lang/Thread;->currentThread()Ljava/lang/Thread;

    move-result-object v0

    .line 8
    .local v0, "cur_thread":Ljava/lang/Thread;
    invoke-virtual {v0}, Ljava/lang/Thread;->getStackTrace()[Ljava/lang/StackTraceElement;

    move-result-object v1

    .line 9
    .local v1, "stack":[Ljava/lang/StackTraceElement;
    const-string v2, "InjectLog"

    new-instance v3, Ljava/lang/StringBuilder;

    invoke-direct {v3}, Ljava/lang/StringBuilder;-><init>()V

    const/4 v4, 0x3

    aget-object v4, v1, v4

    invoke-virtual {v4}, Ljava/lang/StackTraceElement;->toString()Ljava/lang/String;

    move-result-object v4

    invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v3

    const-string v4, "["

    invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v3

    invoke-virtual {v0}, Ljava/lang/Thread;->getId()J

    move-result-wide v4

    invoke-virtual {v3, v4, v5}, Ljava/lang/StringBuilder;->append(J)Ljava/lang/StringBuilder;

    move-result-object v3

    const-string v4, "]"

    invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v3

    invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v3

    invoke-static {v2, v3}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

    .line 10
    return-void
.end method


B.用python把对以上print方法的调用插进每个smali方法中,原贴作者提供了py代码,原代码偶就不贴了。里面几个坑:python要用3.0以上版本,不然clear()方法不支持;编码统一为UTF8,并去掉汉字,不懂编码咋这么乱;如果自己修改py文件,不要用tab键,用空格;原作者代码中遇到没有.prologue的错误处理,会中断执行,把错误处理注释掉。

C.把InjectLog.smali文件保存到相应位置。

D.每次操作都麻烦,偶凑了个批处理,作为androidkiller的自定义工具。
[Asm] 纯文本查看 复制代码
@echo off

color 0A
echo                ===========injectlog===========
:start
cls

set current_dir=%~dp0
pushd %current_dir%

:start
echo.请输入要注入LOG的逆向工程名称(对于ak,是apk 的文件名)
set /p inputgc=

if not exist .\projects\%inputgc%\ (
echo "工程文件夹不存在" 
goto start
 )

xcopy smalihook.py /y .\projects\%inputgc%\Project\smali\
set do_dir=.\projects\%inputgc%\Project\smali\
pushd %do_dir%

call python.exe smalihook.py
del smalihook.py

pushd %current_dir%

xcopy InjectLog.smali /y .\projects\%inputgc%\Project\smali\com\hook\testsmali\


if exist .\projects\%inputgc%\Project\smali_classes2\ (
        echo "存在dex2,继续处理" 
        rem goto starttwo

 ) else (
        goto done
)

:done
echo "已处理完毕" 
pause
exit

:starttwo
xcopy smalihook.py /y .\projects\%inputgc%\Project\smali_classes2\
set did_dir=.\projects\%inputgc%\Project\smali_classes2\
pushd %did_dir%
call python.exe smalihook.py
del smalihook.py
pushd %current_dir%
xcopy InjectLog.smali /y .\projects\%inputgc%\Project\smali_classes2\com\hook\testsmali\
goto done


是个半成品,对多dex的处理没弄完。

E.原作者的py脚本实际使用的时候还有些问题,做了一些改进。

[Asm] 纯文本查看 复制代码
import os  
  
class ParserError(Exception):  
    pass  
  
#   
def inject_code_to_method_section(method_section):  
    #   
    if method_section[0].find("static constructor") != -1:  
        return method_section  
    #   
    if method_section[0].find("synthetic") != -1:  
        return method_section  
    #   
    if method_section[0].find("abstract") != -1:  
        return method_section  
    #   
    inject_code = [  
        '\n',  
        '    invoke-static {}, Lcom/hook/testsmali/InjectLog;->PrintFunc()V\n',  
        '\n'  
    ]
    # 
    for i in range(0, len(method_section)):  
        if method_section[i].find(".prologue") != -1:  
            method_section[i + 1: i + 1] = inject_code
            return method_section
    #
    for ii in range(0, len(method_section)-1):
        if len(method_section[ii].strip())!= 0 and len(method_section[ii+1].strip()) == 0 :   
            method_section[ii + 1: ii + 1] = inject_code
            return method_section
    return method_section  
  
  
def inject_log_code(content):  
    new_content = []  
    method_section = []  
    is_method_begin = False  
    for line in content:  
        if line[:7] == ".method":  
            is_method_begin = True  
            method_section.append(line)  
            continue  
        if is_method_begin:  
            method_section.append(line)  
        else:  
            new_content.append(line)  
        if line[:11] == ".end method":  
            if not is_method_begin:  
                raise ParserError(".method error")  
            is_method_begin = False  
            new_method_section = inject_code_to_method_section(method_section)  
            new_content.extend(new_method_section)  
            method_section.clear()  
    return new_content  
  
  
def main():  
    walker = os.walk("./")  
    for root, directory, files in walker:  
        for file_name in files:  
            if file_name[-6:] != ".smali" or file_name[:5] == "Cocos":  
                continue  
            file_path = root + "/" + file_name  
            print(file_path)  
            file = open(file_path,'r',encoding='UTF-8')  
            lines = file.readlines()  
            file.close()  
            new_code = inject_log_code(lines)  
            file = open(file_path, "w")  
            file.writelines(new_code)  
            file.close()  
  
  
if __name__ == '__main__':  
    main()  


主要是1.跳过cocos2文件,应该这些东西即使不做任何操作,也经常调用,LOG会晃眼;2.脚本有的app会做处理,没有.prologue,本例就是这样,这种情况下会原作者的py脚本会跳过去,那么这个方法是否执行就不知道了,如果不能保证100%知道程序执行逻辑,这个神器的作用就打折扣了。偶改了下逻辑判断,如果存在.prologue就插在.prologue后第一行,如果不存在.prologue,就从方法开始,插到第一个空行。smali文件中方法的头部信息,和正式代码之间,都存在一个空行,嘿嘿。








免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
mmtzwyd + 1 + 1 我很赞同!

查看全部评分

mmji 发表于 2018-3-17 16:36
占楼等更新~
小兴818 发表于 2018-3-17 16:37 来自手机
好复杂啊,感谢楼主分享~
haodana 发表于 2018-3-17 16:41
如果有牛牛路过,能指点一下破解存档,感激不尽。
gunxsword 发表于 2018-3-17 16:44
感谢分享,抽空细细读一下!
ks521 发表于 2018-3-17 16:48
楼主好6,过来学习下
头像被屏蔽
司徒浩 发表于 2018-3-17 16:52
提示: 作者被禁止或删除 内容自动屏蔽
伍六七 发表于 2018-3-17 16:56
学习了~
williamxia 发表于 2018-3-17 16:56
谢谢楼主分享经验!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

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

GMT+8, 2024-4-26 20:04

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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