Android逆向——定位到某书 Sign 算法

640?wx_fmt=jpeg

一、浅谈Okhttp拦截器

添加一个自定义拦截器很简单,只需要实现 Okhttp 的 Interceptor 接口,重写其中的intercept 方法,最后在 OkHttpClient.Builder 链式代码中注册和添加这个拦截器,为什么要称作“自定义“拦截器呢?因为 Okhttp 核心实现就是基于其内部五大拦截器,我们下文再说。

需要注意的是,Retrofit 是基于 Okhttp 的封装,所以当我们谈论“Retrofit的拦截器”,其实就是OKhttp的拦截器。

先看一下上一节,我们用 Okhttp 实现网络请求的 demo

640?wx_fmt=png

对照着看一下 Okhttp 发送请求的执行流程

640?wx_fmt=png

我们分析大红框里的内容,这张图有些繁琐,也可以转化成下图。

640?wx_fmt=png

还是直接上图,就像流水线工厂一样,一个一无所有的 Request,依照顺序依次经过五大拦截器的前置拦截,最后拿到response后,再倒序经过五大拦截器的后置拦截。

我们引用@yuashuai 所举的例子,他表述的既生动又贴切。

640?wx_fmt=jpeg

老张有很多干面条,但是他想吃汤面,可是自己又不会做,但是碰巧村里大郎会做,于是老张拿一包干面条让大郎做成了汤面。但是老张发现他做面不好吃,盐都没放,连个青菜叶子都没有。

这时候老张正好碰到隔壁老王,老王说了这东西我也会做,比他做的好吃多了。于是老张又拿着一包干面条给了老王,老王说老张你等着,我马上回家给你做,做好了就给你送过去。但是老王回家并没有做,而是去家里拿了一包盐,然后去找隔壁老李了,原来老王并不会做面,但是他知道隔壁老李会做,而且做得比较好吃。于是他把干面条和盐都交给了老李。老李对老王说你回去等着吧,做好了马上给你送过去。可是老李同样不会做,但是他知道村里的大郎会做,这时老李首先回厨房拿了两个生菜叶子,然后带着老王给的干面条和盐去找大郎了,对大郎说,生菜叶子,盐,面条都给你了,你快给我做一碗面。大郎对老李说好嘞,3分钟就好了,3分钟后,老李拿着做好了的放了盐和生菜叶子的一碗面回去了。本来打算直接给老王,但是一想,自己放了两个生菜叶子,不吃点这个面吃不是有点亏,于是老李偷偷了吃了几根面。然后老李去找老王说你的面做好了并把面交给了老王。老王一看这面只有两个青菜叶子,营养是不是不够呀!于是老王又买了半斤熟牛肉,切切放了进去。然后老王去找老张说你的面做好了,还说道这么大一碗你也吃不完吧,让小张也吃点。最后老张吃着老王送来的红烧牛肉面感动的肉牛满面。

这里的干面条就可以看做一个最原始的 request,到老王哪里被加了点盐,到老李哪里被加了生菜叶子,于是大郎才能把这个request做成放了盐和生菜叶子的response,这个response回到老李哪里又被啃了几口,到老王哪里又被放了点牛肉。于是最后回到老张里收到的response就是被扣了几口并且加了牛肉的response。这样整个链条是不是就清楚了!

@Wanghao 画了一张非常全面的图。

640?wx_fmt=png

这是 BridgeIntercetpor 的源码

640?wx_fmt=png

我们也可以自定义拦截器,分为应用级别拦截器和网络级别拦截器,在 @Wanghao 的图上我们可以看到它们分别作用的时机。

1.应用级别拦截器:只会调用一次,获取到最终的 response 结果

这两种拦截器在注册方式各不同,分别调用 addInterceptor()以及 addNetworkInterceptor 方法进行注册,但实现拦截器的步骤没有不同,所以我们不做过多笔墨。

640?wx_fmt=png
640?wx_fmt=png

二、分析一个简单的拦截器

我们随便找一个拦截器 demo 分析一下,我们需要熟悉拦截器的具体操作,找到它代码流程上的一些特征,然后根据这些特征在Jadx中搜索反编译后的 Java 代码,去寻找某书实现添加sign等等11个参数的拦截器。

除此之外还有个思路,上文说到,自定义的拦截器分为 Application 拦截器和 Network 拦截器,但不管哪一种,都需要在 OkHttpClient.Builder 链式代码中通过 addInterceptor/addNetworkInterceptor 方法添加和注册后才能生效,那我们可以全局搜索 addInterceptor 以及 addNetworkInterceptor,然后得到拦截器的线索,这样应该也是可以的。我们先看看当前的这个思路。

640?wx_fmt=png

我们可以看到三个明显的特征:

二: 是重写intercept方法“public (可能有修饰符) Response intercept(Chain Chain)……”

三: 是添加字段时调用的 “.addQueryParameter” 方法(这儿我们加了个“.”,可以缩小检索范围),如果是添加Header 头信息,则会调用 ”.header“ 这个方法

如果 App 没有对 Okhttp 类进行混淆,那我们就可以根据这三个特征找到  Retrofit 的拦截器实现。

我们这儿再多说两句,看图中第九行,Request originalRequest = chain.request(); 这个demo作者为什么要将这个Request命名为 originalRequest(original:原先的,原始的)呢?

再回想一下拦截器的工作原理。想象一下一个流水线,request 是材料,response 是产品,从头到尾经历了许多次加工(拦截器)。每个拦截器先通过 chain.request() 得到这个 reqeust,经过一系列操作后的 request,再放回 chain.proceed 方法,最后return 回去,request 经过了一次洗礼,等待它的是不断的新的洗礼。

640?wx_fmt=png

chain.proceed 这个方法名非常精准和有神,Okhttp采用了设计模式中的责任链模式,感兴趣的可以看一下这篇关于责任链的文章https://www.cnblogs.com/aeolian/p/8888958.html。

因此,对于每一个拦截器来说,取到的request就是原始和过去的,命名就可能会用”old“、”original“,返回到reqeust就可能用”final“、”new“修饰,解释到这儿,我想大家对拦截器应该有了一个具象的了解了。

上面我们找到了三个特征,那么接下来我们通过 Jadx 的全局搜索功能,开始寻找某书 Retrofit的拦截器,进而找到 sign 等参数的实现。如果你的电脑内存只有 8G,那么接下来的这一步操作对你十分有用,除此之外,它对我们搜索关键代码点也有奇效。

反编译一个App,你会看到成千上万的类和方法,我们可以简单将这些类和方法分成“具体业务逻辑的代码”和“App架构和工具的代码”,前者的类名一般是包名.xxx,比如某书App包名为 com.xingin.xhs,它的业务代码就是 com.xingin.xxxx;而第二类往往五花八门,比如 Android本身的一些方法类 “android.xx”,腾讯的sdk“com.tencent.xxx”,微博的登录接入sdk“com.weibo.xxx”。

我们想要定位到的内容/加解密逻辑等等,基本都在前者的类里,而后者既缭乱人眼,烦人心神,又占用内存,Jadx给我们提供了屏蔽这些类的方法,屏蔽后Jadx将不再反编译这些类,你也无法再跳转到该方法里,或者在全局检索时看到这些类中扰人的代码。

方法很简单,只需要在类列表中选中某个类,右键 exclude 即可屏蔽这个类。

640?wx_fmt=png

但我们总不可能点几百几千下,这样实在太麻烦了。Jadx 的设计者给我们提供了一种类似正则表达式的匹配方法,可以屏蔽这些讨人厌的第三方类,操作也很简单。打开文件——首选项,如下图,(建议)不要勾选“自动后台反编译”,因为它可能占用你电脑更多的内存。

640?wx_fmt=png

点击编辑,像我这样输入,类名以 baidu 或 android 开头的类就会被 Jadx 屏蔽掉,点击确定后保存设置,Jadx 会重新加载这个 Apk,你会发现已经生效,这些类变成了灰色。

640?wx_fmt=png

我们浏览一下列表,将比较明显的一些刺头并闭掉。

android

你可能会难以把控,怕误伤,比如 com.networkbench.xxx,它似乎很像 App 中用于处理网络请求的模块,怎么避免误伤呢?百度一下就行,如果消息模棱两可,就搁在那儿不排除即可。

640?wx_fmt=png
640?wx_fmt=png
android com.alipay com.baidu com.facebook com.google com.huawei com.meizu com.networkbench com.qiniu com.tencent com.vivo kotlin

一路保存设置,最好重启一下JADX,这样可以释放掉由于反复操作Jadx的设置而导致的内存占用。

640?wx_fmt=png
640?wx_fmt=png

Jadx默认在使用搜索功能时,才开始反编译,点击Jadx左上角魔法棒,然后稍等片刻。

640?wx_fmt=png

原先反编译某书时占用6G左右内存,现在少了几乎一半。

640?wx_fmt=png

经过这一顿调教

640?wx_fmt=jpeg

我们正式开始搜索拦截器相关的特征。

三、定位关键代码

Jadx 并不支持正则表达式的方式进行检索,所以我们这儿搜索 “implements Interceptor {”

640?wx_fmt=png

再试试第二个关键词

640?wx_fmt=png

再试试第三个关键词

640?wx_fmt=png

真是让人惊喜,只有三处适宜的代码,而且其实就在两个类里,先看一下第一个类

640?wx_fmt=png

传入了一个 map 集合,不停 for 循环,按照键和值的方式将内容传进 addQueryParameter 方法中,妙啊,比如map为 {“sign:12345678”},这不就传进去了吗,赶紧右键查找一下用例,只有一处,点进去。

640?wx_fmt=png

代码越来越乱了,这个时候冷静一下,要避免一叶障目,不要先钻进去研究逻辑,保持怀疑,然后直接看下一个符合条件的类,要知道这儿可是出现多处 .addQueryParameter 方法,也很像添加多个参数的操作。

点进去看一下,有些代码会让你觉得很 demo 很像,比如什么request.url().newBuilder(),除此之外,我们看到了大量类似 oldRequest 的提示,这也只能说明这个地方很可疑,就像第一个检索内容一样,但不足以让我们认定它就是我们要找到拦截器。

640?wx_fmt=png

从上往下看一下这个类

640?wx_fmt=png

这儿似乎就是我们需要的那个拦截器类,我们看一下sign的用例,j上右键——查找用例

640?wx_fmt=png

两处方法点开后,仔细瞅瞅,不管是map的put方法,还是newBuilder的add方法,其实都是往集合里塞东西,里面都是两个参数,迷糊的同学可以看一下前面的demo,参数一是键,也就是参数名,在这儿也就是this.j,也就是”sign“,参数二就是具体的sign值,也就是我们要破解的sign。我们可以发现,两处的sign值都是由a方法生成,a方法的传入是一个map集合(linkedhashmap是map的一种),返回一个String。

640?wx_fmt=gif

多半这个a方法就是sign的生成处了,在下一讲中,我们会验证一下是否属实,如果没找错的话,就可以分析这个 sign,进行 sign 的破解。这篇文章就到此为止了。

640?wx_fmt=png

看我教程的人不多,但读者一直支撑着我继续写这些拙劣的文字和教程,非常非常感谢大家。

在这儿做一下更新的说明,我每一节的内容其实量挺大的,再加上我需要反复琢磨表达和思路,所以更新比较慢,以后会减少单篇的内容,在保证质量的前提下尝试一天一更,谢谢三五读者的支持和厚爱

推荐阅读

640?wx_fmt=gif

640?wx_fmt=png

发布了51 篇原创文章 · 获赞 26 · 访问量 9922

猜你喜欢

转载自blog.csdn.net/spider_py/article/details/101691937