美文网首页
腾讯课堂app 离线视频格式破解——完结

腾讯课堂app 离线视频格式破解——完结

作者: 钢琴师2005 | 来源:发表于2019-10-16 23:10 被阅读0次

    之前因为温习3dsmax,在腾讯课堂app上缓存了不少的视频,因为缓存在手机观看屏幕比较小,一直想把缓存的视频转移到PC上看,对于我这种信号时有时无的人来说,手机是最好的找信号工具,故视频缓存在手机上了。

    先放完整代码和转换结果:


    由.sqlite文件转换成的ts片段(还没合并)
    # _*_coding:utf-8 _*_
    # @Time    : 2019/10/16 16:04
    # @Author  : Shek 
    # @FileName: run.py
    # @Software: PyCharm
    import sqlite3 as db
    from Crypto.Cipher import AES
    
    
    def db_fetcher(filename: str):
        '''
        处理.sqlite文件的入口
        :param filename: .sqlite文件名
        :return:
        '''
        caches_table_name = 'caches'
        con = db.connect(filename)
        cu = con.cursor()
        result = cu.execute('SELECT * FROM {}'.format(caches_table_name))
    
        data = result.fetchall()
        AES_KEY = data[1][1]
    
        for i in range(2, len(data)):
            raw = data[i][1]
            dump_name = 'dump-{}.ts'.format(i)
            plain = aes128_decrypt(raw=raw, key=AES_KEY, dump_file=dump_name)
            if plain:
                print('{} of {} dumped succeed'.format(i - 1, len(data)))
    
    
    def aes128_decrypt(raw: bytes, key: bytes, iv: bytes = b'0000000000000000', dump_file: str = ''):
        '''
        二进制文件的AES-128解密
        :param raw: 原始二进制内容
        :param key: AES-128文件二进制内容(16bytes)
        :param iv: AES_IV
        :param dump_file: 保存文件名
        :return: 正常True,异常False
        '''
        data = raw
        cipher = AES.new(key, AES.MODE_CBC, iv)
        plain = cipher.decrypt(data)
        try:
            open(dump_file, 'wb').write(plain)
            return True
        except Exception as e:
            print(e)
            return False
    
    
    db_file = '1e6e9a425ee02902acd996fa5f87eff4.m3u8.sqlite'
    db_fetcher(filename=db_file)
    
    

    缓存数据位置

    腾讯课堂app的数据存储在Android/data/com.tencent.edu文件夹中,并且使用sqlite数据库文件进行离线视频进行存储,这首先就很奇怪了,数据库文件一般存的是数据,怎么会存储媒体文件呢?结合数据库经验,猜测应该是在数据库中以blob类型进行存储。所谓blob类型,就是二进制对象,以二进制格式存储所有类型的数据,尽管blob类型在文件后处理方面有一定的优势,但是会在一定程度上降低数据库的性能(使用Navicat打开的时候,我都怀疑屏幕坏了,一卡一卡的……)

    数据库结构

    使用navicat我们打开其中一个sqlite文件,其中有两张表:metadata和caches,这里我们重点关注caches表。


    caches表内容,另外可右键-设计表可以查看到value字段是blob类型

    这印证了之前的推测,sqlite数据库文件中,利用blob类型形成了“类目录”,在里面塞入了.ts视频文件片段、m3u8目录信息和解密密钥(密钥这个后面说)。

    再回到caches表中,可以将表中数据按行分为3类:

    1.第一行:视频片段目录信息(m3u8)

    2.第二行:AES密钥

    3.第三行以及以后:视频分段文件,一行代表一个

    来看一下第一行数据:

    #EXTM3U

    #EXT-X-VERSION:3

    #EXT-X-KEY:METHOD=AES-128,URI="https://ke.qq.com/cgi-bin/qcloud/get_dk?edk=CiCBJj%2BLeHsBnilhenVC3KnMSpmaTIzwGJ%2FWPpEf7symChCO08TAChiaoOvUBCokOTMyNDg4YmItOWZjYS00MzFiLWJiYjItNjFmMDhjYjNlYmM3&fileId=5285890791427386588&keySource=VodBuildInKMS&token=dWluPTE0NDExNTIxMjMwMDk0MTg2MDt0ZXJtX2lkPTEwMDM5Nzc1Njtwc2tleT07ZXh0PTU1ZTczOGQzNjc1YTI2Nzc2YzkxODA4M2FmZTJiMjMwZDIwNzY4Y2M4MDUyMGI4ZTljMzUyZTZiZDA5NDFlOTA3NWYwZWIzYjY2MjNmNjVmODRhNTFiZjFiYjMzYzMwMDM1Y2NmOTYxMjFmNzgzODRjNTc5NWRiNzlhNDU3ZGJlZDhlMWU2NjlkYTgwZWU4Mw",IV=0x00000000000000000000000000000000

    #EXT-X-MEDIA-SEQUENCE:0

    #EXT-X-TARGETDURATION:10

    #EXT-X-KEY:METHOD=AES-128,URI="https://ke.qq.com/cgi-bin/qcloud/get_dk?edk=CiCBJj%2BLeHsBnilhenVC3KnMSpmaTIzwGJ%2FWPpEf7symChCO08TAChiaoOvUBCokOTMyNDg4YmItOWZjYS00MzFiLWJiYjItNjFmMDhjYjNlYmM3&fileId=5285890791427386588&keySource=VodBuildInKMS&token=dWluPTE0NDExNTIxMjMwMDk0MTg2MDt0ZXJtX2lkPTEwMDM5Nzc1Njtwc2tleT07ZXh0PTU1ZTczOGQzNjc1YTI2Nzc2YzkxODA4M2FmZTJiMjMwZDIwNzY4Y2M4MDUyMGI4ZTljMzUyZTZiZDA5NDFlOTA3NWYwZWIzYjY2MjNmNjVmODRhNTFiZjFiYjMzYzMwMDM1Y2NmOTYxMjFmNzgzODRjNTc5NWRiNzlhNDU3ZGJlZDhlMWU2NjlkYTgwZWU4Mw",IV=0x00000000000000000000000000000000

    #EXTINF:10.000,

    v.f30741.ts?start=0&end=273743&type=mpegts

    #EXT-X-KEY:METHOD=AES-128,URI="https://ke.qq.com/cgi-bin/qcloud/get_dk?edk=CiCBJj%2BLeHsBnilhenVC3KnMSpmaTIzwGJ%2FWPpEf7symChCO08TAChiaoOvUBCokOTMyNDg4YmItOWZjYS00MzFiLWJiYjItNjFmMDhjYjNlYmM3&fileId=5285890791427386588&keySource=VodBuildInKMS&token=dWluPTE0NDExNTIxMjMwMDk0MTg2MDt0ZXJtX2lkPTEwMDM5Nzc1Njtwc2tleT07ZXh0PTU1ZTczOGQzNjc1YTI2Nzc2YzkxODA4M2FmZTJiMjMwZDIwNzY4Y2M4MDUyMGI4ZTljMzUyZTZiZDA5NDFlOTA3NWYwZWIzYjY2MjNmNjVmODRhNTFiZjFiYjMzYzMwMDM1Y2NmOTYxMjFmNzgzODRjNTc5NWRiNzlhNDU3ZGJlZDhlMWU2NjlkYTgwZWU4Mw",IV=0x00000000000000000000000000000000

    #EXTINF:10.000,

    v.f30741.ts?start=273744&end=470959&type=mpegts

    ...

    #EXT-X-KEY:METHOD=AES-128,URI="https://ke.qq.com/cgi-bin/qcloud/get_dk?edk=CiCBJj%2BLeHsBnilhenVC3KnMSpmaTIzwGJ%2FWPpEf7symChCO08TAChiaoOvUBCokOTMyNDg4YmItOWZjYS00MzFiLWJiYjItNjFmMDhjYjNlYmM3&fileId=5285890791427386588&keySource=VodBuildInKMS&token=dWluPTE0NDExNTIxMjMwMDk0MTg2MDt0ZXJtX2lkPTEwMDM5Nzc1Njtwc2tleT07ZXh0PTU1ZTczOGQzNjc1YTI2Nzc2YzkxODA4M2FmZTJiMjMwZDIwNzY4Y2M4MDUyMGI4ZTljMzUyZTZiZDA5NDFlOTA3NWYwZWIzYjY2MjNmNjVmODRhNTFiZjFiYjMzYzMwMDM1Y2NmOTYxMjFmNzgzODRjNTc5NWRiNzlhNDU3ZGJlZDhlMWU2NjlkYTgwZWU4Mw",IV=0x00000000000000000000000000000000

    #EXTINF:1.154,

    v.f30741.ts?start=3967568&end=4075119&type=mpegts

    #EXT-X-ENDLIST

    咋一看,非常典型的配置类信息,这就是M3U8目录信息的存储行(第一行),记录了整个视频文件应该由哪些片段进行合成、时间位置、格式版本号等等。那么M3U8是什么呢?

    M3U8

    简单在网上搜索了一下,参考文章 M3U8格式讲解及实际应用分析,M3U8主要用于多码率适配,根据网络带宽,客户端自动选择一个适合自己码率的文件进行播放,保证视频流的流畅,而M3U8是M3U文件的拓展,对照样本来看一下:

    EXT-X-VERSION:3

    版本信息,可以没有。

    EXT-X-MEDIA-SEQUENCE:0

    定义当前m3u8文件中第一个文件的序列号,每个ts文件有固定的序列号,用于MBR时切换码率进行对齐。

    EXT-X-TARGETDURATION:10

    定义每个TS的最大长度。

    EXT-X-KEY:METHOD=AES-128,URI="https://ke.qq.com/cgi-bin/qcloud/get_dk?edk=CiCBJj%2BLeHsBnilhenVC3KnMSpmaTIzwGJ%2FWPpEf7symChCO08TAChiaoOvUBCokOTMyNDg4YmItOWZjYS00MzFiLWJiYjItNjFmMDhjYjNlYmM3&fileId=5285890791427386588&keySource=VodBuildInKMS&token=dWluPTE0NDExNTIxMjMwMDk0MTg2MDt0ZXJtX2lkPTEwMDM5Nzc1Njtwc2tleT07ZXh0PTU1ZTczOGQzNjc1YTI2Nzc2YzkxODA4M2FmZTJiMjMwZDIwNzY4Y2M4MDUyMGI4ZTljMzUyZTZiZDA5NDFlOTA3NWYwZWIzYjY2MjNmNjVmODRhNTFiZjFiYjMzYzMwMDM1Y2NmOTYxMjFmNzgzODRjNTc5NWRiNzlhNDU3ZGJlZDhlMWU2NjlkYTgwZWU4Mw",IV=0x00000000000000000000000000000000

    定义加密方式和密钥文件的地址,获得16字节的密钥解码ts文件,这里METHOD=AES-128表示使用AES-128进行加密/解密,URI表示密钥文件位置/路径,其中的IV应该是与AES有关的一个参数,类似于偏移量?(在Crypto.Cipher.AES中查看references时看到过)

    EXTINF:10.000,

    v.f30741.ts?start=0&end=273743&type=mpegts

    一些基本信息,数据内容的长度、文件名、时间对齐、文件类型等。

    总结下来就是:
    caches表第一行:m3u8文件内容
    caches表第二行:AES-128解密文件(16bytes)
    caches表其余行:ts文件分片
    现在基本已经明确了腾讯课堂app缓存文件的数据格式,其实就是m3u8的数据库拓展格式,在一个sqlite文件中利用blob存放了m3u8目录文件、ts分片文件和可能用到的AES-128解密文件(16bytes),根据这个思路,下面开始写解密程序,这里需要用到外部库pycrypto。

    import sqlite3 as db
    from Crypto.Cipher import AES
    
    def aes128_decrypt(raw: bytes, key: bytes, iv: bytes = b'0000000000000000', dump_file: str = ''):
        '''
        二进制文件的AES-128解密函数
        :param raw: 原始二进制内容
        :param key: AES-128文件二进制内容(16bytes)
        :param iv: AES_IV
        :param dump_file: 保存文件名
        :return: 正常True,异常False
        '''
        data = raw
        cipher = AES.new(key, AES.MODE_CBC, iv)
        plain = cipher.decrypt(data)
        try:
            open(dump_file, 'wb').write(plain)
            return True
        except Exception as e:
            print(e)
            return False
    
    def db_fetcher(filename: str):
        '''
        处理.sqlite文件的入口
        :param filename: .sqlite文件名
        :return: 
        '''
        caches_table_name = 'caches'
        con = db.connect(filename)
        cu = con.cursor()
        result = cu.execute('SELECT * FROM {}'.format(caches_table_name))
    
        data = result.fetchall()
        AES_KEY = data[1][1]
    
        for i in range(2, len(data)):
            raw = data[i][1]
            dump_name = 'dump-{}.ts'.format(i)
            plain = aes128_decrypt(raw=raw, key=AES_KEY, dump_file=dump_name)
            if plain:
                print('{} of {} dumped succeed'.format(i - 1, len(data)))
    

    执行:

    db_file = '1e6e9a425ee02902acd996fa5f87eff4.m3u8.sqlite'
    db_fetcher(filename=db_file)
    

    总结
    以上代码只是验证,并未完善,有兴趣的朋友可以继续深化,封装类,写GUI等。回头想了想,还是文件名*.m3u8.sqlite给我提供了思路,不然看着那么大一个文件,我应该没什么勇气扔进winhex里比对文件头,第一反应就是整个文件都被加密了,却不曾想到腾讯课堂app中首先在外层套的还是一个正常的外衣:一个数据库,然后在里面存放需要的媒体数据。至于使用的AES-128加密,是归咎于m3u8提供了此选项,并不是腾讯课堂app的设计功能,所以才会导致AES-128解密文件共同存放于一个数据库文件中的情况,没有引起重视。
    文章发布时已通过微信联系微信团队进行处理,只是聊天画风有点:

    马爸爸不要封我

    终。
    --------分割线 2019.10.18更新
    关于m3u8信息、aes密钥和ts片段信息的位置,后来实际使用发现并不是严格的按数据行区分,有时候还会获取多次aes密钥(尽管内容一样)甚至先存储ts片段再下载aes密钥。对于此,本文代码无法完美处理,修正、封装好的类已于本文发布第二天完善,详细请看:(文章未完成)
    --------分割线 2019.11.14更新
    封装好的类已同步更新到原github仓库,同时提供了adb支持,打开手机调试模式,直接转换到本地计算机,详细请看:
    tencent-edu-wrapper:http://github.com/r00t1900/tencent-edu-wrapper


    Vultr 2020年优惠:注册就送100美金,领取你的第一台VPS

    相关文章

      网友评论

          本文标题:腾讯课堂app 离线视频格式破解——完结

          本文链接:https://www.haomeiwen.com/subject/vejpmctx.html