安卓 App 接口逆向:从零到拿到直播源(小白实战教程)---成都橙视 cditv 平台
安卓 App 接口逆向:从零到拿到直播源(小白实战教程)
用你这次真实做过的「金温江 / 金牛」当主线,一步步带你复现。
目标:读完这份文档,你能独立还原一个 App 的接口签名,拿到它的数据。
0. 先建立全局观:到底在干什么?
一个 App 请求服务器数据时,会在请求里带上「签名」(一串看不懂的加密字符串)。服务器验证签名正确才返回数据。
我们要做的,就是搞清楚这串签名是怎么算出来的,然后自己写代码(PHP/Python)伪造出合法请求,绕过 App 直接拿数据。
整个流程就 7 步:
① 抓包找接口 → ② 判断加固 → ③ 脱壳 → ④ 反编译找关键代码
↓
⑤ hook 动态验证 → ⑥ 还原算法并交叉验证 → ⑦ 写代码交付
核心心法:第 ④ 步"看出来的算法"不一定是真的,必须靠第 ⑤⑥ 步用真实抓包数据验证。这一步是灵魂,做对了才算真正还原。
1. 先把这些词搞懂(术语速览)
| 词 | 大白话解释 |
|---|---|
| APK | 安卓安装包,本质是个 ZIP 压缩包 |
| DEX | 安卓 App 的"代码文件",Java 编译后的产物 |
| 加固 | 厂商给 App 套了层壳,把真实代码加密藏起来,防止你直接看 |
| 脱壳 | 把加固壳扒掉,还原出真实代码 |
| 抓包 | 截获 App 和服务器之间传输的请求/响应 |
| 接口 / API | App 请求数据用的网址(如 https://api.xxx.cn/cms/show/...) |
| Header | 请求头,附带在请求里的信息(UA、签名、密钥等) |
| 签名 / sign | 那串加密字符串,服务器用来验证请求合法性 |
| 反编译 | 把 DEX 还原成能读的 Java 代码 |
| hook | 在 App 运行时"挂钩子",拦截它正在调用的方法、拿真实参数 |
| frida | 最主流的 hook 工具 |
2. 准备工具和环境
2.1 手机(关键,最好满足)
- 一台安卓手机,最好是 root 过的(Pixel、一加、小米等好 root 的机型)
- 开启「开发者选项 → USB 调试」
- 用数据线连电脑
为什么必须 root?脱壳和 hook 都需要 root 权限。没 root 也能做一部分(抓包、反编译),但脱壳和 hook 基本做不了。
2.2 电脑(macOS / Windows 都行)
装这几样:
| 工具 | 作用 | 安装方式 |
|---|---|---|
| Python 3 | 跑脚本 | python.org 下载 |
| jadx | 反编译 DEX 看代码 | GitHub 下载 zip 解压即可 |
| frida | hook 工具 | pip install frida |
| frida-dexdump | 脱壳 | pip install frida-dexdump |
| adb | 电脑操作手机 | 安卓 SDK 自带,或 Homebrew brew install android-platform-tools |
2.3 验证环境通了
# 1. 电脑能看到手机
adb devices
# 应该输出类似: 99231FFAZ005SN device
# 2. 手机 root 了(进手机 shell 后能切 root)
adb shell
su
# 出现 # 号说明 root 成功
3. 第 ① 步:抓包,找到目标接口
这是最轻松、最容易有成就感的一步,先从这里练手。
方法 A:手机装抓包 App(推荐新手)
装「HttpCanary」(小黄鸟)或「抓包精灵」,按提示装证书,然后打开目标 App 操作,就能看到它发的所有请求。
方法 B:Charles / Fiddler(电脑端)
手机设代理指向电脑,电脑开 Charles 抓包。
你要找什么?
找到拿直播源/数据的那个请求。以金温江为例,点进直播界面后,会看到类似:
GET https://api.wjnews.cn/cms/show/1533-243.html
Header:
X-Key: gnhBWGSsfxAOslgTwVSkWW
X-Token: 4503a3db26ba97503...(很长一串)
X-Signature: 7.677309910358216
X-Timestamp: 1787105995
记录三样东西:
- 完整 URL(含参数)
- 所有 Header(尤其是带 sign/token/key 字样的)
- 响应内容(返回的 JSON,里面藏着直播源)
💡 抓包这一步就能直接看到直播源地址了(响应 JSON 的
android_url字段)。但它是动态的、会过期,所以还得继续还原签名算法,才能随时自己生成。
4. 第 ② 步:判断 App 有没有加固
# 看 APK 里有没有加固特征文件(360 是 libjiagu,梆梆是 libSecShell 等)
unzip -l 你的.apk | grep -iE "jiagu|shell|protect|libexec"
# 看 dex 数量(加固后往往只剩 1 个壳 dex)
unzip -l 你的.apk | grep classes
MacBook-Pro Downloads % unzip -l com.cditv.rmt.jinniu_2.0.9.apk | grep -iE "jiagu|shell|protect|libexec"
1177848 04-29-2026 10:02 assets/libjiagu_a64.so
914088 04-29-2026 10:02 assets/libjiagu_x64.so
MacBook-Pro Downloads % unzip -l com.cditv.rmt.jinniu_2.0.9.apk | grep classes
10324272 04-29-2026 10:02 classes.dex
判断标准:
- 出现
assets/libjiagu*.so、assets/.jgapp→ 360 加固 classes.dex只有 1 个且很小 → 大概率加固- 反编译后打开 Manifest,
android:name是com.stub.StubApp之类 → 加固
金温江和金牛都是 360 加固,所以必须脱壳。
如果没加固,直接跳过第 ③ 步,从第 ④ 步反编译开始。
5. 第 ③ 步:脱壳(360 加固)
5.1 把 frida-server 装到手机
# 1. 电脑下载 frida-server(版本要和 pip 的 frida 一致)
# 下载地址: https://github.com/frida/frida/releases
# 找 frida-server-<版本>-android-arm64.xz
# 2. 解压后推到手机
adb push frida-server-16.7.19-android-arm64 /data/local/tmp/frida-server
# 3. 给权限 + 以 root 启动
adb shell
su
chmod 755 /data/local/tmp/frida-server
/data/local/tmp/frida-server -D &
5.2 脱壳
# 电脑上,先启动目标 App,再脱壳
frida-dexdump -U -f com.ydtc.goldwenjiang
# 或者 App 已经开着,用:
frida-dexdump -U -n com.ydtc.goldwenjiang
# 会在当前目录生成一堆 dex 文件,这些就是脱壳后的真实代码
⚠️ 版本坑(重要):frida 17.x 起,Java bridge 不再自动注入,Python 直接加载脚本会报
Java is not defined。建议直接用 frida 16.7.19(pip 和 frida-server 都装 16.7.19),省一堆麻烦。
6. 第 ④ 步:反编译,找关键代码
# 用 jadx-gui 打开脱壳后的 dex 目录
jadx-gui 脱壳目录/
怎么搜?从抓包的关键词反推
- 搜接口路径的关键词,比如
getliveurl、cditv、getLiveUrl - 搜签名字段名,比如
X-Key、X-Token、sign - 搜算法关键词,比如
MD5、SHA-512、MessageDigest
金温江案例:最终定位到
com.dlna.http.DlnaPlayController ← 核心类,签名逻辑都在这
打开这个类,能看到几个关键常量和方法:
public static String KS = "CdItV.cN"; // v 参数的盐
public static String CANDO_APP_KEY = "N98oH7wy86y74kX"; // 密钥(注意是默认值)
public static String HEAD_PLAYURL_ZB = "/mobile/cditvplay/getliveurl";
private static String e(JSONObject j) { // 生成 __ 签名的方法
// 1. 取所有 key 排序
// 2. 按序拼接 value
// 3. 加盐后 MD5
}
💡 这一步能看出算法的"雏形",但密钥可能是默认值,运行时会被覆盖。所以不能光看代码,必须做第 ⑤ 步 hook 拿运行时真实值。
7. 第 ⑤ 步:hook 动态验证(灵魂步骤)
写一个 frida 脚本,在 App 运行时拦截它真实发出的请求和真实密钥。
// hook_okhttp.js —— 拦截 okhttp 请求
Java.perform(function () {
var RequestBuilder = Java.use("okhttp3.Request$Builder");
RequestBuilder.build.implementation = function () {
var req = this.build();
var url = req.url().toString();
if (url.indexOf("api.") !== -1) { // 只关心目标域名
console.log("[URL] " + url);
var h = req.headers();
for (var i = 0; i < h.size(); i++) {
console.log("[HDR] " + h.name(i) + ": " + h.value(i));
}
}
return req;
};
});
# 电脑上跑,attach 到 App
python3 attach.py
更狠的招:直接读运行时密钥
不满足于抓请求,直接读关键类的静态字段(这才是真实密钥):
Java.perform(function () {
var D = Java.use("com.dlna.http.DlnaPlayController");
console.log("KEY = " + D.CANDO_APP_KEY.value);
console.log("SERECT = " + D.CANDO_APP_SERCET.value);
console.log("域名 = " + D.SERVERIP_MOB.value);
});
金温江 hook 到的真实值(和代码里的默认值不一样!):
KEY = gnhBWGSsfxAOslgTwVSkWW ← 代码里写的是 N98oH7wy86y74kX
SERECT = KqJGObmMeoUHfeXxykQpxKBNcWdVNHXumeQsaEWX
域名 = https://api.wjnews.cn/cms
⚠️ 360 加固的坑:加固后主进程在 frida 的进程列表里会"隐藏",
enumerate_processes只能看到:pushcore子进程。用adb shell ps -A | grep 包名$拿真实 PID,再按 PID attach。
8. 第 ⑥ 步:还原算法 + 交叉验证(别跳过)
结合第 ④ 步的代码 + 第 ⑤ 步的真实数据,写出算法,然后用真实抓包数据验证。
金温江的完整算法(已 100% 验证)
import hashlib
# 1. v 参数:MD5( 时间戳 + "CdItV.cN" )
t = "1787105995"
v = hashlib.md5((t + "CdItV.cN").encode()).hexdigest()
# = 1a512f6ceaefc649153099bc0be65d4a
# 2. __ 签名:MD5( 按 key 字母序拼接 value(无分隔符) + "Fun.key.CdItV.cN" )
body = {"page":1,"module":"rmt","catid":"1533","contentid":"243",
"reply":"","t":1787105995,"client":"app","v":v}
sorted_keys = sorted(body.keys()) # 字母序
concat = "".join(str(body[k]) for k in sorted_keys) # 拼接 value
__ = hashlib.md5((concat + "Fun.key.CdItV.cN").encode()).hexdigest()
# = a13d17adcfa60abf3944fba3d1426ed1
# 3. X-Token:SHA-512( KEY + (SERECT + signature + timestamp) )
KEY = "gnhBWGSsfxAOslgTwVSkWW"
SERECT = "KqJGObmMeoUHfeXxykQpxKBNcWdVNHXumeQsaEWX"
sig = "7.677309910358216"
token = hashlib.sha512((KEY + (SERECT + sig + t)).encode()).hexdigest()
怎么验证?
拿第 ① 步抓包得到的真实请求,把你算出来的签名和它逐字对比。完全一致才算对。
如果顺序不确定(比如不确定拼接顺序),就全排列爆破:
import itertools, hashlib
parts = ["app", "243", "rmt", "1", "1787105995", "salt"]
target = "抓包得到的签名"
for perm in itertools.permutations(parts):
if hashlib.md5("".join(perm).encode()).hexdigest() == target:
print("匹配顺序:", perm)
9. 第 ⑦ 步:写代码交付(PHP/Python)
算法验证通过后,翻译成目标语言。金温江的 PHP 核心就几行:
$ts = (string)time(); // 秒级时间戳
$signature = strval(mt_rand()/mt_getrandmax()*10.0); // 随机数
$token = hash('sha512', KEY . (SERECT . $signature . $ts)); // X-Token
// 然后带上一堆 X-* header 请求 cms/show 接口,取 android_url
完整可跑的版本见本次交付的 wjnews.php(含温江+金牛多区县版)。
10. 完整复盘:金温江/金牛我是怎么一步步做的
| 步骤 | 实际做了什么 | 关键产出 |
|---|---|---|
| ① 抓包 | frida hook okhttp 拦请求 | 拿到真实 URL + headers + 响应 |
| ② 加固识别 | unzip -l 看到 libjiagu_a64.so |
确认 360 加固 |
| ③ 脱壳 | frida-dexdump | 53 个 dex |
| ④ 反编译 | jadx 搜 getliveurl |
定位 DlnaPlayController |
| ⑤ hook | 读静态字段 | 拿到真实 KEY/SERECT/域名 |
| ⑥ 验证 | Python 复算对比抓包值 | 三套算法全部 100% 匹配 |
| ⑦ 交付 | 写 PHP | 302 跳转直播源 |
金牛的"偷懒"发现:金牛和金温江是同一套 SDK(成都橙视 cditv 平台),类结构、算法、盐值完全一样,唯一区别就是域名 + KEY + SERECT 三处。所以我做金牛时根本没重新脱壳反编译,直接 frida attach 读字段,几分钟搞定。
这个规律记牢:同平台的兄弟 App,逆向成本几乎为零。
11. 我踩过的坑(帮你少走弯路)
| 坑 | 现象 | 解决办法 |
|---|---|---|
| frida 17 的 Java 不可用 | Java is not defined |
降级 frida 16.7.19 |
| 360 加固进程隐藏 | frida 枚举不到主进程 | adb shell ps -A 拿真实 PID attach |
enumerateClassLoaders 报错 |
onMatch of undefined |
别遍历 loader,默认 loader 直接 Java.use |
| 密钥对不上 | 用代码默认值验证失败 | 必须 hook 读运行时值(被 init 覆盖) |
| 接口返回"鉴权失败" | 90003 / 90000 | 检查 X-Token 算法 + 运行时密钥 + 时间戳 |
| 直播源 5 分钟失效 | wsSecret 过期 | 每次实时请求,别缓存 |
12. 给你的学习路径建议
- 先练抓包(第①步):拿任意 App 抓包,看懂请求/响应结构,这是最基础也最好上手的。
- 练反编译(第④步):拿一个没加固的小 App,用 jadx 打开读代码,找接口逻辑。
- 练 hook(第⑤步):装好 frida,hook 一个 App 的请求,能打印出 URL 就算过关。
- 完整走一遍(①→⑦):拿金温江当靶子,从头到尾复现一遍本文档。
- 举一反三:找个成都其他区县的 App(青羊、成华、郫都……大概率
com.cditv.*),用"读字段"的偷懒法,几分钟拿下。
附:关键命令速查
# 看设备
adb devices
# 判断加固
unzip -l app.apk | grep -iE "jiagu|shell|protect"
# 脱壳
frida-dexdump -U -f 包名
# 反编译
jadx-gui 脱壳目录/
# 手机装/启 frida-server
adb push frida-server /data/local/tmp/
adb shell su -c "/data/local/tmp/frida-server -D &"
# 找主进程真实 PID(360 加固必用)
adb shell ps -A | grep 包名$
# 装依赖
pip install frida==16.7.19 frida-dexdump
这份文档对应的实战交付物(PHP、Python 脚本、hook 脚本)都在同一个目录下,对着看更直观。

浙公网安备 33010602011771号