说明:本文只聊接口分析和逆向思路,学习研究用途,不提供任何成品或下载。
一、前言
因为碍于没有钱财购入主题,而各大平台上都没有关于这类的资源,于是结合各路网友经验,从"试用断网下载主题包"这里得到了灵感,结合某米主题商店的接口,摸出了一条资源直链的获取链路。
写这篇主要是想把整个逆向过程记录下来:一是接口是怎么一步步被挖出来的,二是把踩过的坑顺一遍,给有同样需求的朋友留个参考。
二、思路
目标对象是某米自带的主题商店(包名 com.xxx.thememanager,一个系统预装应用)。它里面卖的主题、壁纸,虽然免费,但是字体基本全是付费模式。
最初的想法很简单:付费资源买不起,但"试用"断网因为网速原因,总是还没断网就试用结束了,于是开始做逆向接口,尝试直接获取直链。
三、环境
| 项 |
内容 |
| 目标应用 |
某米主题商店(系统应用) |
| 加固 |
无(代码大量混淆,类名就剩 a/b/c 这种单字母) |
| 抓包 |
手机小蓝鸟抓取 HTTPS 流量 |
| 逆向 |
Jadx 反编译 + Smali 阅读 |
四、定位下载直链
抓"试用"
在应用里随便找一个收费字体点"免费试用",应用会触发下载请求。这时候抓包,能明显看到两类请求:
- 一个详情页请求,返回资源的基本信息(名字、作者、大小、预览图那些)
- 一个下载请求,形如:
GET /thm/download/v2/{资源ID}?category=FONT&device=xxx®ion=CN&... HTTP/1.1
这个 /thm/download/v2/{id} 就是关键。它的返回是一个 JSON:
{
"apiCode": 0,
"apiData": {
"downloadUrl": "https://xxx.market.xxx.com/issue/ThemeMarket/<hash>/资源名.mtz",
"fileHash": "...",
"fileSize": 12345678
}
}
downloadUrl 就是最终能下到 .mtz 包(本质是个 zip,里面装着字体 ttf)的真实地址。到这一步,下载直链基本就拿到了。
逆向拼 URL
光抓到接口不够,还得知道 {资源ID}(下文叫 uuid)从哪来、参数到底要哪些。于是上 Jadx 反编译,直接全局搜字符串 download/v2,能命中一个常量,往上反查调用关系,会落到一个叫 ResourceDownloadManager 的类(混淆后是 com.xxx.t)。
里面核心就一串逻辑,简化一下:
// t.s() 返回最终下载 URL
String url = resource.getOnlinePath(); // 一般没有
if (url == null) {
// 走 online/s.a9(onlineId) 拼出 /thm/download/v2/{id}
RequestUrl req = new OnlineService(ctx).a9(onlineId, info);
url = req.getFinalGetUrl();
}
所以下载 URL 就是一个固定模板 download/v2/{id} 加一堆参数。uuid(资源ID)来自前面的详情请求或者搜索列表。
参数是重点
真正麻烦的是这串请求里跟着的一大堆参数,随便列几个:
device、region、isGlobal、system、version、miuiUIVersion、language、
capability、apk、devicePixel、hwVersion、deviceType、model、networkType……
裸请求一个不带,直接上 /thm/download/v2/{id}?category=FONT,服务端是不认的,要么返回空,要么直接报"下载链接获取失败"。一开始我就是这里卡了很久,以为链接不对,后来对着抓包原样把参数补全才通。
这些参数里有个 capability,值是类似 w,b,s,m,h5,a:2,v:7 这种能力串,也是本地拼出来的,不是哈希,就直接字符串拼凑。整体上这些参数都只是"设备画像",服务端校验的是存在性和格式,不是严格签名——这也是为什么能脱离 App 用脚本模拟请求。
五、搜索拿 uuid
有了下载接口,还得解决 uuid 从哪来。资源列表走的是搜索/推荐接口,路径大概是:
/uipages/search/{TYPE}/index?keywords=xxx&...
其中 TYPE 对应资源类型:THEME(主题)、WALLPAPER(壁纸)、FONT(字体)、RINGTONE(铃声)等。
这里又踩了个坑:字体的 index 搜索接口被服务端特殊对待了。同样带全参数,搜主题能正常返回几十条,搜字体每次都是空。反复换参数、换设备 ID 都没用。
后来发现一个"标签搜索"的旁路:
/uipages/search/tag?type=FONT&tag=行书&cardStart=0&cardCount=30
这个 tag 接口对字体是敞开的。它的语义是"按标签找",搜"行书"会把行书类的字体全捞出来(可能不是精确匹配某个字,但能拿到一批 uuid)。所以最后是做了个 fallback:先试 index 精确搜索,空了自动切到 tag 标签搜索。
六、详情接口
挖到后面还发现一个详情接口:
GET /api/v9/products/{uuid}
带上设备参数,直接返回完整信息,而且连最终下载地址都给了:
{
"name": "××字体",
"downloadUrlRoot": "https://xxx.market.xxx.com/download/",
"downloadUrl": "ThemeMarket/<hash>",
"snapshotsUrl": ["ThemeMarket/...", "..."],
"description": "...",
"score": 5.0,
"downloads": 123456
}
downloadUrlRoot + downloadUrl 一拼,就是完整直链,连 download/v2 那步都省了。预览图也是 downloadUrlRoot + snapshotsUrl[i] 拼出来的。
七、小结
把可复现的接口链规整一下(域名已做模糊处理):
- 搜索:
/uipages/search/{TYPE}/index(精确),失败则 /uipages/search/tag?type={TYPE}&tag={词}
- 详情:
/api/v9/products/{uuid}(自带 downloadUrlRoot+downloadUrl 直链、描述、预览图)
- 下载直链:
/thm/download/v2/{uuid}?category={TYPE}&device=...,返回 downloadUrl
几个印象深的坑,记录一下:
- 设备参数缺一不可:服务端按"参数存在性+格式"校验,别试图精简,老老实实把抓包看到的字段全补上。
- 字体 index 被限:不是你的请求写错了,是服务端对字体这个分类单独做了限制,换个 tag 通道就行。
- 能白嫖详情接口就别调 download/v2:详情接口直接给直链和预览图,少一步请求。
- 自己玩可以,别拿去卖:接口是别人家的服务,批量爬或倒卖容易把口子堵死,也给自己找麻烦。
免责声明:
本文及所涉分析仅限用于学习和研究目的,不得用于商业或非法用途,否则一切后果由使用者自负。请在下载后 24 小时内删除相关样本。喜欢请支持正版软件。