网恋选我,我撒网贼6

 

声明

  1. 本文纯属虚构,若有雷同,纯属巧合。
  2. 本文及本人并未对任何社区进行攻击、扰乱等行为。
  3. 本文中出现的技术仅做讨论、研究之用,不以任何形式开放、出售与 APP 相关的源代码。
  4. 本文中出现的数据、代码、截图均已做脱敏处理。

 

前传

这个事情要从很久很久以前说起…

我有一个朋友,母胎单身,去年不知道从哪拱到了一棵大白菜,感觉人生到达了巅峰。

有一次,我与他相遇夜宵,凑了一局。 两杯啤酒顿顿顿下肚,只见他脸色红润,情绪激昂,只差一匹汗血宝马就可以出征长沙,血战沙场。

于是大放厥词,口出狂言道: 忆往夕,吾已鳏独二十有五载,看今朝,新春必携伴归乡探亲。

……

很可惜的是,可能是因为没有经验或者性 格不合,两人还没到过年就吹了,老死不相往来。

那几天,他悲痛欲绝,哀哀戚戚,无心工作,借酒消愁,感觉人生已经到达了低谷。

然后我去安利他看爱情公寓。 喜剧,也许能缓解一下心情。

后来突然有一天跟我说要他练级,练到 99 级!

咱也不知道咋练、练啥,咱也不敢问。。。

……

这货上网搜了一下陌生人社交软件,下载了一个比较热门的 APP。

练级第一天,点击匹配,匹配成功,开始聊天,于是:

然后就没然后了,他说这是他人生中遇到的第一次滑铁卢,感觉人生已经到达了低谷中的低谷。

练级第二天,看到社区里有漂亮的小姐姐发自拍,能收到好多赞和评论,真是一群有趣的灵魂啊,于是也决定自己发一张。

练级第三天:

不禁令人感慨,人生呐,就是如此的起起落落落落落落。

然后呢,就又来找我了,让我帮帮他。

这位胸 dei,谈恋爱俺不会,聊天俺也不会,那我只能手动帮你点个赞了。

这位仁兄一听,灵机一动,道: “那你帮我搞个软件,能自动帮别人点赞也行。高手靠实力,新手靠运气,只要我给一万个人点赞,总有那么几个人能看到我的自拍,然后被我那无处安放的魅力所吸引的。”

嗯, 他说的好像有道理。

于是有了正文。

 

正文

因年代久远,当时的分析与现在 APP 的协议已经很多对不上,我只能尽量复盘。

大伙们,编故事真的不容易。

……

需求很明确:

  1. 从社区里获取小姐姐们的动态
  2. 点赞

抓包

这个 soeasy ,主要是抓获取动态和点赞的两个请求。

首先打开 Charles,然后给把代理端口反向转发到手机里。

adb reverse tcp:8888 tcp:8888

再给手机设置代理

adb shell settings put global http_proxy 127.0.0.1:8888

结果发现一挂上代理就无法正常发送请求。

这种情况一般有几种可能:

  1. SSL Pinning 证书绑定
  2. 代理检测
  3. SSL 双向校验

根据我多年的经验,第一判断就是 sslpinning,于是我用 3 秒钟时间打开 objection, 执行 android sslpinning disable。 关于 Fridaobjection 的安装和使用,我的另一位母胎不单身的好友已经写过多遍,可以参阅。

然后再次抓包,然鹅不管用。

那么先观察一下抓包的情况,发现 Charles 并没有出现 SSL 错误的提示,可以完整的看到请求的报文。

然鹅,response 却返回 400。由此可得,这是一个 SSL 双向校验,因为代理可以正常拦截到发送包,说明不是代理检测,如果有代理检测,那么这个数据包并不会通过代理发送出去。而且可以正常解密,更是说明也没有做证书绑定。

因为做双向验证,就需要在客户端里放一个证书,那么直接在 APK 里的 assets 中就可以找到一个 .p12 和一个 .cer 的文件,那么这两个就是客户端的证书了。

但是想要使用这个证书来与服务端通信,还需要一个密码。

一般来说,我们可以静态分析代码,搜索 KeyStore 或者 client.p12 来逆向分析找到密码,然鹅,这个 APP 做过加固,看不到代码。 那么可以采用更加高效的方式: Hook

Hook 的原理不再多说,可参阅 《Android 动态分析攻防小结》
直接上 Frida 脚本:

Java.perform(function () {
    console.log("Hooking...");
    var PKCS12KeyStoreSpi = Java.use("com.android.org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi");
    var String = Java.use("java.lang.String");
    PKCS12KeyStoreSpi.cryptData.implementation = function(a, b, c, d, e){
        console.log(String.$new(c));
        return this.cryptData(a, b ,c ,d ,e);
    }
});

拿到密码之后,就可以打开 CharlesSSL Proxy Settings 里导入 p12 证书了,随后在抓包的时候,Charles 会提示你输入密码。 而想要在 Python 脚本里使用,还需要使用 openssl 转换成 pem 证书,然后就可以像这样正常发起 Https 请求了:

response = requests.post(url="https://{}{}".format(ip, path), headers=headers, data=data, verify=False, cert="./client.pem")

于是,终于跨过抓包这个坎了。

Sign

一款安全性合格的产品,请求里必然会有 sign 字段,可以防止篡改的同时还能提高逆向分析的难度。

抓包分析之后发现请求的的 headers 里头有一个 APISign 字段,每一个请求都会通过这个字段对当前的数据以及时间戳进行校验。

按照常规套路,就是反编译之后搜索相关字符串,然后找到加密算法的地方,抠出来。

然鹅,这个 APP 是加固的,首先尝试一下快速脱壳

结果好像还不错,dump 下来很多 dex,可以开始一顿搜索了。
然鹅:

搜索并没有得到结果,我以为是字符串进行了加密,然后手动打开这堆了 dex 文件,也搜索了其他的一些关键字、API,发现确实没有。 另外发现应该少了一些代码,这说明有一个重要的 dex 可能没有脱下来

逆向分析就跟股市一样,喜欢起起落落落落落落。短时间内也不纠结为什么脱不下来了,来一次不脱壳的纯动态分析逆算法。

无法脱壳下的动态分析口诀只有两个字:猜、试。 说详细一点,就是信息收集之后一步一步的进行猜想和验证。

因为 APISIGN 是一个 32 位长的 hex 字符串,那么可以初步猜想,这可能是个 MD5, SHA1 之类的摘要算法。那么这个时候,如果开发者使用了系统自带的摘要 API 那我们就可以通过 Hook 来得到计算之前的数据,从而倒推出签名算法。

关键类: java.security.MessageDigest, 通过对这个类的 getInstance, update, digest 方法进行 hook, 可以得到所有调用系统 API 的摘要算法、摘要数据、摘要结果。 代码这里不给了,自己实现一份不难。

想象很美好,现实却很残酷,通过这个方法并没有成功 Hook 到与请求数据相符合的调用结果,那么基本上这个算法就是自己实现的了。

大部分 APP 的网络请求,都是通过调用第三方网络框架发出去的,那么,我们可以先尝试一下能不能找到这款 APP 所使用的网络请求框架。

第一个肯定是龙头:OKHttp

因为之前有写过现成的针对 OKHttp 的抓包、解密工具,所以我就直接尝试启动了一下,结果:

[*OKHttp-InfoIntercept] not found RealInterceptorChain class
[*OKHttp-PacketIntercept] not found CallServerInterceptor class

找不到预设的类名,可能是因为混淆或者 APP 在集成框架的时候,很多人会选择修改包名,因为工具之前没有写智能查找的功能,那么可以使用 objection 手动查找一下。

使用命令: android hooking search classes Interceptor, 先搜一下带 Interceptor 的类名,因为一般来说可以通过他们,直接拦截到发包的调用。

结果发现,还是可以找到 okhttp 的类的。

那么将找到的这几个类进行一个 android hooking watch class。在发送一个请求,发现:

可以成功拦截到调用。那么,观察调用栈:

执行 android hooking watch class_method okhttp3.logging.HttpLoggingInterceptor.intercept --dump-args --dump-backtrace --dump-return

于是我们得到了 RealInterceptorChain 的真正类名

很多时候,OKHttp 对请求的加密、Sign 都是在开发者自定义的 Interceptor 里完成的,而 RealInterceptorChain 这个类,是贯穿全部 Interceptor 的一个关键点,具体详情可以阅读 OKHttp 的源码。对其 proceed 方法进行 Hook 在调用栈中即可看到所有的 Interceptor

于是: android hooking watch class_method okhttp3.internal.http.f.proceed --dump-args --du mp-backtrace --dump-return

运气很不错,一下子就打出来了一条 APP 包名代码里的调用栈,而且还是 pkgname + .net 的包名,一看就是用于网络请求的,信息情报加一: 代码里有一个 .net 的包, 而且这个包并没有被脱下来。这个包基本上一看就知道很关键了。

那么再继续对找到的这个 Interceptor 类进行 hook, 仔细观察返回结果以及调用栈。

很可惜的是,并未在返回结果里找到与请求相符合的 sign 字符串,这说明,sign 的算法方法并不在这个类里面。

调用栈里也没有什么可疑的类或者方法,可以说这条线在这里已经断了。

从二中,我们收获到了一个情报: 未脱下来的代码中有一个 pkgname + .net 的包,那么可以先搜索一下,看看这个包中都有一些什么类:

[usb] # android hooking search classes xxx.xx.xxx.net
...... # 具体的类名就不列了
......
Found 54 classes

排除掉一些明显不相关的类,剩下的类不多,只有十几个。一个一个对其进行尝试 hook,功夫不负有心人,在一个没有做符号混淆的类中发现了一个方法:

那么观察一下这个方法的输入输出

看到这个,我就觉得基本上已经结束了。

再详细分析一下输入参数,分别是 URL PATH、用户 ID、时间戳等信息,都是已知或者可以自己生成的参数,返回结果就是 HTTP 报文里的 APISign

至于为什么这么方法和类没有做符号混淆,原因是这个算法是在 SO 里面的,需要在混淆配置里面排除。

由此,我们又得到了一条新的经验: 下次 Hook 不到算法就直接看 SO

算法调用方法有了,那我们还需要自己逆算法么?no。

Frida-RPC,懒人必备,在也不用手撕 OLLVM, VMP

rpc.exports = {
    sign: function (xxx1, path, xxx2, t, xxx3, xxx4) {
        var result;
        Java.perform(function () {
            result = xxxxxxx.yyyyyyySign(context, xxx1, path, xxx2, t, xxx3, xxx4);
        });
        return result;
    }
};
sign = script.exports.sign(xxx1, path, xxx2, t, xxx3, xxx4)

完全 OK。 然后再构造一下包体,编码一下逻辑,一个无情的自动点赞机器人就诞生了。

 

正正文

作为一个新世纪的颜值主义者,虽然这哥们长得丑,但我还是希望他能找到一个漂亮的女朋友。于是我添加了一段人脸识别和打分的代码…

原本的逻辑是这样的:

遍历动态列表 -> 是个女的 -> 点赞

然后我加上了 F4ce+-B4ldu 的 AI 颜值评分系统之后,逻辑是这样的:

遍历动态列表 -> 是个女的 -> 识别动态里的全部人脸 -> 评分 -> 大于80分 -> 点赞 -> 关注

最终效果咋样咱也不知道,反正这哥们那几天就抱着个手机在那一直傻乐,前几天跟我说准备跟新的女朋友去旅游,咱也不知道这是哪个新女朋友,咱也不敢问。

 

总结

后来我拿这哥们的账号提取了一点数据,事实证明,B4ldu 的颜值评分比较靠谱。

(完)