生命在于运动,不在于体育打卡。

fake run start !
fake run start !
fake run start !
fake run start !

在西安交通大学中国西部科技创新港,每天做牛做马的研究生们有5个学期的体育打卡要求,其中每学期要求60次体育打卡,每天签到签退间隔不少于30分钟,而且只能在校内规定的运动区域连接校园网进行打卡。其实要求不算太苛刻,但是因为平时忙于上课与科研,常常忽视了打卡的需求,有时一时兴起签到了,但是又把签退忘了,又或者想起来要签到,但是却不在指定的运动区。上学期尽力强迫自己要记住每天打卡了,但直到最后还是没打满60次,转眼间这学期也快进入尾声了,可是我的打卡次数只有可怜的10次,痛定思痛下,我决定放过自己,解放生产力,实现体育打卡自动化。


我应该做什么

大的目标很明确,就是要实现移动交通大学的自动签到和签退。自动化在这样的语境下,其实就是用程序模拟人签到和签退的过程,因此我首先考虑一次正常的签到签退需要哪些因素。

  1. 需要一个移动交通大学 app。
  2. 需要到达指定的运动区域。
  3. 需要模拟签到和签退的指令。
  4. 控制模拟签到和签退的时间。

因此,关键就在于如何通过计算机技术来解决上述4个问题。

由于我是 ios 系统,没有移动交通大学的安装包,而且 ios 系统对软件的保护比较好,所以我选择在电脑上安装一个安卓模拟器,从使用安卓手机的同门那要来了移动交通大学的 apk,将其安装在了模拟器中。

经过对市面上几种模拟器的了解与尝试后,我决定使用网易的 MuMu 模拟器,它自带虚拟定位功能,而且可以设置开启 root 权限与修改系统盘,比较方便我后续的调试工作。但是在后续的调试过程中我发现当模拟器开启 root 权限时,移动交大 app 会闪退,所以只能在需要权限的时候开启,不需要的时候就要关掉它。

模拟签到和签退有两种方法。第一种是通过 adb 直接对模拟器进行操作,通过一系列的 adb 指令模拟人操作模拟器进行签到和签退的动作。但是这样做的缺点在于脚本执行没有交互,无法得到某个动作是否被响应的信息,直白来说就是我点击了但我不知道 app 反应了没有。再加上模拟器上 app 响应的时间延迟不确定,所以这种方法尝试了之后就 pass 了。

而另外一种方法则是模拟客户端与服务器之间的数据包传输。通过本科半吊子的计网学习,我了解到当前的网络传输基本是基于 https 协议的,如果我能够捕获一次正常签到签退过程中客户端与服务器之间传输了哪些数据包,我就可以通过模拟这些数据包的发送来完成对签到签退的模拟。我觉得这种方法更好且更稳定。


发现问题然后解决

抓包的原理

https 协议是一种非常安全的数据传输协议,它结合了非对称加密与对称加密保证数据传输的安全性,非对称加密用于确保客户端可以安全地获取到服务器的真实公钥,对称加密用于确保客户端和服务端的数据传输不会被任何人窃听。

而用户想对 https 请求进行抓包,就需要在客户端安装一个抓包软件的证书。这个证书就是整个工作原理的核心,如果没有它,那么 https 就是不可能被抓包的。而安装证书需要由用户主动去操作,说明用户知道自己在干什么,自然也应该承担相应的风险。

我们将实现原理分成两个部分来解析,第一部分是客户端如何安全地获取服务器的真实公钥,第二部分是客户端如何与服务器商定用于对称加密的密钥。

CA 机构专门用于给各个服务器签发数字证书,从而保证客户端可以安全地获得服务器的公钥。服务器的管理员可以向CA机构进行申请,将自己的公钥提交给 CA 机构。CA 机构则会帮忙制作证书,并使用自己的私钥对其加密,然后将加密后的信息返回给服务器。这样,当客户端想要去获取某个服务器的公钥时,服务器会将CA机构签发的那段加密信息返回。那么客户端要如何解密这段信息呢?主流 CA 机构的公钥都是被内置到操作系统当中的,所以只要是服务器的数字证书是由正规 CA 机构签发的,那么就一定可以被解密成功,从而客户端也就能安全地获取到服务器的公钥了。

抓包工具的工作是介于客户端和服务器中间的,它是一个中间人。中间人会先于客户端接收到服务器返回的加密数据,然后用操作系统内置的 CA 公钥对这段数据解密,从而得到服务器的公钥,并先将这个公钥保存起来。然后,中间人会将解密出来的数据进行调包,将其中服务器返回的公钥替换成自己的一个公钥,然后使用自己的非对称加密私钥对数据重新加密,并将这段重新加密后的数据返回给客户端。

但是,中间人用自己的私钥加密后的数据,客户端是解密不出来的,因为中间人的公钥并没有内置到操作系统当中。所以我们就一定要在客户端安装抓包软件的证书,这样用户才能解密被中间人调包后的数据。这样一来,客户端仍然得到了一个公钥,并且还以为这个公钥是服务器返回的,但实际上是被中间人调包后的公钥,而服务器返回的真实公钥被中间人保存了起来。

接下来是商定对称加密密钥的过程。

客户端利用随机算法在本地生成一个对称加密密钥,并用服务器返回的公钥进行加密,然后发送给服务器。由于公钥加密的数据只能用私钥解密,因此只有服务器能破解出客户端生成的对称加密密钥到底是什么。

可是,在有中间人的情况下,客户端拿到的其实根本就不是服务器的公钥,而是中间人的公钥。所以,客户端生成的对称加密密钥是用中间人的公钥来进行加密的,中间人用自己的私钥就可以获取这个密钥。由于中间人还在之前就保存了服务器返回的真实公钥,所以他可以用真实的服务器公钥再次加密这个密钥发送给服务器。对于服务器而言,它并不知道客户端这边发生了什么事,也不知道中间人的存在。它只知道,用自己的私钥可以解密出客户端发来的数据,获得对称加密的密钥。

现在客户端、服务器、中间人,这三者都知道对称加密的密钥是什么,而且之后客户端与服务器之间的所有通讯都会使用这个密钥加密后再进行传输。在这样的情况下,作为中间人的抓包软件就可以完美地监听客户端与服务器之间的通讯内容了。

Charles 抓包

了解了原理后,我使用 Charles 进行抓包。

首先是要制作安装在客户端的证书,用 Charles 导出证书然后使用 OpenSSL 签发证书,按照签发后的提示重命名证书。然后开启 MuMU 模拟器的 root 权限,并设置可写系统盘,通过 adb 工具连接模拟器,将刚刚制作好的证书放到系统盘中,证书就装好了。最后设置 Charles 代理,将网络代理设置为主机的 8888 端口,这样模拟器中的数据传输就会经过抓包软件后再传输。此时打开 Charles ,关闭其对主机流量的代理,这样一来就可以抓取模拟器中的数据包了。

证书配置详解

数据包分析

能抓包后自然是直奔主题,直接看看点击签到后会发送什么数据包。数据包的请求体中是一些经纬度的信息,请求头中则有一个 token,在网上查了一下发现这是一个 JWT 形式的 token。数据包的响应体中则显示了返回的成功与否的信息。

因此,待解决的目标转移成了,如何获取 JWT token。

JWT 全称 JSON Web Token,它的作用是用户授权而不是用户认证。用户认证指使用用户名和密码验证当前的用户身份,简单来说就是用户登录,而用户授权则指用户登录后当前用户是否有足够的权限访问特定的资源。对于 JWT 而言,当用户使用账号和密码登录系统时,服务器会验证账号密码是否正确,如果登录成功,服务器就会返回一个加密文档,这个文档就是 JWT token,用户如果需要访问某些权限,就只要把 JWT token 放在 https 的请求头中。

JSON Web Token 原理

所以,我们需要在登录的过程中获取 JWT token。通过对登录过程的抓包分析,发现得到 JWT 的流程是这样的:

  1. 通过账号和密码获取用户的 id 与 token 。
  2. 通过账号和 token 获取一个 ticket 。
  3. 通过账号、id、token 获取一个认证码 authCode。
  4. 通过账号、ticket、authCode 得到 JWT。

因此只要模拟这个过程就可以实现签到和签退了。

python-requests

最后就是写代码,由于目前还在用这个脚本,所以就不公开了,等我研三毕业了再说

总体上就是伪造一个 headers 然后用 requests 的 get 或 post 方法将数据传给要发送的 url,然后解析它的返回值。

分析好数据包的流程以后代码就是照着写就行。


感想

本科就学了半吊子的计网,磕磕绊绊地就一步步钻研过来了,这可能就是西电李龙海老师说的“黑整”吧,明明什么都不会但是可以边学边做,发现问题、了解问题、解决问题,实现目的的过程中也收获了知识。最开始不知道模拟器怎么用,先是了解要怎么调试模拟器,再到研究抓包工具的使用,如何发证书,最后抓到包了如何分析数据,每一步都走了很多弯路,但好在最后都解决了。

甚至因为两个同门给我发的移动交通大学 apk 版本不同,导致抓到的包不同。一开始用的是高版本的apk,应该是被学校加密过了,抓不到什么有用的包,所以逼不得已决定用暴力破解 JWT 的 HS256 密钥,跑了一晚上还是失败了。后来觉得可能密钥嵌在apk里,就学了一下安卓逆向,反编译得到 java 代码,想在其中找密钥。但是逻辑太复杂了,没有找到密钥,不过发现了两个 apk 的差异,从而选择用低版本的 apk 抓包,最后终于成功了。

想来学校也不会蠢到用暴力就能破解的密钥,也不会蠢到把密钥明晃晃地放在 apk 里,是我天真了。不过还是在低版本的移动交通大学抓包过程中发现了一个神奇的明文字段 secret_key ,被用于获取 ticket,可能算是彩蛋吧哈哈。