吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]
查看: 32222|回复: 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] 纯文本查看 复制代码
1
2
3
4
.sparse-switch
    0x1771 -> :sswitch_0
    0x1f40 -> :sswitch_0
    0x2328 -> :sswitch_0


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

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


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

[Java] 纯文本查看 复制代码
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
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] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
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] 纯文本查看 复制代码
1
2
3
4
5
6
7
    .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] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
    .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] 纯文本查看 复制代码
1
2
.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] 纯文本查看 复制代码
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
{
"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] 纯文本查看 复制代码
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
.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] 纯文本查看 复制代码
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
@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] 纯文本查看 复制代码
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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, 2025-3-13 11:30

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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