前段时间看了别人的一个写了羊了个羊刷次数网页版,但是 js 代码做了混淆,然后我的那个解混淆的工具还没适配上,短时间内还原不了。但由于是网页版,所以抓包数据还是能看到的,于是就准备复刻了一个。
可在此体验:7y8y.vercel.app (当然由于官方改动,现在功能已经失效了,但看看页面到不成问题,可能需要科学上网)
原本我是不考虑写的,但是这背后所涉及到的技术以及技术框架我是特别想聊聊,加之以后我也有很大的可能会再写一个类似的刷 xx 的网页版,所以就考虑写一个类似的模板以便后续应用需求。
与此同时,我也快有半年的时间没碰 协议复现(网络通信协议重新实现,后文都简称协议复现)。我更喜欢说这个词,也有的人会说模拟请求,对应的关键词可能有 post 请求,抓包,发包,爬虫等等,但大致的意思是抓取请求数据包,然后脱离宿主机(浏览器,手机),将抓取的数据包重新发送一遍。
你也可以理解成爬虫,但和爬虫相比,要做的不只是爬取数据,而是要基于某些请求包(或者说调用他人不提供的 api 接口,即爬取),来实现一定的功能。比如登录协议,签到协议,抢购协议,游戏封包等等,然后不依靠宿主机(即不用登录浏览器或者应用设备)就能实现诸如登录,签到等功能(在后台记录是有的)。因为这些都是基于网络通信协议的,只要抓包(抓取数据包),然后使用编程提供的网络请求模块来模拟请求,达到重新发包,重新请求的目的。在网页中有 http 协议,websocket 协议,而游戏中有相应的与游戏服务器对应的协议,邮件短信文件又是不同的协议(这里的协议都叫网络通信协议),所以我个人更倾向于称之为协议复现。
所以要做协议复现,那基本上有一定的逆向功底和爬虫能力,还有网络通信协议相关的知识了。此次的开发也算是回顾下这些相关技术了。
小区开门应用
在这里容我多废话几句,讲一个我之前的一次开发经历,可以说这次的开发经历算是这篇文章的由来。
应用需求
在之前住的一个小区,有个门禁系统,需要安装一个开门的 app(后文都称开门 app),然后注册一个账号到物业那边登记为户主或家庭成员。
每次开门的时候,都需要打开这个开门 app,然后点击你要打开的门,接着门就打开了。或者叫保安开个门,总之就是特别麻烦,还不提供创建应用快捷方式。
于是我想的是将接口数据“偷”了过来,将大门列表展示在前端上,然后点击对应的大门,然后将大门 id 转发给原 app 的服务器,就实现了开门的效果,也就是这个小区开门的网页版的核心逻辑。
当时设计的界面大致如下,展示小区的大门,点击即可远程开门。
因为是网页版的,所以只需要在浏览器打开对应的网址,点击对应的大门按钮即可。而开发的初衷是这个 app 不提供桌面快捷方式,点击这个 app 还需要观看首屏广告,此外他人也不用到物业登记,就能开门,对于一些朋友或者住户来说,省去了物业登记的繁琐。
不过这个软件还是有挺多要注意的点:首先就是鉴权了,由于我当时主要目的是为了我自己和身边朋友,网站也没有特意发布到互联网上,所以就没做鉴权相关的,不然正常情况下是一定要做鉴权和调用记录的,以及 ip 白名单的。否则搞不好登录原 app 的账号直接因为调用过于频繁直接给禁用了;最主要的安全问题,这里的安全可不只是网站的安全,而是现实的安全。想想如果有一个可以随意进出小区大门的程序,那么任何人都可以进入这个小区,小区的公共设施,业主生活质量安全等等谁来保障?而且最主要所绑定的账号还是我的,万一小区真出了事,那么我的责任将会非常大。
综合考量,这个应用是绝对不可能大肆发布到网上的。个人自用问题还是不大,因为这种调用量对服务器几乎没有什么压力。
在当时我甚至想基于手机的 GPS 定位,来实现靠近小区自动开门。真羡慕当时我的一堆想法,但也遗憾当时没有去尝试实现这一个想法。
开发
这个应用的起源就说到这了,接下来我要说说其开发形态了,这也就是本文说要的重点内容了。下面是我当时的项目结构:
不难看出,这是一个前后 端分离的项目,其中前端使用 uniapp 来开发一套代码多端运行,并且使用的是 Hbuilder 编辑器来开发。而后端就是常规的 Node 后端服务,使用的是 Express 框架。
技术栈就介绍完毕,这里我要介绍整个开门实现流程。
就说说获取大门列表和开门的两个接口请求:
获取大门列表
后端接口:http://localhost:3000/api/list
这个接口主要的作用就是获取原开门 app 的大门列表,这里简单介绍下代码
router.get('/list', async (req, res, next) => {
// 模拟请求获取所有大门数据
let url = `https://xxx.com/api/getDoorList`
let data = {
xxx: {},
}
let json = await (await axios.post(url, data)).data
return json // [{...},{...},{...}]
})
然后前端请求后,将列表数据渲染到页面上。
开门请求
后端接口:http://localhost:3000/api/open
router.get('/open', async (req, res, next) => {
let { id } = req.query
// 模拟请求开门
let url = `https://xxx.com/api/openDoorControl`
let data = {
id: id,
}
let json = await (await axios.post(url, data)).data
return json // { "code": 0 ,"msg": "success" }
})
这里的代码也仅仅只是作为演示,实际代码可不止这么简单,因为还需要涉及到登录,加密等等环节。
我的前端页面访问地址是 http://localhost:5000,我需要访问后端接口 http://localhost:3000/api/list 和 http://localhost:3000/api/openDoor。
对于不了解 web 开发的人员可能会问为啥要后端服务,不直接在前端向开门 app 的服务器发送请求,然后将响应直接渲染到前端上。比如直接在前端代码中写 openopenDoor 函数
async function openDoor(id) {
// 模拟请求开门
let url = `https://xxx.com/api/openDoorControl`
let data = {
id: id
}
let json = await (await axios.post(url, data)).data
return json // { "code": 0 ,"msg": "success" }
})
这个疑惑在我初次想使用 web 端来实现协议复现的时候也考虑过,但浏览器为了安全考虑而不支持。这也是我下面所要说的
同源策略 跨域
一般用户的浏览器是有非常强的页面安全策略的,这里要说的就是同源策略,更细分点就是跨域。比如说 kuizuo.cn 这个站点,想要向 baidu.com 发送请求,请求是能够正常发送过去的,但是 kuizuo.cn 这个站点是接收不到任何数据。因为 kuizuo.cn 和 baidu.com 根本不是同一个网址,专业点说就是不同源,这种不同源的请求在 浏览器,称为跨域请求。
跨域请求如果请求的服务端不允许跨域,即响应协议头没有如下内容
access-control-allow-credentials: true
access-control-allow-headers: Content-Type, Authorization, X-Requested-With
access-control-allow-methods: GET, POST, PUT, DELETE, OPTIONS
access-control-allow-origin: *
浏览器会直接拒绝接收响应,但浏览器确实将请求发送给了服务端,并且你打开控制台中的网络是看不到该请求的响应结果的。
跨域限制只存在于浏览 器端,在其他环境下是不存在,请求都是能够发送出去,并且是可以接收到的。所以说为什么不在前端直接向原应用程序的服务器发送请求,罪魁祸首也就是同源策略。
不支持修改协议头
像 origin,reference,user-agents 等协议头在浏览器是无法修改的
origin: https://xxx.com
referer: https://xxx.com/api/test
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 Edg/105.0.1343.42
而有些请求是会效验你的设备信息,来源地址,这些内容在浏览器中都写在协议头中,且浏览器不支持修改。使用浏览器来发送数据,无疑就是告诉服务器我是浏览器发送的。服务器判断来源不是自家的域名,那就直接拒绝响应,像防盗链就是检测 referer 。
方案
桌面端应用开发
正是因为同源策略的限制,导致做协议复现时往往都会选择在本地中直接运行,比如使用易语言,python 等语言,将应用打包成 exe,然后跑在 window 系统上。
这样的应用会有以下几点缺陷:
-
易破解:由于你的大部分核心逻辑最终都会进行编译打包成 exe,如果会些破解技术,恰好你不做任何防护手段,要破解你的程序非常容易。所以也就为什么很多 exe 程序(尤其是易语言)会带有
.vmp.exe
后缀也就是 vmp 加壳,让程序很难被分析与破解。并且我就可以开启系统抓包,就可以看到应用程序模拟发送的请求数据包是什么。 -
不易跨平台:大多数的协议复现都是 exe 桌面应用程序,虽然也有安卓,但一般比较少。对于非 windows 用户或者说手头不方便用电脑的用户就很难体验到,并且还要特意安装一个应用,应用程序更新也需要重新安装。