HTML5视频解密的方法(widevine的破解思路)
本帖最后由 jimmyzang 于 2020-4-30 07:17 编辑首先这是继续上一篇的m3u8通用下载, https://www.52pojie.cn/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加密)。
长话短说,先找到我们的目标网站。为了不引起版权纠纷,这里采用了某个 https://shaka-player-demo.appspot.com/ 这个被网上广泛公开用来测试widevine是否可用的网站。
这网站打开后有五个视频,中间一个A Blender Foundation short film, protected by Widevine encryption. 就是用widevine加密,我们这次就是针对这个视频的破解。
让您刚刚编译的浏览器支持widevine,这一步真是让人头疼,网上没有任何文档对于windows编译的描述,我之前一直以为需要响应的sdk,这个sdk需要和google签订协议,而且要经过一次考试。。。基本上个人不可能拿到,当时就卡在这里。
后来翻遍全网连猜带蒙才发现,支持widevine在自己编译的chromium上只需要两步:
2.1 打开编译开关enable_widevine = true
2.2 在这个地址下载google的widevine库,https://dl.google.com/widevine-cdm/4.10.1582.2-win-x64.zip,在编译好的chrome目录新建一个目录,WidevineCdm,把解压缩的东西放进去,就可以成功。检查log可以发现如下字样就是成功了:
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解释一下逻辑
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)
void DecoderStream<StreamType>::Decode(scoped_refptr<DecoderBuffer> buffer) //利用template,不管是audio,video继续走decode方法
对应log:decoder_stream.cc(434)] Decode<video>
void DecoderStream<StreamType>::DecodeInternal//继续调用到decodeinternal
对应log:decoder_stream.cc(459)] DecodeInternal<video>
void DecryptingVideoDecoder::Decode( //检查当前线程,调用这个类的DecodePendingBuffer方法。
对应log:decrypting_video_decoder.cc(95)] Decode()
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(
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数据证明当前图形是解码过的实际图形而非乱码不可见。
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 >> 4;
text.append(&pszNibbleToHex,1);
nNibble = memory & 0x0F;
text.append(&pszNibbleToHex,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,
伪码贴一下,做个备忘。
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(
(int32_t, const BitstreamBufferMetadata& metadata) {
EXPECT_EQ(is_keyframe, metadata.key_frame);
}));
mojo_vea()->Encode(video_frame, is_keyframe);
附件是解密过的视频文件的前10几秒,不是任何工具,或者源码,仅仅是证明可行性,如无必要请不必下载。
ZHANDOU 发表于 2022-5-12 18:10
大佬你好,萌新大学期间只学习了HTML和PHP的相关编写知识,有Python基础,感谢您提供的方法,但是萌新能力 ...
①上一篇文章里提到的chromium编译的目录貌似和我的Chrome的目录不太相同,我的目录没有chromium/src......这种目录地址
【这是我很早之前写的文章了,我现在本地没有代码,你如果下载了源码,可以直接安装文件名查找。也可以用chrome的web页面访问源码,比如,https://source.chromium.org/chromium/chromium/src/+/main:media/mojo/clients/mojo_audio_decoder.cc;l=1?q=mojo_audio_decoder.cc&sq=&ss=chromium(如何访问你懂的)】
②本篇文章从enable_widevine = true开始的代码输入是在哪里进行编写的?
【同样,可以搜索一下这句话,我印象中是在widevine的配置文件中,可能是这个
third_party/widevine/cdm/widevine.gni】
jimmyzang 发表于 2020-5-3 07:39
widevine解密和解码是在一个库里面的,我看上去是没有方法直接decrypt,但不decoded的。其实chrome里面有 ...
几个月前按照论坛某大佬的思路操作过一次:https://www.52pojie.cn/thread-609243-1-1.html 最终也是发现视频一直都只会使用DecryptAndDecodeFrame来进行解密与解码,无论是音视频是否分离的情况下,只有音频会调用Decrypt解密,与其类似还有个DecryptAndDecodeSamples方法根本就不会使用,我就很纳闷了。所以最终也是卡在yuv420i转h264上面了(测试时候发现一个四五百兆的某酷视频如果把yuv写到文件貌似至少10G以上,真够蛋疼的,由于本人主要编程语言是java,c++只是业余,所以编码视频懒得瞎折腾了,据我所知可以利用ffmpeg的管道来进行实时编码而不用将yuv写到文件之后再编码),总感觉这种操作还不如录屏来得方便。还有个需要注意的地方楼主是没有提到的,就是DecryptAndDecodeFrame可能会出现重复视频帧,有兴趣的朋友可以参看我当时做的一些小记录:https://0o0.me/java/crack-widevine-drm.html 之前的大神解密chrome插件被关停了,现在再看看文章学习一下思路! jixun66 发表于 2020-5-15 19:28
解密不等于解码… 我只能说这是我自己推测的原理,因为我没搞过 widevine,但其它方向的加解密应该都差不 ...
如果经过解码到屏幕这一过程后 dump,那和我搞个外置采集卡的画质没多大区别了 _(:3__对于最后这一点不怎么认同,网站扒视频不仅仅考虑画质,如何快速抓取也是非常重要的,录屏的缺点就是视频播放时间有多久就得录多久(不知道能不能快进录屏,没研究过,个人感觉应该不行),而hook widevine的DecryptAndDecodeFrame方法可以利用浏览器的快进功能实现快速将整个视频抓取下来(实际上也就是快速将原始加密数据输入到解密方法进行解密,而这个过程还原出来的视频也是正常速度的) dnv123820 发表于 2020-6-24 12:52
这是一款叫做 SmartNovel 的工具,它来自吾爱破解的 jimmyzang,你只要输入一句话,它就能根据你选的主题自 ...
https://www.52pojie.cn/forum.php?mod=viewthread&tid=1147758&page=166#pid32557155 本帖最后由 jimmyzang 于 2020-4-24 06:35 编辑
Hmily 发表于 2020-4-22 19:13
代码用代码库处理下。
已经处理了。@Hmily 本帖最后由 jimmyzang 于 2020-4-24 13:46 编辑
帖子每次修改都要被审核,不敢改了,新想法贴后面吧。
Widevine破解后,原帖子中写的是输出成PNG文件,今天突然想到chromium里面有一个方法是mojovideoencode,可以用这个方法编码输出成H264,
伪码贴一下,做个备忘。
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(
(int32_t, const BitstreamBufferMetadata& metadata) {
EXPECT_EQ(is_keyframe, metadata.key_frame);
}));
mojo_vea()->Encode(video_frame, is_keyframe); 代码用代码库处理下。 … 但这样就是 webrip (类似翻录)了,不是 webdl (源文件下载解密,不经过二次转码)。 jixun66 发表于 2020-4-22 20:01
… 但这样就是 webrip (类似翻录)了,不是 webdl (源文件下载解密,不经过二次转码)。
本身widevine就是解码和解密绑定在一起的,不可能不经过二次转码的呀。 很高深!触不可及{:1_921:} 兄弟你的私聊一下,有个问题请教你,方便加个联系方式吗 学习了,想清楚了许多东西,谢谢 学习到了,感谢