ts帧加密案例(一)

查看 144|回复 11
作者:我是不会改名的   
ts帧加密案例(一)
一、前言
最近比较忙,期末了,抽了点空写了下(抽了半天复习了一下,然后就考试了),写这个主要是总结一下学的知识,做个记录,预计两到三个案例,争取春节前写完。
看本文之前先学习一下下面几篇,了解一下基础知识
FLV:不许动手动脚
m3u8的ts文件的PES加解密分析以及示例
某德地图矢量瓦片逆向(快速wasm逆向),c/c++/c#可调用,执行wasm2c翻译出来的c代码一
二、配置相关环境
以win10为例
配置FFmpeg
下载FFmpeg,在这里https://github.com/BtbN/FFmpeg-Builds/releases,选择ffmpeg-master-latest-win64-gpl-shared.zip
解压(最好不要有中文),添加到环境变量

测试环境

配置C语言

下载wabt工具包
https://github.com/WebAssembly/wabt/releases/tag/1.0.34
添加到环境变量,略
环境测试
我用的clion,其他工具应该差不多
创建一个项目,c和c++无所谓

在CMakeLists.txt里面添加
set(FFMPEG_DIR D:\\ffmpeg)#ffmpeg的路径
include_directories(${FFMPEG_DIR}\\include)
link_directories(${FFMPEG_DIR}\\lib)
link_libraries(avcodec  avformat avutil )
运行下面代码,输出hello 52pojie,没有报错说明没有问题了
#include
/*
for c
#include "libavutil/log.h"
#include "libavutil/aes_ctr.h"
*/
//c++ 必须加extern "C",否则会报错
extern "C" {
#include "libavutil/log.h"
#include "libavutil/aes_ctr.h"
}
int main(void) {
    av_log_set_level(AV_LOG_INFO);
    char tmp[128] = {0};
    char plain[16] = "hello 52pojie";
    char result[128] = {0};
    int ret = 1;
    const uint8_t *iv;
    struct AVAESCTR *ae = av_aes_ctr_alloc();
    struct AVAESCTR *ad = av_aes_ctr_alloc();
    const char *key = "hello 52pojie!!!";
    if (av_aes_ctr_init(ae, (const uint8_t *) key)
`
三、怎么判断是否是帧加密
ts加密通常分为整体加密,文件头加密,帧加密以及混合加密。
整体加密
整体加密顾名思义就是对文件整体加密,这是最常见的加密方式。只需要按照188字节一组,判断开头是不是0x47就知道了
文件头加密
只对文件开头一定长度加密或者插入另外文件格式的文件头,盗版网站用的比较多,毕竟白嫖图床谁不喜欢。同样的只需要按照188字节一组,判断第多少组以后是0x47,或者直接看文件头内容
帧加密
帧加密就比较麻烦了,也分为很多种。
首先在学习了参考上面文章后,可以把帧加密分为,整体pes加密,nalu头加密,nalu内容加密。
首先是pes整体加密,有代表性的就是某里系列。
利用010,并下载h264模板来分析。

首先是未加密的ts

然后是加密的ts

很明显看到出来,少了很多nal单元,只有pes头,其余信息都不存在了。
然后是nal头加密,比较常见的就是v13

可以看到能识别出nalu,但是缺少了很多,比如sps,sei,还有这个pps大的离谱了,关键帧也不见了。
最后就是nalu内容加密,直接播放视频就行,有声音花屏,也有可能音频也加密了;

四、某网站ts加密分析
目标网站aHR0cHM6Ly90di5jY3R2LmNvbS8yMDIzLzEwLzMwL1ZJREVBSEdrWnBqMldYcm1oUWV3dzVVMDIzMTAzMC5zaHRtbD9zcG09Qzg0MTExLlBaTzIySm1qTWhKRS5TMTU0NDAuMTE=
js分析
js就不多分析了,就一个wasm加载的js文件是ob类混淆,唯一需要注意的是,流媒体播放基本都是采用多线程,worker来播放,需要手动添加断点替换js文件

断下以后,看看堆栈,根据不同nalu类型判断是否加密,首先实现解析出nalu类型以及长度

FFmpeg解析出pes
创建项目配置CMakeLists.txt和上面一样
首先初始化目录以及设置log,初始化上下文打开文件等
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
AVFormatContext *fmt_ctx = NULL;
const AVInputFormat *fmt = NULL;
const char *filename;
const char *audiofile;
const char *videofile ;
FILE *faudio;
FILE *fvideo;
int video_index = -1;
int audio_index = -1;
void init() {
    if (chdir("../")
然后打开输入流,查找相关信息
    if (avformat_open_input(&fmt_ctx, filename, fmt, NULL)
bool find_streams() {
    for (int i = 0; i nb_streams; i++) {
        if (fmt_ctx->streams->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_index = i;
        } else if (fmt_ctx->streams->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            audio_index = i;
        }
    }
    if (video_index == -1 || audio_index == -1) {
        printf("find video stream or audio stream failed\n");
        return false;
    }
    //av_dump_format(fmt_ctx, 0, filename, 0); //打印视频信息
    return true;
}
分离音视频流,需要注意的是这里的packet并不是完整的pes流,而是纯码流,所以pes整体加密方式这样是不行的。
int split() {
    AVPacket *packet = av_packet_alloc();
    while (av_read_frame(fmt_ctx, packet) >= 0) {
        if (packet->stream_index == video_index) {
            fwrite(packet->data, 1, packet->size, fvideo);
        } else if (packet->stream_index == audio_index) {
            fwrite(packet->data, 1, packet->size, faudio);
        }
        av_packet_unref(packet);
    }
    av_packet_free(&packet);
    return 0;
}
解析nalu单元
主要参考这个代码cgts_nal_adts_parse.c
int decode_video(AVPacket *packet) {
    AVCodecParameters *codecpar = fmt_ctx->streams[video_index]->codecpar;
    uint32_t buf_start_pos = 0;
    uint32_t nal_start_pos = 0;
    uint32_t nal_end_pos = 0;
    uint8_t nalu_type;
    uint32_t nalu_header_size=3;
    while (find_nal_unit(packet->data, packet->size, buf_start_pos, &nal_start_pos, &nal_end_pos)) {
        uint32_t len = nal_end_pos - nal_start_pos+1-nalu_header_size;
        if (codecpar->codec_id == AV_CODEC_ID_H264) {
            uint8_t *nalu_start_ptr = packet->data + nal_start_pos;
            nalu_type = find_nal_type_avc(nalu_start_ptr);
        } else if (codecpar->codec_id == AV_CODEC_ID_HEVC) {
            uint8_t *nalu_start_ptr = packet->data + nal_start_pos;
            nalu_type = find_nal_type_hevc(nalu_start_ptr);
        }
        if (nalu_type==1||nalu_type==25||nalu_type==5){
            uint8_t *nal = packet->data + nal_start_pos+nalu_header_size;
        }
        buf_start_pos = nal_end_pos + 1;
    }
    return 0;
}
和在线对比一下,部分长度多了1

对应到ts上发现,这部分下一个文件头都是0x00,0x00,0x00,0x01开头的,修改下代码

find_nal_unit
        if ( nalu_start_found == true && nalu_end_found == true ) {
            (* nal_start_pos) = nalu_start_pos;
            (* nal_end_pos) = nalu_end_pos;
            return true;
        }
        if ( nalu_start_found == true && nalu_end_found == true ) {
                    if (buf[nalu_end_pos] == 0x00 && buf[nalu_end_pos+1] == 0x00 && buf[nalu_end_pos+2] == 0x00 && buf[nalu_end_pos+3] == 0x01) {
                nalu_end_pos = nalu_end_pos - 1;
            }
            (* nal_start_pos) = nalu_start_pos;
            (* nal_end_pos) = nalu_end_pos;
            return true;
        }

这次就一样了
wasm转c
wasm文件下载,大多数网站并不会直接给wasm文件地址,而是以base形式给出,直接搜索data:application/octet-stream;base64,
或者AGFzbQEAAAABm。除此之外,还有某些网站压缩了wasm,文件以br结尾。如果都不行就需要hook对应函数了。或者找到加载部分。
看了上面wasm转c转dll,案例可以知道,我们主要需要的是导入部分。
而web端导出参数,是在加载部分。开发文档介绍中

WebAssembly.Instance() 构造函数以同步方式实例化一个WebAssembly.Module 对象。然而,通常获取实例的方法是通过异步函数WebAssembly.instantiate() .

很容易就定位到,传入了两个参数,一个wasm文件,一个导入参数

主要需要实现的是env,导入了函数,表,内存以及全局变量等
下面就讲讲如何用c去实现
首先利用wabt里面的wasm2c,将代码转换为c,wasm2c int.wasm -o out.c,然后将wbat工具包目录下的wasm-rt.h、wasm-rt-impl.c、wasm-rt-impl.h,以及转出来的文件放一起。
转出来的文件很大尽量不用动,新创建一个imp.c,方便调试需要调试对应代码,直接从out.c里面复制出来,不然直接卡死了。
然后就是在js端对所有导入函数下断点,判断是否用到或者视必须的。
#include "cctvn.c"
u32 *w2c_env_DYNAMICTOP_PTR(struct w2c_env * v) {
    return &v->DYNAMICTOP_PTR;
}
u32 *w2c_env_0x5F_table_base(struct w2c_env *v) {
    return &v->__table_base;
}
wasm_rt_memory_t *w2c_env_memory(struct w2c_env  *v) {
    return &v->memory;
}
static wasm_rt_funcref_table_t table;
wasm_rt_funcref_table_t *w2c_env_table(struct w2c_env * v) {
    return &v->table;
}
void w2c_env_0x5F_0x5FsetErrNo(struct w2c_cctv*, u32){
    return;
}
/* import: 'env' '___syscall140' */
u32 w2c_env_0x5F_0x5Fsyscall140(struct w2c_cctv*, u32, u32){
    return 0;
}
/* import: 'env' '___syscall146' */
u32 w2c_env_0x5F_0x5Fsyscall146(struct w2c_cctv*, u32, u32){
    return 31;
}
/* import: 'env' '___syscall54' */
u32 w2c_env_0x5F_0x5Fsyscall54(struct w2c_cctv*, u32, u32){
    return 0;
}
/* import: 'env' '___syscall6' */
u32 w2c_env_0x5F_0x5Fsyscall6(struct w2c_cctv*, u32, u32){
    return 0;
}
/* import: 'env' '__emscripten_fetch_free' */
void w2c_env_0x5F_emscripten_fetch_free(struct w2c_cctv*, u32){
    return;
}
/* import: 'env' '_emscripten_asm_const_ii' */
u32 w2c_env_0x5Femscripten_asm_const_ii(struct w2c_cctv* v, u32 i, u32 i1){
    u32 len=1;
    uint8_t *d;
    switch (i) {
        case 0:
        case 1:
            if (28352 == i1) {
                d = (uint8_t *) malloc(1);
                memcpy(d, "", 1);
            } else if(28384 == i1) {
                d=(uint8_t *)malloc(56);
                memcpy(d,"https://tv.cctv.com/3cba73e8-4f6c-4d45-a53f-9131c471990a",56);
            }
        case 2:
            if (28480 == i1) {
                d = (uint8_t *) malloc(5);
                memcpy(d, "blob:", 5);
            } else if(28512 == i1) {
                d=(uint8_t *)malloc(56);
                memcpy(d,"https://tv.cctv.com/3cba73e8-4f6c-4d45-a53f-9131c471990a",56);
            }
    }
    u32 ret = w2c_cctv_0x5Fmalloc(v, len);
    memcpy(v->w2c_env_memory->data + ret, d, len);
    return ret;
}
/* import: 'env' '_emscripten_get_heap_size' */
u32 w2c_env_0x5Femscripten_get_heap_size(struct w2c_cctv* v)
{
    return v->w2c_env_memory->size;
}
/* import: 'env' '_emscripten_is_main_browser_thread' */
u32 w2c_env_0x5Femscripten_is_main_browser_thread(struct w2c_cctv*){
    return 0;
}
/* import: 'env' '_emscripten_memcpy_big' */
u32 w2c_env_0x5Femscripten_memcpy_big(struct w2c_cctv* v, u32 dst, u32 src, u32 len){
    memcpy(v->w2c_env_memory->data + dst, v->w2c_env_memory->data + src, len);
}
/* import: 'env' '_emscripten_resize_heap' */
u32 w2c_env_0x5Femscripten_resize_heap(struct w2c_cctv*, u32){
    return 0;
}
/* import: 'env' '_emscripten_start_fetch' */
void w2c_env_0x5Femscripten_start_fetch(struct w2c_cctv*, u32){
    return;
}
/* import: 'env' 'abort' */
void w2c_env_abort(struct w2c_cctv*, u32){
    return;
}
/* import: 'env' 'abortOnCannotGrowMemory' */
u32 w2c_env_abortOnCannotGrowMemory(struct w2c_cctv*, u32){
    return 0;
}
/* import: 'env' 'getTempRet0' */
u32 w2c_env_getTempRet0(struct w2c_cctv*){
    return 0;
}
/* import: 'env' 'jsCall_ii' */
u32 w2c_env_jsCall_ii(struct w2c_cctv*, u32, u32){
    return 0;
}
/* import: 'env' 'jsCall_iidiiii' */
u32 w2c_env_jsCall_iidiiii(struct w2c_cctv*, u32, u32, f64, u32, u32, u32, u32){
    return 0;
}
/* import: 'env' 'jsCall_iiii' */
u32 w2c_env_jsCall_iiii(struct w2c_cctv*, u32, u32, u32, u32){
    return 0;
}
/* import: 'env' 'jsCall_jiji' */
u32 w2c_env_jsCall_jiji(struct w2c_cctv*, u32, u32, u32, u32, u32){
    return 0;
}
/* import: 'env' 'jsCall_v' */
void w2c_env_jsCall_v(struct w2c_cctv*, u32){
    return ;
}
/* import: 'env' 'jsCall_vi' */
void w2c_env_jsCall_vi(struct w2c_cctv*, u32, u32){
    return ;
}
/* import: 'env' 'jsCall_vii' */
void w2c_env_jsCall_vii(struct w2c_cctv*, u32, u32, u32){
    return ;
}
/* import: 'env' 'setTempRet0' */
void w2c_env_setTempRet0(struct w2c_cctv*, u32){
    return ;
}
void w2c_cctv_f57(w2c_cctv* instance, u32 var_p0, u32 var_p1) {
我这里把一部分w2c_env替换了,主要是懒得复制内存,web也是共享的内存。
然后就是加载wasm,到这里可能就有疑问了,转出来的c和上面案例的格式都不太一样,还多了个参数。
实际上是官方,修改了代码,加入更多特性,案例参考git
参考官方案例
.c
void wasm_init(){
    wasm_rt_init();//初始化wasm
    wasm_rt_allocate_memory(&cctv_env.memory, 256, 256, false);//分配内存
    cctv_env.DYNAMICTOP_PTR = 28144u;//设置DYNAMICTOP_PTR
    cctv_env.__table_base = 0;//设置__table_base
    wasm_rt_allocate_funcref_table(&cctv_env.table, 160, 160);//分配函数表
    wasm2c_cctv_instantiate(&cctv, &cctv_env);//注册一个wasm实例
    memcpy(cctv.w2c_env_memory->data + 28144u, data_dynamic_base, 4);//设置DYNAMICTOP_PTR的值,因为是外部导入的内存,所以需要手动设置
     //cctv.w2c_env_DYNAMICTOP_PTR = &cctv_env.DYNAMICTOP_PTR;//设置DYNAMICTOP_PTR的指针
}
.h
#include
w2c_cctv cctv;
w2c_env cctv_env;
static const u8 data_dynamic_base[] = {
        0x10, 0x6E, 0x50, 0x00,
};
然后就是实现调用函数
uint32_t i=1;
const char *cctva="https://tv.cctv.com";
uint8_t *decrypt( uint8_t *nal,uint32_t nal_len) {
    u32 ptr_nal = w2c_cctv_0x5Fmalloc(&cctv, nal_len+1024);
    uint32_t a=0;
    memcpy(cctv.w2c_env_memory->data + ptr_nal, nal, nal_len);
    if (i){
        memcpy(cctv.w2c_env_memory->data + ptr_nal+nal_len,cctva,strlen(cctva));
        a+=strlen(cctva);
        i=0;
    }
    uint32_t len=w2c_cctv_0x5Fvodplay(&cctv, ptr_nal, nal_len, a);
    uint8_t *out_nal = (uint8_t *) malloc(nal_len);
    memcpy(out_nal, cctv.w2c_env_memory->data + ptr_nal, len);
    w2c_cctv_0x5Ffree(&cctv, ptr_nal);
    return out_nal;
}
        printf("nalu_type:%d\tnalu_pyload_len:%d\n", nalu_type, len);
        if (nalu_type==1||nalu_type==25||nalu_type==5){
            uint8_t *nal = packet->data + nal_start_pos+nalu_header_size;
            if (len>32){
                uint8_t *out_nal = decrypt(nal,len);
                memcpy(packet->data + nal_start_pos+nalu_header_size, out_nal, len);
            }
        }
运行,查看转出的h264,然后


看来还是有哪里不对,那就只有尝试还原了,然后把编译出的exe拖进ida,查看对应函数,然后,就没有然后了

分析是不可能分析的,这辈子都不可能


000.gif (120.12 KB, 下载次数: 1)
下载附件
2023-12-31 23:27 上传

按下f5,

那就只有靠猜了,利用插件查找加密函数,发现有一个tea

然后在网页中调试,然后就发现func57是tea,func60传入了原始数据地址,解密后地址以及长度,还有个计数的感觉没啥用

那么就可以直接写了
u64 num=0u;
uint8_t *decrypt2( uint8_t *nal,uint32_t nal_len){
    u32 in_nal = w2c_cctv_0x5Fmalloc(&cctv, nal_len+1024);
    memcpy(cctv.w2c_env_memory->data + in_nal, nal, nal_len);
    u32 out_nal = w2c_cctv_0x5Fmalloc(&cctv, nal_len+1024);
    uint32_t len=w2c_cctv_f60(&cctv,nal_len, in_nal, out_nal, num);
    num++;
    uint8_t *out_nal2 = (uint8_t *) malloc(nal_len);
    memcpy(out_nal2, cctv.w2c_env_memory->data + out_nal, len);
    w2c_cctv_0x5Ffree(&cctv, in_nal);
    w2c_cctv_0x5Ffree(&cctv, out_nal);
    return out_nal2;
}
运行

终于可以过个跨年夜了,over!!
五、后记
有人可能会问,转出来的h264和acc怎么重新转为ts,直接用FFmpeg就行了,具体网上搜。但我个人不建议在转换为ts了,直接提取所有ts的码流,合并成mp4就行了。因为,你最后ts转mp4还是要先提取码流在合并,除非你是二进制合并。

文件, 函数

sbwfnhn   

从2023年开始,各平台的视频加密越来越复杂,而且每隔一段时间就变换。
这么下去,没钱没技术的穷人,连个电视都看不起了,BT也不能用,以前免费装广播的年代没了
小叔sir   

看了大佬的帖子,好佩服啊,其实我就是那种自己网站做视频教程的小白中的小白~,也想给自己的网站视频加密~想让别人只可以在我网站看,但是不想他可以下载盗走~~无奈目前只会用ffmpeg做个简单的AES加密~但是不了解怎么样从自己把MP4剪切为ts之后,对这种自己的ts和M3U8后续的文件,如何再加密,还是怎么处理,就全然不了解了,,如果天神大哥,可以出一些后面关于加密的教程或方法就好了,谢谢大哥!
ofo   

写得很详细
emptynullnill   

(和剩下的两三个一起先预支一下精华)
zwtstc   

很详细。大家元旦快乐
webphp   

写的不错,感谢
真爱贤   

大神写得不错啊,
mj6810   

本来我也遇到个ts加密的网站,不知道怎么搞,看到你的分析那么复杂,我还是放弃了吧
tafeita   

强,顶一个!
您需要登录后才可以回帖 登录 | 立即注册