千城彩票

吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.5esh.com

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

搜索
查看: 8962|回复: 108
上一主题 下一主题

[原创] HTML5视频解密的方法(widevine的破解思路)

    [复制链接]
跳转到指定楼层
楼主
jimmyzang 发表于 2020-4-22 17:00 回帖奖励
本帖最后由 jimmyzang 于 2020-4-30 07:17 编辑

首先这是继续上一篇的m3u8通用下载, http://www.5esh.com/thread-1161169-1-1.html  这篇文章之所以没有和上一篇合并,原因主要是上篇介绍的是一个通用方法,这篇主要是针对于widevine的加密提供一个crack思路。

这篇文章比上一篇讨论的更深入了一些。上一篇讨论的是js调用的appendbuff 方法,这篇讨论的是widevine加密部分。

在正文开始之前,还是强调一下请不要问我要成型的软件工具,无论如何都不提供这里仅仅提供技术讨论,不提供任何工具。谢谢。

如果您在实践中遇到什么技术难题,可以回帖讨论。我会尽我所能的提供帮助。

感谢@逍遥一仙给在第一篇帖子里提了一个好问题,引导我去考虑widevine的解密。整个思路在实现过程得到了他的支持,思想碰撞才能有火花,在此表示感谢。


前言:
经常有人问为啥研究这个,不是发现m3u8后随便找个下载器就可以下载了么?

先大概介绍一下情况:
1:市面上大部分采用的都是HLS加密,通过下载m3u8文件,就可以下载绝大多数,最多就是m3u8里面隐藏一下key,换换偏移之类,这些都是js层的捉迷藏游戏,提供给浏览器底层的其实还是解密后的数据。

没有采用加密方式的,基本上随便找个m3u8下载器就可以下载如果有一些偏移或者aes加密的,可以用某些下载器下载,其实是下载器的作者分析过这些代码逻辑。下载后帮大家解密过了,但是如果换一个网站就不一定可以适用了

所以我上篇文章提到过的通过编译chrome,改写appendbuff方法,从接口拦截数据,基本上可以扫清市面上能见到的js层的加密,而不用去和js捉迷藏(其实主要是我不懂js,所以才去搞了这个方法。呵呵。。。)

2:还有一些网站,比如某酷,某异 有些电影启用了google提供的widevine加密方式,这个加密方式笼统的说是解码库和解密库集成在了一起,不在js层,是在底层解密,一般的m3u8下载器都会失效。
(并非所有视频都采用widewine加密的,这些网站上还有一些视频时没有widevine加密的。一般国外电影或者注重版权的会有widevine加密)。


长话短说,先找到我们的目标网站。为了不引起版权纠纷,这里采用了某个 这个被网上广泛公开用来测试widevine是否可用的网站。
这网站打开后有五个视频,中间一个A Blender Foundation short film, protected by Widevine encryption. 就是用widevine加密,我们这次就是针对这个视频的破解

让您刚刚编译的浏览器支持widevine,这一步真是让人头疼,网上没有任何文档对于windows编译的描述,我之前一直以为需要响应的sdk,这个sdk需要和google签订协议,而且要经过一次考试。。。基本上个人不可能拿到,当时就卡在这里。

后来翻遍全网连猜带蒙才发现,支持widevine在自己编译的chromium上只需要两步

2.1 打开编译开关
[C++] 纯文本查看 复制代码
enable_widevine = true

2.2 在这个地址下载google的widevine库,http://dl.google.com/widevine-cdm/4.10.1582.2-win-x64.zip,在编译好的chrome目录新建一个目录,WidevineCdm,把解压缩的东西放进去,就可以成功。检查log可以发现如下字样就是成功了:
[C++] 纯文本查看 复制代码
Preinstalled component found for Widevine Content Decryption Module at widevine\WidevineCdm with version 4.10.1582.2.
FinishRegistration for Widevine Content Decryption Module
Component ready, version 4.10.1582.2 in E:\chrome_dev\depot_tools\chromium\src\out\widevine\WidevineCdm
Component ready, version 4.10.1582.2 in E:\chrome_dev\depot_tools\chromium\src\out\widevine\WidevineCdm
Register Widevine CDM with Chrome
Register Widevine CDM with Chrome


顺便说一句:
网上只看到过linux的下载地址,这个win的下载地址从来没有看到过,也许有地方记载,反正我没找到,我是根据命名规则猜出来的。
有人在linux下用chrome的自带dll也可以成功,也有人失败,我机器上不行,可能是因为缺少sig文件?反正用google的这个文件没问题。


下面解释一下widevine的解码逻辑:

万事俱备,只欠东风,用编译好的chrome打开上面提到的网站,视频应该是可以播放状态,play按钮可以点击了。播放视频,跟踪log解释一下逻辑

[C++] 纯文本查看 复制代码
void DecoderStream<StreamType>::OnBufferReady  //读取buff,这里buff已经把audio video分流了。video解码一路,audio解码一路


对应log: decoder_stream.cc(680)] OnBufferReady<video>: 0, timestamp=0 duration=42000 size=169 side_data_size=0 is_key_frame=1 encrypted=0 discard_padding (us)=(0, 0)


[C++] 纯文本查看 复制代码
void DecoderStream<StreamType>::Decode(scoped_refptr<DecoderBuffer> buffer)         //利用template,不管是audio,video继续走decode方法

对应log:decoder_stream.cc(434)] Decode<video>


[C++] 纯文本查看 复制代码
void DecoderStream<StreamType>::DecodeInternal  //继续调用到decodeinternal

对应log:decoder_stream.cc(459)] DecodeInternal<video>



[C++] 纯文本查看 复制代码
void DecryptingVideoDecoder::Decode(                //检查当前线程,调用这个类的DecodePendingBuffer方法。

对应log:decrypting_video_decoder.cc(95)] Decode()

[C++] 纯文本查看 复制代码
 
void MojoDecryptor::DecryptAndDecodeVideo(            //这里开始解码了。remote_decryptor_应该就是widevine的解码方法,直接出来的videoframe就是视频图片。
    scoped_refptr<DecoderBuffer> encrypted,
    const VideoDecodeCB& video_decode_cb) {
  DVLOG(3) << __func__ << ": " << encrypted->AsHumanReadableString();
  DCHECK(thread_checker_.CalledOnValidThread());

  mojom::DecoderBufferPtr mojo_buffer =
      video_buffer_writer_->WriteDecoderBuffer(std::move(encrypted));
  if (!mojo_buffer) {
    video_decode_cb.Run(kError, nullptr);
    return;
  }

  remote_decryptor_->DecryptAndDecodeVideo(
      std::move(mojo_buffer),
      base::BindOnce(&MojoDecryptor::OnVideoDecoded, weak_factory_.GetWeakPtr(),
                     mojo::WrapCallbackWithDefaultInvokeIfNotRun(
                         ToOnceCallback(video_decode_cb), kError, nullptr)));
}

对应log:mojo_decryptor.cc(180)] DecryptAndDecodeVideo: timestamp=0 duration=42000 size=169 side_data_size=0 is_key_frame=1 encrypted=0 discard_padding (us)=(0, 0)


所以最后调到了MojoDecryptor::OnVideoDecoded。video_frame就是解密+解码后的数据。
这个函数里面的void MojoDecryptor::OnVideoDecoded(
[C++] 纯文本查看 复制代码
    VideoDecodeOnceCB video_decode_cb,
    Status status,
    const scoped_refptr<VideoFrame>& video_frame,
    mojo::PendingRemote<mojom::FrameResourceReleaser> releaser) {



检验是否正确也很简单我们再log里面把这个frame转换为png输出即可,具体代码如下:我把这个frame转换成png图片输出到log,看看是不是解码后的图片。

灰色字体是原有代码,蓝色为添加代码。主要逻辑,用libyuv转换I420到ARGB,在转换为PNG输出到log,通过查看png数据证明当前图形是解码过的实际图形而非乱码不可见。

[C++] 纯文本查看 复制代码
 DCHECK(thread_checker_.CalledOnValidThread());

//added by jimmyzang
  DVLOG(1) << "this is the jimmyzang output : " << video_frame->AsHumanReadableString();


                 gfx::Rect visible_rectrc = video_frame->visible_rect();
                 auto argb_out_frame = VideoFrame::CreateFrame(
                         VideoPixelFormat::PIXEL_FORMAT_ARGB, visible_rectrc.size(), visible_rectrc, visible_rectrc.size(),
                         base::TimeDelta());
                  const int width = visible_rectrc .width();
                  const int height = visible_rectrc .height();
                  uint8_t* const dst_argb = argb_out_frame.get()->data(VideoFrame::kARGBPlane);
                  const int dst_stride = argb_out_frame.get()->stride(VideoFrame::kARGBPlane);
                  libyuv::J420ToARGB(video_frame->data(VideoFrame::kYPlane),
                                video_frame->stride(VideoFrame::kYPlane),
                                video_frame->data(VideoFrame::kVPlane),
                                video_frame->stride(VideoFrame::kVPlane),
                                video_frame->data(VideoFrame::kUPlane),
                                video_frame->stride(VideoFrame::kUPlane),
                                dst_argb, dst_stride, width, height);
  
        // Convert the ARGB frame to PNG.
        std::vector<uint8_t> png_output;
        gfx::PNGCodec::Encode(
                argb_out_frame->data(VideoFrame::kARGBPlane), gfx::PNGCodec::FORMAT_BGRA,
                argb_out_frame->visible_rect().size(),
                argb_out_frame->stride(VideoFrame::kARGBPlane),
                true, /* discard_transparency */
                std::vector<gfx::PNGCodec::Comment>(), &png_output);
  
        const int size = base::checked_cast<int>(png_output.size());


  unsigned char * memory = png_output.data();
  const char* pszNibbleToHex = {"0123456789ABCDEF"};
  size_t nNibble;
  int i;
  int infoLength = size;
  std::string text = "";


  if (infoLength > 0) {
                for (i = 0; i < infoLength; i++) {
                    nNibble = memory[i] >> 4;
                                text.append(&pszNibbleToHex[nNibble],1);
                    nNibble = memory[i] & 0x0F;
                                text.append(&pszNibbleToHex[nNibble],1);
                }
        } 
        


         DVLOG(1) << "this is find picture: " << text;
  
  //end by jimmyzang
  
  // If using shared memory, ensure that |releaser| is closed when
  // |frame| is destroyed.
  if (video_frame && releaser) {


注意,这里用到了libyuv这个转化库,chromium\src\media\mojo\clients\BUILD.gn中需要加上这个的引用。

然后从log中导出对应的png文件,采用ffmpeg转换生成avi看看是不是正确。就是大家看到的附件中的那个视频了。


已知问题:
1:我代码有问题,内存没释放,反正理论验证就不改了。
2:应该不用直接写png文件经过转码后再直接输出,生成视频文件,这样就占用内存小,而且灵活。
3:audio decrpt没有做,道理是一样。
4:x酷和x异 html播放器会提示浏览器不支持,我查看了他们js里面代码,应该是html5里面判断过,绕过即可,反正浏览器源码就在那里,我不懂js,具体那句我不知道。。。。汗留给大家发挥吧。

总的来说,chromium浏览器编译进行html5视频解密告一段落了,基本上没啥挑战了,收工。。。

Widevine破解后,原帖子中写的是输出成PNG文件,今天突然想到chromium里面有一个方法是mojovideoencode,可以用这个方法编码输出成H264,
伪码贴一下,做个备忘。


[C++] 纯文本查看 复制代码
auto mock_vea_client = std::make_unique< MojoVideoEncodeAccelerator>();
      Initialize(mock_vea_client.get());
base::UnsafeSharedMemoryRegion shmem =
    base::UnsafeSharedMemoryRegion::Create(
        VideoFrame::AllocationSize(PIXEL_FORMAT_I420, kInputVisibleSize) *
        2);
base::WritableSharedMemoryMapping mapping = shmem.Map();
const scoped_refptr<VideoFrame> video_frame = VideoFrame::WrapExternalData(
    PIXEL_FORMAT_I420, kInputVisibleSize, gfx::Rect(kInputVisibleSize),
    kInputVisibleSize, mapping.GetMemoryAsSpan<uint8_t>().data(),
    mapping.size(), base::TimeDelta());
video_frame->BackWithSharedMemory(&shmem);
const bool is_keyframe = true;
 
EXPECT_CALL(*mock_vea_client, BitstreamBufferReady(kBistreamBufferId, _))
    .WillOnce(testing::Invoke(
        [is_keyframe](int32_t, const BitstreamBufferMetadata& metadata) {
          EXPECT_EQ(is_keyframe, metadata.key_frame);
        }));
 
mojo_vea()->Encode(video_frame, is_keyframe);


附件是解密过的视频文件的前10几秒,不是任何工具,或者源码,仅仅是证明可行性,如无必要请不必下载。
video.rar (678.75 KB, 下载次数: 104)





免费评分

参与人数 41吾爱币 +46 热心值 +38 收起 理由
lj3572996 + 1 + 1 热心回复!
Mouse + 1 + 1 用心讨论,共获提升!
风化血月 + 1 + 1 我很赞同!
gary0806 + 1 + 1 谢谢@Thanks!
marlborogolo + 1 + 1 谢谢@Thanks!
AngelEyes145 + 1 + 1 我很赞同!
redp + 2 + 1 热心回复!学习了
MR.lrc + 1 + 1 我很赞同!
ycwq110 + 1 + 1 用心讨论,共获提升!
菜姬 + 1 + 1 留下了没有技术的泪水
魅夜 + 1 + 1 我很赞同!
geniusrot + 1 + 1 没看懂,但是感觉好厉害
夜泉 + 2 + 1 谢谢@Thanks!
Ansr + 1 + 1 用心讨论,共获提升!
ends + 1 + 1 谢谢@Thanks!
幻Illusion + 1 + 1 用心讨论,共获提升!
52pojie666z + 1 + 1 用心讨论,共获提升!
开盒人 + 1 用心讨论,共获提升!
tzxinqing + 2 + 1 大佬能研究下blob协议的视频抓取下载么
温柔的一哥 + 1 + 1 用心讨论,共获提升!
先给自己打个气 + 1 用心讨论,共获提升!
Peppermint + 1 用心讨论,共获提升!
echo_454 + 1 + 1 谢谢@Thanks!
poisonbcat + 1 + 1 谢谢@Thanks!
独行风云 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
Grandsong + 1 + 1 我很赞同!
lookerJ + 1 + 1 谢谢@Thanks!
yixi + 1 + 1 谢谢@Thanks!
gaosld + 1 + 1 谢谢@Thanks!
风少666666 + 2 + 1 我很赞同!
zhuzhuxia111 + 1 + 1 我很赞同!
天山雪 + 1 + 1 用心讨论,共获提升!
zys_ + 1 + 1 我很赞同!
start100 + 1 谢谢@Thanks!
不被承认的好人 + 1 + 1 用心讨论,共获提升!
Ycbx + 1 + 1 用心讨论,共获提升!
水泥杀手 + 1 + 1 用心讨论,共获提升!
wjz3688 + 1 + 1 谢谢@Thanks!
haoren + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
逍遥一仙 + 3 + 1 用心讨论,共获提升!
51m0n + 1 我很赞同!

查看全部评分

本帖被以下淘专辑推荐:

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

推荐
 楼主| jimmyzang 发表于 2020-4-24 13:44 |楼主
本帖最后由 jimmyzang 于 2020-4-24 13:46 编辑

帖子每次修改都要被审核,不敢改了,新想法贴后面吧。

Widevine破解后,原帖子中写的是输出成PNG文件,今天突然想到chromium里面有一个方法是mojovideoencode,可以用这个方法编码输出成H264,

伪码贴一下,做个备忘。


  
[C++] 纯文本查看 复制代码
    auto mock_vea_client = std::make_unique< MojoVideoEncodeAccelerator>();
          Initialize(mock_vea_client.get());
    base::UnsafeSharedMemoryRegion shmem =
        base::UnsafeSharedMemoryRegion::Create(
            VideoFrame::AllocationSize(PIXEL_FORMAT_I420, kInputVisibleSize) *
            2);
    base::WritableSharedMemoryMapping mapping = shmem.Map();
    const scoped_refptr<VideoFrame> video_frame = VideoFrame::WrapExternalData(
        PIXEL_FORMAT_I420, kInputVisibleSize, gfx::Rect(kInputVisibleSize),
        kInputVisibleSize, mapping.GetMemoryAsSpan<uint8_t>().data(),
        mapping.size(), base::TimeDelta());
    video_frame->BackWithSharedMemory(&shmem);
    const bool is_keyframe = true;

    EXPECT_CALL(*mock_vea_client, BitstreamBufferReady(kBistreamBufferId, _))
        .WillOnce(testing::Invoke(
            [is_keyframe](int32_t, const BitstreamBufferMetadata& metadata) {
              EXPECT_EQ(is_keyframe, metadata.key_frame);
            }));

    mojo_vea()->Encode(video_frame, is_keyframe);
3#
Hmily 发表于 2020-4-22 19:13
4#
jixun66 发表于 2020-4-22 20:01
… 但这样就是 webrip (类似翻录)了,不是 webdl (源文件下载解密,不经过二次转码)。
5#
 楼主| jimmyzang 发表于 2020-4-22 20:50 |楼主
本帖最后由 jimmyzang 于 2020-4-24 06:35 编辑
Hmily 发表于 2020-4-22 19:13
代码用代码库处理下。

已经处理了。@Hmily
6#
 楼主| jimmyzang 发表于 2020-4-22 21:14 |楼主
jixun66 发表于 2020-4-22 20:01
… 但这样就是 webrip (类似翻录)了,不是 webdl (源文件下载解密,不经过二次转码)。

本身widevine就是解码和解密绑定在一起的,不可能不经过二次转码的呀。

点评

解码图像后 dump 出来的图像保存完全有机会被打水印(查到就封号)或再编码的时候损失画质。网站不可能针对每个人都编码一份原文件,成本太高且速度慢。 我相信网上那些做 web-dl 的小组有研究怎么去下源文件然后解  详情 回复 发表于 2020-5-6 19:07
7#
89684828 发表于 2020-4-25 14:17
很高深!触不可及
8#
NiSandy 发表于 2020-4-25 14:53
兄弟你的私聊一下,有个问题请教你,方便加个联系方式吗
9#
九天临兵帝 发表于 2020-4-25 20:09
学习了,想清楚了许多东西,谢谢
10#
cydt0816 发表于 2020-4-25 21:36
学习到了,感谢
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|联系我们|吾爱破解 - LCG - LSG ( )

GMT+8, 2020-6-2 19:51

Powered by Discuz!

千城彩票Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表
杏耀平台-主页 金福彩票-主页 北京彩票-主页 万福彩票-主页 吉林体彩网-主页 广东福彩网-主页 360彩票-主页 凤凰体彩-主页 爱波网-主页 广东十一选五-主页