这是一次完整的 Android 直播 App 逆向调试记录。目标是“音速直播”(hdpfans/bukatv 6.0.2)。文章按真实时间线展开,几十个 mod 迭代(内部代号 M4 → M22 v8.7)每一步都有命令、报错和根因证据。先把结论放前面:登录页能干净拆掉、频道解密链能完全接管、最后甚至让一批地方台重新出画——但 App 自带那套 tvbus 直播源,被一道“签名与运行代码文件级绑定”的墙锁死,客户端改包从根上过不去。 这篇讲的就是这道墙是怎么被一层层证明为“此路不通”的,以及最后绕过它的工程落点。
一、目标与调试环境
想做什么
- 去掉启动时的登录/会员遮罩,让 App 直接进播放界面;
- 让 App 自带的直播源(CCTV、各省台、各类第三方聚合源)正常出画。
第一条最终达成了,而且是整个项目第一次让 mod 出画的里程碑。第二条牵出一条极长的逆向链,分化成两个结论:App 自带的主力源(tvbus)客户端无解,但可以“换血”——用自愈源把一批频道重新打活。
环境与工具链
- 真机:Android 平板 TB331FC(Android 15),通过内网反向隧道(lan-debug 一键接管)+ termux 里的
adb/sshd远程操作,全程自己装包、拉日志、抓 logcat,不靠人工点屏。 - 反编译:
apktool 2.10、jadx 1.5.0、androguard(dex 全量指令解析,dexdump 在 qemu 下 SIGILL 不可用)。 - 构建容器是
aarch64,而 Android SDK build-tools 是x86_64,所以aapt2/zipalign必须套qemu-x86_64-static:
# 真二进制改名 .real,wrapper 转发给 qemu
# wrapper 里 dirname 必须用单引号/printf 写,否则 $(dirname $0) 会被提前展开成 "."
exec /usr/bin/qemu-x86_64-static "$(dirname "$0")".real "$@"
- 签名:自建 keystore(
CN=mod,一套证书贯穿所有 mod),adb install -r可覆盖安装。 apksigner在 qemu 下 launcher 脚本会 RC=1 无输出,直接java -jar lib/apksigner.jar绕开。
两个贯穿全程的坑:
- 设备是
non-debuggable的 release 环境,adb shell只有uid=2000(shell),读不到 App 私有目录/data/user/0/bukatv.com/,运行时数据只能靠 logcat 探针回传。 - 无线 adb 端口在每次覆盖安装 /
pm clear后被系统随机重配(历史端口 35805→37097→33425→41027…)。纪律是:连不上不猜旧端口、不全端口扫描(曾把设备扫到 adb offline)、绝不adb kill-server(会顺手砍掉设备 SSH),直接问新端口,所有命令显式-s 127.0.0.1:<port>。
二、第一个幻觉:P2P 引擎的 cid empty
点开任意频道,logcat 第一时间报:
[Native] start failed, cid empty
Pcdn errorcdoe:10100 缺少 client_id 参数
看着像阿里 PCDN 加速引擎没拿到 client_id。顺着 smali 追调用链:
App.smali:499
PcdnManager.start(ctx, "live", pt(ctx), null, "speed", null)
└─ v3 = HdpParams.pt(ctx) // native
→ AccMgr.start(ctx, clientId=pt结果, …)
→ startAcc → PcdnAcc.start(accClientId, …) // accClientId 空 → 10100
client_id 来源就是 native 方法 pt(),在 libhdp_params.so(30KB,armeabi-v7a)里。这是动态注册的 JNI(nm 看不到),自己写 pyelftools + capstone 脚本反汇编:
JNI_OnLoad→FindClass("com/hdpfans/params/HdpParams")→RegisterNatives注册pt/os/ok三个方法,注册表在0x3f1c;- 导入符号表全是 C++ 标准库与异常处理符号,零 syscall、零属性读取、零网络 →
pt()是纯计算函数,输出与设备/签名/网络无关。
既然纯计算就该能脱机跑出来。于是 Unicorn 模拟 v1→v16(pt_unicorn1.py~pt_unicorn16.py):处理 R_ARM_RELATIVE 重定位、栈 canary、NEON vpush/vpop 被当非法指令要 hook 跳过(0x446a vpush、bfc 被误跳的修正放到 v16)、Thumb 对齐、GOT 应用……始终取不回明文(0x4118 那段是解码表不是字符串)。又试 qemu-arm + stub so(mkstub.sh 造空 libandroid/log/m/dl/c/c++_shared),dlopen 因 bionic NEEDED 与 glibc 冲突失败;termux 32 位编译缺 crt。模拟路线整体放弃。
改用最笨最可靠的办法——真机日志补丁 M21:在 App.smali 的 PcdnManager.start 之后插一段 Log 打 pt() 返回值(插在 v0-v2 被重赋值之前的安全区)。装机,等约 32 秒(Android 15 后台启动限制,装完要等前台),日志出来:
M21TAG: pt()=[] len=0
pt() 恒返回空串。 client_id 从来就是空的——不是 mod 改坏,是原版就这样。那个 cid empty 在原版上一直在报。
结论石锤:阿里 PCDN 引擎(期望监听 58088 端口)在这个 App 上从未真正启动过,是个纯摆设。 追 client_id 是条死胡同——引擎不起根本不影响播放,因为真正的播放压根不走它。
这是第一个方法论教训:报错最响的地方不一定是问题根源。 排错前先确认“原版是否也这样”。
三、解密链还原:8 对字符替换 + 把 native 搬进 Java
播放不走 P2P 引擎,那真实地址从哪来?追 LivePlayActivity 的数据流,还原出完整解码链:
hd_config/channel-configs.json(匿名)
→ hdptvapk.maoxiongtv.com/data/channels_new_v2/{组}/{版本}_product.zip(匿名, 11KB)
→ zip 内单文件 JSON: live[].urllist("#" 分隔的密文串)
→ StreamDecode.decode() // 8 对字符替换
→ norm() // 协议归一化
→ playUrl
原版 decode 的隐藏校验
原版 StreamDecode.decode 是 native(libstream_decode.so)。从 init_array CTOR trace 把它逆出来,机制很阴:
- decode 先跑一个 helper(raw syscall 不经 libc),拿返回值跟一个循环变换缓冲的首字节比:
- 相等 → 原样透传输入(密文直达播放器 →“数据或服务异常”);
- 不等 → 走替换分支,遍历全局链表(占位符 → 实际串)重组 URL。
- helper 干了什么:
openat /proc/self/cmdline读包名(包名就是密钥来源之一);openat /proc/self/maps逐块读,在内容里indexOf8 个 OEM 包名(bukatv.com、com.dazzle.tv…),提取 maps 里以.apk结尾的路径行做多轮魔数变换。
这解释了三个实测现象:
- 官方原版能解:包名
bukatv.com+ 原版 base.apk → helper 校验过 → 链表非空 → 出明文; - 改包名 mod:helper 返回 == C → 透传 →“数据或服务异常”(M5/M6 日志 DEC-IN==DEC-OUT 实锤);
- 原包名重签名:helper ≠ C 走替换分支,但对 maps 里 base.apk 路径的文件校验失败 → 链表空 → 输出空串 →“当前频道无法播放”。
也就是说,原版 decode 的密钥与(包名 + base.apk 文件本身)双重绑定,改包名或重签名都不行。
关键一刀:把 native 搬进 Java(M7)
既然算法逆清了,就不跟 native 校验硬刚——M7 直接用 Java smali 复刻 decode,整体绕开 native 校验。复刻出来的解密表是 8 对纯字符替换(键是 2 字符 {X,没有右括号,很反直觉):
{r → http {q → p2p {y → :// {1 → .
{k → fans {x → m3u8 {g → dli {e → com
真机 A/B 对比实锤:M6(native)段 DEC-OUT==DEC-IN 透传;M7(Java)段全部解密,8 对码表逐一命中,CCTV9/7/东方/6/13 零异常。
这一刀的意义极大:解码链从此完全在 Java 层掌控,成为注入任意自定义协议处理的完美挂点——后面 M22 的自愈源正是挂在这里。
CCTV-1 的 12 条线全部解码成功,形态五花八门:
b4bus{y…tv→b4bus://…tv(tvbus 风格 token)06m{y49{1232{1143{1153:82/…→06m://49.232.143.153:82/…(明文 IP,但 403)20gu{y6088074mi→20gu://6088074mi→ norm →p2p://migu2_6088074(咪咕 contId)/1{yproxy_{g0387{q→p2p://proxy_dli0387/…(远程代理解析)
norm() 是复刻时补齐的 6 条协议归一规则(r0-r5,非原版逻辑):虎牙 huya5://、tvbus://、proxy_dli、http、咪咕 migu2_、线路代号 m/t→http 各一条。
到这里解码链 100% 掌握。但解出的地址能不能播,是下一个问题。
四、源地址的真相:五类源,五种死法
把解密探针放行 [Core][Resolve] 标签后,拿到每频道逐源的明文 + 播放结果。逐类定性:
- 明文 IP 直链(
49.232.143.153:82/49.233.252.171:81,84,腾讯云):VPS 出口 403、家宽出口 405,裸路径同样 403/405,与 sign/UA/Range/Referer 全无关 → 源站两层过滤(安全组白名单 + nginx 规则)。原版同样放不出。 - heibaitv http 源:服务端返回
{"code":1008,"message":"sign值超时"}。sign 是打包时写死在 zip 里的(md5hex-<num>np),客户端续不了;换 num 还是 1008(盐在服务端)。官方拉同一个 zip、同一个过期 sign,官方也播不了 heibaitv。 http://6652489143789527这类裸数字:非法 URL 占位(原版就这样,不是残缺数据)→ 秒败。p2p://proxy_dli远程解析:http://p.maoxiongtv.com:8088/dli<KEY>/<anything>?v=2&key=<任意>—— 服务活着,key 还不校验,但无论传什么都返回同一条兜底流(360 看点新闻直播);而且play5.kandian.360.cn的 CNAME 已查无 A 记录(360 云下线)。活着但没用:全频道映射到同一画面,没意义。huya5:///ya3:///migu2://:见下文与第十节。
Spider 插件链:为什么“原版也播不了”
App 的 APK 里只有 dex,没有解析器本体。真正干活的是运行时热更下载的插件包 hdp.jar(@Plugin 注解 10 个 API 类全指 pack="hdp.jar"、main=HdpPluginApi)。手里这份 200KB 的 hdp.jar 解开只有 classes.dex + META-INF,没有任何 .so。
还原 j/b.java(SpiderBoss 基类,TAG 经 XOR 解密确认),看懂了那个让人迷惑的“原样回显”:
j.b.c(str):
1. 查缓存(分钟级)
2. b(str) → 子类解析
3. 成功 → 写缓存返回真实 URL
4. 失败/返回 null → 【原样回显 str】 ← fallback
所以 logcat 里看到 解析数据 -> tvbus://xxxx(协议串原样出现)不是 bug,是解析失败的兜底回显,playUrl 实际拿到的就是这个播不了的原串。
写 scan_e_table.py 把 18 个 Spider 文件的 e() 前缀认领表全 XOR 解密出来(共 85 个 Spider):
l/*→ 各省台 HTTP 解析(黑龙江 / 重庆 / 甘肃 / 合肥 / 四川…)t/b(WebViewSpider)→ 按 63 条域名数组认领(浏览器自动化)r/a/r/b/r/c→p2p://proxy_dli的三个变体(proxy_dli0364/proxy_dli185/proxy_dli6/)
关键发现:huya5://、ya3://、migu2:// 全插件无任何 Spider 认领。 也就是说——原版 App 装在原厂环境里,这些源同样播不出来。服务器频道表仍在下发这些串(机房数据没清、留给旧版插件)。local 组 1168 台里还有 45 个坏模板(y4290{{r 残缺串,福建县级台那批)。
这一条彻底澄清责任:不是 mod 弄坏的,是源生态本身烂掉了。
虎牙源:签名墙,搁置
huya5:// 对应虎牙。页面 gameStreamInfoList 能取 sStreamName/sHlsUrl/sHlsAntiCode。实测:房间 113528/113529/660134 离播;/lpl 在播,抽到完整流信息,直接拉流 403;按标准 anticode(fm base64 解码 → md5(wsSecret),wsSecret=md5(seq+rand+uid+streamName+wsTime+secret))重签仍 403;海外 IP 403、家宽 http 80 节点 405。虎牙流签名 2024 后多轮加固,社区方案接连失效。自实现性价比太低,搁置。
五、tvbus 签名墙:三变量对照把根因钉死
剩下的主力源是 tvbus://(约 160 条,占源主体),走 TVBus P2P 引擎 libtvcore.so。关键疑问只有一个:官方能播 tvbus,mod 不能——差异到底在包名、文件内容,还是签名?
用严格的单变量对照钉死:
- 先纠正一个早期错判:曾以为“官方 APK 里没有
libtvcore.so”——那是看错了下发清单,不是 APK 本体。libtvcore.so(856612B)就在官方 APKlib/里,mod 也有,同一个文件。所以 tvbus 不是天然死源,官方能播。 - M16(只改包名):
bukatv.mod→bukatv.com(Manifest package + 4 个 provider authority + BuildConfig 同步)。实测行为与改之前完全一致 → 包名不是判据。 - M17(官方原包字节不动,仅重签):真机无画面 → 实锤引擎按 APK 签名 拒服。
三变量(包名 / 内容 / 签名)逐一单变量对照完成:内容无关(M17 用原包)、包名无关(M16)、签名 = 决定因素。
libtvcore.so 里有 jni_package_name/hash、getSignHashCode、MD5/RSA/ECC——这是 binstream 商用 SDK 的原生 license 校验(读 Context 签名)。但函数体静态加密(ELF 头明文、.text/.rodata 高熵、init_array 10 个函数;capstone 反汇编几条即断)。入口 0x5e408 一片混乱,静态 patch 死路。官方原版 SDK 同样加壳。
于是方向转向 Java 层签名欺骗——让 native 经 Context → PackageManager → PackageInfo.signatures 拿到官方证书。这条路走了很久,也翻了很多车。
六、签名欺骗的九条命:M18 系列翻车实录
目标:App.attachBaseContext 把 base context 包成 SignContext,其 getPackageManager() 返回 SignPackageManager,后者在 getPackageInfo 里把 signatures[0] 换成官方证书 DER(895B)。听起来简单,实际一路是 ART 校验的雷:
- M18(首版):
SignPackageManager只实现 9/95 个抽象方法 →PackageManager抽象类一被全局替换,App 启动即AbstractMethodError崩,YDebug都没建起来 → 表现成“日志功能被删”。教训:子类化系统抽象类必须补全所有抽象方法。 - 写生成器(
javap -p PackageManager.class→ smali 转发桩)把 95 个方法全转发 base,只在getPackageInfo两个重载里 swap。参数/返回泛型要剥(<.*>),move-result按返回类型选-object/-wide/裸。 - M18 日志版:
SignHolder.<clinit>寄存器覆盖 bug——new-instance v0覆盖了 v0 里的byte[],invoke-direct {v0,v0}=Signature.<init>(Signature 自身)→ VerifyError →ExceptionInInitializerError。修:.locals 2,byte[]留 v0,Signature存 v1。 - M18g(反射二次 attach):
getDeclaredMethod("attachBaseContext")抛NoSuchMethodException——它声明在父类ContextWrapper。改 superclass 上溯。 - M18n(StackOverflow):
SignContext.getPackageManager里invoke-virtual {p0} ContextWrapper.getPackageManager→ 虚分派回自己 → 无限递归。教训:smali 里写父类名 ≠ super 调用,改getBaseContext().getPackageManager()。 - M18n2(ClassCastException):
androidx.startup InitializationProvider把 context 强转Application,getApplicationContext()返回SignContext就炸。铁律:getApplicationContext()必须返回真Application。 -
真凶现形(M18j 隔离诊断包):把包装全摘掉,只在 onCreate(日志就绪后)隔离构造
SignPackageManager+ swap,全 catch。日志打出spm-fail:VerifyError: SignPackageManager.extendVerificationTimeout(IIJ)Vinvoke只传了 4 个寄存器,而J型long占 2 个槽,漏了一个 → ART 类验证直接拒载 →SignPackageManager永远加载不了。这就是 M18e/g/h/i 全部“闪退 + 零日志”的唯一根因。 补上槽位,全类参数槽审计仅此一处。
这一串翻车本身就是极好的教材:apktool 构建不做链接期符号/寄存器校验,错误全推迟到运行时 ART 校验才爆;注入前必须 grep 目标方法签名逐参核对、审寄存器宽度。
七、平板实测的决定性判决:native 根本不走 Java
签名欺骗类加载终于通了(spm-constructed + spm-swap siglen=895,gpi-called 72+ 次,App 自身和系统调用都在经欺骗 PM)。真机上机验证,结果是决定性的:
- M18q(App 层
getPackageManager覆写):引擎jtvcore [Inited]:0 0 0成功,但PLUGIN_SpiderBoss解析tvbus://仍原样回显(null)= 引擎拒服。解析线程全程gpi-called=0→ native 不走 Java PM 取证。 - M18r(再覆写
getApplicationInfo):Java 层调用 93/71 次全过覆写,但引擎 init 线程与解析线程零 gpi / 零 gai → native 连ApplicationInfo都不碰。
对照官方版(同机):tvbus → Started:40 → Prepared: http://127.0.0.1:8602/mpegts_live → Stats 520KB 流量,引擎完整工作。
结论闭环:libtvcore.so 拿到 context 后,直接读 APK 文件层签名块(或经 ContextImpl 直达系统 PM / 从 /proc/self/maps 的 base.apk 路径读),完全不经任何 App 实例的 Java 面。 所以:
- ① Java PM 签名欺骗 —— 死(与 M17 重签黑屏互证);
- ②
sourceDir/ApplicationInfo重定向 —— 死(native 不碰 Java ApplicationInfo)。
递进梯只剩 ③:绕开 tvbus 引擎换源。静态 patch 加密函数体 / 运行时 self-dump + 内存 patch(注入 native 读 /proc/self/maps + mprotect 回写)属高复杂度高风险,不划算。
八、M19:拆登录页,拿到第一帧画面
把登录机制逆清(全 smali 实锤):接口 POST /user/checkoutLoginInfo(token) 失败 → LiveCorePresenter 的生命周期回调里 token 空就 getLoginType → utype!=1 → 强跳 LoginActivity/PaymentActivity(半透明遮罩);非空则会员信息流失败 → SP.clear() 清登录态 + 回登录页。
关键事实:播放管线(presenter 层)零 member/Login 引用。 登录是账号运营层,和播放能力彻底解耦。Jay 记忆里“能播、过一会弹登录框”= 官方包在 utype=1 免登录窗口期播放照常启动(半透明遮罩后面视频在演),服务器动态调 utype(时段/灰度)才弹框,不是客户端检测。
M19 的四刀全在 Java 层,与签名战场无关:
LiveCorePresenter的清 SP 方法 → NOP;- token 空的强跳分支 → NOP;
- 失败回调 → 仅
super(不清号不跳); - 仓库
SP.clear→ NOP(任何路径都清不掉登录态)。
实测:首启 6 秒直达 LivePlayActivity(此前所有版本首启都停在登录页),连续 60+ 秒 0 次 LoginActivity 启动,进程稳定。
2026-10-05 12:00 实机确认 M19 正常播放——这是本项目首次 mod 出画面。心跳日志实锤播放来源:每分钟 PLUGIN_RemoteApiBaseEp "读取缓存: 20261005/migu-631780532",走插件 RemoteApi 缓存路径(前一天缓存的直连 URL),不经 tvbus 引擎(全时段 0 条 jtvcore 解析行)。这正解释了为什么签名墙没拦住它——它走的是另一条后备路径。
九、“中午能播晚上死”的真相:服务端黑名单
M19 出画后,很快出现“这台中午能看、晚上黑屏”。根因在 channel-configs.json 里挖到一个远程可变的黑名单开关:
blockVersionChannel = {
channelNums: [1..142 几乎全台],
streamPregs: [migi_.*, migu2_.*, proxy_dli0116.*, proxy_dli10086.*, migux_.*]
}
消费链:播放前 presenter 判 num ∈ channelNums 且 getCurrentEpgIsBlocked(epgId)=true → urls 里匹配 streamPregs 的源全部 removeAll 剔除 → 咪咕 / p2p 族源在播放前被静默拔掉。
这就是“中午能播晚上死”“以前能看现在不能”的机制:服务端按台、按 EPG 时段开关的黑名单。M19 时代咪咕能播,靠的是 RemoteApi 前一天的缓存绕过了 Spider;服务器收紧 + 缓存过期后全灭。咪咕官方 API 设备端裸请求也 403(需 app 内鉴权),自实现也不通。
十、M22 HealSource:自愈源,让地方台重新出画
既然解码链已经完全在 Java 手里(第三节那一刀),就在这里挂一个自愈层:把播不了的 stub 源,换成一个独立维护的、可达的直连源。
找到一个真实可用的源站
http://74.91.26.218:82/live/{name}hd.m3u8是真多频道源:cctv1hd~cctv15hd全 200,301 按次签 token → 后端:88/applive/<dir>/segment_NNNNN.ts,不同频道不同目录,分片真实可下(实测 1.97MB TS)。设备家宽直达,无需授权。- 风险:主页是“服务器升级及付费服务公告”页,随时可能收费/关停 → 架构必须可换源。
iptv-org等公开源列表在设备上全灭(海外域名 DNS 不通)。
挂点与迭代(v3 → v8.7)
HealSource.smali(新类,挂在 Java 版 StreamDecode.decode 之后):remember(stub,name) 把 tvbus:///p2p:///huya5:// 前缀的源存进 Map,resolve(url) 命中则归一化频道名拼成 74.91.26.218 的直连 m3u8。
真正的播放挂点踩了好几次:
setVideoURI路径不生效,真实播放调用栈是IjkVideoView的混淆方法ʻᐧ()(openVideo)→AndroidMediaPlayer.setDataSource,于是 hookʻᐧ:读Uri字段后 resolve,不同就写回。- 频道名从哪拿:试了
reloadChannel(model=null 空转)、switchChannel(根本没被调),最后锁定onPlayChannel入口统计桥,每次播放必走,setName(频道名)稳。
测活方法论:200 不够,要下分片
一个关键教训:那个源站对任意名字都回 200 + 错误 JSON(假阳性),所以活台判定必须:① 验返回内容是 #EXTM3U;② 下载首个 TS 分片 > 10KB(segtest)——湘潭两台就是靠这步被剔除的。
设备端对 local 组 561 台全量 probe:223 台 200 可用,DIRECT_MAP 收 223 条(218 分片验证 + 4 省级卫视 + 广丰)。
实机验证成功
direct hit: 云南卫视 -> yunnanhd.m3u8(stub→learn→查表→换直连全链贯通);- v8.7 补上“http 死线也改写”(表内活台原生 urllist 第一条常是死线,而 App 播死线不 fallback → 黑屏):记忆台 1010 山东教育卫视 →
direct hit(http line)×3 →sdetv活源 →RX_DELTA=965KB/15s(≈65KB/s 真拉流)。
在役 yinsu_M22_heal v8.7(DIRECT_MAP 223 条)。验收路径:切到清单内台(1010 山东教育 / 803 浙江系 / 2221 内蒙古系等)即出画。
残余缺口诚实列出:轮播台(carousel 组 99 台 = 虎牙房间号)无 Spider + 虎牙 anti-403,无解;南昌/江西等真地方台原生线全死(proxy_dli 废 + 直连 403),74.91 源也没有对应真台;各省非上述卫视没有直连源。
十一、最终结论与几条经验
把所有线索收拢,这个 App 的“直播源”最终可行域:
- tvbus://(约 160 条,源主力):✗ 客户端无解。
libtvcore.so按 APK 文件层签名拒服,native 不经 Java 面,静态 patch(函数体加密)死路。三变量对照(M16 包名 / M17 重签 / 内容)+ 平板调用探针(gpi/gai 全零)双重实锤。 - 明文 IP 直链:✗ 源站白名单,VPS 403 / 家宽 405,原版同样放不出。
- heibaitv http:✗ sign 服务端时效,写死在 zip 里续不了,官方拉同包也死。
- proxy_dli:✗ 解析后端已废,只剩一条指向已下线 360 CDN 的兜底流。
- huya5 / ya3 / migu2:✗ 原版无 Spider 认领 + 虎牙签名墙 + 服务端
blockVersionChannel黑名单。 - 自愈直连源(M22,223 条地方台):✓ 可用。但依赖一个随时可能收费的第三方源站,架构必须可换源。
两条现实路线:
- 路线 A:官方包 + 运行时屏蔽登录(M19 四刀),接受“只有部分源能播”。但官方源今天实际可播率已很低(主力 tvbus 被签名锁、其余后端腐烂)。
- 路线 B:把去登录页的壳当播放器,自己灌直连源 / 接独立维护的 IPTV(M3U + EPG)。路线 B 才可持续——路线 A 依赖的是别人正在腐烂的后端。
几条经验
- 报错最响的地方不一定是根源。
cid empty响了一路,最后发现 PCDN 引擎原版就是摆设。排错前先确认“原版是否也这样”。 - 把 native 搬进 Java,是拿控制权最值的一刀。
StreamDecode一旦落到 Java,解码链随便挂钩(M22 自愈源全靠它),比硬刚加壳 so 划算得多。 - 模拟执行(Unicorn/qemu)对 bionic native 性价比极低。 16 版模拟没出结果,一条真机日志补丁 5 分钟见真相。能上真机别死磕模拟。
- apktool 不做链接期检查,错误全推迟到 ART 校验。 寄存器宽度(
long占 2 槽)、抽象方法补全、super调用不能靠写父类名——注入前逐参核对,否则就是“闪退 + 零日志”。 - 判根因要用单变量对照 + 调用探针,别靠推测。 tvbus 签名墙是靠三变量对照 + gpi/gai 全零钉死的;中途两次“定性推翻”都因证据面不全——探针过滤串必须先枚举目标日志标签全集再定。
- “活着”不等于“有用”。 proxy_dli 服务在线、不校验 key,却只回兜底死流;源站对任意名字回 200——测活必须下分片验内容,别信状态码。
- 区分“我没搞定”和“这事本来做不到”。 大量时间花在证明后者——把“原版也播不了”钉死,和找到一个能播的挂点,是同等重要的结论。
- 源生态的腐烂速度决定了这类聚合 App 的保质期。 再漂亮的客户端 mod 也救不回一个停更的后端;唯一可持续的是自己掌握源。
本文记录一次纯技术性的逆向分析,所有操作针对本人持有的设备与 App 副本,用于研究其内部机制。文中涉及的授权密钥、签名参数、代理凭据等均为分析过程描述,具体值不公开。




Comments | NOTHING