作为新一代国产跨平台 HTTP 抓包调试工具,Reqable 继承了它的前身 HttpCanary 小黄鸟的优良传统,免费版足以胜任开发、逆向、调试,是真正的生产力工具。

未命名.jpg

不同于其他基于 Electron 的跨平台应用那么臃肿,它使用 Fultter 开发,软件打开速度那叫一个快。不止拥有强大的抓包功能,还可以打断点、脚本、重写、HTTP 请求调试,甚至可以在抓包时复制任意一条记录到接口测试页,可重放发包和修改后发包,是非常高效的功能。对于抓包,可以进行域名和路径的收集和筛选;对于 HTTP 调试,可以做多个接口测试页,方便整理和生成模板代码。是 Fiddler/BurpSuite/Charles 的平替,是 Postman/Apipost/Apifox 的下位替代品,至于具体场景哪个顺手就用哪个啦~

博主了解到这款工具时,它已经发布正式版5个月,截至这篇文章编写时,最新的版本是V1.6.0

未命名.jpg

准备工作

今天博主就利用它强大的脚本功能,探究网易云音乐PC客户端的 API

首先第一步当然是去官网 https://reqable.com/zh-CN/download 下载软件了,它目前支持 Windows/Linux/MacOS 三种 PC 端平台,分别对应 .exe/.deb/.dmg 安装包,这里使用 Windows 10 系统,首先安装软件

屏幕截图 2023-10-17 153757.png

现在绝大多数 C/S 架构的应用程序与服务器之间的连接使用 SSL/TLS 加密,即 https,防止在传输途径中被中间人监听和篡改,保证了数据的安全性和完整性

抓包软件作为中间人,必然是需要它有监听和篡改的权限。因此,第一次使用 Reqable 需要配置 CA 根证书,只有系统中添加了信任的 CA 根证书,才可以正常绕过 SSL 的服务端验证并加解密 https 协议

屏幕截图 2023-10-17 154814.png

这里 Reqable 的默认代理端口是9000无需修改,要让网易云音乐客户端走这个代理,而不是直接访问互联网,就需要在设置中配置它,在工具中添加自定义代理,地址填127.0.0.1本地回环,端口填9000

屏幕截图 2023-10-17 160757.png

接下来,就可以愉快地进入网络协议的世界了

抓包分析

修改代理后,网易云音乐客户端就会自动重启,同时在调试选项卡可以看到软件刚启动时收发的数据包记录

下方一排按钮可以对结果进行筛选,针对协议类型、响应类型、状态字这几个维度,筛选出最关注的类型,提升调试效率

屏幕截图 2023-10-17 162623.png

点击左侧工作台按钮,可以按照域名、应用程序、路径进行筛选,适合用来分析 REST API

屏幕截图 2023-10-17 165009.png

如果稍有经验,就可以判断出哪些请求是在线的静态资源,哪些是 REST API

图中选中的区域都是 REST API 类型的请求

双击任意一条请求,即可在右边窗格中分析请求与响应的详细信息,如头部、Cookie、表单、时间、证书等等

屏幕截图 2023-10-17 171329.png

对于响应体为图片的请求,还可以对图片进行解析显示

屏幕截图 2023-10-17 173431.png

切换头部选项卡,可以查看解析后的请求与响应头部字段,是 key-value 格式,其中请求头以:起始的都是元字段,如路径、方法等等

查看的这个请求的方法是 POST,根据头部字段Content-Type的值,请求体类型为application/x-www-form-urlencoded,即 urlquery 表单格式;响应体的类型是application/json;charset=UTF-8,即 json

屏幕截图 2023-10-17 174054.png

解密数据

我们了解完这款软件的基本使用方法,就可以直接开抓了

但网易云音乐客户端的接口并不友好,可以看出请求体表单只有一个字段params,它的值是一段毫无规律的数字与字母组合,并没有其他的字段,由于只有0-9A-F那么它很有可能是一段以 base16 编码的二进制数据;响应体也并非 json,而是一段乱码,可能也是某种人类无法直接读取的编码

这是因为网易云音乐 API 请求体与响应体都是加密的数据,需要进行解密才能获取到真实的数据

屏幕截图 2023-10-17 175436.png

经过一番STFW(在网上查找资料)后,发现这是一段用 AES-ECB 对称加密算法加密后的密文,密钥为e82ckenh8dichen8,通过硬编码写死在程序中

所以我们很容易对它们进行解密,这里使用 Python 语言做验证

屏幕截图 2023-10-17 213358.png

解密后的明文数据还需要对它进行处理,首先去除末尾的\x0b(AES 的特性导致,输入数据必须是 16Byte 的整数倍),再以-36cd479b6b5-作为分隔符拆分文本数据的三部分,分别对应接口路径 请求参数 接口sign

其中只关注请求参数即可,这是一段 json 数据

屏幕截图 2023-10-17 220733.png

所以可以把这一系列操作封装为一个函数,输入params字段的密文,输出接口路径和请求参数

import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import json

EAPI_KEY = b"e82ckenh8dichen8"
EAPI_CRYPTOR = AES.new(EAPI_KEY, AES.MODE_ECB)

def eapi_decrypt(enc_data):
    message = unpad(EAPI_CRYPTOR.decrypt(base64.b16decode(enc_data)), 16).decode()
    path, json_val, hash = message.split("-36cd479b6b5-")
    return path, json.loads(json_val)

接下来再聚焦那一段“乱码”的响应体,它与请求体使用相同的方式加解密(这里为了方便复制使用 base16,实际上是二进制字节流

不难发现,响应体也是一段 json 数据

屏幕截图 2023-10-17 222039.png

同样可以将它封装成一个函数,以二进制输入,输出 json 格式的真实数据

import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import json

EAPI_KEY = b"e82ckenh8dichen8"
EAPI_CRYPTOR = AES.new(EAPI_KEY, AES.MODE_ECB)

def data_decrypt(enc_data):
    data = unpad(EAPI_CRYPTOR.decrypt(enc_data), 16).decode()
    return json.loads(data)

编写解密脚本

有了上面这些成功处理数据的实例,如何对接口数据解密已经不是难事,但从抓包记录中来回复制数据、手动运行解密程序只能徒增人力,浪费调试与研究的时间

恰好,Reqable 一大亮点就是支持脚本功能,使用 Python 作为脚本语言,事件化、异步化处理请求与响应,脚本具有读取和修改 HTTP 数据包的权限,这就给自动化解密提供了基础

首先点击工具栏上的脚本按钮,或者按 Shift+Ctrl+P 开启脚本功能,需要确保系统中有 Python3 运行环境

屏幕截图 2023-10-17 223132.png

当看到这个界面时,证明脚本运行环境已经正确

如果看到提示脚本环境异常,就去 python.org 自己下一个吧

2023-10-17T14:41:34.png

这时距离脚本环境配置完毕只有一步之遥,还需要安装一个脚本的 API 库reqable-scripting,使用 pip 安装即可,它的源码在 https://github.com/reqable/python-scripting-api

pip install reqable-scripting

2023-10-17T14:44:49.png

接下来右键点击工具栏上的脚本按钮,再点击新建脚本(或者快捷键 Shift+Alt+P 也行

2023-10-17T14:46:05.png

这时会弹出一个脚本编辑器的窗口,它可以直接编写 Python 脚本,无需第三方编辑器(但没有补全和静态分析

这个脚本非常容易上手,只有两个关键的入口函数onRequest(context, request)onResponse(context, response),分别对应请求和响应的事件函数

参数context对于两个函数意义相同,可以读取当前捕获数据包的传输层——会话层信息,还可以从onRequest()onResponse()传递任意数据

参数request对于请求数据包,可以读写 url、方法、头部等,也可以读写请求体;与之对应的response就是响应数据包了,也可以读写 响应状态字、头部、响应体等

所以把这两个函数、三个参数玩明白后,你就掌握了 Reqable 脚本的全部编写技巧,可以实现高级的数据包加工了,具体的接口使用方法可以查阅官方文档 https://reqable.com/docs/capture/addons

至于上面的名称URL,就容易理解多了,Reqable 支持管理多个脚本,可以为它命名和独立开关,URL参数使用通配符匹配它针对的 URL 地址,减少脚本的编写难度和 bug

2023-10-17T14:47:43.png

由于网易云音乐的 REST API 都是以https://interface.music.163.com/eapi/起始的,所以我们可以为其写一个通配符*加在末尾,匹配符合条件的 URL

这里使用request.methodrequest.path分别打印请求方法和请求路径,测试脚本是否正常执行,可以看到右侧的调试窗口已经能输出信息了

2023-10-17T15:49:38.png

接下来就是使用它捕获请求并实时解析,访问request.body.payload提取请求体,并使用 Python 的内置库urllib.parse对 url query 表单进行解析,提取出params字段的值,送入之前定义好的函数进行解密,再打印出返回的两个参数到控制台

可以看出已经成功解析不少接口的请求数据了

2023-10-17T16:08:16.png

至于响应体的解密,如法炮制,在onResponse()函数下添加相应的处理流程

在脚本编辑器的右侧调试窗口,可以看到响应体已经成功被解密了,看内容大致应该是和 VIP 提示相关的

2023-10-19T03:03:03.png

输出的内容有优化空间,可以适当加一些标识符,用来区分每条消息的类型,也可以关闭时间戳,让输出看起来更简洁

另外,根据多个抓包记录的共同特征,请求体中的header字段为头部信息,是一个序列化后的 json 字符串,内部包含设备特征标识、登录用户的 Token 等等敏感信息,用作测试为了脱敏和减少干扰信息,可以把这部分替换为***

请求体消息相对较少,为了方便调试可以让它以多行输出,使用 python 的内置库pprint

2023-10-19T05:11:45.png

最终的脚本代码如下,可以作为实验的参考:

from reqable import *
import urllib.parse
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import json
from pprint import pprint

EAPI_KEY = b"e82ckenh8dichen8"
EAPI_CRYPTOR = AES.new(EAPI_KEY, AES.MODE_ECB)

def eapi_decrypt(enc_data):
    message = unpad(EAPI_CRYPTOR.decrypt(base64.b16decode(enc_data)), 16).decode()
    path, json_val, hash = message.split("-36cd479b6b5-")
    return path, json.loads(json_val)

def data_decrypt(enc_data):
    data = unpad(EAPI_CRYPTOR.decrypt(enc_data), 16).decode()
    return json.loads(data)

def onRequest(context, request):
  enc_params = urllib.parse.parse_qs(request.body.payload)['params'][0]
  path, params = eapi_decrypt(enc_params)
  params['header'] = '***'
  print('========== ReqStart ==========')
  print(context.url)
  print(path)
  pprint(params, width=1, indent=2)
  print('========== ReqEnd ==========')
  return request

def onResponse(context, response):
  enc_data = response.body.payload
  data = data_decrypt(enc_data)
  print('========== RspStart ==========')
  print(context.url)
  print(data)
  print('========== RspEnd ==========')
  return response

可能这时候就会有冰雪⑨小聪明会问“为啥一定要在调试窗口中查看参数呢?”

非常好,其实姐姐我也想在 Reqable 内置的协议分析窗格中查看啊,用脚本把加密的数据解密回去,虽然可以在 Reqable 中看到,但服务端和客户端只接收密文,那么一定会造成解密失败无法执行

希望 Reqable 开发团队可以加入脚本只解析并修改用于展示的数据功能

2023-10-19T06:00:34.png

开始抓包分析

对于抓包,是一个技术与经验叠加的工作,只看各种零散的数据包是远远不够的,需要观察进行了什么操作后会产生数据包,接收到响应后视图内容发生了什么变化,接口返回和视图所展示的信息有什么关联

这里我抓取一个短信验证码登录的接口,当按下“发送短信验证码”后,观察调试终端中输出了相关的数据和接口 URL

这里 URL 是https://interface.music.163.com/eapi/sms/captcha/sent这是一个标准的 REST API,即通过路径对接口功能分类,是一种约定成俗的 C/S 网络接口编写范式,那么根据字面含义,这个接口的功能就是发送短信验证码

2023-10-19T06:29:48.png

请求体数据如下,这里忽略掉公共环境参数e_rheader,所以关注的参数就是cellphonectcode,它们两个字段的值刚好对应前端输入的“国际冠字号”和“手机号码”,与字段名的含义一致

{
    "cellphone": "153********",
    "ctcode": "86",
    "e_r": true,
    "header": "***"
}

至于返回格式,就是一个标准的 json RPC,它具有返回值和返回信息,因为前端没有看到任何报错,即调用成功

{
    "code": 200,
    "data": true
}

收到验证码后填写到文本框中,点击登录后立即产生了一个请求,URL 是https://interface.music.163.com/eapi/w/login/cellphone即手机号+短信验证码登录接口

请求数据中含有与上一个接口一致的countrycodephone参数,短信验证码captcha这里是5256,也有checkToken这种与之前接口没有明显联系的数据,但看到它的数据是一段 base16,应该是某种签名校验的 hash

登录成功后这个接口的响应数据中就会存在类似token这样的用户认证数据,作为其他接口的身份验证依据,是客户端个性推荐、收藏、社交、会员等交互功能的基本材料

2023-10-19T06:50:04.png

当点击播放一首音乐时,分别产生了如下几个请求,播放地址、歌词、相关推荐、评论区

但产生的请求响应中没有歌曲信息的数据,这是因为播放的入口是收藏歌单,这些数据在加载歌单时已经进行了一个 preload,如果从搜索或者外部链接进入,那么就会单独拉取歌曲的基本信息

2023-10-19T07:09:22.png

这个接口https://interface.music.163.com/eapi/song/enhance/player/url/v1的作用就是获取播放地址,它的返回数据中包含了一个 mp3 文件的 http 直链

2023-10-19T07:26:52.png

获取歌词的接口也是单独存在,URL 为https://interface.music.163.com/eapi/song/lyric,这个接口会根据请求参数中的歌曲资源 id 查找对应的歌词,返回三种类型的歌词,分别是原始歌词、翻译歌词、罗马音歌词,都是标准的 lrc 格式

2023-10-19T07:35:05.png

对照接口数据与试图中的内容,这个 URLhttps://interface.music.163.com/eapi/discovery/simiPlaylist应该是相似歌单

2023-10-19T07:45:15.png

那么最后一个接口就是评论区了,返回内容很长,里面包含评论区的评论正文

2023-10-19T07:56:14.png

所以只要掌握了方法,懂得按照路径名和请求参数去归纳分析,有操作后观察产生请求的意识,就可以知道每一个 REST API 所调用的功能了

对比返回数据与视图中内容的联系,并且猜测字段名,就能知道其中的数据含义

结语

总体使用下来,Reqable 是一款定位明确、功能强大的工具,除了主要的抓包和调试功能外,还有很多调试接口常用小工具。作为一位野生开发者,之前都是使用 Postman+Fiddler 进行调试,可谓两个重量级工具,现在有了 Reqable 就可以将它们功能合一使用。

目前这款软件在 PC 三大平台还在频繁更新版本中,移动端计划发布,期待能拿到移动端的内测权限,推荐指数⭐️⭐️⭐️⭐️⭐️