Diving into the depths of Widevine L3
Diving into the depths of Widevine L3
深入探索 Widevine L3
Authored by: Felipe 作者:Felipe
Widevine is a DRM scheme by Google that is used to securely deliver content to the end user; either by secure hardware or obfuscated software. In this post, I will explain how to use the Qiling emulation framework and apply different techniques to break the software-only DRM. In particular, we will cover how to load Android libraries into Qiling, apply Differential Fault Analysis (DFA) on a real-world target, and how emulation can assist in deobfuscating code. Widevine 是 Google 开发的一种 DRM(数字版权管理)方案,用于向终端用户安全地分发内容,其实现方式包括安全硬件或混淆软件。在本文中,我将解释如何使用 Qiling 模拟框架并应用各种技术来破解纯软件实现的 DRM。具体来说,我们将涵盖如何将 Android 库加载到 Qiling 中、如何在真实目标上应用差分故障分析(DFA),以及模拟技术如何辅助代码去混淆。
Let’s start with a quick overview of Widevine. Widevine requires three servers: a Provisioning Server, a License Server, and a Content Server. The root of trust is called the keybox. It’s a binary blob with the following structure: 让我们先快速了解一下 Widevine。Widevine 需要三台服务器:配置服务器(Provisioning Server)、许可证服务器(License Server)和内容服务器(Content Server)。其信任根被称为 keybox,它是一个具有以下结构的二进制数据块:
- KeyboxSize (bytes): 0x20
- Device ID: 0x10
- Data (version specific): 0x48
- Magic (“kbox”): 0x4
- Checksum: 0x4
- Total = 0x80
There are three provisioning models: 配置模型共有三种:
- Request a new license directly with a token from the keybox. 直接使用来自 keybox 的令牌请求新许可证。
- Use the keybox to request a device certificate that can be used to request a license. 使用 keybox 请求设备证书,进而用于请求许可证。
- Let the OEM handle the certificates (which can be used to provision L1 as an OTA update; we’ll come back to what L1 means in just a moment). 由 OEM 处理证书(这可用于通过 OTA 更新配置 L1;我们稍后会解释 L1 的含义)。
Google operates the Provisioning Server and License Server, which handle the creation and management of device certificates. A third-party license proxy connects to the License Server and checks whether a client can request a license for specific content. The Content Server then provides the encrypted content. Google 运营着配置服务器和许可证服务器,负责设备证书的创建和管理。第三方许可证代理连接到许可证服务器,检查客户端是否可以请求特定内容的许可证。随后,内容服务器提供加密后的内容。
Widevine has three security levels, L1, L2, and L3. They all have different requirements. L1 ensures that Widevine DRM keys and decrypted content are handled exclusively by secure hardware, preventing exposure to the host CPU. L2 only ensures that the Widevine DRM keys are stored by secure hardware, and the host CPU handles the decrypted content. L3 runs everything on the host CPU; the keys must be reasonably protected. The content provider can restrict access to specific content (such as high-definition streams) depending on the security level. Widevine 具有三个安全级别:L1、L2 和 L3,它们的要求各不相同。L1 确保 Widevine DRM 密钥和解密后的内容完全由安全硬件处理,防止暴露给主机 CPU。L2 仅确保 Widevine DRM 密钥存储在安全硬件中,而解密后的内容由主机 CPU 处理。L3 则完全在主机 CPU 上运行,密钥必须得到合理的保护。内容提供商可以根据安全级别限制对特定内容(如高清流媒体)的访问。
Why did I want to break Widevine
我为什么要破解 Widevine
I wanted to break Widevine mostly out of curiosity. Widevine L3 has been broken a lot in recent years, but the projects I knew of all use Frida hooks to dump the device key from a running Widevine session. While this only requires root access to an Android device, it felt like there has to be another way which requires fewer privileges. 我破解 Widevine 主要是出于好奇。近年来 Widevine L3 已被多次破解,但我所知的项目大多使用 Frida hook 从运行中的 Widevine 会话中转储设备密钥。虽然这只需要 Android 设备的 root 权限,但我认为应该有另一种权限要求更低的方法。
I was encouraged by reading the paper Exploring Widevine for Fun and Profit, which provides a really nice overview on how the architecture works. They claim that the keybox itself is not really protected and can just be dumped from memory. The paper also mentions that the actual cryptography has been broken twice: once by David Buchanan, who used Differential Fault Analysis to break the white-box AES used to protect the keybox of Chrome’s Widevine version in 2019, and a year later by Tomer Hadad, who broke an updated version that used RSA and released a Chrome extension that could be used to decrypt the content. However, it seems controversial who was involved in it, according to this post. 阅读《Exploring Widevine for Fun and Profit》这篇论文让我深受启发,它对该架构的工作原理进行了非常好的概述。文中声称 keybox 本身并没有得到真正的保护,可以直接从内存中转储出来。论文还提到,其加密机制已被破解过两次:一次是 David Buchanan 在 2019 年利用差分故障分析破解了用于保护 Chrome 版 Widevine keybox 的白盒 AES;一年后,Tomer Hadad 破解了使用 RSA 的更新版本,并发布了一个可用于解密内容的 Chrome 扩展程序。不过,根据这篇文章,关于谁参与了破解似乎存在争议。
How did I want to break Widevine
我打算如何破解 Widevine
At this point, I knew nearly nothing about the inner workings of the L3 version I was planning to work on. I just followed a blog post that used the Android Studio Emulator and dumped the device key using the Frida script mentioned in the blog post in order to understand the flow a bit better and later verify whether my approach works or not. 当时,我对计划研究的 L3 版本内部工作原理几乎一无所知。我只是按照一篇博客文章的指引,使用 Android Studio 模拟器,并通过文中提到的 Frida 脚本转储了设备密钥,以便更好地理解流程,并在后续验证我的方法是否有效。
To understand how OEMCrypto (the component used for DRM on Android) works, I did some dynamic analysis using Frida to figure out how the different functions for the L3 interface behave and which arguments they need. The Exploring Widevine for Fun and Profit paper contains a reverse-engineered symbol mapping. For example, oecc01 initialized the DRM context, and the _lccXX functions are the corresponding L3 functions. While Frida is a powerful dynamic analysis tool, I prefer a more isolated and reproducible environment to play around with.
为了理解 OEMCrypto(Android 上用于 DRM 的组件)的工作原理,我使用 Frida 进行了一些动态分析,以弄清 L3 接口的不同函数如何运行以及它们需要哪些参数。《Exploring Widevine for Fun and Profit》论文中包含了一个逆向工程得到的符号映射表。例如,oecc01 用于初始化 DRM 上下文,而 _lccXX 函数则是对应的 L3 函数。虽然 Frida 是一个强大的动态分析工具,但我更喜欢在一个更隔离、可复现的环境中进行研究。
When solving and writing CTF challenges, I got familiar with Qiling, a Python project that uses Unicorn under the hood to emulate the userspace and reimplement the OS layer. It is quite powerful and easily hackable if something doesn’t work as intended (which happens often, but that’s fine). So, my first goal was to get Widevine to run inside Qiling. This is also where I spent most of my time during the project. 在解决和编写 CTF 题目时,我熟悉了 Qiling。这是一个使用 Unicorn 作为底层引擎的 Python 项目,用于模拟用户空间并重新实现操作系统层。它非常强大,如果某些功能未按预期工作(这种情况经常发生,但这没关系),也很容易进行修改。因此,我的首要目标是让 Widevine 在 Qiling 中运行。这也是我在整个项目中花费时间最多的地方。
Getting Widevine to run inside Qiling for easier analysis
让 Widevine 在 Qiling 中运行以便于分析
To be able to run Widevine, I first had to load it in the emulator. The library implementing all Widevine L3 operations is libwvhidl.so. I just wanted to instrument this library, not the whole program used on a real Android device (android.hardware.drm@1.1-service.widevine, the Android DRM service). Therefore, I tried to trick the loader into thinking the library was just an executable so that it loads all libraries it depends on for me.
为了运行 Widevine,我首先必须将其加载到模拟器中。实现所有 Widevine L3 操作的库是 libwvhidl.so。我只想对这个库进行插桩,而不是真实 Android 设备上使用的整个程序(android.hardware.drm@1.1-service.widevine,即 Android DRM 服务)。因此,我试图欺骗加载器,让它认为该库是一个可执行文件,从而让它自动加载该库所依赖的所有其他库。
I have done this a few times, and it is usually really easy using LIEF. There is even a short post on how to do it in their docs, but for some reason, the library resulted in a corrupted state, which resulted in some problems when applying relocations. After hours of debugging, I figured out that I could get a working ELF like this: 我以前做过几次这种操作,使用 LIEF 通常非常简单。他们的文档中甚至有一篇关于如何操作的简短文章,但由于某种原因,该库最终处于损坏状态,导致在应用重定位时出现了一些问题。经过数小时的调试,我发现可以通过以下方式获得一个可用的 ELF 文件:
lib = lief.parse(src)
lib[lief.ELF.DynamicEntry.TAG.from_value(0x6000000F)].value = (
lib[lief.ELF.DynamicEntry.TAG.from_value(0x6000000F)].value + 0x1000)
lib.interpreter = b"/system/bin/linker"
lib.write(dst)
The loader happily loads the library, and it magically works ¯_(ツ)/¯ With this, I could manually execute the _lcc01 function to initialize the DRM, which creates /data/vendor/mediadrm/IDM1013/L3/ay64.dat, the encrypted keybox. To verify if everything works as intended, I just copied all files of /data/vendor/mediadrm/ into my Qiling root filesystem and initialized the DRM. However, it didn’t “accept” my keybox and always created a new one, so I wasn’t sure whether it was a proper key or contained a “garbage” key.
加载器愉快地加载了该库,它奇迹般地运行起来了 ¯_(ツ)/¯ 有了它,我可以手动执行 _lcc01 函数来初始化 DRM,这会创建 /data/vendor/mediadrm/IDM1013/L3/ay64.dat,即加密的 keybox。为了验证一切是否按预期工作,我将 /data/vendor/mediadrm/ 中的所有文件复制到我的 Qiling 根文件系统中并初始化了 DRM。然而,它并没有“接受”我的 keybox,而是总是创建一个新的,所以我无法确定它是一个有效的密钥,还是包含了一个“垃圾”密钥。