记一次游戏通关

前段时间发现了 quora-api 这个项目,当时心想我应该写个知乎的版本试试,正好可以用用flask,然后因为毕业论文和答辩的事情就一直拖着,现在终于开始动手了。

稍稍瞄了一眼 quora-api 这个项目,发现这个项目跟我想的思路是一样的,写爬虫获得数据,再通过 flask 搭建 api 服务即可,从 commit 的记录来看,quora 的爬虫部分最开始是写在 quora-api 里面的,后来作者把这部分分离出去:

pyquora

单独打了一个包:pyquora

我想我也应该如此,毕竟已经写过不少爬虫了,重点在于熟悉flask,就用现成的吧,把爬虫的部分分离出去,也便于维护。比较了几个关于知乎爬虫的repo,找到了 zhihu-oauth,其实 7sDream 在新建这个 repo 的时候我就 star 了,但并没有仔细看,现在看来这个并不是简单的爬虫。我从这篇游戏攻略开始了旅程:zhihu-oauth-doc

7sDream 在文档里写着:「本来想的是写一篇博客的,但是果然还是不要太张扬,写在文档里算了」,我却在博客里宣传了,好像不是很道德?嗯,还是不要给自己加那么多束缚吧。我不打算把攻略重写一遍,只是记录一下遇到的坑,反正我的博客基本没人知道,没几个人看,github 对百度搜索引擎做了限制,百度爬取不到,Google 倒是可以,但是用 Google 搜中文资料的也太少了吧?如果影响不好我就删掉这篇。

我照着这篇攻略开始了。攻略里写的准备工作是:

  • 一台 Android 设备(我用的是 Nexus 7, 6.0.1,CM 13)
  • 一台电脑,系统随意,有 Android studio 最好,没有也行
  • 支持 HTTPS 的抓包工具(我用的是 Android 上的 Packet Capture)
  • APK 反编译工具(我用的是 jadx)

在 Android 设备上安装上知乎客户端,如果已经安装了的话就强行停止, 然后清除数据和缓存。

手边暂时没有 Android 设备,用的是 iPhone ,在 iPhone 上抓包好像挺麻烦?果断用电脑抓包啊,也便于分析,如果不是抓https协议的内容,只抓http协议的内容,用 Wireshark 就够了,有过这样的尝试,见这条微博。要保证安全性,APP的数据传输一般会用https,因为可以用ssl层来加密,知乎客户端用的是https。Wireshark 当然可以抓https协议的内容,但抓手机端的就有点麻烦,见这篇博客,果断跳过这个坑。我的方案是用 charles for mac + iPhone。

用 charles 抓取 APP 网络请求的大致步骤是:

  • 手机和电脑连接同一个局域网
  • 在手机端改 http 代理

在 Charles的 Proxy Setting 可以看到默认的端口号:

Charles Proxy Setting

用 ifconfig 查看 mac 的 ip 地址:

mac ip

在手机上改成对应的即可:

iPhoneproxysetting

  • 在手机上需要打开 legacy-ssl-proxying,安装 SSL 证书

  • 在 Charles SSL Proxying 设置界面打开 ssl 的 抓取:

SSL Proxying setting

与 Wireshark 不同的是,Charles 打开即开始抓包,抓去过程中我遇到一个坑,是编码问题,有不少乱码,搜索了一下,有这样的方案:使用Charles,在mac环境下对android app抓包,但我的 info.plist 并没有 vmoptions,看来我用的 Charles 比较新,求助 google,最终解决了,info.plist 改动的部分:

jvmoptions

解决了乱码问题。

实现了抓包,就可以开始分析知乎的 api 到底怎么用了。

在手机上重新登陆,抓取登陆时的网络请求,发送的登陆参数如下:

login_https

逐个解释这些参数:

  • 第一项是 client_id,这是 OAuth 里需要申请的,表明一个应用的 APPID 值,我申请过 twitter 的 api,貌似这个每个 APPID 都应该不一样才对?攻略里写的是 「Android 知乎客户端的这个值应该都是一样的」,看来目前只有两个,一个表明是 Android 端,一个表明是 iOS 端,以后知乎开放了 api 开发者拿到的 id 应该就是唯一的了。

  • 第二项是授权类型,「password 表示我们通过提供用户账户的密码 来获取用户令牌。其他的方式大概还有 OAuth 登录(就是像微博那样弹个小网页让你登录), 第三方登录(通过微博,QQ什么的)」

  • 第三项 密码

  • 第四项 请求签名,这个显然是一种加密措施,这个是最难搞的。7sDream 说 「为了破解这个签名,我费了挺大功夫的,大概一晚上加一早上吧」(啊哈哈哈 我不信)

  • 第五项 source,请求来源,目前应该就两种,一个 Android 端,一个 iOS 端

  • 第六项 时间戳,请求签名的加密算法会用到

  • 第七项 用户名,这里我用的是邮箱账号

除了请求签名,其他都是很容易构造的,所以最关键的就是搞定知乎的加密算法了。

(写到这,真的挺佩服7sDream,貌似是天津大学的大三学生?曾跟他聊过几句,非常有极客风范,而且他还比我小,唉真是惭愧,要好好加油。)

文档中记录了破解加密算法的过程。总结一下,掌握一定的逆向工程技术是必须的,7sDream 用 jadx 这个工具反编译了知乎的 Android 应用包,找到了 Authorisation.java 中的 createBaseAuthorisation 方法,根据代码尝试,然后推测知乎用的是 Google 推荐的 OAuth 的签名方法,文档在这:get-api-key。思路非常棒,不过他说的「大概一晚上加一早上吧」 我是真的不信。。。。。

搞定加密算法,用程序模拟客户端发送请求即可,可以收到客户端返回的结果:

login_response

接下来请求 api 数据的时候只需要加上 access_token 这个值即可获取,例如请求首页的数据:

top_story

玩到这,游戏基本就通关了。登陆操作其实还有一个隐含关卡,攻略中写了知乎 OAuth 的验证码策略的6点,不知道作者获悉这6点的思路是什么,其实试错的价值也很大,这里需要自己好好研究一下,暂时还没遇到这种情况,遇到了再 hack 一下。

好了,搞定了这些,写代码模拟这个过程就行了。zhihu-oauth 的代码基本上就是模拟这个过程。源代码也很有研究价值。

总结一下

这份攻略干货十足,它完整地记录了如何破解手机应用 api ,我想不仅仅是知乎,对于其他的应用,比如quora 啊,豆瓣等,都可以借鉴这种思路。这里面大概有这么一些技能点:http 传输协议,逆向工程能力,基本加密方法和应用,都值得更进一步学习。

说起来,不知道那些快30,快40的老一辈程序员会不会不屑于干这种事情,这种事当然算不上光彩,是破坏商业规则的行为,触碰了商业公司的利益,但我觉得非常有意思,这种探索让我感到兴奋,希望自己在30、40岁的时候仍然可以保持这种钻研精神。

接下来的计划是:好好读读 zhihu-oauth 的源代码,利用 zhihu-oauth 的功能实现与quora-api 类似的知乎的版本(repo 地址在),再接下来,我大概会把 EE-Book 中知乎爬虫的部分去掉,改用已经破解的知乎api,再接下来,还有什么好玩的事情呢?