吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1084|回复: 16
上一主题 下一主题
收起左侧

[Android 原创] 【网络逆向】TS到视频HLS加密简单逆向实录

[复制链接]
跳转到指定楼层
楼主
JiGuro 发表于 2026-5-3 14:08 回帖奖励
本帖最后由 JiGuro 于 2026-5-3 14:18 编辑

很久没发关于技术的帖子了,因为最近深陷 Java 的泥潭无法自拔,一直在开发我的 BetterVia 模块,所以就把实战放下了
最近,这个软件点燃了我心中残存的火种,虽然思路非常简单,但是我还是想将思路整理成文章发出来,提供给逆向和网安小白学习借鉴,大佬勿喷。

开篇严正声明:本文仅用于学习逆向、网络安全相关技术,未掺杂任何不良目的。文中的软件已对其名字、图标及相关敏感信息做了模糊处理,本人也不会提供软件原包样品,内容仅作学习交流使用!

让我们进入正题。某一天深夜,我无意间 (实际并非无意) 下载并打开了这个软件:“某心破解版”



找到一个视频,刚想看看,发现竟然要VIP,要不然不给看,感情它“破解”了个寂寞,这简直就是对我的侮辱!于是,我准备挑战该软件。

但是,在动手之前,有一道难题摆在我们面前,我们可以看到播放页面,视频下方进度条展现的视频总秒数仅有10秒,结合弹窗给出的信息,很显然这10秒钟就是视频软件提供给我们的预览片段,这一条视频也不可能只有10秒钟的时长。
这里有两种可能:
1、软件请求服务器,服务器返回完整视频,软件检测到用户不是VIP,于是本地只展示视频的10秒切片;
2、软件请求服务器,服务器验证用户并非VIP,于是只返回10秒切片,软件也就理所应当只获取到了10秒视频。


我们当然希望,实际情况是第1种,因为这样完全可以通过逆向的方式让软件强制展示完整视频。而现实中,这种软件大多都是套壳软件,往往多个软件共用同一个服务器资源,所以一般都会采取第1种方式,这也就大大增加了破解成功的希望。然而,也有少数软件采取第2种方式,平时如果我们遇到第2种情况,都会直接放弃,因为这种情况叫做“服务器验证”,除非你是黑客,能黑进服务器中修改用户信息,要不然几乎无解。

想清楚这些后,开始动手。先用 MT 提取安装包,如图:



查看详细信息,一看包名就知道这是典型的 flutter 架构,一般这种类型的软件都会使用 flutter ,以达到批量套壳生产的目的。
热的心凉了半截,flutter 也代表着逆向难度指数级升高,抱着死马当做活马医的心态,我们尝试打开 UN管理器 ,使用其中的 flutter 应用分析功能瞅两眼:



好嘛,完了,竟然还被混淆了



尝试在常量中寻找类似 VIP 、会员 的字样,不出意料,一无所获。只能看见一堆乱码,没有任何价值。

看来这还是个硬茬,难道就这样放弃吗?不,还有招数!我们的目标是什么?不一定要破解会员,只要能看到完整视频就行。这又回到我们一开始提出的两种猜测,如何验证该软件使用的是哪一种方案呢?此时很多大佬应该都想到了,那就是——抓包。

关于抓包和SSL证书的问题,我之前已经提过了,还有不懂的可以参阅我之前的帖子:https://www.52pojie.cn/thread-2063949-1-1.html (【解密实战】简单的图片加密分析 )

同样,我们使用军哥的 JustTrustMe++ 模块,对某心破解版的 SSL 证书检查机制进行 Hook ,使其能够信任我们的证书。(因为手机没有 Root ,这里使用 LSPatch 对该软件进行修补)





在做完这步之后,我们就可以开始抓包了。
这里使用大家熟知的 小黄鸟(HttpCancry) 进行抓包,打开小黄鸟,并设置目标软件为某心破解版,如图:



我们点击右下角按钮,使小黄鸟进入小窗模式,并打开该软件,重新加载之前需要VIP的视频。

回到小黄鸟,在排除很多无关数据包后,我们可以很清楚地看到抓取到的 M3U8 视频链接,详细信息如下:





但是,刚看到链接的一瞬间,悬着的心终于还是死了 从链接中我们就可以看到:



该链接的形式为:
https://xxx.com/video/2024-08-02/18/1819323645960531968/9db974a1f1604e7192ece82052f8f227_preview.m3u8?t=69f62d81&us=2050613724956127235&sign=cf0c146e9ecafcebb86a16b537ed196102fb9955

我们可以很清楚的看到:一长串字符后面跟着 _preview 这个后缀,翻译成中文就是预览的意思。这意味着,我们好巧不巧的碰到了上述第2种情况——由服务器判断用户是否是会员,然后再根据判断结果返回相应的视频。这意味着,逆向破解软件的思路彻底封死了,就算真有水平能够破解,那么也过不了服务器验证,最终获取的视频还是10秒。当然,肯定有很多同学会想能不能把 _preview 后缀去掉,说不定你就能获取完整的视频流。这种方法我已经替你们试过了,是没用的。

相信很多同学到了这里就肯定会放弃了,但是搞技术靠的就是灵活的思维和应变能力。再继续往下看之前,我觉得有必要给各位普及一下 M3U8 这个格式。

M3U8 本质上并不是视频,而是纯文本索引文件,核心作用是告诉播放器:按什么顺序、用什么密钥、取哪些切片。

先从链接分析,链接后跟着一长串字符,这是典型的CDN 防盗链签名:



该签名的意义在于,即使你知道 M3U8 的路径结构,没有正确的 sign 参数,CDN 会返回  403 Forbidden

再看 M3U8 内容,我们可以在小黄鸟中看到 M3U8 文件的原文:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:11
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="/keyhome/video.key",IV=0x4ff43fb9b8921a8f12fb0f3a08570689
#EXTINF:10.759233,
https://xxx.com/.../xxx.ts
#EXT-X-ENDLIST
我们可以一行一行分析:



所以,M3U8 并不是视频的本质所在,真正能被播放器解码的是上面看到的 TS 文件。
TS(Transport Stream)是 MPEG-2 标准定义的容器格式,设计初衷是抗丢包、抗干扰,适合有线电视广播。TS 的优势在于:
1、TS 是流式容器,不需要像 MP4 那样先读取 moov 头
2、可以在任意位置切割,不需要重新编码
3、每个 TS 切片包含完整的 PES 包,可以单独播放


一个 TS 文件由固定大小的 188 字节包(Packet) 组成,每个包以  0x47  开头:
[4字节 Transport Stream Header][184字节 Payload]
Payload 里封装的是:
PAT(Program Association Table):PMT 包的信息
PMT(Program Map Table):视频流、音频流的 PID
PES(Packetized Elementary Stream):实际的 H.264/H.265 视频帧、AAC 音频帧

我们可以尝试下载该片段的 TS 文件,理论上该文件可以直接被播放器解码。但是,当我们用 MT 管理器打开该 TS 文件时,却报了错:



显然,该 TS 被加密了。这就要引入 HLS 加密技术 了。HLS 规范(RFC 8216)定义了两种加密方式,分别为:

AES-128:
#EXT-X-KEY:METHOD=AES-128,URI="...",IV=...
算法参数:
算法:AES-128-CBC(128 位密钥,CBC 模式)
密钥长度:16 字节(128 位)
块大小:16 字节
填充方式:PKCS#7(如果原始数据不是 16 字节整数倍,末尾补填充字节)

SAMPLE-AES:
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="...",IV=...
这是苹果推出的部分加密方案,不加密整个 TS 包,只加密视频中的关键样本(如 H.264 的 NAL 单元),需配合 FairPlay DRM 使用,需要硬件级解密支持,普通工具无法处理。

我们抓取到的显然是第一种加密,这导致播放器无法正确处理,因为 TS 文件实际上已经经过一层加密。
加密流程如下:
明文 TS 文件
    ↓
分块(每块 16 字节)
    ↓
第一块 + IV → AES 加密 → 密文块 1
    ↓
密文块 1 + 明文块 2 → AES 加密 → 密文块 2
    ↓
... 以此类推
    ↓
加密后的 TS 文件

我们可以注意到,M3U8 文件中存在着 IV 定义:
IV=0x4ff43fb9b8921a8f12fb0f3a08570689
IV 是 16 字节(32 个 hex 字符),它的作用是让相同的明文加密后产生不同的密文。

同时,M3U8 文件中也存在着 Key 定义:
URI="/keyhome/video.key"
这是一个相对路径。所以我们完全可以根据这个相对路径拼接出 key 的绝对地址:
M3U8: https://xxx.com/video/2024-08-02/18/.../xxx.m3u8
Key:  https://xxx.com/keyhome/video.key

当然,我们也可以在抓包软件中看到 key 响应:





Content-Length: 16
Body: sdj3nxuuw3koukvs(16 字节二进制)

虽然终端显示看起来像字符串,但 HTTP Body 是原始二进制数据。sdj3nxuuw3koukvs 这 16 个字符正好是 16 字节,直接作为 AES-128 的密钥使用。

为了验证以上推理,我们可以通过 Python 脚本对加密的试看 TS 进行解密:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import os

try:
    from Cryptodome.Cipher import AES
    from Cryptodome.Util.Padding import unpad
except ImportError:
    from Crypto.Cipher import AES
    from Crypto.Util.Padding import unpad

def decrypt_ts_file(input_path, output_path, key_bytes, iv_bytes):
    """
    解密单个 TS 文件
    :param input_path: 加密 TS 文件路径
    :param output_path: 解密后输出路径
    :param key_bytes: 16 字节密钥
    :param iv_bytes: 16 字节 IV
    """
    with open(input_path, "rb") as f:
        encrypted_data = f.read()

    cipher = AES.new(key_bytes, AES.MODE_CBC, iv=iv_bytes)
    decrypted = cipher.decrypt(encrypted_data)

    # 移除 PKCS7 填充
    try:
        decrypted = unpad(decrypted, AES.block_size)
    except ValueError:
        # 如果填充异常,手动移除末尾填充字节
        pad_len = decrypted[-1]
        if 1 <= pad_len <= 16:
            decrypted = decrypted[:-pad_len]

    with open(output_path, "wb") as f:
        f.write(decrypted)

    print(f"解密完成: {output_path} ({len(decrypted)} bytes)")

def decrypt_ts_bytes(data: bytes, key_bytes: bytes, iv_bytes: bytes) -> bytes:
    """
    解密字节流(适合内存处理)
    :return: 解密后的明文 bytes
    """
    cipher = AES.new(key_bytes, AES.MODE_CBC, iv=iv_bytes)
    decrypted = cipher.decrypt(data)

    try:
        return unpad(decrypted, AES.block_size)
    except ValueError:
        pad_len = decrypted[-1]
        if 1 <= pad_len <= 16:
            return decrypted[:-pad_len]
        return decrypted

if __name__ == "__main__":

    # 字符串密钥(16 字符)
    key_str = "sdj3nxuuw3koukvs"
    key = key_str.encode("utf-8")

    # IV(32 位 hex)
    iv = bytes.fromhex("4ff43fb9b8921a8f12fb0f3a08570689")

    # 文件路径
    input_file = "2024-08-0268.ts"      # 加密的 TS 文件
    output_file = "2024-08-0268_dec.ts" # 解密后的输出
   
    if len(sys.argv) >= 3:
        input_file = sys.argv[1]
        output_file = sys.argv[2]

    if not os.path.exists(input_file):
        print(f"[错误] 文件不存在: {input_file}")
        sys.exit(1)

    if len(key) != 16:
        print(f"[警告] 密钥长度 {len(key)} 字节,AES-128 需要 16 字节")

    decrypt_ts_file(input_file, output_file, key, iv)
运行脚本:





TS 成功解密,可以直接使用播放器播放!

清楚了以上内容,再经过一系列抓包的检验,我们就可以推测出这套机制的安全漏洞:
1、静态密钥
Key 文件长期不变 (经过测试多个视频可知) ,一次泄露永久失效
2、密钥无鉴权
video.key  可以匿名下载,不校验请求者身份
3、IV 可能固定
试看版使用固定 IV,且其他视频试看版 IV 与该视频也相同,完整版极大可能复用相同 IV
4、签名仅保护索引
TS 和 Key 直链无签名,绕过 M3U8 也能直接访问
5、试看版参数泄露
xxx_preview.m3u8  极有可能与完整版同源同密钥,成为信息泄露通道 (同上 IV 固定漏洞)

当然,有以上信息肯定还不够。我们仍然无法获取完整的 M3U8 文件或完整的分片文件。我们还需要找到临门一脚的关键漏洞。相信看到这里有眼尖的同学已经明白了。我们之前说过,M3U8 本质上就是个索引,索引的是 TS 文件。仔细观察 TS 文件链接:
https://xxx.com/video/2024-08-02/18/1819323645960531968/2024-08-0268.ts

不难发现,链接的组成方式为:
https://xxx.com/video/年-月-日/18(未知)/1819323645960531968(类似ID)/年-月-日68.ts

最显眼的冗余元素就是最后的“68”。由于很多 M3U8 索引的 TS 文件都是按编号从小到大按顺序依次排列的,我们可以大胆猜想,最后的“68”数字指的就是第“68”号分片文件。
我们可以在浏览器中将最后的“68”改成其他二位数字,发现可以成功下载到 TS 文件,这证明我们的猜想大概率是对的!

所以,此时我们找到了最大的一个漏洞:切片名可预测
2024-08-02N.ts  理论上顺序递增,结合前面的加密分析和其他漏洞,我们完全可以写一个 Python 脚本进行遍历暴力下载解码:
https://share.weiyun.com/vAQM2wsp
(由于代码太长和文章长度限制,这里附上源码链接,各位也可以在文末下载到)

大概逻辑就是,从 M3U8 中获取 IV 和 Key,然后根据试看 TS 从编号“0”开始遍历 TS 文件,如果连续遇到三个 404 ,那么就代表分片已经结束,然后调用 ffmpeg 合并即可。





可以看到,经过一系列解析,已经下载下来了非常多的 TS 分段





最后,一条 300多MB 的 VIP 视频就被这样下下来了,可以完整不卡顿地观看。可以看到最后实际视频时长为:23分52秒,甚至还比一开始软件中展示的视频时长多了10秒 (后来看多出来的10秒钟应该是下集预告)



感谢你能看到这里!当最后一个 TS 片段被解密、当 ffmpeg 的进度条走到 100%、当播放器里终于出现完整的画面——此刻带来的满足感,远不止“下载了一个视频”那么简单。
回顾整个过程,我们从一条孤零零的 M3U8 链接出发,像拼图一样还原出整套 HLS 加密体系的轮廓:解析 M3U8 的索引逻辑、理解 AES-128-CBC 的加密原理、发现静态密钥分发的脆弱性、绕过 _preview 的访问控制、在 Termux 中重建完整视频,最终用 143 个明文切片拼出完整的视频流。
技术层面的收获是具体的:你掌握了 HLS 协议的结构、AES 加密在流媒体中的实际应用、以及如何用 Python 在移动端完成密码学操作。
但更深层的价值在于思维方式的训练——面对一个封闭系统,如何从最小信息切入,逐层剥离其安全假设,找到设计与实现之间的缝隙。这种“单点突破、体系还原”的能力,从流媒体安全延伸到 Web 渗透、逆向工程、物联网固件分析,本质上是同一套方法论。

这也给各位站长们一个启示:平台方应把 L0 的静态密钥升级到 L3 的动态鉴权,把可预测的切片名替换为随机 UUID,把试看版与完整版的密钥彻底隔离。

我希望各位不止带走了一段能运行的 Python 脚本,更带走了一个问题:下一次当你看到一个 .m3u8 、一个 .mpd 、甚至一个 WebSocket 推流地址时,作为一个技术爱好者,你的第一反应不再是“视频有多好看”,而是“我是否能理解播放和解析的原理?”
答案往往是:能。而且通常比你想象的更容易。

码字不易,点赞可有?
理论上这种方法对于涉及以上未动态分链鉴权漏洞的网站或APP都有效果 (其实60~70%的这类软件都存在这个漏洞,别问我是怎么知道的)。如果有实力的话,甚至可以将这个方案内置进安装包中,直接请求完整视频。
帖子内所有工具都已放在下面,可供大家练手,评论自取。
最后,恭祝各位劳动节快乐!

Python脚本 + 工具:
https://jiguro.lanzouw.com/iiOMO3ol52de

免费评分

参与人数 7吾爱币 +7 热心值 +6 收起 理由
小黄路边歌手 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
jiyu0418 + 1 + 1 谢谢@Thanks!
yangchang1 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
lianliangfo + 1 + 1 谢谢@Thanks!
lp55535 + 1 + 1 非常详细!
tzxinqing + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
52pojieplayer + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
lzy8108 发表于 2026-5-4 07:07
wg521125 发表于 2026-5-4 06:23
感谢大佬分析思路,

逆向和网安小白完整看完,为楼主大佬的精彩分享喝彩点赞。虽然有的地方看不懂,但也不妨碍我对大佬技术的崇拜和向往,对大佬技术面之广的由衷佩服和感叹!还有大佬的文笔也非常不错,逻辑思维和表达能力都非常强,整篇文章环环相扣,引入入胜,是一篇值得推荐的深度技术好文。
推荐
涛之雨 发表于 2026-5-4 13:44
之前试过另外一个视频学习网页就是preview是另外的接口,
每个视频的key是随机的,链接是类似于md5拼接的,没有过滤
导致现在学习受阻
沙发
wg521125 发表于 2026-5-4 06:23
4#
52pojieplayer 发表于 2026-5-4 08:05
感谢大佬分享!对于各个技术要点写得很详细,由点到面,层层推进,逻辑清晰,技术全面!涨知识了
5#
quiet0 发表于 2026-5-4 08:08
大佬,谢谢。学习了亿点点。懂点原理了
6#
晚晚丿轻风 发表于 2026-5-4 10:38
感谢大佬的分享分析
8#
huluwa123 发表于 2026-5-4 17:07
按理说key应该二次加密
9#
tiantangniaone3 发表于 2026-5-4 21:46
卧槽,新人看的好兴奋
10#
loveppya 发表于 2026-5-5 19:47
都找不到样本&#129300;
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-5-12 00:02

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表