In this article, I'll walk you through the process of how I managed to reconstruct a complete video file from encrypted video stream segments. At the end of the article, you'll also find the source code for the script that I used to download the video.

I recently bought a movie from a platform that was very restrictive. You could only watch the movie for a very limited period of time and from a very limited number of devices.

I'm a media hoarder and naturally I wanted to download the movie for later. However, downloading it was not trivial as you'll see in a bit.

I couldn't resist the temptation to roll up my sleeves and reverse-engineer the stream. With a few hours of investigation and scripting, I was able to bypass the protection and piece together the entire movie using go & ffmpeg.

Needless to say, the movie was not distributed and is only being used personally.

The usual approach

Generally, whenever I need to download a video, I grab the URL and pass that on to my trusty yt-dlp tool.

yt-dlp 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'

For other websites, I open the developer tools, go to the network tab, filter by media, and then grab the video stream URL.

But this time, the movie didn't have a single URL. In fact, there were no network calls showing up under the media category at all.

Reconnaissance

What I noticed is that there were a lot of HTTP calls to different segments of the video.

/<ContentID>/1080p/video-0.ts?v=0&resolution=1080p&server=1223&contextId=<ContextID>
/<ContentID>/1080p/video-1.ts?v=0&resolution=1080p&server=1223&contextId=<ContextID>
/<ContentID>/1080p/video-2.ts?v=0&resolution=1080p&server=1223&contextId=<ContextID>
/<ContentID>/1080p/video-3.ts?v=1&resolution=1080p&server=1223&contextId=<ContextID>
/<ContentID>/1080p/video-4.ts?v=1&resolution=1080p&server=1223&contextId=<ContextID>

Where
  - <ContentID> is the UUID of the movie
  - <ContextID> seems to be a session specific identifier. It's a 32 character long base64 encoded string.

This endpoint returned a binary data of content type video/mp2t - which is just a stream of digital media. I tried downloading one of the segments and playing it with VLC but that didn't work.

In between those calls, there were also calls to get some sort of key

/.drm/<ContextID>/1080p.drmkey?v=0
/.drm/<ContextID>/1080p.drmkey?v=1
/.drm/<ContextID>/1080p.drmkey?v=2

This endpoint also returned binary data. The name drmkey in the url was quite interesting. Upon googling, I found out that drm stands for Digital Rights Management and it relies on encryption to protect the content.

At this point I was almost certain that the

  • /<ContentID>/1080p/video-N.ts endpoint was returning a small encrypted segment of the video and
  • the /.drm endpoint was returning the encryption key for the web client to decrypt it.

If I could figure out the encryption algorithm used then I could download all the segments and the keys and then decrypt them one by one.

Encryption algorithm

The /.drm endpoint returned a 128-bit encryption key (e.g. 9036fb5d2f6af3c153acecd37bde6da7 in hexadecimal). That's a strong signal that it's most likely using AES-128 - a very popular symmetric encryption algorithm.

With the ciphertext (from /<ContentID>/1080p/) and the encryption key in hand, I tried decrypting it using openssl

openssl enc -d -aes-128-ecb -in video-0.ts -out videos/first.mp4 -K 87807cf39672d9f399dadf0a5b071e72 -nopad

However, when I tried to play the resulting file in VLC, it didn’t work – nothing happened.

I went back to examine the network traffic and found another intriguing endpoint:

/<ContentID>/1080p/video.drm?contextId=<ContextID>

It returned a plain-text response like this

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-KEY:METHOD=AES-128,URI="https://drm-key-redacted.com/.drm/<ContextID>/1080p.drmkey?v=0",IV=0x45454545454545454545454545454545
#EXTINF:4.000000,
https://redacted-cdn.com/<ContentID>/1080p/video0.ts?v=0&resolution=1080p&server=1223&contextId=<ContextID>
#EXTINF:4.000000,
https://redacted-cdn.com/<ContentID>/1080p/video1.ts?v=0&resolution=1080p&server=1223&contextId=<ContextID>
#EXTINF:4.000000,
https://redacted-cdn.com/<ContentID>/1080p/video2.ts?v=0&resolution=1080p&server=1223&contextId=<ContextID>
#EXT-X-KEY:METHOD=AES-128,URI="https://drm-key-redacted.com/.drm/<ContextID>/1080p.drmkey?v=1",IV=0x45454545454545454545454545454545
#EXTINF:4.000000,
https://redacted-cdn.com/<ContentID>/1080p/video3.ts?v=1&resolution=1080p&server=1223&contextId=<ContextID>
#EXTINF:4.000000,
https://redacted-cdn.com/<ContentID>/1080p/video4.ts?v=1&resolution=1080p&server=1223&contextId=<ContextID>
#EXTINF:4.000000,
https://redacted-cdn.com/<ContentID>/1080p/video5.ts?v=1&resolution=1080p&server=1223&contextId=<ContextID>
#EXT-X-KEY:METHOD=AES-128,URI="https://drm-key-redacted.com/.drm/<ContextID>/1080p.drmkey?v=2",IV=0x45454545454545454545454545454545
#EXTINF:4.000000,
https://redacted-cdn.com/<ContentID>/1080p/video6.ts?v=2&resolution=1080p&server=1223&contextId=<ContextID>
#EXTINF:4.000000,

...

Trimmed for brevity & sensitive parts have been redacted.
The file was around 4000-5000 lines long

M3U8

Now that's something! It's a m3u8 file which contains a list of urls to all the segments of the video file.

There are some pretty interesting stuff in here. Let's go through them

| Line | Directive | Description | | ---- | -------------------------- | ------------------------------------------------------------- | | 2 | #EXT-X-VERSION:3 | uses the extended version 3 of m3u that supports encryption | | 5 | EXT-X-PLAYLIST-TYPE:VOD | Indicates that each url points to a video | | 6 | EXT-X-KEY:METHOD=AES-128 | we see the encryption algorithm used - AES-128 | | 7 | EXTINF:4.000000 | Indicates that this segment is 4 seconds long |

In the EXT-X-KEY directive, we can also see IV=0x45454545454545454545454545454545. That most likely represents the initialization vector of the AES-128 encryption.

Let's try decrypting once again using the CBC mode

openssl enc -aes-128-cbc -d -K '87807cf39672d9f399dadf0a5b071e72' \
  -iv 45454545454545454545454545454545 -in cipher-0.ts \
  -out videos/video-0.mp4

And voila! I was able to play the video.

Visit the movie page.HTML contains a<ContextID>Get the m3u playlistGet the DRM KeyGet the encryptedstream

Code

Now all I needed to do was write a script to download all the segments and their keys and then decrypt them individually. And finally piece them together with ffmpeg.

You can find the source code at github.com/adityathebe/drm.

--

In hindsight this was pretty straightforward. The endpoints hinted the use of DRM and it turns out most modern DRM technologies use AES with 128-bit keys [1].

To validate the approach, I purchased another movie and successfully downloaded the entire film.

References

[1]. https://docs.axinom.com/services/drm/general/what-is-drm/