{"id":60595,"date":"2024-12-04T01:19:39","date_gmt":"2024-12-03T22:19:39","guid":{"rendered":"https:\/\/packetstormsecurity.com\/files\/182931\/wbpmp_doc.md.txt"},"modified":"2024-12-04T01:19:39","modified_gmt":"2024-12-03T22:19:39","slug":"microsoft-warbird-and-pmp-security-research","status":"publish","type":"post","link":"https:\/\/afaghhosting.net\/blog\/microsoft-warbird-and-pmp-security-research\/","title":{"rendered":"Microsoft Warbird and PMP Security Research"},"content":{"rendered":"<p>&#8220;`<br \/># Microsoft Warbird and PMP security research<br \/># (c) Security Explorations 2008-2024 Poland<br \/># (c) AG Security Research 2019-2024 Poland<br \/>&#8220;`<\/p>\n<p>&#8220;&#8230;If you decide to make it public&#8230;, stress the fact that it is not a<br \/>security issue in PlayReady or any Microsoft technology; it&#8217;s a security<br \/>issue in the STB&#8221; &#8211; Microsoft, Nov 2022<\/p>\n<p># INTRODUCITON<br \/>This package presents security analysis conducted with respect to Microsoft <br \/>Warbird and Protected Media Path technologies.<\/p>\n<p>A more general description of the results obtained and their impact is<br \/>described at project web page:<\/p>\n<p>&#8220;`<br \/>https:\/\/security-explorations.com\/microsoft-warbird-pmp.html<br \/>&#8220;`<\/p>\n<p>This document provides a more in-depth technical explanation, illustration and<br \/>verification of discovered attacks along toolsets used for their implementation.<\/p>\n<p>We hope this document provides both important contribution along a valuable<br \/>perspective on the state of the art \/ security provided by PlayReady content<br \/>security technology (vide the nature of the issues uncovered \/ verification of<br \/>vendor&#8217;s claims).<\/p>\n<p>For a more complete picture, you are encouraged to get familiar with our prior<br \/>PlayReady research from 2022 (along associated technical doc):<\/p>\n<p>&#8220;`<br \/>https:\/\/security-explorations.com\/microsoft-playready.html<br \/>&#8220;`<\/p>\n<p>## DISCLAIMER<br \/>The goal of this research is not to promote PayTV or VOD content piracy in any<br \/>way. It is to both increase awareness and trigger more work at vendor&#8217;s end to <br \/>make PayTV \/ content piracy harder to accomplish.<\/p>\n<p>The results obtained indicate that there is much space for the improvement<br \/>through better technical means.<\/p>\n<p>Information in this document is provided purely for educational purposes only. <br \/>It is expressly forbidden to use them for any purposes that would violate any<br \/>domestic or international laws.<\/p>\n<p>## ON MICROSOFT \/ SHARING OF THE RESEARCH<br \/>We believe the following is worth to mention regarding Microsoft. Please, keep<br \/>in mind that we have 28 years of experience when it comes to dealing with<br \/>various SW \/ HW vendors over security issues.<br \/>&#8211; contact with Microsoft Security Response Center has been a questionable<br \/>experience:<br \/>* all messages are signed semi-anonymously (as MSRC), as a result one has<br \/>the impression of no accountability and anonymity, the experience is more<br \/>like talking to a customer service of a big retailer, not security contact<br \/>of a major SW vendor (other vendors we dealt with used real names),<br \/>* MSRC responses are not prompt, simple confirmation of a successful download<br \/>\/ decryption of a research material takes ~3 days for the company, this<br \/>doesn&#8217;t look like 24\/7 global team operations, this doesn&#8217;t look like &#8220;we<br \/>take security issues seriously&#8221;<br \/>* MSRC can occasionally lose the topic (doesn&#8217;t do the basic work that could<br \/>clarify the context of a given message such as to ask internally employees<br \/>CCed), this creates the impression that responses get redacted or are sent<br \/>in a hurry \/ without due care<br \/>* in 2019, Microsoft didn&#8217;t respond to our inquiry, we didn&#8217;t assume this<br \/>was intentional until cease of a communication, which took place in early<br \/>2023 (as a follow up of our inquiries regarding PlayReady security, which <br \/>hasn&#8217;t been answered btw.)<br \/>&#8211; Microsoft claims that it rewards security research, well the devil lies in a <br \/>detail, more specifically:<br \/>* `Microsoft Bounty Terms and Conditions` implicate commercial use with <br \/>unknown payment terms, all non-negotiable and under Microsoft control<br \/>* there are no categories for PlayReady \/ content protection (there has never<br \/>been such categories),<br \/>* our research didn&#8217;t need to rely on any privilege elevation\/kernel exploit<br \/>(direct base for no bug stance)<br \/>* Microsoft claimed no bug at its end in 2022 (with above, solid rationale<br \/>for similar evaluation in 2024)<br \/>* the company hasn&#8217;t expressed \/ signaled any interest to discuss access to<br \/>this research on a fair and commercial basis (regulating conditions of IP<br \/>\/ know-how use, mutual agreement on a price, etc.) for the last 8 months, <br \/>the company was well aware of research impact (PlayReady for Windows being<br \/>broken to pieces) along the amount of work it required<br \/>&#8211; Microsoft sort of played with us in 2022, which is hard to perceive in other<br \/>terms than disrespect, company&#8217;s claims regarding fixing and taking the issue<br \/>seriously didn&#8217;t reflect the reality, it took nearly 2 years to have the cert<br \/>confirmed by Microsoft as compromised to be revoked, it&#8217;s even hard to think <br \/>of this action as revocation as the service got simply shut down, the company<br \/>avoided to admit that PlayReady might provide little or no security upon<br \/>client compromise, not much security improvements has been noticed since 2022<br \/>when we signaled the need for it (this research sort of proves it), this all<br \/>built solid base for no trust to the company at our end.<\/p>\n<p>We decided to give Microsoft (a company consisting of 100,000+ SW engineers, <br \/>with access to all the know-how, internal docs and source codes) approx. the <br \/>same amount of time to fix \/ address the issues as it took us (a 1 man shop <br \/>relying on binaries and public info only) to analyze and reverse engineer the<br \/>technology, discover the issues, develop illustrating POCs and dedicated<br \/>toolsets.<\/p>\n<p>We finally provided Microsoft with access to the complete research package<br \/>comprising of a technical document, all toolsets with sources and test data<br \/>(285MB ZIP file) on Nov 18, 2024 and free of any charge (exactly two weeks<br \/>prior to the planned disclosure and as agreed with the company).<\/p>\n<p>But, Microsoft is only partly the winner here as its engineers likely failed<br \/>to locate \/ address the root cause of the issues over the recent 8 months (no<br \/>fixes \/ mitigations observed). That&#8217;s quite a shame in our opinion. The other<br \/>source for the shame lies in the nature of the issues and attacks described<br \/>in this doc.<\/p>\n<p>## PACKAGE DESCRIPTION<br \/>The package comprises of the following tools:<br \/>&#8211; `Warbird Reverse Engineering toolkit`<br \/>Standalone toolkit making it possible to investigate Warbird protected binary <br \/>files and facilitating their static and dynamic analysis. The toolkit makes<br \/>it possible to perform dynamic analysis of arbitrary PlayReady functionality<br \/>such as activation \/ individualization, license acquisition or license blob<br \/>decryption (content key decryption), private ECC key discovery, XOR key<br \/>discovery, white-box crypto key discovery, etc.<\/p>\n<p>&#8211; `Content key sniffer`<br \/>The tool making it possible to extract plaintext values of content keys from<br \/>Protected Media Path process (SW DRM on Windows 10 \/ 11 case).<\/p>\n<p>&#8211; `Test LS`<br \/>Simple PlayReady License Server with a basic functionality to handle PlayReady<br \/>individualization and license acquisition requests.<\/p>\n<p>&#8211; `MSPR Toolkit`<br \/>MSPR toolkit with an update for client identity import, inspection and key<br \/>exports, sniffer data import and dump along reverse engineering support for<br \/>XOR key discovery and decryption of license server responses acquired with<br \/>the use of a web browser (its built-in network monitoring functionality \/<br \/>developer console)<\/p>\n<p>### DIRECTORY STRUCTURE<br \/>The package follows directory structure described below:<\/p>\n<p>&#8211; `binaries`<br \/>it contains base version of W10 PlayReady library used during reversing \/<br \/>analysis process<\/p>\n<p>&#8211; `binaries\\dynamic`<br \/>decrypted version of a base library, which has been setup for dynamic analysis<br \/>(output of<br \/>`wbsetup -r` command)<\/p>\n<p>&#8211; `binaries\\static`<br \/>decrypted version of the above base library, which has been setup for static<br \/>analysis (output of `wbsetup -s` command)<\/p>\n<p>&#8211; `tests\\trace`<br \/>sample trace logs and scripts<\/p>\n<p>&#8211; `tests\\xor_key`<br \/>tests conducted with respect to XOR key value for various versions of PlayReady<br \/>library and Windows OS<\/p>\n<p>&#8211; `tests\\web_soap_licenses`<br \/>tests illustrating web browser exploitation scenario<\/p>\n<p>&#8211; `tests\\sniffdata`<br \/>license sniffer outputs conducted for various VOD platforms and Live TV<\/p>\n<p>&#8211; `tests\\win_sniffed_key_for_canalp_vod`<br \/>exploit scenario illustrating the use of a key acquired by the sniffer to<br \/>successfully decrypt the movie of Canal+ VOD<\/p>\n<p>&#8211; `tests\\wincert_use_for_canalp_vod`<br \/>exploit scenario illustrating the use of Windows based certificate \/ PlayReady<br \/>identity issued for arbitrary VOD service to obtain access to movies (download<br \/>and decryption) of Canal+ VOD service available to STB devices<\/p>\n<p>&#8211; `tests\\sniffer_test_nov2024`<br \/>test of the HW DRM disabling \/ MFPMP process attach \/ sniffer operation done in<br \/>Nov 2024<\/p>\n<p>&#8211; `tools\\sniffer`<br \/>content key sniffer<\/p>\n<p>&#8211; `tools\\test_ls`<br \/>Test LS<\/p>\n<p>&#8211; `tools\\mspr_toolkit`<br \/>MSPR Toolkit<\/p>\n<p>&#8211; `tools\\wret_toolkit`<br \/>Warbird Reverse Engineering toolkit<\/p>\n<p>Additionally, the following directories contain some helper data used by MSPR<br \/>toolkit:<\/p>\n<p>&#8211; `tools\\mspr_toolkit\\cdm`<br \/>CDM content with arbitrary real PlayReady identities <\/p>\n<p>&#8211; `tools\\mspr_toolkit\\decdata`<br \/>some predefined ECC points (with specific signature \/ content key patterns for<br \/>Point.X), which can be used to generate license blobs (with specific signature<br \/>\/ license key)<\/p>\n<p>## ASSUMPTIONS AND TERMS USED ACROSS THE DOC<br \/>Most code snippets used in this document and illustrating implementation of <br \/>PlayReady client library (`Windows.Media.Protection.PlayReady.dll`) are relying<br \/>on its Windows 10 version depicted below (denoted as &#8220;sample prlib&#8221; throughout <br \/>the doc):<\/p>\n<p>&#8220;`<br \/>wret&gt; peinfo<br \/>PEInfoCmd::run<br \/>[PE file]name w10_prlib.dll<br \/>path w10_prlib.dll<br \/>size 10347408<br \/>base 180000000<br \/>sections: 15<br \/>symbols: 0<br \/>time: Thu Apr 29 11:08:30 2021<br \/>sha256:<br \/>0x00000000: e0 e7 ee c2 0e bf 12 2c b6 fb 13 ec f9 ff cd 94 &#8230;&#8230;.,&#8230;&#8230;..<br \/>0x00000010: 39 f3 86 06 0d 74 46 e3 be d8 b0 da e1 af 37 ac 9&#8230;.tF&#8230;&#8230;.7.<br \/>&#8220;`<\/p>\n<p>The focus on a given single client library should not be perceived in terms of<br \/>any limitation. All libraries we tested follow the same implementation when it<br \/>comes to core functionality pertaining to identity handling and license blobs<br \/>decryption.<\/p>\n<p>All tests \/ analysis have been conducted in Windows 10\/11 x64 environment.<\/p>\n<p>Addresses displayed by the WRET tool with respect to PlayReady image are RVAs &#8211;<br \/>relative virtual addresses.<\/p>\n<p>WRET toolkit should be perceived in terms of a Proof of Concept. As such, it<br \/>might not work in some cases \/ with respect to all Warbird binaries, it does<br \/>not do proper cleanup with respect to Warbird \/ hooking setup. The way it<br \/>implements some features are far from being perfect too.<\/p>\n<p>The following nomenclature is used with respect to extensions of identity<br \/>files:<br \/>&#8211; `*.enc.prv`<br \/>Obfuscated private ECC key component used for license blob encryption<br \/>&#8211; `*.enc.pub`<br \/>Public ECC key component used for license blob encryption<br \/>&#8211; `*.enc.plain`<br \/>Plaintext value of a private ECC key component used for license blob encryption<br \/>&#8211; `*.sig.prv`<br \/>Obfuscated private ECC key component used for signing license requests<br \/>&#8211; `*.sig.pub`<br \/>Public ECC key component used for signing license requests<br \/>&#8211; `*.sig.plain`<br \/>Plaintext value of ECC key component used for signing license requests<\/p>\n<p>The plaintext content key value used in this document corresponds to the movie, <br \/>which is not available any more in a target VOD platform (HTTP request for url <br \/>denoting Manifest file was verified to return error, VOD search box for movie <br \/>title didn&#8217;t show the movie).<\/p>\n<p>## TOOLS&#8217; BUILDING<br \/>The tools requires the following software for building:<br \/>&#8211; Java SE implementation such as Coretto from Amazon<br \/>`https:\/\/aws.amazon.com\/corretto\/`<br \/>&#8211; Microsoft\u2019s Visual Studio Community Edition<br \/>`https:\/\/visualstudio.microsoft.com\/pl\/vs\/community\/`<\/p>\n<p>Installation paths to the above software need to be setup in `tools\\config.bat`<br \/>file prior to the build process or tools usage:<\/p>\n<p>&#8220;`<br \/>set vcdir=&#8221;c:\\_SOFTWARE\\Microsoft Visual Studio\\2022\\Community&#8221;<br \/>set javadir=&#8221;c:\\_SOFTWARE\\Coretto\\jdk21.0.5_11&#8243;<br \/>&#8220;`<\/p>\n<p>Each tool can be built be running `build.bat` script residing in a tool&#8217;s main<br \/>directory:<\/p>\n<p>&#8220;`<br \/>c:\\_MNT\\PROJECTS\\_PROJECTS\\MSPR\\MS_PKG\\tools\\wret_toolkit&gt;build<\/p>\n<p>**********************************************************************<br \/>** Visual Studio 2022 Developer Command Prompt v17.6.1<br \/>** Copyright (c) 2022 Microsoft Corporation<br \/>**********************************************************************<br \/>[vcvarsall.bat] Environment initialized for: &#8216;x64&#8217;<\/p>\n<p>Microsoft (R) Program Maintenance Utility Version 14.36.32532.0<br \/>Copyright (C) Microsoft Corporation. All rights reserved.<\/p>\n<p>del out\\test<br \/>Could Not Find c:\\_MNT\\PROJECTS\\_PROJECTS\\MSPR\\MS_PKG\\tools\\wret_toolkit\\out\\test<br \/>del obj\\*.obj<br \/>ml64.exe \/c \/Foobj\\ src\\asm\\wb.asm src\\asm\\tramp.asm src\\asm\\policy.asm src\\asm\\comhelper.asm src\\asm\\appmodel.asm<br \/>Microsoft (R) Macro Assembler (x64) Version 14.36.32532.0<br \/>Copyright (C) Microsoft Corporation. All rights reserved.<\/p>\n<p>Assembling: src\\asm\\wb.asm<br \/>Assembling: src\\asm\\tramp.asm<br \/>Assembling: src\\asm\\policy.asm<br \/>Assembling: src\\asm\\comhelper.asm<br \/>Assembling: src\\asm\\appmodel.asm<br \/>cl.exe \/nologo \/EHsc \/GS \/Zc:wchar_t \/ZI \/Gm- \/Od \/sdl \/Zc:inline \/fp:precise \/D &#8220;_CONSOLE&#8221; \/D &#8220;_CRT_SECURE_NO_WARNINGS&#8221; \/WX- \/Zc:forScope \/RTC1 \/Gd \/MDd \/FC \/std:c++17 \/c \/Foobj\\ src\\*.cpp<br \/>Aes.cpp<br \/>App.cpp<br \/>AppContainer.cpp<br \/>Cmd.cpp<br \/>ComLib.cpp<br \/>Dbg.cpp<br \/>DebugProc.cpp<br \/>Dll.cpp<br \/>DllMap.cpp<br \/>DynCall.cpp<br \/>Error.cpp<br \/>FS.cpp<br \/>&#8230;<br \/>Generating Code&#8230;<br \/>Compiling&#8230;<br \/>WinRT.cpp<br \/>Generating Code&#8230;<br \/>cl.exe \/nologo \/INCREMENTAL:NO \/Gm- \/Gy- \/F 0x400000 \/Feout\\test.exe obj\\*.obj bcrypt.lib kernel32.lib user32.lib ole32.lib WindowsApp.lib<br \/>Aes.obj : warning LNK4075: ignoring &#8216;\/EDITANDCONTINUE&#8217; due to &#8216;\/OPT:ICF&#8217; specification<br \/>c:\\_MNT\\PROJECTS\\_PROJECTS\\MSPR\\MS_PKG\\tools\\wret_toolkit&gt;<br \/>&#8220;`<\/p>\n<p>## TOOLS&#8217; EXECUTION<br \/>Each tool can be executed by invoking `run.bat` script residing in a tool&#8217;s <br \/>main directory.<\/p>\n<p>The WRET and MSPR Toolkit tools work in a shell-like manner. As such they<br \/>implement a set of custom commands.<\/p>\n<p>The sniffer tool can be provided with a single argument:<\/p>\n<p>&#8220;`<br \/>licsniff [-t | outfile]&#8220;`<\/p>\n<p>where:<br \/>&#8211; `-t` indicates that a stack dump should be performed for a target PMP <br \/>process (for debugging purposes)<br \/>&#8211; `outfile` denotes the target file where sniffed data such as content \/<br \/>white-box keys should be stored to<\/p>\n<p>The `test_ls` server by default listens on port 8080 and default host IP<br \/>address.<\/p>\n<p>## MSPR TOOLKIT COMMANDS<br \/>The list of all commands implemented by the tool can be obtained with the use <br \/>of `help` command. For each command, brief information about the arguments<br \/>taken is displayed.<\/p>\n<p>Below, more details regarding commands and their arguments is given. The list<br \/>is limited to the commands added to MSPR toolkit since 2022 (updated commands).<\/p>\n<p>&#8211; import license sniffer file<br \/>&#8220;`<br \/>implicdata license_sniff_file<br \/>&#8220;`<\/p>\n<p>&#8211; show, export or decrypt license information for a given keuid<br \/>&#8220;`<br \/>licdata [-k keyid][-e outfile][-r outfile][-c prvkey][-v][-l]&#8220;`<\/p>\n<p>&#8211; show, export or decrypt license information for a given keyid<br \/>&#8220;`<br \/>licdata [-k keyid][-e outfile][-r outfile][-c prvkey][-v][-l]&#8220;`<\/p>\n<p>&#8211; generate license blob for a given identity or key, with explicit value at<br \/>given pos<br \/>&#8220;`<br \/>genlicense -k pubkey | -i identity [-v val][-p pos]&#8220;`<\/p>\n<p>&#8211; geenrate intermediate decryption data (license blobs with predefined values) <br \/>for given identity or public key<br \/>&#8220;`<br \/>gendecdata -k pubkey | -i identity<br \/>&#8220;`<\/p>\n<p>&#8211; generate license blob with a special singature \/ content key pattern, use<br \/>predefined ECC point file for that purpose<br \/>&#8220;`<br \/>genzlicense -k pubkey | -i identity [-p pointfile][-o outfile]&#8220;`<\/p>\n<p>&#8211; list identities or expost a given identity (its keys) to files<br \/>&#8220;`<br \/>identity [identity] [-v][-e outfile]&#8220;`<\/p>\n<p>&#8211; check a given private and public ECC key pair for match<br \/>&#8220;`<br \/>checkkeypair prvky pubkey<br \/>&#8220;`<\/p>\n<p>&#8211; check if a given ECC point is on ECC curve (NIST P-256)<br \/>&#8220;`<br \/>oncurve point<br \/>&#8220;`<\/p>\n<p>&#8211; calculate AES CMAC for given data and key<br \/>&#8220;`<br \/>aescmac data key<br \/>&#8220;`<\/p>\n<p>&#8211; do bijection test<br \/>&#8220;`<br \/>bijtest data key<br \/>&#8220;`<\/p>\n<p>&#8211; performul ECC MUL operation for a given point and val<br \/>&#8220;`<br \/>eccmulp point val<br \/>&#8220;`<\/p>\n<p>&#8211; generate ECC key pair from for a fixed value of a private key<br \/>&#8220;`<br \/>genfixedkey outfile kval<br \/>&#8220;`<\/p>\n<p>&#8211; decrypt HTTP SOAP license server response (such as the one acquired with the<br \/>help of a web brower)<br \/>&#8220;`<br \/>dechttplicense httprespfile [-r][-v]&#8220;`<\/p>\n<p>&#8211; decode Base64 data<br \/>&#8220;`<br \/>b64dec file [-o outfile]&#8220;`<\/p>\n<p>## WRET TOOLKIT COMMANDS<br \/>Similarly to MSPR toolkit, WRET tool works in a shell-like fashion. The list<br \/>of all commands implemented by the tool can be obtained with the use of `help`<br \/>command:<\/p>\n<p>&#8220;`<br \/># Warbird RE toolkit (WRET)<br \/># (c) Security Explorations 2016-2024 Poland<br \/># (c) AG Security Research 2019-2024 Poland<br \/>wret&gt;<br \/>wret&gt; help<br \/>test test<br \/>exit exit shell<br \/>help print help information<br \/>verbose set verbose printing<br \/>image load image<br \/>save save image<br \/>images show loaded images<br \/>dllmap show dll maps<br \/>peinfo show various PE image information<br \/>mdinfo show various Metadata image information<br \/>wbinfo show various Warbird information<br \/>wbdesc show Warbird segment information<br \/>wbop do Warbird operation for address (encrypt, decrypt, lookup, restart)<br \/>wbsetup setup Warbird for static or dynamic analysis<br \/>cominfo show COM image information<br \/>include load header file<br \/>eccdata show ECC data information from image<br \/>getlicense issue PlayReady license request<br \/>declicense decrypt PlayReady license<br \/>decsignkey decrypt signing key<br \/>decenckey decrypt encryption key<br \/>prinit initialize PlayReady<br \/>prinfo show PlayReady information<br \/>prsubs show PlayReady subroutines<br \/>ektable show Expand Key table<br \/>ekkey get content key from expanded key file<br \/>xorkey show or set XOR key<br \/>eccpctab show ECC precalc table<br \/>pkdata set, restore or show private ECC Key data<br \/>dmem dump memory<br \/>wmem write memory<br \/>smem save memory to file<br \/>sstr search for string<br \/>sstrw search for wide string<br \/>wstr write string to memory<br \/>wstrw write wide string to memory<br \/>uuid show uuid representation<br \/>trace trace calls<br \/>ps show process list<br \/>pid show process id<br \/>debug debug process<br \/>ptr show ptr info<br \/>pkgs show app packages<br \/>pkg set app package<br \/>funcs show image functions<br \/>fun set function attribute<br \/>handler manage call handlers<br \/>logcfg manage global logging configuration<br \/>msprtrace manage MSPR tracing configuration<br \/>syminfo show symbol information<br \/>metadata load metadata<br \/>lskey load license server key<br \/>lsurl set license server url<br \/>prlib set default PlayReady library<br \/>start start process<br \/>aeskey show AES key information<br \/>aestecheck check AES table for bijection<br \/>gmul multply operation in GF(2^8) space<br \/>wret&gt;<br \/>&#8220;`<\/p>\n<p>All implemented commands are defined in a global `_cmd_table` variable of the<br \/>`Shell` class (`tools\\wret_toolkit\\src\\Shell.cpp`):<\/p>\n<p>&#8220;`<br \/>vector&lt;Cmd*&gt; Shell::_cmd_table={<br \/>new TestCmd(&#8220;w[name=word,int]&#8221;, &#8220;test&#8221;),<br \/>new ExitCmd(NULL, &#8220;exit shell&#8221;),<br \/>new HelpCmd(&#8220;1[name=command,str]&#8221;, &#8220;print help information&#8221;),<br \/>new VerboseCmd(&#8220;1[name=option,str]&#8221;, &#8220;set verbose printing&#8221;),<\/p>\n<p>new ImageCmd(&#8220;1[name=image,str]&#8221;, &#8220;load image&#8221;),<br \/>new SaveCmd(&#8220;1[name=image,str]&#8221;, &#8220;save image&#8221;),<br \/>new ImagesCmd(&#8220;a[name=all,flag]&#8221;, &#8220;show loaded images&#8221;),<br \/>new DllmapCmd(&#8220;1[name=libname,str]&#8221;, &#8220;show dll maps&#8221;),<br \/>new PEInfoCmd(&#8220;h[name=header,str]&#8221;<br \/>&#8220;s[name=sections,flag]&#8221;<br \/>&#8220;i[name=imports,flag]&#8221;<br \/>&#8220;d[name=delayimports,flag]&#8221;<br \/>&#8220;e[name=exports,flag]&#8221;<br \/>&#8220;l[name=loadconfig,flag]&#8221;<br \/>&#8220;x[name=exceptions,flag]&#8221;<br \/>&#8220;L[name=libraries,flag]&#8221;<br \/>&#8220;v[name=verbose,flag]&#8221;, &#8220;show various PE image information&#8221;),<br \/>new MDInfoCmd(&#8220;h[name=header,str]&#8221;<br \/>&#8220;s[name=stream,str]&#8221;<br \/>&#8220;t[name=table,str]&#8221;<br \/>&#8220;d[name=dump,str]&#8221;<br \/>&#8220;v[name=verbose,flag]&#8221;, &#8220;show various Metadata image information&#8221;),<br \/>new WBInfoCmd(&#8220;s[name=summary,flag]&#8221;<br \/>&#8220;H[name=heapexecdesc,flag]&#8221;<br \/>&#8220;S[name=segmentdesc,flag]&#8221;<br \/>&#8220;d[name=descriptor,int]&#8221;<br \/>&#8220;v[name=verbose,flag]&#8221;, &#8220;show various Warbird information&#8221;),<br \/>new WBDescCmd(&#8220;H[name=heapexecdesc,int]&#8221;<br \/>&#8220;S[name=segmentdesc,int]&#8221;<br \/>&#8220;v[name=verbose,flag]&#8221;, &#8220;show Warbird segment information&#8221;),<br \/>new WBOpCmd(&#8220;r[name=restart,flag]&#8221;<br \/>&#8220;l[name=lookup,int]&#8221;<br \/>&#8220;e[name=encrypt,int]&#8221;<br \/>&#8220;d[name=decrypt,int]&#8221;<br \/>&#8220;v[name=verbose,flag]&#8221;, &#8220;do Warbird operation for address (encrypt, decrypt, lookup, restart)&#8221;),<br \/>new WBSetupCmd(&#8220;s[name=static,flag]&#8221;<br \/>&#8220;r[name=runtime,flag]&#8221;<br \/>&#8220;v[name=verbose,flag]&#8221;, &#8220;setup Warbird for static or dynamic analysis&#8221;),<\/p>\n<p>&#8230;<\/p>\n<p>&#8220;`<\/p>\n<p>Details regarding each command implementation can be further investigated in<br \/>the body of a designated class.<\/p>\n<p># TECHNICAL DESCRIPTION<br \/>A more detailed overview of the key problems encountered along exploitation<br \/>techniques used is given below.<\/p>\n<p>## WARBIRD DECRYPTION PRIMITIVES<br \/>Binaries produced by Warbird are usually encrypted and its code obfuscated. <br \/>These binaries can execute &#8220;encrypted&#8221; code too.<\/p>\n<p>Warbird binaries can be easily identified as they usually contain a special<br \/>PE Image section named `?g_Encr`.<\/p>\n<p>Windows 10\/11 contains several images with such sections of which PlayReady<br \/>client library is a signature example:<\/p>\n<p>&#8220;`<br \/>wret&gt; image w10_prlib.dll<br \/>ImageCmd::run<br \/>&#8211; Dll init w10_prlib.dll<br \/>size 10347408 bytes<br \/>base 180000000<br \/>wret&gt; prlib w10_prlib.dll<br \/>PRLib::run<br \/>wret&gt; peinfo -s<br \/>PEInfoCmd::run<br \/>[SECTIONS]* section: .text va 1000 size 6d6a00 char 60000020<br \/>* section: ?g_Encr va 6d8000 size a00 char 60000020<br \/>* section: ?g_Encr va 6d9000 size 800 char 60000020<br \/>* section: ?g_Encr va 6da000 size e400 char 60000020<br \/>* section: ?g_Encr va 6e9000 size 800 char 60000020<br \/>* section: ?g_Encr va 6ea000 size 600 char 60000020<br \/>* section: ?g_Encr va 6eb000 size d800 char 60000020<br \/>* section: ?g_Encr va 6f9000 size 200 char 60000020<br \/>* section: ?g_Encr va 6fa000 size 200 char 60000020<br \/>* section: .rdata va 6fb000 size 291200 char 40000040<br \/>* section: .data va 98d000 size b284 char c0000040<br \/>* section: .pdata va 999000 size 24400 char 40000040<br \/>* section: .didat va 9be000 size 200 char c0000040<br \/>* section: .rsrc va 9bf000 size 9600 char 40000040<br \/>* section: .reloc va 9c9000 size a600 char 42000040<br \/>&#8230;<br \/>&#8220;`<\/p>\n<p>Warbird binaries can contain both data and code sections encrypted. Summary of <br \/>the encryption rate used with respect to various sections for sample prlib can<br \/>be seen below:<\/p>\n<p>&#8220;`<br \/>wret&gt; wbinfo -s<br \/>WBInfoCmd::run<br \/>Warbird encrypted binary<br \/>* section: .text va 1000 size 6d6a00 encr_size 580f8a (80%)<br \/>* section: .rdata va 6fb000 size 291200 encr_size 12821d (45%)<br \/>* section: .data va 98d000 size b284 encr_size 4aae (41%)<br \/>&#8220;`<\/p>\n<p>In order to support encrypted code and data, Warbird binaries embed data<br \/>structures (WB segment descriptors) describing encrypted memory portions<br \/>(segments) of Warbird image along some metadata needed for their handling <br \/>(decryption or encryption).<\/p>\n<p>There are two such base structures<br \/>1) WB segment descriptors, which usually describe encrypted data located in <br \/>`.data` or `.rdata` sections<br \/>2) WB heap execute segment descriptors, which describe encrypted code located <br \/>in `.text` section<\/p>\n<p>WB segments descriptors used by sample prlib is shown below:<\/p>\n<p>&#8220;`<br \/>wret&gt; wbinfo -S<br \/>WBInfoCmd::run<br \/>Warbird encrypted binary<br \/>&#8211; wb segment<br \/>desc_va: 0x6d8000 size 100<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>segments (num 1, data size 223):<br \/>* [0000] encr_va 6b3d00 size 0000df section .text<br \/>&#8211; wb segment<br \/>desc_va: 0x6d8100 size 100<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>segments (num 1, data size 415):<br \/>* [0000] encr_va 6b3a40 size 00019f section .text<br \/>&#8211; wb segment<br \/>desc_va: 0x6d8200 size 10c<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>segments (num 2, data size 2306):<br \/>* [0000] encr_va 6b3120 size 000401 section .text<br \/>* [0001] encr_va 6b3530 size 000501 section .text<br \/>&#8211; wb segment<br \/>desc_va: 0x6d830c size 100<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>segments (num 1, data size 1590):<br \/>* [0000] encr_va 656b50 size 000636 section .text<br \/>&#8211; wb segment<br \/>desc_va: 0x6d840c size 100<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>segments (num 1, data size 1607):<br \/>* [0000] encr_va 657190 size 000647 section .text<br \/>&#8211; wb segment<br \/>desc_va: 0x6d850c size 100<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>segments (num 1, data size 3676):<br \/>* [0000] encr_va 65a220 size 000e5c section .text<br \/>&#8211; wb segment<br \/>desc_va: 0x6d8618 size 100<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>segments (num 1, data size 3568):<br \/>* [0000] encr_va 65b090 size 000df0 section .text<br \/>&#8211; wb segment<br \/>desc_va: 0x6d8718 size 100<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>segments (num 1, data size 213):<br \/>* [0000] encr_va 6b3df0 size 0000d5 section .text<br \/>&#8211; wb segment<br \/>desc_va: 0x6d9000 size 100<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>segments (num 1, data size 260):<br \/>* [0000] encr_va 6b3bf0 size 000104 section .text<br \/>&#8211; wb segment<br \/>desc_va: 0x6d9100 size 100<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>segments (num 1, data size 5246):<br \/>* [0000] encr_va 6577e0 size 00147e section .text<br \/>&#8211; wb segment<br \/>desc_va: 0x6d9200 size 100<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>segments (num 1, data size 198):<br \/>* [0000] encr_va 6b3050 size 0000c6 section .text<br \/>&#8211; wb segment<br \/>desc_va: 0x6d9300 size 100<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>segments (num 1, data size 24955):<br \/>* [0000] encr_va 6509c0 size 00617b section .text<br \/>&#8211; wb segment<br \/>desc_va: 0x6d9400 size 100<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>segments (num 1, data size 37303):<br \/>* [0000] encr_va 6a19e0 size 0091b7 section .text<br \/>&#8211; wb segment<br \/>desc_va: 0x6d9500 size 10c<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>segments (num 2, data size 48910):<br \/>* [0000] encr_va 152b70 size 00beec section .text<br \/>* [0001] encr_va 7d1a20 size 000022 section .rdata<br \/>&#8211; wb segment<br \/>&#8230;<br \/>&#8220;`<\/p>\n<p>WB heap execute descriptors for the same lib is listed below:<\/p>\n<p>&#8220;`<br \/>wret&gt; wbinfo -H<br \/>WBInfoCmd::run<br \/>Warbird encrypted binary<br \/>&#8211; wb heap exec<br \/>desc_va: 0x3080 size f0<br \/>encr_code: va 0x6bacf0 size 0x17f<br \/>&#8211; wb heap exec<br \/>desc_va: 0x33c0 size f0<br \/>encr_code: va 0x123950 size 0x300<br \/>&#8211; wb heap exec<br \/>desc_va: 0x3700 size f0<br \/>encr_code: va 0xce7d0 size 0x55c<br \/>&#8211; wb heap exec<br \/>desc_va: 0x3a40 size f0<br \/>encr_code: va 0x1abde0 size 0xd9<br \/>&#8211; wb heap exec<br \/>desc_va: 0x3d80 size f0<br \/>encr_code: va 0x6c3d50 size 0x19d<br \/>&#8211; wb heap exec<br \/>desc_va: 0x40c0 size f0<br \/>encr_code: va 0x1acd10 size 0x17a<br \/>&#8211; wb heap exec<br \/>desc_va: 0x4400 size f0<br \/>encr_code: va 0x10c580 size 0x103<br \/>&#8211; wb heap exec<br \/>desc_va: 0x4740 size f0<br \/>encr_code: va 0x1ba61c size 0x319<br \/>&#8211; wb heap exec<br \/>desc_va: 0x4a80 size f0<br \/>encr_code: va 0x6bde70 size 0x1e0<br \/>&#8211; wb heap exec<br \/>desc_va: 0x4dc0 size f0<br \/>encr_code: va 0x150100 size 0x106<br \/>&#8211; wb heap exec<br \/>desc_va: 0x5100 size f0<br \/>encr_code: va 0x15ebb0 size 0x18a<br \/>&#8211; wb heap exec<br \/>desc_va: 0x5440 size f0<br \/>encr_code: va 0x6c0cf0 size 0x3a<br \/>&#8211; wb heap exec<br \/>desc_va: 0x5780 size f0<br \/>encr_code: va 0x1097e0 size 0x306<br \/>&#8211; wb heap exec<br \/>desc_va: 0x5ac0 size f0<br \/>encr_code: va 0x14b9e0 size 0x1665<br \/>&#8211; wb heap exec<br \/>desc_va: 0x5e00 size f0<br \/>encr_code: va 0x15ea70 size 0xdb<br \/>&#8211; wb heap exec<br \/>desc_va: 0x6140 size f0<br \/>encr_code: va 0x1a3b90 size 0x1c3<br \/>&#8211; wb heap exec<br \/>desc_va: 0x6480 size f0<br \/>encr_code: va 0x1523b0 size 0x282<br \/>&#8211; wb heap exec<br \/>desc_va: 0x67c0 size f0<br \/>encr_code: va 0x178be0 size 0x57c<br \/>&#8211; wb heap exec<br \/>desc_va: 0x6b00 size f0<br \/>encr_code: va 0x14e780 size 0x486<br \/>&#8211; wb heap exec<br \/>desc_va: 0x6e40 size f0<br \/>encr_code: va 0x6be370 size 0x25b<br \/>&#8211; wb heap exec<br \/>desc_va: 0x7180 size f0<br \/>encr_code: va 0x1482b0 size 0x114<br \/>&#8211; wb heap exec<br \/>desc_va: 0x74c0 size f0<br \/>encr_code: va 0x1b4560 size 0x7e<br \/>&#8230;<br \/>&#8220;`<\/p>\n<p>WB segment descriptors can group several memory chunks (segments), which is the<br \/>case for WB descriptor 0x6d8200 depicted above.<\/p>\n<p>Information about target encrypted memory region and its size is denoted by the<br \/>`encr_va` and `size` props.<\/p>\n<p>Further information about WB descriptor such as encryption key \/ Feistel <br \/>symmetric algorithm rounds&#8217; data can be displayed too:<\/p>\n<p>&#8220;`<br \/>wret&gt; wbdesc -S 0x6d8000<br \/>WBDescCmd::run<br \/>Warbird encrypted binary<br \/>&#8211; wb descriptor<br \/>digest:<br \/>0x00000000: a5 23 6e 27 11 7f d3 e3 d6 8f 72 b5 91 5f ad 87 .#n&#8217;&#8230;&#8230;r.._..<br \/>0x00000010: 94 a1 6b ba c5 58 83 8b ce d8 2a ca 82 78 2c ba ..k..X&#8230;.*..x,.<br \/>size: 0x100<br \/>v1: 0x0<br \/>desc_va: 0x6d8000<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>encr_va: 0x0 num 0x80000000<br \/>v2: 0x1<br \/>v3: 0x1<br \/>v4: 0x1<br \/>key: 0x82b8c764000b3d0f<br \/>0 Feistel round<br \/>v1: 0x3<br \/>v2: 0x6b<br \/>v3: 0xb2<br \/>v4: 0x11<br \/>1 Feistel round<br \/>v1: 0x2<br \/>v2: 0xf5<br \/>v3: 0x33<br \/>v4: 0x34<br \/>2 Feistel round<br \/>v1: 0xc<br \/>v2: 0xfd<br \/>v3: 0xef<br \/>v4: 0x96<br \/>3 Feistel round<br \/>v1: 0x1e<br \/>v2: 0xf1<br \/>v3: 0x48<br \/>v4: 0xd3<br \/>4 Feistel round<br \/>v1: 0x17<br \/>v2: 0xc9<br \/>v3: 0x15<br \/>v4: 0xf4<br \/>5 Feistel round<br \/>v1: 0x4<br \/>v2: 0x67<br \/>v3: 0xa<br \/>v4: 0xde<br \/>6 Feistel round<br \/>v1: 0x1<br \/>v2: 0x4b<br \/>v3: 0x5f<br \/>v4: 0x6c<br \/>7 Feistel round<br \/>v1: 0xf<br \/>v2: 0xeb<br \/>v3: 0x41<br \/>v4: 0xb1<br \/>8 Feistel round<br \/>v1: 0xb<br \/>v2: 0x5d<br \/>v3: 0x7f<br \/>v4: 0xf2<br \/>9 Feistel round<br \/>v1: 0x13<br \/>v2: 0xa8<br \/>v3: 0xf6<br \/>v4: 0xcf<br \/>&#8220;`<\/p>\n<p>Similarly, WB heap execute descriptors can be inspected in a detail too:<\/p>\n<p>&#8220;`<br \/>wret&gt; wbdesc -H 0x3080<br \/>WBDescCmd::run<br \/>Warbird encrypted binary<br \/>&#8211; wb descriptor<br \/>digest:<br \/>0x00000000: 26 a5 94 8b d5 7a 6f a4 5d 9d f6 43 ad 45 e4 cf &amp;&#8230;.zo.]..C.E..<br \/>0x00000010: a8 a8 aa 26 86 3f 72 f9 ce 45 97 30 67 cb 83 28 &#8230;&amp;.?r..E.0g..(<br \/>size: 0xf0<br \/>v1: 0xffffff00<br \/>desc_va: 0xf0003080<br \/>relocs_va: 0x0 num 0xffff00c3<br \/>encr_va: 0xf06bacf0 num 0xf000017f<br \/>v2: 0xf0000000<br \/>v3: 0xf0000000<br \/>v4: 0xf0000000<br \/>key: 0x843cdaecb328ce8a<br \/>0 Feistel round<br \/>v1: 0x8<br \/>v2: 0x3f<br \/>v3: 0xf<br \/>v4: 0xb<br \/>1 Feistel round<br \/>v1: 0x7<br \/>v2: 0x30<br \/>v3: 0x9<br \/>v4: 0xdb<br \/>2 Feistel round<br \/>v1: 0x17<br \/>v2: 0x2e<br \/>v3: 0x0<br \/>v4: 0x36<br \/>3 Feistel round<br \/>v1: 0xf<br \/>v2: 0x83<br \/>v3: 0x44<br \/>v4: 0xba<br \/>4 Feistel round<br \/>v1: 0xe<br \/>v2: 0x51<br \/>v3: 0x42<br \/>v4: 0x34<br \/>5 Feistel round<br \/>v1: 0x1a<br \/>v2: 0xa5<br \/>v3: 0x48<br \/>v4: 0x98<br \/>6 Feistel round<br \/>v1: 0x1e<br \/>v2: 0xd6<br \/>v3: 0x1<br \/>v4: 0xdd<br \/>7 Feistel round<br \/>v1: 0xc<br \/>v2: 0x46<br \/>v3: 0xc9<br \/>v4: 0x72<br \/>8 Feistel round<br \/>v1: 0xa<br \/>v2: 0x86<br \/>v3: 0x37<br \/>v4: 0x1c<br \/>9 Feistel round<br \/>v1: 0x14<br \/>v2: 0xe<br \/>v3: 0xe3<br \/>v4: 0xa3<br \/>&#8220;`<\/p>\n<p>Support for Warbird segments is implemented at the kernel level through <br \/>`NtQuerySystemInformation` system call (WB system call):<\/p>\n<p>&#8220;`<br \/>#define WB_SYSCALL_NUMBER 0x36<br \/>&#8220;`<\/p>\n<p>and dedicated system information call:<\/p>\n<p>&#8220;`<br \/>#define WB_SYSTEM_INFORMATION_CLASS 0xb9<br \/>&#8220;`<\/p>\n<p>A pointer to the following structure specifies arguments for target WB call:<\/p>\n<p>&#8220;`<br \/>typedef struct {<br \/>DWORD64 op;<br \/>union {<br \/>DecryptEncryptionSegment_args_t decrypt;<br \/>ReencryptEncryptionSegment_args_t reencrypt;<br \/>RemoveProcess_args_t remove;<br \/>ProcessStartup_args_t startup;<br \/>};<br \/>} WarbirdCallArgs_t;<br \/>&#8220;`<\/p>\n<p>The following Warbird operations are supported:<\/p>\n<p>&#8220;`<br \/>typedef enum {<br \/>WbDecryptEncryptionSegment=1,<br \/>WbReEncryptEncryptionSegment,<br \/>WbHeapExecuteCall,<br \/>WbSetTrapFrame,<br \/>WbInvalidOp1,<br \/>WbInvalidOp2,<br \/>WbRemoveWarbirdProcess,<br \/>WbProcessStartup,<br \/>WbProcessModuleUnload<br \/>} wb_call_t;<br \/>&#8220;`<\/p>\n<p>It looks all data needed to handle encrypted WB segments are contained in a<br \/>target segment itself. As such it should be possible to decrypt arbitrary WB <br \/>segments as long as details of the Feistel algorithm implemented at kernel<br \/>side is reverse engineered (such as on a non-Windows system).<\/p>\n<p>A more straightforward approach has been taken by us though as the kernel<br \/>provides functionality to both decrypt and reencrypt WB segments.<\/p>\n<p>Such a decryption is illustrated below for sample WB segment:<\/p>\n<p>&#8220;`<br \/>&#8211; wb segment<br \/>desc_va: 0x6f9000 size 10c<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>segments (num 2, data size 605):<br \/>* [0000] encr_va 5a1b60 size 000087 section .text<br \/>* [0001] encr_va 5a1bf0 size 0001d6 section .text<br \/>&#8220;`<\/p>\n<p>Below, content of its encrypted memory is shown (encrypted code):<\/p>\n<p>&#8220;`<br \/>wret&gt; dmem 0x5a1bf0<br \/>DmemCmd::run<br \/>va 5a1bf0<br \/>0x00000000: a3 ce 76 4c 0f 61 2b 24 3f 1e 5b 38 ef 7b 06 52 ..vL.a+$?.[8.{.R<br \/>0x00000010: f4 58 19 1b 32 ba 88 5c 89 44 6c d2 b9 9e a5 df .X..2..\\.Dl&#8230;..<br \/>0x00000020: eb 3e dd 2c 7e 58 d3 1a e7 00 35 2e 01 fc 59 b1 .&gt;.,~X&#8230;.5&#8230;Y.<br \/>0x00000030: 79 95 e8 3f c0 9e 04 0a a8 6f 2b 63 8f c8 de ea y..?&#8230;..o+c&#8230;.<br \/>0x00000040: 6b f0 51 7e 5c 90 25 ee 29 3d 49 df 5e c8 4c 27 k.Q~\\.%.)=I.^.L&#8217;<br \/>0x00000050: 40 7f 04 27 cd ef 0c d1 76 3a 49 af 91 f8 c9 91 @..&#8217;&#8230;.v:I&#8230;..<br \/>0x00000060: 6f 09 b4 a7 b0 3e 1b 3b 08 09 d4 84 c0 f6 5d 71 o&#8230;.&gt;.;&#8230;&#8230;]q<br \/>0x00000070: 06 c8 93 50 b7 93 fa 0d 52 fd 14 a1 21 7e 96 a4 &#8230;P&#8230;.R&#8230;!~..<br \/>0x00000080: 70 8d 6d d4 84 2c d9 c3 3c 62 e9 7e 12 55 13 0f p.m..,..&lt;b.~.U..<br \/>0x00000090: af 70 02 d3 7d 45 76 59 bd 6b fd ac bf 95 46 c7 .p..}EvY.k&#8230;.F.<br \/>0x000000a0: 43 da ee 1f 6a 26 c4 f1 5e ab 81 4f 35 33 71 28 C&#8230;j&amp;..^..O53q(<br \/>0x000000b0: c5 f0 05 e2 79 cd d0 65 20 bf cb 35 58 1a 88 32 &#8230;.y..e ..5X..2<br \/>0x000000c0: b6 64 28 3c 3c 3a f4 7d 03 bf d1 eb c8 50 15 84 .d(&lt;&lt;:.}&#8230;..P..<br \/>0x000000d0: b9 78 f8 58 7f bc 9a 74 f3 ff a9 5e d0 04 4b a9 .x.X&#8230;t&#8230;^..K.<br \/>0x000000e0: a3 22 e1 d9 6f 90 11 c6 11 c2 39 7b f9 11 8f 91 .&#8221;..o&#8230;..9{&#8230;.<br \/>0x000000f0: dd a3 a5 dd e2 64 2f 04 02 7a a6 c8 3f 0c 8d cb &#8230;..d\/..z..?&#8230;<br \/>&#8220;`<\/p>\n<p>Arbitrary decryption operation is shown below:<\/p>\n<p>&#8220;`<br \/>wret&gt; wbop -d 0x5a1b60 -v<br \/>WBOpCmd::run<br \/>&#8211; wb segment<br \/>digest:<br \/>0x00000000: 96 35 e0 86 ca d1 dc b2 7d 21 3b 7d 23 98 4c 48 .5&#8230;&#8230;}!;}#.LH<br \/>0x00000010: 62 1e 12 36 cc d7 6f b9 2d 9b 46 e1 b0 c6 ee cd b..6..o.-.F&#8230;..<br \/>desc_va: 0x6f9000 size 10c<br \/>relocs_va: 0x9436f0 num 0x5197<br \/>key: 0x2ad255175333d38b<br \/>segments (num 2, data size 605):<br \/>* [0000] encr_va 5a1b60 size 000087 section .text<br \/>flags 30c|READWRITE|WRITECOPY|GUARD|NOCACHE<br \/>* [0001] encr_va 5a1bf0 size 0001d6 section .text<br \/>flags 30c|READWRITE|WRITECOPY|GUARD|NOCACHE<br \/>&#8211; WarbirdCallArgs<br \/>* op: WbDecryptEncryptionSegment<br \/>* segdesc: 7ffd2ca39000<br \/>* image_base1: 7ffd2c340000<br \/>* image_base2: 7ffd2c340000<br \/>* relocs: 7ffd2cc836f0<br \/>* relocs_num: 5197<br \/>&#8211; NtQuerySystemInformation res: 0<br \/>&#8211; segment status: CHANGED<br \/>decrypted<br \/>&#8220;`<\/p>\n<p>It can be verified that the content of memory got changed as a result of the<br \/>op (it got decrypted).<\/p>\n<p>&#8220;`<br \/>wret&gt; dmem 0x5a1b60<br \/>DmemCmd::run<br \/>va 5a1b60<br \/>0x00000000: 19 91 16 4e a3 bf 6a e0 1a 02 76 30 44 75 25 f4 &#8230;N..j&#8230;v0Du%.<br \/>0x00000010: d0 40 37 2d 8c 85 95 88 78 d3 91 8f b0 38 c9 fc .@7-&#8230;.x&#8230;.8..<br \/>0x00000020: b3 a5 ac 0d 65 df 0c b0 88 82 3c d7 8c 68 bd 7e &#8230;.e&#8230;..&lt;..h.~<br \/>0x00000030: 08 51 19 6e 48 2d 64 31 89 da ec 72 38 dc 5e c0 .Q.nH-d1&#8230;r8.^.<br \/>0x00000040: c1 24 71 4f e1 e9 3b da c3 8b 45 eb 1f ef 9f b1 .$qO..;&#8230;E&#8230;..<br \/>0x00000050: f7 55 18 53 47 ed 44 6f 62 71 2c 93 f9 79 af c9 .U.SG.Dobq,..y..<br \/>0x00000060: 77 f6 cc d3 03 47 41 29 b1 51 21 35 44 1c 30 80 w&#8230;.GA).Q!5D.0.<br \/>0x00000070: b4 ba e9 58 39 ea 91 65 4e 24 0c 55 43 b6 c3 02 &#8230;X9..eN$.UC&#8230;<br \/>0x00000080: 94 6d 2d 39 39 6e 70 cc cc cc cc cc cc cc cc cc .m-99np&#8230;&#8230;&#8230;<br \/>0x00000090: a3 ce 76 4c 0f 61 2b 24 3f 1e 5b 38 ef 7b 06 52 ..vL.a+$?.[8.{.R<br \/>0x000000a0: f4 58 19 1b 32 ba 88 5c 89 44 6c d2 b9 9e a5 df .X..2..\\.Dl&#8230;..<br \/>0x000000b0: eb 3e dd 2c 7e 58 d3 1a e7 00 35 2e 01 fc 59 b1 .&gt;.,~X&#8230;.5&#8230;Y.<br \/>0x000000c0: 79 95 e8 3f c0 9e 04 0a a8 6f 2b 63 8f c8 de ea y..?&#8230;..o+c&#8230;.<br \/>0x000000d0: 6b f0 51 7e 5c 90 25 ee 29 3d 49 df 5e c8 4c 27 k.Q~\\.%.)=I.^.L&#8217;<br \/>0x000000e0: 40 7f 04 27 cd ef 0c d1 76 3a 49 af 91 f8 c9 91 @..&#8217;&#8230;.v:I&#8230;..<br \/>0x000000f0: 6f 09 b4 a7 b0 3e 1b 3b 08 09 d4 84 c0 f6 5d 71 o&#8230;.&gt;.;&#8230;&#8230;]q<br \/>&#8220;`<\/p>\n<p>Decryption of heap execute segments is not that straightforward. Upon a WB<br \/>system call, the following happens at kernel side:<br \/>&#8211; a dynamic memory block gets allocated for a user process and encrypted code<br \/>block is decrypted into it, the block gets marked as no-write (exec only)<br \/>&#8211; registers and stack gets filled with arguments provided through WB system<br \/>call structure, this is done in a special way though:<br \/>* top of the stack doesn&#8217;t contain the usual return address (`[rsp]`), but a<br \/>pointer to data structure containing ptr to subroutine result and its<br \/>arguments<br \/>* the `rcx` is loaded with this pointer at the beginning of WB heap exec call<br \/>* subroutine arguments do not follow the usual arguments passing convention,<br \/>the arguments are references through `rcx` pointer<br \/>* subroutine arguments are put into a contiguous block (they are serialized),<br \/>as a result they may occupy different offsets (with respect to `rcx` for<br \/>different subroutines, pointers might not be aligned)<br \/>&#8211; the decrypted subroutine call is executed through the APC (Asynchronous<br \/>Procedure Call) mechanism (the call to `PspSetContextThreadInternal` is used<br \/>for the setup), queued APC is executed prior to user thread&#8217;s resuming<br \/>execution (prior to syscall return) <br \/>&#8211; subroutine result (if any) gets stored to `[rcx]` memory location<br \/>&#8211; upon call completion, return from WB system call is done.<\/p>\n<p>The above construction is done for the following reason:<br \/>&#8211; to make traps \/ breakpoints setting difficult (target calls are executed at<br \/>semi-random addresses)<br \/>&#8211; hijacking of WB heap execution calls becomes problematic (there is ptr to <br \/>args in place of return addr)<br \/>&#8211; everything happens in one shot \/ semi-atomic way &#8211; the call is seen as if it<br \/>has been executed by WB system call.<\/p>\n<p>We have found a way to decrypt heap execute segments though. One can simply do<br \/>the following:<br \/>&#8211; change the valid WB segment descriptor and point it to heap execute descriptor<br \/>(mimic encrypted heap execute WB segment as if it was a pure WB segment),<br \/>&#8211; compute the SHA256 digest to make any changes conducted to the WB segment<br \/>look legitimate to the kernel<br \/>&#8211; issue WbDecryptEncryptionSegment WB system call op.<\/p>\n<p>The implementation of this is shown below for the following heap exec segment:<\/p>\n<p>&#8220;`<br \/>&#8211; wb heap exec<br \/>desc_va: 0x3080 size f0<br \/>encr_code: va 0x6bacf0 size 0x17f<\/p>\n<p>wret&gt; wbop -d 0x6bacf0 -v<br \/>WBOpCmd::run<br \/>ORG CUSTDESC<br \/>0x00000000: fd bb 33 d3 a2 c0 1d 7d a9 36 be 94 f9 23 c2 68 ..3&#8230;.}.6&#8230;#.h<br \/>0x00000010: 32 00 22 79 28 d3 28 dd bc bb 97 2b 25 36 96 85 2.&#8221;y(.(&#8230;.+%6..<br \/>0x00000020: 00 01 00 00 00 00 00 00 00 81 6d 00 f0 36 94 00 &#8230;&#8230;&#8230;.m..6..<br \/>0x00000030: 97 51 00 00 00 00 00 00 00 00 00 80 01 00 00 00 .Q&#8230;&#8230;&#8230;&#8230;..<br \/>0x00000040: a1 00 00 00 00 00 00 00 85 42 bd 41 c6 55 87 6a &#8230;&#8230;&#8230;B.A.U.j<br \/>0x00000050: 0c 00 00 00 69 00 00 00 67 00 00 00 d5 00 00 00 &#8230;.i&#8230;g&#8230;&#8230;.<br \/>0x00000060: 13 00 00 00 92 00 00 00 de 00 00 00 dc 00 00 00 &#8230;&#8230;&#8230;&#8230;&#8230;.<br \/>0x00000070: 01 00 00 00 81 00 00 00 11 00 00 00 39 00 00 00 &#8230;&#8230;&#8230;&#8230;9&#8230;<br \/>0x00000080: 11 00 00 00 17 00 00 00 45 00 00 00 2a 00 00 00 &#8230;&#8230;..E&#8230;*&#8230;<br \/>0x00000090: 1d 00 00 00 60 00 00 00 21 00 00 00 0f 00 00 00 &#8230;.`&#8230;!&#8230;&#8230;.<br \/>0x000000a0: 10 00 00 00 5f 00 00 00 a6 00 00 00 d8 00 00 00 &#8230;._&#8230;&#8230;&#8230;..<br \/>0x000000b0: 16 00 00 00 1e 00 00 00 55 00 00 00 ef 00 00 00 &#8230;&#8230;..U&#8230;&#8230;.<br \/>0x000000c0: 07 00 00 00 ad 00 00 00 05 00 00 00 09 00 00 00 &#8230;&#8230;&#8230;&#8230;&#8230;.<br \/>0x000000d0: 18 00 00 00 27 00 00 00 7d 00 00 00 dd 00 00 00 &#8230;.&#8217;&#8230;}&#8230;&#8230;.<br \/>0x000000e0: 0e 00 00 00 d4 00 00 00 ca 00 00 00 4f 00 00 00 &#8230;&#8230;&#8230;&#8230;O&#8230;<br \/>0x000000f0: 01 00 00 00 0c 03 00 00 40 3a 6b 00 9f 01 00 00 &#8230;&#8230;..@:k&#8230;..<br \/>BEFORE DECRYPTION 6bacf0<br \/>0x00000000: 9d a4 f6 33 08 80 2c 6b 48 06 5d 65 47 e1 87 c2 &#8230;3..,kH.]eG&#8230;<br \/>0x00000010: 73 4e db b7 85 dd d5 a8 94 86 3b 41 b7 5c f7 1e sN&#8230;&#8230;..;A.\\..<br \/>0x00000020: 76 1e a6 14 51 79 31 ab 1a 68 48 c5 e5 b4 ec 5e v&#8230;Qy1..hH&#8230;.^<br \/>0x00000030: de 5f da 99 7d 40 3f 5f fa e0 a6 fe 44 3b 38 fc ._..}@?_&#8230;.D;8.<br \/>0x00000040: e6 e2 e4 ab 60 ab e1 74 1e 1b f6 60 0e 4a e2 a5 &#8230;.`..t&#8230;`.J..<br \/>0x00000050: 94 2f ef 6d 84 84 4d 52 75 bf 4b ac 6a 0f ee 4d .\/.m..MRu.K.j..M<br \/>0x00000060: 37 47 77 b4 3f 63 f7 03 23 5e da 8d be bc 2f 23 7Gw.?c..#^&#8230;.\/#<br \/>0x00000070: 5e ee b4 e9 13 fd 08 e9 7f 14 16 0e 49 03 cf 4a ^&#8230;&#8230;&#8230;..I..J<br \/>0x00000080: 67 8b cc ce 28 9a 6b bd 4c 23 8d 36 d0 9b 5d 26 g&#8230;(.k.L#.6..]&amp;<br \/>0x00000090: 45 e6 cd 34 c3 87 76 17 29 b8 86 ab a8 e9 57 d2 E..4..v.)&#8230;..W.<br \/>0x000000a0: bd da 3e 0b c2 d1 fa 56 5f 46 60 b1 4a e1 10 55 ..&gt;&#8230;.V_F`.J..U<br \/>0x000000b0: a6 47 9d 5b 63 df f2 e4 5e 92 99 35 44 73 00 f2 .G.[c&#8230;^..5Ds..<br \/>0x000000c0: f1 50 5c d7 af fa cb 9e b3 7c 22 4f f4 d6 7e 14 .P\\&#8230;&#8230;|&#8221;O..~.<br \/>0x000000d0: f6 59 6d 8c 54 f0 86 af e4 3f 27 9d 01 22 3e 9a .Ym.T&#8230;.?&#8217;..&#8221;&gt;.<br \/>0x000000e0: 23 81 30 33 a9 2e eb fd 9b 19 6f b0 4f d5 33 ff #.03&#8230;&#8230;o.O.3.<br \/>0x000000f0: 19 99 fb 7a 95 56 8e ae 3c 97 ac 0e d0 ad fe 91 &#8230;z.V..&lt;&#8230;&#8230;.<br \/>0x00000100: ec e1 26 1b e6 e5 40 70 b6 4a ac 8f 1e 11 be 00 ..&amp;&#8230;@p.J&#8230;&#8230;<br \/>0x00000110: 50 47 8c 57 c1 fe e6 4d d8 26 be 23 ca b0 16 48 PG.W&#8230;M.&amp;.#&#8230;H<br \/>0x00000120: 7b c5 59 a0 9f 59 81 3c 6b 6f cc 3e 07 6e 41 c9 {.Y..Y.&lt;ko.&gt;.nA.<br \/>0x00000130: 9a 33 2e b6 c9 ec 4b ca e5 0d 05 aa 43 d5 0f a0 .3&#8230;.K&#8230;..C&#8230;<br \/>0x00000140: 40 d5 79 3b 8b 80 f4 6c 60 55 eb 00 dd 2a 31 66 @.y;&#8230;l`U&#8230;*1f<br \/>0x00000150: 92 b7 51 c7 1d f9 3f 60 5f 22 fe ee 3b 3b 05 21 ..Q&#8230;?`_&#8221;..;;.!<br \/>0x00000160: 85 bf 90 72 33 4e c1 4c 72 67 4f c7 31 b4 99 9f &#8230;r3N.LrgO.1&#8230;<br \/>0x00000170: b1 20 ca 8a 09 5c ab 0d 4b da 8c 30 37 83 9f . &#8230;\\..K..07..<br \/>FAKE CUSTDESC<br \/>0x00000000: bd d8 c4 24 a8 2d 15 03 1f 95 96 c4 99 ad 0f 75 &#8230;$.-&#8230;&#8230;&#8230;u<br \/>0x00000010: 9a e2 74 64 3b f3 89 20 8c 78 eb a4 50 fd 68 fa ..td;.. .x..P.h.<br \/>0x00000020: 00 01 00 00 00 00 00 00 00 81 6d 00 f0 36 94 00 &#8230;&#8230;&#8230;.m..6..<br \/>0x00000030: 97 51 00 00 00 00 00 00 00 00 00 80 01 00 00 00 .Q&#8230;&#8230;&#8230;&#8230;..<br \/>0x00000040: 00 00 00 f0 00 00 00 00 8a ce 28 b3 ec da 3c 84 &#8230;&#8230;&#8230;.(&#8230;&lt;.<br \/>0x00000050: 08 00 00 00 3f 00 00 00 0f 00 00 00 0b 00 00 00 &#8230;.?&#8230;&#8230;&#8230;..<br \/>0x00000060: 07 00 00 00 30 00 00 00 09 00 00 00 db 00 00 00 &#8230;.0&#8230;&#8230;&#8230;..<br \/>0x00000070: 17 00 00 00 2e 00 00 00 00 00 00 00 36 00 00 00 &#8230;&#8230;&#8230;&#8230;6&#8230;<br \/>0x00000080: 0f 00 00 00 83 00 00 00 44 00 00 00 ba 00 00 00 &#8230;&#8230;..D&#8230;&#8230;.<br \/>0x00000090: 0e 00 00 00 51 00 00 00 42 00 00 00 34 00 00 00 &#8230;.Q&#8230;B&#8230;4&#8230;<br \/>0x000000a0: 1a 00 00 00 a5 00 00 00 48 00 00 00 98 00 00 00 &#8230;&#8230;..H&#8230;&#8230;.<br \/>0x000000b0: 1e 00 00 00 d6 00 00 00 01 00 00 00 dd 00 00 00 &#8230;&#8230;&#8230;&#8230;&#8230;.<br \/>0x000000c0: 0c 00 00 00 46 00 00 00 c9 00 00 00 72 00 00 00 &#8230;.F&#8230;&#8230;.r&#8230;<br \/>0x000000d0: 0a 00 00 00 86 00 00 00 37 00 00 00 1c 00 00 00 &#8230;&#8230;..7&#8230;&#8230;.<br \/>0x000000e0: 14 00 00 00 0e 00 00 00 e3 00 00 00 a3 00 00 00 &#8230;&#8230;&#8230;&#8230;&#8230;.<br \/>0x000000f0: 01 00 00 00 0c 03 00 00 f0 ac 6b 00 7f 01 00 00 &#8230;&#8230;&#8230;.k&#8230;..<br \/>AFTER DECRYPTION 6bacf0<br \/>0x00000000: 48 8d 15 f9 ff ff ff 48 8b 0c 24 48 89 5c 24 10 H&#8230;&#8230;H..$H.\\$.<br \/>0x00000010: 48 89 6c 24 18 56 57 41 54 41 56 41 57 48 83 ec H.l$.VWATAVAWH..<br \/>0x00000020: 60 4c 8b 79 18 48 8b ea 4c 8b 61 10 48 8b 79 08 `L.y.H..L.a.H.y.<br \/>0x00000030: 4c 8b 31 48 8b 5a f0 48 8d 05 92 57 a1 ff 48 8b L.1H.Z.H&#8230;W..H.<br \/>0x00000040: 72 f8 48 8b cf 48 8d 15 a4 b1 10 00 48 03 c3 48 r.H..H&#8230;&#8230;H..H<br \/>0x00000050: 03 d3 ff d0 85 c0 75 41 48 8d 15 81 b1 10 00 48 &#8230;&#8230;uAH&#8230;&#8230;H<br \/>0x00000060: 8b cf 48 8d 05 67 57 a1 ff 48 03 d3 48 03 c3 ff ..H..gW..H..H&#8230;<br \/>0x00000070: d0 85 c0 75 24 48 8d 15 6c 32 10 00 48 8b cf 48 &#8230;u$H..l2..H..H<br \/>0x00000080: 8d 05 4a 57 a1 ff 48 03 d3 48 03 c3 ff d0 85 c0 ..JW..H..H&#8230;&#8230;<br \/>0x00000090: 75 07 bf 01 40 00 80 eb 54 48 89 7c 24 48 48 8d u&#8230;@&#8230;TH.|$HH.<br \/>0x000000a0: 05 6b 89 94 ff 48 03 c3 48 c7 44 24 30 03 00 00 .k&#8230;H..H.D$0&#8230;<br \/>0x000000b0: 00 48 89 44 24 38 48 8d 54 24 30 48 8d 84 24 90 .H.D$8H.T$0H..$.<br \/>0x000000c0: 00 00 00 4c 89 64 24 50 48 89 44 24 40 41 ba b9 &#8230;L.d$PH.D$@A..<br \/>0x000000d0: 00 00 00 48 8b c6 4c 89 7c 24 58 41 b8 30 00 00 &#8230;H..L.|$XA.0..<br \/>0x000000e0: 00 45 33 c9 0f 05 8b bc 24 90 00 00 00 48 8d 05 .E3&#8230;..$&#8230;.H..<br \/>0x000000f0: 2c 2b 2d 00 48 03 c3 48 8d 0d 22 2b 2d 00 48 39 ,+-.H..H..&#8221;+-.H9<br \/>0x00000100: 04 0b 74 47 85 ff 79 43 48 8d 05 11 2b 2d 00 48 ..tG..yCH&#8230;+-.H<br \/>0x00000110: 8b 04 03 f6 40 1c 08 74 32 48 8d 0d 00 2b 2d 00 &#8230;.@..t2H&#8230;+-.<br \/>0x00000120: 89 7c 24 20 48 8b 0c 0b 4c 8d 05 c9 31 10 00 4c .|$ H&#8230;L&#8230;1..L<br \/>0x00000130: 8d 15 1a fe a0 ff ba 0f 00 00 00 44 8b cf 4c 03 &#8230;&#8230;&#8230;..D..L.<br \/>0x00000140: c3 4c 03 d3 48 8b 49 10 41 ff d2 4c 8d 5c 24 60 .L..H.I.A..L.\\$`<br \/>0x00000150: 41 89 3e 49 8b 5b 38 48 8b c6 49 8b 6b 40 49 8b A.&gt;I.[8H..I.k@I.<br \/>0x00000160: e3 41 5f 41 5e 41 5c 5f 5e 49 ba b9 00 00 00 00 .A_A^A\\_^I&#8230;&#8230;<br \/>0x00000170: 00 00 00 48 33 d2 4d 33 c0 4d 33 c9 0f 05 c3 &#8230;H3.M3.M3&#8230;.<br \/>WB PROLOG FOUND<br \/>decrypted<br \/>&#8220;`<\/p>\n<p>It&#8217;s worth to note that some other issues could be potentially exploited for a<br \/>successful WB heap execute calls&#8217; decryption \/ hijacking such as the following:<br \/>1) executing WB heap execute calls with all register content set to invalid<br \/>values (invalid pointer values in particular), as a result of accessing<br \/>memory through the invalid pointer a trap is raised (as early as possible), <br \/>user thread is stopped and WB heap exec segment is in a decrypted state<br \/>2) reusing the cached (kernel side LRU list) WB heap exec segments, some early<br \/>tests indicated same (predictable) segments&#8217; uses (user level adresses) for<br \/>wb heap exec segments, this makes it possible to set debug breakpoint (such <br \/>as INT3) at the start of the execution of a target heap exec code (stop <br \/>execution of a decrypted heap exec code)<\/p>\n<p>## STATIC AND DYNAMIC ANALYSIS SETUP<br \/>Generic ability to decrypt any Warbird segment constitutes a base for whole <br \/>image decryption.<\/p>\n<p>WRET tool contains support for such a decryption. It also makes it possible to<br \/>prepare Warbird binaries for static or dynamic analysis.<\/p>\n<p>The following illustrates preparation of arbitrary PlayReady binary for static<br \/>analysis:<\/p>\n<p>&#8220;`<br \/>wret&gt; image w10_prlib.dll<br \/>ImageCmd::run<br \/>&#8211; Dll init w10_prlib.dll<br \/>size 10347408 bytes<br \/>base 180000000<br \/>wret&gt; prlib w10_prlib.dll<br \/>PRLib::run<br \/>wret&gt;\\<br \/>wret&gt; wbsetup -s<br \/>WBSetupCmd::run<br \/>Warbird encrypted binary<br \/>### setting up Warbird for static analysis<br \/>&#8211; scanning for heap exec descriptors<br \/>found: 981<br \/>&#8211; scanning for segment descriptors<br \/>found: 37<br \/>&#8211; decrypting heap exec descriptors<br \/>&#8211; decrypting segment descriptors<br \/>&#8211; adjusting relocations<br \/>total: 402<br \/>&#8211; adjusting code refs<br \/>total: 1470<br \/>&#8211; adjusting data refs<br \/>total: 0<br \/>wret&gt; save w10_prlib_static.dll<br \/>SaveCmd::run<br \/>&#8211; saving image [w10_prlib_static.dll]&#8220;`<\/p>\n<p>The following illustrates preparation of PlayReady binary for dynamic analysis:<\/p>\n<p>&#8220;`<br \/>wret&gt; image w10_prlib.dll<br \/>ImageCmd::run<br \/>&#8211; Dll init w10_prlib.dll<br \/>size 10347408 bytes<br \/>base 180000000<br \/>wret&gt; prlib w10_prlib.dll<br \/>PRLib::run<br \/>wret&gt; wbsetup -r<br \/>WBSetupCmd::run<br \/>Warbird encrypted binary<br \/>### setting up Warbird for runtime analysys<br \/>&#8211; scanning for heap exec descriptors<br \/>found: 981<br \/>&#8211; scanning for segment descriptors<br \/>found: 37<br \/>&#8211; decrypting heapexec descriptors<br \/>&#8211; decrypting segment descriptors<br \/>&#8211; locating ret syscalls<br \/>total: 1023<br \/>&#8211; locating heapexec syscalls<br \/>total: 911 (WB_MOV10_B9 880, WB_MOV10_REG 10, WB_LEA10 3, WB_SHARED 18)<br \/>&#8211; adjusting code refs<br \/>total: 1470<br \/>&#8211; adjusting data refs<br \/>total: 2<br \/>&#8211; patching self LEAs<br \/>total: 981<br \/>&#8211; patching ret syscalls<br \/>&#8211; patching heapexec syscalls<br \/>&#8211; patching wb call<br \/>&#8211; patching antidebug call<br \/>&#8211; patching App Policy<br \/>&#8211; Dll init Windows.Media.dll<br \/>size 7145640 bytes<br \/>base 180000000<br \/>&#8211; patching App Model<br \/>&#8211; Dll init Windows.Storage.ApplicationData.dll<br \/>size 435248 bytes<br \/>base 180000000<br \/>&#8211; disabling thread library calls<br \/>wret&gt; save w10_prlib_dynamic.dll<br \/>SaveCmd::run<br \/>&#8211; saving image [w10_prlib_dynamic.dll]wret&gt;<br \/>&#8220;`<\/p>\n<p>Both processes are a little bit more complex due to the following:<br \/>&#8211; WB heap execute calls can embed (issue) other WB system calls (recursive WB<br \/>calls, nested calls make use of SYSCALL instead of `NtQuerySystemInformation`<br \/>call, there is a dedicated WB SYSCALL used to mark return from the call)<br \/>&#8211; the assumption that PlayReady runs as App Container, which requires some App<br \/>Policy to be adjusted too (we mimic the code runs in App Container of MSEdge<br \/>application through the instrumentation of `GetCurrentPackageId`,<br \/>`GetCurrentApplicationUserModelId` and `GetCurrentPackageFamilyName` calls<br \/>in particular).<\/p>\n<p>The static analysis changes offsets used by WB heap execute calls and denoting<br \/>WB segments. The original code such as this one:<\/p>\n<p>&#8220;`<br \/>.text:00000001800CE770 public MSPRMFGetClassObject<br \/>.text:00000001800CE770 MSPRMFGetClassObject proc near ; DATA XREF: .rdata:000000018073821C\u2193o<br \/>.text:00000001800CE770 ; .rdata:off_180988FE8\u2193o &#8230;<br \/>.text:00000001800CE770<br \/>.text:00000001800CE770 arg_0 = dword ptr 8<br \/>.text:00000001800CE770<br \/>.text:00000001800CE770 mov r11, rsp<br \/>.text:00000001800CE773 sub rsp, 58h<br \/>.text:00000001800CE777 xor r9d, r9d ; ReturnLength<br \/>.text:00000001800CE77A mov [r11-20h], rcx<br \/>.text:00000001800CE77E lea rax, qword_180003080 &lt;&#8212;- PTR to WB segment descriptor<br \/>.text:00000001800CE785 mov [r11-18h], rdx<br \/>.text:00000001800CE789 mov [r11-30h], rax<br \/>.text:00000001800CE78D lea rdx, [r11-38h] ; SystemInformation<br \/>.text:00000001800CE791 lea rax, [r11+8].text:00000001800CE795 mov [r11-10h], r8<br \/>.text:00000001800CE799 lea r8d, [r9+30h] ; SystemInformationLength<br \/>.text:00000001800CE79D mov qword ptr [r11-38h], 3<br \/>.text:00000001800CE7A5 mov ecx, 0B9h ; SystemInformationClass<br \/>.text:00000001800CE7AA mov [r11-28h], rax<br \/>.text:00000001800CE7AE call NtQuerySystemInformation<br \/>.text:00000001800CE7B3 mov eax, [rsp+58h+arg_0].text:00000001800CE7B7 add rsp, 58h<br \/>.text:00000001800CE7BB retn<br \/>.text:00000001800CE7BB MSPRMFGetClassObject endp<\/p>\n<p>.text:0000000180003080 qword_180003080 dq 0A46F7AD58B94A526h, 0CFE445AD43F69D5Dh, 0F9723F8626AAA8A8h<br \/>.text:0000000180003080 ; DATA XREF: MSPRMFGetClassObject+E\u2193o<br \/>.text:0000000180003080 dq 2883CB67309745CEh, 0FFFFFF00000000F0h, 0F0003080h, 0F06BACF0FFFF00C3h<br \/>.text:0000000180003080 dq 0F0000000F000017Fh, 0FFFFFFFFF0000000h, 843CDAECB328CE8Ah<br \/>.text:0000000180003080 dq 3F00000008h, 0B0000000Fh, 3000000007h, 0DB00000009h<br \/>.text:0000000180003080 dq 2E00000017h, 3600000000h, 830000000Fh, 0BA00000044h<br \/>.text:0000000180003080 dq 510000000Eh, 3400000042h, 0A50000001Ah, 9800000048h<\/p>\n<p>&#8220;`<br \/>is replaced with the following sequence, which has WB segment pointers resolved<br \/>so that they point to actual code (decrypted one, provided as argument to `LEA`<br \/>instruction):<\/p>\n<p>&#8220;`<br \/>.text:00000001800CE770 MSPRMFGetClassObject proc near ; DATA XREF: .rdata:000000018073821C\u2193o<br \/>.text:00000001800CE770 ; .rdata:off_180988FE8\u2193o<br \/>.text:00000001800CE770<br \/>.text:00000001800CE770 arg_0 = dword ptr 8<br \/>.text:00000001800CE770<br \/>.text:00000001800CE770 mov r11, rsp<br \/>.text:00000001800CE773 sub rsp, 58h<br \/>.text:00000001800CE777 xor r9d, r9d ; ReturnLength<br \/>.text:00000001800CE77A mov [r11-20h], rcx<br \/>.text:00000001800CE77E lea rax, sub_1806BACF0 &lt;&#8212;- PTR to actual code executed by WB syscall<br \/>.text:00000001800CE785 mov [r11-18h], rdx<br \/>.text:00000001800CE789 mov [r11-30h], rax<br \/>.text:00000001800CE78D lea rdx, [r11-38h] ; SystemInformation<br \/>.text:00000001800CE791 lea rax, [r11+8].text:00000001800CE795 mov [r11-10h], r8<br \/>.text:00000001800CE799 lea r8d, [r9+30h] ; SystemInformationLength<br \/>.text:00000001800CE79D mov qword ptr [r11-38h], 3<br \/>.text:00000001800CE7A5 mov ecx, 0B9h ; SystemInformationClass<br \/>.text:00000001800CE7AA mov [r11-28h], rax<br \/>.text:00000001800CE7AE call NtQuerySystemInformation<br \/>.text:00000001800CE7B3 mov eax, [rsp+58h+arg_0].text:00000001800CE7B7 add rsp, 58h<br \/>.text:00000001800CE7BB retn<br \/>.text:00000001800CE7BB MSPRMFGetClassObject endp<br \/>&#8220;`<\/p>\n<p>The above allows to restore the code flow obscured through WB syscalls (such as<br \/>call hierarchy, subroutine uses, etc.).<\/p>\n<p>For dynamic analysis, after decrypting all WB segments (data and code related),<br \/>all invocations of WB system calls encountered in the code gets patched. This<br \/>involves patching `NtQuerySystemInformation` call (patch of IAT entry) along <br \/>the syscall opcode used for embedded WB calls. This also requires some <br \/>instruction reordering \/ fitting due to the need to make place for the call <br \/>instruction transferring execution to common WB glue code (2 bytes long <br \/>`syscall` instruction is replaced with 5 bytes long `call` one). This faces <br \/>some challenges due to insufficient space in target code blocks, shared code<br \/>blocks (single WB invocations used by more than one code path), various<br \/>registers uses, etc.<\/p>\n<p>Without going into the details, it is sufficient to say that the dynamic <br \/>analysis is accomplished through a common WB call glue code, which:<br \/>1) does nothing for segment encrypt \/ decrypt operations (no need to as these <br \/>got decrypted as part of the setup process)<br \/>2) setups \/ restores stack frame and registers for WB heap execute calls as if<br \/>the call has been executed by the OS kernel (thus the &#8220;glue&#8221; name).<\/p>\n<p>Fragment of a main glue code and its &#8220;pass through&#8221; nature is shown below:<\/p>\n<p>&#8220;`<br \/>&#8230;<br \/>;Warbird calls<br \/>WbDecryptEncryptionSegment EQU 1<br \/>WbReEncryptEncryptionSegment EQU 2<br \/>WbHeapExecuteCall EQU 3<br \/>WbSetTrapFrame EQU 4<br \/>WbInvalidOp1 EQU 5<br \/>WbInvalidOp2 EQU 6<br \/>WbRemoveWarbirdProcess EQU 7<br \/>WbProcessStartup EQU 8<br \/>WbProcessModuleUnload EQU 9<br \/>&#8230;<br \/>asm_wb_glue_syscall proc frame<br \/>OPTION PROLOGUE:NONE, EPILOGUE:NONE<br \/>.endprolog<br \/>ALLOC_SPARGS<br \/>SAVE_REGS<br \/>&#8230;<br \/>cmp rdx,0<br \/>je _heap_exec_call_ret<\/p>\n<p>cmp qword ptr [rdx],WbDecryptEncryptionSegment<br \/>je _DecryptEncryptionSegment<\/p>\n<p>cmp qword ptr [rdx],WbReEncryptEncryptionSegment<br \/>je _ReEncryptEncryptionSegment<\/p>\n<p>cmp qword ptr [rdx],WbHeapExecuteCall<br \/>je _HeapExecuteCall<\/p>\n<p>cmp qword ptr [rdx],WbSetTrapFrame<br \/>je _SetTrapFrame<\/p>\n<p>cmp qword ptr [rdx],WbInvalidOp1<br \/>je _InvalidOp1<\/p>\n<p>cmp qword ptr [rdx],WbInvalidOp2<br \/>je _InvalidOp2<\/p>\n<p>cmp qword ptr [rdx],WbRemoveWarbirdProcess<br \/>je _RemoveWarbirdProcess<\/p>\n<p>cmp qword ptr [rdx],WbProcessStartup<br \/>je _ProcessStartup<\/p>\n<p>cmp qword ptr [rdx],WbProcessModuleUnload<br \/>je _ProcessModuleUnload<\/p>\n<p>_DecryptEncryptionSegment:<br \/>DO_OK_RET<\/p>\n<p>_ReEncryptEncryptionSegment:<br \/>DO_OK_RET<\/p>\n<p>_HeapExecuteCall:<br \/>;save rdx value (for return result store)<br \/>; push rdx<\/p>\n<p>sub rsp,GLUE_STACK_SIZE<\/p>\n<p>;setup [rsp] to point to result ptr and args<br \/>; lea rax,qword ptr [rdx+10h]lea rax,(WB_HEAPEXEC_CALL ptr [rdx])._res_ptr<br \/>mov qword ptr [rsp],rax<\/p>\n<p>;load target sub addr<br \/>mov rax,(WB_HEAPEXEC_CALL ptr [rdx])._sub_addr<\/p>\n<p>;call target sub<br \/>jmp rax<\/p>\n<p>_heap_exec_call_ret:<br \/>;skip ret addr pushed by the call to ret from heap exec handler<br \/>add rsp,8<\/p>\n<p>add rsp,GLUE_STACK_SIZE<br \/>;restore rdx value<br \/>; pop rdx<\/p>\n<p>DO_OK_RET<\/p>\n<p>_SetTrapFrame:<br \/>DO_OK_RET<\/p>\n<p>_InvalidOp1:<br \/>DO_OK_RET<\/p>\n<p>_InvalidOp2:<br \/>DO_OK_RET<\/p>\n<p>_RemoveWarbirdProcess:<br \/>DO_OK_RET<\/p>\n<p>_ProcessStartup:<br \/>DO_OK_RET<\/p>\n<p>_ProcessModuleUnload:<br \/>DO_OK_RET<\/p>\n<p>&#8220;`<\/p>\n<p>The dynamic analysis makes it possible to trace execution of PlayReady library <br \/>as a response to various actions such as activation \/ individualization or <br \/>license request.<\/p>\n<p>It also makes it possible to call arbitrary code such as COM or WB subroutines<br \/>in particular.<\/p>\n<p>## COM information<br \/>PlayReady implementation heavily relies on Component Object Model. While there<br \/>are some public COM objects, some are not.<\/p>\n<p>WRET tool makes it possible to extract COM information from target binary:<\/p>\n<p>&#8220;`<br \/>wret&gt; cominfo -m<br \/>COMInfoCmd::run<br \/>[COMModuleBase]&#8211; destructor_va cf980<br \/>&#8211; IncrementObjectCount_va cf6e0<br \/>&#8211; DecrementObjectCount_va cf6c0<br \/>&#8211; GetObjectCount_va cdb60<br \/>&#8211; GetFirstEntryPointer_va cdb70<br \/>&#8211; GetMidEntryPointer_va cdb80<br \/>&#8211; GetLastEntryPointer_va cdb90<br \/>&#8211; GetLock_va cdba0<br \/>&#8211; RegisterWinRTObject_va cf690<br \/>&#8211; UnregisterWinRTObject_va cf690<br \/>&#8211; RegisterCOMObject_va cf690<br \/>&#8211; UnregisterCOMObject_va cf690<br \/>### COM classes<br \/>### WinRT classes<br \/>* [Microsoft.Media.PlayReadyClient.NDStreamParserNotifier]* [Microsoft.Media.PlayReadyClient.NDDownloadEngineNotifier]* [Microsoft.Media.PlayReadyClient.NDStorageFileHelper]* [Microsoft.Media.PlayReadyClient.PRRemoteObjectFactory]* [Microsoft.Media.PlayReadyClient.PlayReadyITADataGenerator]* [com.microsoft.playready.ContentDecryptionModuleFactory]* [com.microsoft.playready.software.CdmFactory]* [com.microsoft.playready.hardware.CdmFactory]* [com.microsoft.playready.CdmFactory]* [Microsoft.Media.PlayReadyClient.PlayReadyWinRTTrustedInput]* [Microsoft.Media.PlayReadyClient.PlayReadyRevocationServiceRequestReactive]* [Microsoft.Media.PlayReadyClient.PlayReadyRevocationServiceRequest]* [Microsoft.Media.PlayReadyClient.PlayReadyMeteringReportServiceRequestReactive]* [Microsoft.Media.PlayReadyClient.PlayReadyMeteringReportServiceRequest]* [Microsoft.Media.PlayReadyClient.PlayReadyDomainLeaveServiceRequestReactive]* [Microsoft.Media.PlayReadyClient.PlayReadyDomainLeaveServiceRequest]&#8230;<br \/>&#8220;`<\/p>\n<p>It can extract some information about COM interfaces and their factories too:<\/p>\n<p>&#8220;`<br \/>wret&gt; cominfo -f<br \/>### WinRT factories<br \/>[Microsoft.Media.PlayReadyClient.NDStreamParserNotifier-Factory]* initializer e3a40<br \/>* trust level BaseTrust<br \/>* interfaces:<br \/>if 6fd620<br \/>* uuid 00000035-0000-0000-c000-000000000046<br \/>* off 0<br \/>instances:<br \/>[Microsoft.Media.PlayReadyClient.NDDownloadEngineNotifier-Factory]* initializer e3950<br \/>* trust level BaseTrust<br \/>* interfaces:<br \/>if 6fd660<br \/>* uuid 00000035-0000-0000-c000-000000000046<br \/>* off 0<br \/>instances:<br \/>[Microsoft.Media.PlayReadyClient.NDStorageFileHelper-Factory]* initializer e3860<br \/>* trust level BaseTrust<br \/>* interfaces:<br \/>if 6fd6a0<br \/>* uuid 00000035-0000-0000-c000-000000000046<br \/>* off 0<br \/>instances:<br \/>[Microsoft.Media.PlayReadyClient.PRRemoteObjectFactory-Factory]* initializer e3770<br \/>* trust level BaseTrust<br \/>* interfaces:<br \/>if 6fd6e0<br \/>* uuid 00000035-0000-0000-c000-000000000046<br \/>* off 0<br \/>instances:<br \/>[Microsoft.Media.PlayReadyClient.PlayReadyITADataGenerator-Factory]* initializer e3680<br \/>* trust level BaseTrust<br \/>* interfaces:<br \/>if 6fd720<br \/>* uuid 00000035-0000-0000-c000-000000000046<br \/>* off 0<br \/>instances:<br \/>[com.microsoft.playready.ContentDecryptionModuleFactory-Factory]* initializer e3590<br \/>* trust level BaseTrust<br \/>* interfaces:<br \/>if 6fd760<br \/>* uuid 00000035-0000-0000-c000-000000000046<br \/>* off 0<br \/>instances:<br \/>[com.microsoft.playready.software.CdmFactory-Factory]* initializer e34a0<br \/>* trust level BaseTrust<br \/>* interfaces:<br \/>if 6fd7a0<br \/>* uuid 00000035-0000-0000-c000-000000000046<br \/>* off 0<br \/>instances:<br \/>[com.microsoft.playready.hardware.CdmFactory-Factory]* initializer e33b0<br \/>* trust level BaseTrust<br \/>* interfaces:<br \/>if 6fd7e0<br \/>* uuid 00000035-0000-0000-c000-000000000046<br \/>* off 0<br \/>instances:<br \/>[com.microsoft.playready.CdmFactory-Factory]* initializer e32c0<br \/>* trust level BaseTrust<br \/>* interfaces:<br \/>if 6fd820<br \/>* uuid 00000035-0000-0000-c000-000000000046<br \/>* off 0<br \/>instances:<br \/>[Microsoft.Media.PlayReadyClient.PlayReadyWinRTTrustedInput-Factory]* initializer e31d0<br \/>* trust level BaseTrust<br \/>* interfaces:<br \/>if 6fd860<br \/>* uuid 00000035-0000-0000-c000-000000000046<br \/>* off 0<br \/>instances:<br \/>&#8230;<br \/>[Microsoft.Media.PlayReadyClient.PlayReadyContentHeader-Factory]* initializer e1580<br \/>* trust level BaseTrust<br \/>* override interfaces:<br \/>if 6fc1b0<br \/>* uuid 00000035-0000-0000-c000-000000000046<br \/>* off 0<br \/>if 6fc178<br \/>* uuid d1239cf5-ae6d-4778-97fd-6e3a2eeadbeb<br \/>* off 8<br \/>if 6fc130<br \/>* uuid cb97c8ff-b758-4776-bf01-217a8b510b2c<br \/>* off 10<br \/>if 6fc0f8<br \/>* uuid d0d7d187-d16c-4165-b39c-b93c423e88e3<br \/>* off 20<br \/>if 6fc0b0<br \/>* uuid 7de8418e-efed-4eda-ab18-9ae5bf00a069<br \/>* off 28<br \/>* interfaces:<br \/>if 6fe5c0<br \/>* uuid 00000035-0000-0000-c000-000000000046<br \/>* off 0<br \/>if 6fe588<br \/>* uuid d1239cf5-ae6d-4778-97fd-6e3a2eeadbeb<br \/>* off 8<br \/>if 6fe540<br \/>* uuid cb97c8ff-b758-4776-bf01-217a8b510b2c<br \/>* off 10<br \/>if 6fe508<br \/>* uuid d0d7d187-d16c-4165-b39c-b93c423e88e3<br \/>* off 20<br \/>if 6fe4c0<br \/>* uuid 7de8418e-efed-4eda-ab18-9ae5bf00a069<br \/>* off 28<br \/>instances:<br \/>&#8220;`<\/p>\n<p>## LOGGING \/ TRACING AND HANDLERS<br \/>WRET tool makes it possible to show the trace of executed subroutines (log their<br \/>nesting, arguments used, etc.).<\/p>\n<p>For standard API calls, the arguments can be described through loading of header<br \/>file information (more specificallt, preprocessed header).<\/p>\n<p>WRET makes it possible to either change or handle arbitrary subroutine calls in<br \/>in a special way. This is accomplished through the idea of call handlers.<\/p>\n<p>The following handlers are currently supported:<br \/>&#8211; `Logger`<br \/>It makes it possible to log calls with various configuration options (with <br \/>the possibility to select arbitrary arguments to show, select the types of <br \/>calls to show in the trace such as WB calls, symbol \/ API calls (such as <br \/>imports \/ delayed imports), any near calls, whether call nesting should be<br \/>visible<br \/>&#8211; `SupressActHandler`<br \/>It makes it possible to suppress activation (make PlayReady believe that <br \/>it is activated \/ individualization is not needed), this is important if <br \/>security \/ reverse analysis is conducted in an offline manner (our company <br \/>policy)<br \/>&#8211; `SupressLicHandler`<br \/>It makes it possible to suppress certain license server errors, so that <br \/>the license response processing doesn&#8217;t stop early (such as upon invalid<br \/>signature, expired license, etc.)<br \/>&#8211; `PrvKeyExtractor`<br \/>The handler executed for the purpose of extracting private ECC encryption <br \/>key.<\/p>\n<p>### SAMPLE TRACE SETUP<br \/>The following steps can be used to setup a sample trace for a PlayReady <br \/>`getlicense` request communication with a test license server.<\/p>\n<p>Starting sample license server:<\/p>\n<p>&#8220;`<br \/>c:\\_MNT\\PROJECTS\\_PROJECTS\\MSPR\\MS_PKG\\tools\\test_ls&gt;run<br \/>## Local HTTP Server #<br \/>## (c) SECURITY EXPLORATIONS 2018 Poland #<br \/>## http:\/\/www.security-explorations.com #<br \/>## (c) AG Security Research 2019-2022 Poland #<br \/>## http:\/\/www.agsecurityresearch.com #<br \/>## #<\/p>\n<p>loaded server key from file<\/p>\n<p>LICENSE SERVER ECC KEY<br \/>&#8211; prv: efa20855ae243ce098b4f6382fb8b703fdb9f7c567f5c0e003c9c13eae6a9001<br \/>&#8211; pub:<br \/>X: 1f3c71ab5aa90dcc1c192c99eb21b13454fa47fa107a8278e5344f4600613a1<br \/>Y: 59b0c0ad7fe7fcb02ea879a7c586ce616036e46defbc8fce6d54bda8dfe09fa2<br \/>HTTP server [class HTTPServer$SEServer] listening on: 8080<br \/>&#8220;`<\/p>\n<p>Starting WRET toolkit:<\/p>\n<p>&#8220;`<br \/>c:\\_MNT\\PROJECTS\\_PROJECTS\\MSPR\\MS_PKG\\tools\\wret_toolkit&gt;out\\test.exe<br \/>main<br \/>argc: 1<br \/>container: none<br \/># Warbird RE toolkit (WRET)<br \/># (c) Security Explorations 2016-2024 Poland<br \/># (c) AG Security Research 2019-2024 Poland<br \/>wret&gt;<br \/>&#8220;`<\/p>\n<p>Setting up target PlayReady library for dynamic analysis (in another command<br \/>shell window):<\/p>\n<p>&#8220;`<br \/>wret&gt; image w10_prlib.dll<br \/>ImageCmd::run<br \/>&#8211; Dll init w10_prlib.dll<br \/>size 10347408 bytes<br \/>base 180000000<br \/>wret&gt; prlib w10_prlib.dll<br \/>PRLib::run<br \/>wret&gt; wbsetup -r<br \/>WBSetupCmd::run<br \/>Warbird encrypted binary<br \/>### setting up Warbird for runtime analysys<br \/>&#8211; scanning for heap exec descriptors<br \/>found: 981<br \/>&#8211; scanning for segment descriptors<br \/>found: 37<br \/>&#8211; decrypting heapexec descriptors<br \/>&#8211; decrypting segment descriptors<br \/>&#8211; locating ret syscalls<br \/>total: 1023<br \/>&#8211; locating heapexec syscalls<br \/>total: 911 (WB_MOV10_B9 880, WB_MOV10_REG 10, WB_LEA10 3, WB_SHARED 18)<br \/>&#8211; adjusting code refs<br \/>total: 1470<br \/>&#8211; adjusting data refs<br \/>total: 2<br \/>&#8211; patching self LEAs<br \/>total: 981<br \/>&#8211; patching ret syscalls<br \/>&#8211; patching heapexec syscalls<br \/>&#8211; patching wb call<br \/>&#8211; patching antidebug call<br \/>&#8211; patching App Policy<br \/>&#8211; Dll init Windows.Media.dll<br \/>size 7145640 bytes<br \/>base 180000000<br \/>&#8211; patching App Model<br \/>&#8211; Dll init Windows.Storage.ApplicationData.dll<br \/>size 435248 bytes<br \/>base 180000000<br \/>&#8211; disabling thread library calls<br \/>wret&gt;<br \/>&#8220;`<\/p>\n<p>Loading ECC server key used by the license server (patching default <br \/>`WMRMECC256PubKey` key):<\/p>\n<p>&#8220;`<br \/>wret&gt; lskey ..\\test_ls\\ecc.key<br \/>LSKey::run<br \/>WMRMECC256PubKey at 7dc840<br \/>&#8220;`<\/p>\n<p>Setting up license server url (to point to the url of the test license<br \/>server setup above):<\/p>\n<p>&#8220;`<br \/>wret&gt; lsurl http:\/\/192.168.56.1:8080<br \/>LSUrl::run<br \/>&#8220;`<\/p>\n<p>Suppressing PlayReady activation errors (for offline testing):<\/p>\n<p>&#8220;`<br \/>wret&gt; handler -a supressact<br \/>HandlerCmd::run<br \/>wret&gt;<br \/>&#8220;`<\/p>\n<p>Enabling logging functionality:<\/p>\n<p>&#8220;`<br \/>wret&gt; handler -a logger<br \/>HandlerCmd::run<br \/>wret&gt;<br \/>&#8220;`<\/p>\n<p>Setting up logging of return values and args with call nesting enabled <br \/>(paddings):<\/p>\n<p>&#8220;`<br \/>wret&gt; logcfg rets,pads,args<br \/>flag rets val 10<br \/>flag pads val 40<br \/>flag args val 80<br \/>LogcfgCmd::run<br \/>wret&gt;<br \/>&#8220;`<\/p>\n<p>Loading preprocessed include file for API calls resolving:<\/p>\n<p>&#8220;`<br \/>wret&gt; include inc.i<br \/>&#8220;`<\/p>\n<p>Setting tracing for import calls (for any CALL instruction additional `-t` <br \/>argument could be used):<\/p>\n<p>&#8220;`<br \/>wret&gt; trace -i<br \/>TraceCmd::run<br \/>&#8211; tracing: GetTraceLoggerHandle<br \/>&#8211; tracing: GetTraceEnableFlags<br \/>&#8211; tracing: RegisterTraceGuidsW<br \/>&#8211; tracing: GetTraceEnableLevel<br \/>&#8211; tracing: TraceMessage<br \/>&#8211; tracing: UnregisterTraceGuids<br \/>&#8211; tracing: LCMapStringW<br \/>&#8211; tracing: GetCPInfo<br \/>&#8211; tracing: GetOEMCP<br \/>&#8230;<br \/>&#8211; trace call at 6d7295 to 63c650<br \/>&#8211; trace call at 6d7505 to cff60<br \/>&#8211; trace call at 6d7529 to cd640<br \/>&#8211; trace call at 6d758d to cd550<br \/>&#8211; trace call at 6d759e to cd550<br \/>&#8211; trace call at 6d7656 to d68c0<br \/>&#8211; trace call at 6d7884 to 213f84<br \/>total: locations 21014 calls 13861<br \/>wret&gt;<br \/>&#8220;`<\/p>\n<p>Triggering the invocation of get license request with a log trace:<\/p>\n<p>&#8220;`<br \/>wret&gt; getlicense<br \/>GetLicenseCmd::run<br \/>GET LICENSE 3<br \/>&#8211; ComLib init w10_prlib.dll<br \/>* hlib: 7ffd3cf50000<br \/>* get_activation_factory: 7ffd3d01df50<br \/>w10_prlib.dll!cad55 : @wb_decrypt!6eb000<br \/>w10_prlib.dll!cdfae -&gt; api-ms-win-core-synch-l1-1-0.dll!EnterCriticalSection(lpCriticalSection = 7ffd3d8e5e08)<br \/>w10_prlib.dll!cdfae &lt;- ret 0<br \/>w10_prlib.dll!cdfbb -&gt; api-ms-win-core-winrt-string-l1-1-0.dll!WindowsGetStringRawBuffer(arg1 = 21fd3febb8,arg2 = 21fd3fe9c8,arg3 = 30,arg4 = 0,arg5 = 0,arg6 = 7ffd846d1f34)<br \/>w10_prlib.dll!cdfbb &lt;- ret 7ff694b9bc00<br \/>w10_prlib.dll!ce28f -&gt; api-ms-win-core-synch-l1-2-0.dll!InitOnceExecuteOnce(InitOnce = 7ffd3d8e5670,InitFn = 7ffd3d01f9e0,Parameter = 0,Context = 0)<br \/>w10_prlib.dll!ce28f &lt;- ret 1<br \/>w10_prlib.dll!ce2a7 -&gt; api-ms-win-core-winrt-string-l1-1-0.dll!WindowsIsStringEmpty(arg1 = 21fd3febb8,arg2 = 7ffd3d01f9e0,arg3 = 0,arg4 = 0,arg5 = 0,arg6 = 7ffd846d1f34)<br \/>w10_prlib.dll!ce2a7 &lt;- ret 0<br \/>w10_prlib.dll!ce2bc -&gt; api-ms-win-core-winrt-string-l1-1-0.dll!WindowsStringHasEmbeddedNull(arg1 = 21fd3febb8,arg2 = 21fd3fe9c4,arg3 = 0,arg4 = 0,arg5 = 0,arg6 = 7ffd846d1f34)<br \/>w10_prlib.dll!ce2bc &lt;- ret 0<br \/>w10_prlib.dll!ce2d9 -&gt; api-ms-win-core-winrt-string-l1-1-0.dll!WindowsGetStringRawBuffer(arg1 = 21fd3febb8,arg2 = 0,arg3 = 0,arg4 = 0,arg5 = 0,arg6 = 7ffd846d1f34)<br \/>w10_prlib.dll!ce2d9 &lt;- ret 7ff694b9bc00<br \/>w10_prlib.dll!63b89e -&gt; api-ms-win-core-heap-l1-1-0.dll!HeapAlloc(hHeap = 138c4cc0000,dwFlags = 0,dwBytes = 50)<br \/>w10_prlib.dll!63b89e &lt;- ret 138c4cc0860<br \/>&#8230;<br \/>w10_prlib.dll!1ff104 -&gt; 1c1f80(arg1 = 21fd3fe2e4,arg2 = 21fd3fddd0,arg3 = 138d2fe5918,arg4 = 138ca599068,arg5 = 138ca599614,arg6 = 0)<br \/>w10_prlib.dll!1c202e -&gt; 1c0dd0(arg1 = 21fd3fddd0,arg2 = 21fd3fdf30,arg3 = 21fd3fdd50,arg4 = 1250,arg5 = 21fd3fdcf0,arg6 = 170)<br \/>w10_prlib.dll!1c202e &lt;-<br \/>w10_prlib.dll!1ff104 &lt;- ret 0<br \/>w10_prlib.dll!1ff1ca -&gt; 600700(arg1 = 21fd3fddd0,arg2 = 138ca599624,arg3 = fd3fe2c400001250,arg4 = 13800000021,arg5 = 138ca599614,arg6 = 0)<br \/>w10_prlib.dll!600809 -&gt; 5e0100(arg1 = 21fd3fddd0,arg2 = 138ca599624,arg3 = 7ffd3d11202e,arg4 = 1250,arg5 = 21fd3fe008,arg6 = 138ca599624)<br \/>w10_prlib.dll!5e0173 -&gt; 1c2070(arg1 = 21fd3fddd0,arg2 = 138ca599624,arg3 = 138ca599624,arg4 = 138ca599624,arg5 = 21fd3fdc20,arg6 = 138ca599624)<br \/>w10_prlib.dll!1c20fc -&gt; 1c13f0(arg1 = 138ca599624,arg2 = 138ca599624,arg3 = 21fd3fddd0,arg4 = 21fd3fddd0,arg5 = 21fd3fdba0,arg6 = 21fd3fdc80)<br \/>w10_prlib.dll!1c20fc &lt;-<br \/>w10_prlib.dll!5e0173 &lt;- ret 13800000000<br \/>w10_prlib.dll!600809 &lt;- ret 0<br \/>w10_prlib.dll!6008f4 -&gt; 5e0100(arg1 = 21fd3fddd0,arg2 = 138ca599634,arg3 = 7ffd3d11202e,arg4 = 1250,arg5 = 21fd3fe008,arg6 = 138ca599624)<br \/>w10_prlib.dll!5e0173 -&gt; 1c2070(arg1 = 21fd3fddd0,arg2 = 138ca599634,arg3 = 138ca599624,arg4 = 138ca599624,arg5 = 21fd3fdc20,arg6 = 13800000000)<br \/>w10_prlib.dll!1c20fc -&gt; 1c13f0(arg1 = 138ca599634,arg2 = 138ca599634,arg3 = 21fd3fddd0,arg4 = 21fd3fddd0,arg5 = 21fd3fdba0,arg6 = 21fd3fdc88)<br \/>w10_prlib.dll!1c20fc &lt;-<br \/>w10_prlib.dll!5e0173 &lt;- ret 13800000000<br \/>&#8230;<br \/>&#8220;`<\/p>\n<p>The trace shows all WB calls. Heap execute calls are shown without annotation.<br \/>Decryption \/ encryption calls are annotated with `@wb_` prefix:<\/p>\n<p>&#8220;`<br \/>w10_prlib.dll!ec879 : @wb_decrypt!6d9200<br \/>w10_prlib.dll!ec0f9 : @wb_decrypt!6d840c<br \/>w10_prlib.dll!ec059 : @wb_reencrypt!6d840c<br \/>w10_prlib.dll!ec9b9 : @wb_decrypt!6d9300<br \/>w10_prlib.dll!ec919 : @wb_reencrypt!6d9300<br \/>w10_prlib.dll!ec7d9 : @wb_reencrypt!6d9200<br \/>w10_prlib.dll!ebe79 : @wb_decrypt!6d8200<br \/>w10_prlib.dll!ebdd9 : @wb_reencrypt!6d8200<br \/>w10_prlib.dll!ec559 : @wb_reencrypt!6d9000<br \/>&#8220;`<\/p>\n<p>It should be possible to have tracing work across multiple binaries loaded <br \/>into a target process (such as `PlayReady.dll`, `mf.dll`, etc.).<\/p>\n<p>Sample trace log files along scripts can be found in `tests\\trace` directory.<\/p>\n<p>It should be mentioned that occasionally, PlayReady identity could be wiped <br \/>out as a result of detecting expired or invalid identity. In such cases,<br \/>placing arbitrary copy of `hds` and `bla` files to PlayReady CDM directory <br \/>helps avoid errors during tracing. See `prepare.bat` script for details.<\/p>\n<p>## KEY SUBROUTINES<br \/>The developed toolset made it possible to conduct an in-depth static and <br \/>dynamic analysis of PlayReady operation with respect to some key functionality<br \/>such as activation \/ individualization (`prinit` cmd) and license acquisition<br \/>(`getlicense` cmd).<\/p>\n<p>General protocol operation has been reverse engineered by us in 2022 (CANAL+ <br \/>STB device environment), which made things easier (we knew what artifacts to <br \/>look for).<\/p>\n<p>As a result of the analysis, some key subroutines pertaining to cryptographic<br \/>operations conducted on identity and license blobs have been discovered. What&#8217;s<br \/>worth to note is that their addresses could be established in a completely<br \/>automatic fashion across various PlayReady library versions used in both <br \/>Windows 10 and 11 environments:<\/p>\n<p>&#8220;`<br \/>wret&gt; prsubs<br \/>PRSubsCmd::run<br \/>[w10_prlib.dll]* main obj [vtable 713148]&#8211; module init 215400<br \/>&#8211; decrypt license 217500<br \/>&#8211; sign data 217570<br \/>&#8211; decrypt domain key 2155b0<br \/>* decrypt license internal 6c6560<br \/>* get license decryptor 215600<br \/>* decrypt license blob 2228a0<br \/>* expand key 6509c0<br \/>* decrypt signing key 222650<br \/>&#8220;`<\/p>\n<p>Actual implementation details can be found in source code (`prsubs` command<br \/>implementation of WRET toolkit).<\/p>\n<p>The script `genprsubs.bat` located in `tests\\xor_key` directory can <br \/>automatically discover `prsubs` values for various versions of PlayReady <br \/>libraries present in Windows 10 \/ 11 x64 systems across various builds from <br \/>late 2022 till Nov 2024:<\/p>\n<p>&#8220;`<br \/>c:\\_MNT\\PROJECTS\\_PROJECTS\\MSPR\\_MS.NOV.2024\\WBPMP\\tests\\xor_key&gt;genprsubs<br \/>### GENERATING PRSUBS<br \/>w10\\w10_22h2_aug22\\Windows.Media.Protection.PlayReady.dll<br \/>w10\\w10_22h2_aug24\\Windows.Media.Protection.PlayReady.dll<br \/>w10\\w10_22h2_dec22\\Windows.Media.Protection.PlayReady.dll<br \/>w10\\w10_22h2_dec23\\Windows.Media.Protection.PlayReady.dll<br \/>w10\\w10_22h2_jul24\\Windows.Media.Protection.PlayReady.dll<br \/>w10\\w10_22h2_jun23\\Windows.Media.Protection.PlayReady.dll<br \/>w10\\w10_22h2_jun24\\Windows.Media.Protection.PlayReady.dll<br \/>w10\\w10_22h2_mar23\\Windows.Media.Protection.PlayReady.dll<br \/>w10\\w10_22h2_mar24\\Windows.Media.Protection.PlayReady.dll<br \/>w10\\w10_22h2_nov24\\Windows.Media.Protection.PlayReady.dll<br \/>w10\\w10_22h2_oct24\\Windows.Media.Protection.PlayReady.dll<br \/>w10\\w10_22h2_sep23\\Windows.Media.Protection.PlayReady.dll<br \/>w10\\w10_22h2_sep24\\Windows.Media.Protection.PlayReady.dll<br \/>w11\\w11_22h2_dec22\\Windows.Media.Protection.PlayReady.dll<br \/>w11\\w11_22h2_mar23\\Windows.Media.Protection.PlayReady.dll<br \/>w11\\w11_22h2_sep22\\Windows.Media.Protection.PlayReady.dll<br \/>w11\\w11_23h2_aug24\\Windows.Media.Protection.PlayReady.dll<br \/>w11\\w11_23h2_jul24\\Windows.Media.Protection.PlayReady.dll<br \/>w11\\w11_23h2_mar24\\Windows.Media.Protection.PlayReady.dll<br \/>w11\\w11_23h2_nov24\\Windows.Media.Protection.PlayReady.dll<br \/>w11\\w11_23h2_oct24\\Windows.Media.Protection.PlayReady.dll<br \/>w11\\w11_23h2_sep24\\Windows.Media.Protection.PlayReady.dll<br \/>&#8220;`<\/p>\n<p>The name of the output file is `prsubs.txt&#8217;.<\/p>\n<p>## PRIVATE SIGNING KEY DISCOVERY<br \/>License request issued to the license server requires the use of a private <br \/>client identity key for signing the request.<\/p>\n<p>The trace of a license server revealed the subroutine that conducted decryption<br \/>of that key (prsubs addr denoted by &#8220;decrypt signing key&#8221;):<\/p>\n<p>&#8220;`<br \/>.text:0000000180222650 agsr_decrypt_secret_key proc near ; DATA XREF: agsr_sign_data:loc_180217628\u2191o<br \/>.text:0000000180222650 ; .rdata:000000018097A6E8\u2193o &#8230;<br \/>.text:0000000180222650<br \/>.text:0000000180222650 var_1E0 = qword ptr -1E0h<br \/>.text:0000000180222650 var_1D8 = qword ptr -1D8h<br \/>.text:0000000180222650 var_1D0 = qword ptr -1D0h<br \/>.text:0000000180222650 var_1C8 = qword ptr -1C8h<br \/>.text:0000000180222650 var_1C0 = qword ptr -1C0h<br \/>.text:0000000180222650 var_1B8 = dword ptr -1B8h<br \/>.text:0000000180222650 var_1B0 = byte ptr -1B0h<br \/>.text:0000000180222650 var_1A0 = byte ptr -1A0h<br \/>.text:0000000180222650 var_30 = qword ptr -30h<br \/>.text:0000000180222650 var_28 = qword ptr -28h<br \/>.text:0000000180222650 arg_0 = dword ptr 10h<br \/>.text:0000000180222650 arg_8 = dword ptr 18h<br \/>.text:0000000180222650 arg_10 = dword ptr 20h<br \/>.text:0000000180222650<br \/>.text:0000000180222650 lea rdx, unk_180998298<br \/>.text:0000000180222657 mov rcx, [rsp+0].text:000000018022265B push rbp<br \/>.text:000000018022265C push rdi<br \/>.text:000000018022265D push r12<br \/>.text:000000018022265F push r14<br \/>.text:0000000180222661 push r15<br \/>.text:0000000180222663 lea rbp, [rsp-0C0h].text:000000018022266B sub rsp, 1C0h<br \/>.text:0000000180222672 cmp dword ptr [rcx+10h], 20h ; &#8216; &#8216; ; arg2=encrypted key size<br \/>.text:0000000180222676 mov r15, rdx<br \/>.text:0000000180222679 mov r12, [rcx] ; res ptr<br \/>.text:000000018022267C mov rax, rcx<br \/>.text:000000018022267F mov rdi, [rdx-8].text:0000000180222683 mov r14, [rdx-10h].text:0000000180222687<br \/>.text:0000000180222687 loc_180222687: ; DATA XREF: .rdata:000000018097A6E8\u2193o<br \/>.text:0000000180222687 ; .rdata:000000018097A6FC\u2193o &#8230;<br \/>.text:0000000180222687 mov [rsp+1E0h+var_28], rbx<br \/>.text:000000018022268F mov ebx, 10h<br \/>.text:0000000180222694 jz short loc_1802226A1 ; arg1 = encrypted key<br \/>.text:0000000180222696 mov r8d, 80070057h<br \/>.text:000000018022269C jmp loc_180222838<br \/>.text:00000001802226A1 ; &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>.text:00000001802226A1<br \/>.text:00000001802226A1 loc_1802226A1: ; CODE XREF: agsr_decrypt_secret_key+44\u2191j<br \/>.text:00000001802226A1 mov rcx, [rcx+8] ; arg1 = encrypted key<br \/>.text:00000001802226A5 test rcx, rcx<br \/>&#8220;`<\/p>\n<p>Decryption of the signing key was thus straightforward. All that was required <br \/>to accomplish that was to call that subroutine with an encrypted key provided <br \/>as an argument. One needs to keep in mind that this was WB subroutine normally <br \/>invoked through the `NtQuerySystemInformation` (this is manifested through <br \/>`rcx` being loaded from stack top \/ ret addr location and arguments accessed <br \/>through `rcx`).<\/p>\n<p>The following procedure has been used for decryption of the signing key and <br \/>verification of the process.<\/p>\n<p>MSPR toolkit has been used to save public and encrypted keys for a given chosen<br \/>identity:<\/p>\n<p>&#8220;`<br \/>c:\\_MNT\\PROJECTS\\_PROJECTS\\MSPR\\MS_PKG\\tools\\mspr_toolkit&gt;run<br \/># MS Play Ready \/ Canal+ VOD toolkit<br \/># (c) Security Explorations 2016-2019 Poland<br \/># (c) AG Security Research 2019-2022 Poland<\/p>\n<p>loaded cdn [CDN helper]loaded mspr [MS Play Ready toolkit]loaded vod [CANALP VOD toolkit]loaded cgaweb [CANALP CGAWeb toolkit]&#8220;`<\/p>\n<p>Listing identities (as denoted by CDM pointed by CDN_DIR variable):<\/p>\n<p>&#8220;`<br \/>msprcp&gt; identity<br \/>0C86330B0E98CD7C586F336088DAFA0E<br \/>4F72F3CBDC81C849F635AE556A73679F<br \/>902B255736B6E891F3AF30F98B0A5DBA<br \/>D9A5C7A90F8DEA029AA8FB1C95887BE3<br \/>E82DFAE7A9DB21FC1ECF33C1DADC54B7<br \/>&#8220;`<\/p>\n<p>Exporting client identity (signing and encryption keys):<\/p>\n<p>&#8220;`<br \/>msprcp&gt; identity -e 0C86330B0E98CD7C586F336088DAFA0E<br \/>0C86330B0E98CD7C586F336088DAFA0E.enc.pub (public encryption key)<br \/>0C86330B0E98CD7C586F336088DAFA0E.enc.prv (private encryption key)<br \/>0C86330B0E98CD7C586F336088DAFA0E.sig.pub (public signing key)<br \/>0C86330B0E98CD7C586F336088DAFA0E.sig.prv (private signing key)<br \/>&#8220;`<\/p>\n<p>Now, the WRET toolkit can be used to decrypt the signing key:<\/p>\n<p>&#8220;`<br \/>wret&gt; decsignkey ..\\mspr_toolkit\\0C86330B0E98CD7C586F336088DAFA0E.sig.prv -o ..\\mspr_toolkit\\0C86330B0E98CD7C586F336088DAFA0E.sig.plain<br \/>DecSignKeyCmd::run<br \/>keydata<br \/>0x00000000: d7 60 5c 71 57 a0 01 7c 58 e2 e7 79 a8 b1 12 55 .`\\qW..|X..y&#8230;U<br \/>0x00000010: 1d 72 14 f0 d9 2c ef 04 6c cc 57 c1 2e 9b e3 b4 .r&#8230;,..l.W&#8230;..<br \/>target 7ffd3d172650<br \/>wb res: 0 call res 0<br \/>output key<br \/>0x00000000: cc 55 f7 54 f7 62 4f e2 b0 07 d8 30 2a 34 80 8c .U.T.bO&#8230;.0*4..<br \/>0x00000010: ad e4 9b 6e 78 2b 8c de 1e c3 56 96 20 c4 24 f3 &#8230;nx+&#8230;.V. .$.<br \/>saving to ..\\mspr_toolkit\\0C86330B0E98CD7C586F336088DAFA0E.sig.plain file<br \/>wret&gt;<br \/>&#8220;`<br \/>We can verify whether the decrypted private key matches the public one:<\/p>\n<p>&#8220;`<br \/>msprcp&gt; checkkeypair 0C86330B0E98CD7C586F336088DAFA0E.sig.plain 0C86330B0E98CD7C586F336088DAFA0E.sig.pub<br \/>KEY CHECK:<\/p>\n<p>&#8211; prv: cc55f754f7624fe2b007d8302a34808cade49b6e782b8cde1ec3569620c424f3<br \/>&#8211; pub:<br \/>X: 42b2a0ff381c34cc67063b50e12e0dde7449552938ef660c605c909f8cb04943<br \/>Y: fe7a81f2f675ab2905c3e2e996219b44a398b23645e4cd7cc9538bd3cd32bf7<br \/>KEY CHECK OK<br \/>&#8220;`<\/p>\n<p>It is worth to mention that the signing key decryption subroutine has been <br \/>highly obfuscated (on purpose). This was completely irrelevant from a target <br \/>security analysis though &#8211; there was no need to dive into the obfuscated code <br \/>\/ no need to understand how it works. What was relevant was subroutine <br \/>identification and its API functionality (input args with decrypted key -&gt; <br \/>some black box key processing -&gt; output plaintext key).<\/p>\n<p>## CONTENT KEY SNIFFER<br \/>Analysis of the license request processing was not sufficient for us to reach <br \/>a point where private client identity key could be used for license blob <br \/>decryption. Thus, we needed to move our work to online environment and conduct<br \/>some more in-depth analysis of MS Edge and PMP environment in order to find<br \/>out PlayReady operation following license server response (how license blobs <br \/>are decrypted and content keys obtained).<\/p>\n<p>As a result of the above, successful XMR license and content key sniffing could<br \/>be achieved. All regardless of the protections implemented at OS kernel level <br \/>such as protected process and `PEAuth`, which is responsible for guarding that <br \/>PMP environment is authorized to process protected content.<\/p>\n<p>`PEAuth` is implemented in both kernel (`PEAuth` driver) and user land (calls <br \/>from PlayReady library). It&#8217;s worth to note that `PEAuth` driver can execute <br \/>encrypted code too:<\/p>\n<p>&#8220;`<br \/>INIT:00000001C00C6582 lea rdx, unk_1C0031340<br \/>INIT:00000001C00C6589 mov cs:dword_1C003144C, 1<br \/>INIT:00000001C00C6593 lea rcx, byte_1C002EC30 ; -&gt; ptr to encrypted segment descriptor<br \/>INIT:00000001C00C6593 ; off 0x50 contains memory RVA:<br \/>INIT:00000001C00C6593 ; .rdata:00000001C002EC80 db 8<br \/>INIT:00000001C00C6593 ; .rdata:00000001C002EC81 db 90h<br \/>INIT:00000001C00C6593 ; .rdata:00000001C002EC82 db 3<br \/>INIT:00000001C00C6593 ; .rdata:00000001C002EC83 db 0<br \/>INIT:00000001C00C659A call agsr_decrypt_code?<br \/>INIT:00000001C00C659F test eax, eax<br \/>INIT:00000001C00C65A1 js short loc_1C00C65BB<br \/>INIT:00000001C00C65A3 call loc_1C0039008 ; -&gt; call into just decrypted memory<br \/>INIT:00000001C00C65A8 lea rdx, unk_1C0031340<br \/>INIT:00000001C00C65AF lea rcx, byte_1C002EC30<br \/>INIT:00000001C00C65B6 call agsr_encrypt_code? ; reencrypt memory<br \/>&#8220;`<\/p>\n<p>In general `PEAuth` driver assists user code (PMP process) by periodically <br \/>exchanging some crypto challenges with it.<\/p>\n<p>At the time of returning result to the calling process, it verifies whether<br \/>it is protected:<\/p>\n<p>&#8220;`<br \/>PAGE:00000001C003B2CC loc_1C003B2CC: ; CODE XREF: agsr_check_and_set_ioctl_result+E0\u2191j<br \/>PAGE:00000001C003B2CC mov eax, [r12+8] ; protected process flag (=1 dla protected)<br \/>PAGE:00000001C003B2D1 cmp esi, 4 ; u mnie 1<br \/>PAGE:00000001C003B2D4 jz short loc_1C003B33F<br \/>PAGE:00000001C003B2D6 test eax, eax<br \/>PAGE:00000001C003B2D8 jnz short loc_1C003B2E6 ; -&gt; jump for protected process<br \/>PAGE:00000001C003B2DA<br \/>PAGE:00000001C003B2DA loc_1C003B2DA: ; CODE XREF: agsr_check_and_set_ioctl_result+169\u2193j<br \/>PAGE:00000001C003B2DA mov dword ptr [rbx+4], 0C00D7165h ; store result<br \/>PAGE:00000001C003B2DA ;<br \/>PAGE:00000001C003B2DA ; \/\/<br \/>PAGE:00000001C003B2DA ; \/\/ MessageId: MF_E_NON_PE_PROCESS<br \/>PAGE:00000001C003B2DA ; \/\/<br \/>PAGE:00000001C003B2DA ; \/\/ MessageText:<br \/>PAGE:00000001C003B2DA ; \/\/<br \/>PAGE:00000001C003B2DA ; \/\/ A non-PE process tried to talk to PEAuth.%0<br \/>PAGE:00000001C003B2DA ; \/\/<br \/>PAGE:00000001C003B2DA ; #define MF_E_NON_PE_PROCESS _HRESULT_TYPEDEF_(0xC00D7165L)<br \/>&#8220;`<\/p>\n<p>For non-protected processes, it returns error, which informs the PMP environment<br \/>that an attempt is made to launch PlayReady DRM (process protected content) in<br \/>an untrusted env.<\/p>\n<p>### PMP PROCESS HIJACK<br \/>Upon initialization of Protected Media Path (and PlayReady DRM), PMP process <br \/>gets started by MS Edge.<\/p>\n<p>Process launch is conducted with `CREATE_PROTECTED_PROCESS` creation flags <br \/>(from `mfcore.dll` and `mfpmp.exe`):<\/p>\n<p>&#8220;`<br \/>.text:000000018009E5FC jnz loc_180177651<br \/>.text:000000018009E602 test r14b, 1 ; jezeli r14b == 1, to mam flage<br \/>.text:000000018009E602 ; MFPMPSESSION_UNPROTECTED_PROCESS<br \/>.text:000000018009E602 ;<br \/>.text:000000018009E602 ; flags for MFCreatePMPMediaSession function<br \/>.text:000000018009E602 ;<br \/>.text:000000018009E602 ; typedef enum MFPMPSESSION_CREATION_FLAGS {<br \/>.text:000000018009E602 ; MFPMPSESSION_UNPROTECTED_PROCESS = 0x1,<br \/>.text:000000018009E602 ; MFPMPSESSION_IN_PROCESS = 0x2<br \/>.text:000000018009E602 ; } ;<br \/>.text:000000018009E606 jnz short loc_18009E617 ; -&gt; go unprotected<br \/>.text:000000018009E608 call agsr_check_protected_process_bypass ; check whether to start protected process<br \/>.text:000000018009E608 ; (check for registry override)<br \/>.text:000000018009E60D test eax, eax ; set protected process if eax==0<br \/>.text:000000018009E60F mov ecx, 40000h ; CREATE_PROTECTED_PROCESS!!!!<br \/>.text:000000018009E614 cmovz esi, ecx ; dwCreationFlags<br \/>.text:000000018009E617<\/p>\n<p>&#8230;<\/p>\n<p>.text:000000018009E7C4 and [rsp+380h+var_348], 0<br \/>.text:000000018009E7CA and [rsp+380h+var_350], 0<br \/>.text:000000018009E7D0 mov [rsp+380h+dwCreationFlags], esi ; dwCreationFlags<br \/>.text:000000018009E7D4 and dword ptr [rsp+380h+var_360], 0<br \/>.text:000000018009E7D9 call cs:CreateProcessW<br \/>&#8220;`<\/p>\n<p>With MS Edge running with user privileges, process creation can be easily <br \/>bypassed. What is needed for the purpose is the hijack of `CreateProcessW`<br \/>call and clearing of the flag.<\/p>\n<p>### PEAUTH BYPASS<br \/>`PEAuth` checks are called both during the PMP environment initialization and <br \/>also periodically (every 5000 decoded MPEG frames).<\/p>\n<p>Running PMP process as unprotected is not sufficient for PlayReady DRM<br \/>operation (no license request \/ processing is done if insecure env is<br \/>detected) for the reasons depicted above.<\/p>\n<p>There is however a way to bypass `PEAuth` checks and have PlayReady DRM run <br \/>in full as part of the unprotected process.<\/p>\n<p>Initial `PEAuth` check is invoked from `IMFInputTrustAuthority::RequestAccess` <br \/>call. The code that is part of ITA verification call inspects a value of some<br \/>cached variable prior to the `PEAuth` check: <\/p>\n<p>&#8220;`<br \/>.text:0000000180152BDE<br \/>.text:0000000180152BDE loc_180152BDE: ; CODE XREF: agsr_ITA_verify????+4E\u2191j<br \/>.text:0000000180152BDE ; agsr_ITA_verify????+54\u2191j<br \/>.text:0000000180152BDE xor eax, eax ; =0<br \/>.text:0000000180152BE0 mov [rsp+10h+arg_11F8], r12<br \/>.text:0000000180152BE8 mov esi, eax ; result = 0 (ok)<br \/>.text:0000000180152BEA cmp [r14+0C8h], eax<br \/>.text:0000000180152BF1 jnz short loc_180152C17<br \/>.text:0000000180152BF3 cmp [r14+0B8h], eax ; some special flag (trusted env \/ skip PEAuth ?)<br \/>.text:0000000180152BFA jnz short loc_180152C17<br \/>.text:0000000180152BFC mov [rbp+10F0h+var_1058], eax<br \/>&#8220;`<\/p>\n<p>What&#8217;s interesting in this variable is that it is set to 1 upon successful <br \/>completion of the `PEAuth` check. In that context, the variable acts as a <br \/>cached value for the `PEAuth` check. So, setting this variable to 1 prior to <br \/>the check will simply skip it.<\/p>\n<p>Again, while ITA verification code is quite long and highly obfuscated, one <br \/>doesn&#8217;t need to analyze it at all. It is sufficient to find out that it signals<br \/>an error at the time of detecting insecure (unprotected) PMP process and how to<br \/>override it (skip it).<\/p>\n<p>Setting the `PEAuth` check variable described above requires that it is done <br \/>at proper time and context (this object instance context). This is not that<br \/>straightforward taking into account that ITA verify subroutine is available in <br \/>plaintext form only for the time of its execution (the code is dynamically <br \/>decrypted and reencrypted):<\/p>\n<p>&#8220;`<br \/>.text:00000001806BC641 js loc_1806BC331 ; -&gt; error<br \/>.text:00000001806BC647 lea rax, agsr_wb_decrypt_0<br \/>.text:00000001806BC64E add rax, rsi<br \/>.text:00000001806BC651 call rax<br \/>.text:00000001806BC653 test eax, eax<br \/>.text:00000001806BC655 js loc_1806BC76A ; -&gt; error<br \/>.text:00000001806BC65B lea rax, agsr_ITA_verify???? ; sets [this+0xb8] to 1 if PEAuth OK<br \/>.text:00000001806BC662 add rax, rsi<br \/>.text:00000001806BC665 lea rcx, [r15-10h] ; this-10h<br \/>.text:00000001806BC669 call rax<br \/>.text:00000001806BC66B mov r12d, eax<br \/>.text:00000001806BC66E lea rax, agsr_wb_reencrypt_0<br \/>.text:00000001806BC675 add rax, rsi<br \/>&#8220;`<\/p>\n<p>Additionally, the code is executed through WB heap execute call:<\/p>\n<p>&#8220;`<br \/>.text:000000018015F620 agsr_RequestAccess proc near ; DATA XREF: .rdata:0000000180710A08\u2193o<br \/>.text:000000018015F620 ; .rdata:000000018073AF08\u2193o &#8230;<br \/>.text:000000018015F620<br \/>.text:000000018015F620 var_18 = dword ptr -18h<br \/>.text:000000018015F620 arg_8 = dword ptr 10h<br \/>.text:000000018015F620<br \/>.text:000000018015F620 mov r11, rsp<br \/>.text:000000018015F623 sub rsp, 58h<br \/>.text:000000018015F627 mov [r11-20h], rcx<br \/>.text:000000018015F62B lea rax, agsr_RequestAccess_wrap<br \/>.text:000000018015F632 mov [r11-30h], rax<br \/>.text:000000018015F636 xor r9d, r9d ; ReturnLength<br \/>.text:000000018015F639 lea rax, [r11+10h].text:000000018015F63D mov qword ptr [r11-38h], 3<br \/>.text:000000018015F645 mov [r11-28h], rax<br \/>.text:000000018015F649 mov ecx, 0B9h ; SystemInformationClass<br \/>.text:000000018015F64E mov [rsp+58h+var_18], edx ; MFPOLICYMANAGER_ACTION Action<br \/>.text:000000018015F652 lea rdx, [r11-38h] ; SystemInformation<br \/>.text:000000018015F656 mov [r11-14h], r8 ; IMFActivate **ppContentEnablerActivate<br \/>.text:000000018015F65A lea r8d, [r9+2Ch] ; SystemInformationLength<br \/>.text:000000018015F65E call NtQuerySystemInformation<br \/>.text:000000018015F663 mov eax, [rsp+58h+arg_8].text:000000018015F667 add rsp, 58h<br \/>.text:000000018015F66B retn<br \/>.text:000000018015F66B agsr_RequestAccess endp<br \/>&#8220;`<\/p>\n<p>We have figured out the way to accomplish the patching though. As WB call above<br \/>is done through `NtQuerySystemInformation`, one can hijack the IAT entry for it<br \/>and conduct some processing upon detection of the target call location (from <br \/>within `RequestAccess`).<\/p>\n<p>The overwrite of IAT along hijack of arbitrary code into target PMP process is<br \/>possible (due to no process protection, this got disabled at the time of PMP<br \/>process launch). Detection of a target call location is accomplished through <br \/>the following:<br \/>&#8211; code pattern match (same pattern is present for Windows 10 and 11 PlayReady<br \/>binaries),<br \/>&#8211; matching of the `MFPOLICYMANAGER_ACTION` argument (it should denote <br \/>`PEACTION_PLAY`)<br \/>&#8211; verification of a virtual methods table (vtable) pointer (whether vtable slot<br \/>corresponding to the `RequestAccess` method corresponds to the WB code called)<\/p>\n<p>At this point it is worth to mention that the hijacking of WB code implemented <br \/>by the sniffer demonstrates two potential weaknesses of Warbird \/ PMP:<br \/>1) virtual methods table calls constituting a gap in the encrypted call chains,<br \/>vtable slots usually point to plaintext code wrappers that invoke WB code <br \/>(setup WB args and invoke WB syscall), a call to `NtQuerySystemInformation` <br \/>used by it can be intercepted (break up of thesemi-atomic invocation chain<br \/>\/ possibility of the hooking),<br \/>2) no additional memory protection for the PlayReady image upon memory load<br \/>(protected process is not enough)<\/p>\n<p>It&#8217;s worth to mention that similar bypass should work with respect to periodic<br \/>`PEAuth` check conducted every 5000 samples. The code below indicates that there<br \/>are some instance variables checked prior to the call that denote the code runs<br \/>in a trusted environment:<\/p>\n<p>&#8220;`<br \/>.text:0000000180179960 agsr_check_pe_trusted proc near ; DATA XREF: agsr_LongRunning_DecryptSample:loc_180180F16\u2193o<br \/>.text:0000000180179960 ; agsr_DoProcessSample+DA\u2193o &#8230;<br \/>.text:0000000180179960<br \/>.text:0000000180179960 var_78 = dword ptr -78h<br \/>.text:0000000180179960 var_70 = dword ptr -70h<br \/>.text:0000000180179960 var_68 = qword ptr -68h<br \/>.text:0000000180179960 var_60 = qword ptr -60h<br \/>.text:0000000180179960 var_58 = qword ptr -58h<br \/>.text:0000000180179960 var_50 = qword ptr -50h<br \/>.text:0000000180179960 var_48 = qword ptr -48h<br \/>.text:0000000180179960 var_38 = qword ptr -38h<br \/>.text:0000000180179960 arg_0 = dword ptr 8<br \/>.text:0000000180179960 arg_8 = dword ptr 10h<br \/>.text:0000000180179960 arg_10 = dword ptr 18h<br \/>.text:0000000180179960<br \/>.text:0000000180179960 lea rdx, unk_180998298<br \/>.text:0000000180179967 mov rcx, [rsp+0].text:000000018017996B push rbx<br \/>.text:000000018017996C push rbp<br \/>.text:000000018017996D push rsi<br \/>.text:000000018017996E push rdi<br \/>.text:000000018017996F push r12<br \/>.text:0000000180179971 push r15<br \/>.text:0000000180179973 sub rsp, 68h<br \/>.text:0000000180179977 mov r8, [rcx+10h] ; arg2 &#8211; ptr to invoke counter<br \/>.text:000000018017997B mov r15, rdx<br \/>.text:000000018017997E mov rdi, [rcx+8] ; this<br \/>.text:0000000180179982 mov r12, [rcx].text:0000000180179985<br \/>.text:0000000180179985 loc_180179985: ; DATA XREF: .rdata:000000018096E54C\u2193o<br \/>.text:0000000180179985 ; .rdata:000000018096E55C\u2193o &#8230;<br \/>.text:0000000180179985 mov [rsp+98h+var_38], r14<br \/>.text:000000018017998A mov rbp, [rdx-8].text:000000018017998E xor ebx, ebx<br \/>.text:0000000180179990 mov rsi, [rdx-10h].text:0000000180179994 mov [rsp+98h+arg_0], ebx<br \/>.text:000000018017999B test r8, r8<br \/>.text:000000018017999E jnz short loc_1801799AA ; -&gt; ok<br \/>.text:00000001801799A0 mov ebx, 80070057h<br \/>.text:00000001801799A5 jmp loc_180179ADD<br \/>.text:00000001801799AA ; &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>.text:00000001801799AA<br \/>.text:00000001801799AA loc_1801799AA: ; CODE XREF: agsr_check_pe_trusted+3E\u2191j<br \/>.text:00000001801799AA mov eax, [r8].text:00000001801799AD cmp eax, 1388h ; =5000 dec<br \/>.text:00000001801799B2 jbe loc_180179AD8 ; PE security every 5000 invocation<br \/>.text:00000001801799B8 mov [r8], ebx ; clear counter<br \/>.text:00000001801799BB cmp [rdi+0BC0h], ebx<br \/>.text:00000001801799C1 jz short loc_1801799D2<br \/>.text:00000001801799C3<br \/>.text:00000001801799C3 loc_1801799C3: ; CODE XREF: agsr_check_pe_trusted+B7\u2193j<br \/>.text:00000001801799C3 mov dword ptr [rdi+0BF0h], 1 ; trusted pe flag ?<br \/>.text:00000001801799CD jmp loc_180179ADD<br \/>&#8220;`<\/p>\n<p>Finally, it&#8217;s worth to mention that the analysis of the `PEAuth` was made much<br \/>easier thanks to `Media Foundation Samples&#8217; and the code for `clearkeyStoreCDM`,<br \/>which seemed to be a mirror of actual CDM used by PlayReady:<\/p>\n<p>&#8220;`<br \/>.text:0000000180199860 agsr_VerifyState?? proc near ; DATA XREF: sub_180179960+C2\u2191o<br \/>.text:0000000180199860 ; .rdata:0000000180970E4C\u2193o &#8230;<br \/>.text:0000000180199860<br \/>.text:0000000180199860 var_258 = qword ptr -258h<br \/>.text:0000000180199860 var_250 = qword ptr -250h<br \/>.text:0000000180199860 var_248 = qword ptr -248h<br \/>.text:0000000180199860 var_240 = qword ptr -240h<br \/>.text:0000000180199860 var_238 = qword ptr -238h<br \/>.text:0000000180199860 var_230 = dword ptr -230h<\/p>\n<p>&#8230;.<\/p>\n<p>.text:0000000180199C01 lea rax, [r12+10h].text:0000000180199C06 mov r8d, 10h<br \/>.text:0000000180199C0C lea r9, [rbp+170h+var_1C8+4].text:0000000180199C10 sub r9, rax<br \/>.text:0000000180199C13<br \/>.text:0000000180199C13 loc_180199C13: ; CODE XREF: sub_180199860+3C5\u2193j<br \/>.text:0000000180199C13 movzx ecx, byte ptr [r9+rax].text:0000000180199C18 cmp cl, [rax].text:0000000180199C1A ja short loc_180199C3A<br \/>.text:0000000180199C1C jb short loc_180199C3A<br \/>.text:0000000180199C1E inc rax<br \/>.text:0000000180199C21 sub r8, 1<br \/>.text:0000000180199C25 jnz short loc_180199C13<br \/>.text:0000000180199C27 mov eax, [r12+0Ch].text:0000000180199C2C mov ecx, 0C00D715Fh ; \/\/<br \/>.text:0000000180199C2C ; \/\/ MessageId: MF_E_GRL_VERSION_TOO_LOW<br \/>.text:0000000180199C2C ; \/\/<br \/>.text:0000000180199C2C ; \/\/ MessageText:<br \/>.text:0000000180199C2C ; \/\/<br \/>.text:0000000180199C2C ; \/\/ The current GRL on the machine does not meet the minimum version requirements.%0<br \/>.text:0000000180199C2C ; \/\/<br \/>.text:0000000180199C2C ; #define MF_E_GRL_VERSION_TOO_LOW _HRESULT_TYPEDEF_(0xC00D715FL)<br \/>&#8220;`<\/p>\n<p>the original code is shown below:<\/p>\n<p>&#8220;`<br \/>HRESULT Cdm_clearkey_PEAuthHelper::VerifyState()<br \/>{<br \/>HRESULT hr = S_OK;<br \/>PEAUTH_MSG oMsg = { 0 };<br \/>PEAUTH_MSG_RESPONSE oRsp = { 0 };<\/p>\n<p>\/\/<br \/>\/\/ Obtain the current state of the protected environment from the kernel<br \/>\/\/<br \/>IF_FAILED_GOTO( _GenerateKernelInquiryMessage( m_fRequireTrustedKernel, m_dwMinimumGRLVersionRequired, &amp;oMsg ) );<br \/>IF_FAILED_GOTO( this-&gt;TransmitPEAuthMessage( &amp;oMsg, sizeof( oMsg ), &amp;oRsp, sizeof( oRsp ) ) );<br \/>IF_FAILED_GOTO( _VerifyResponseMessageStatus( m_fRequireTrustedKernel, m_fRequireTrustedUsermode, &amp;oRsp ) );<br \/>IF_FAILED_GOTO( _VerifyBinarySigning( oRsp.ResponseBody.Status.ResponseFlag, m_fBlockTestSignatures ) );<br \/>&#8230;<br \/>&#8220;`<\/p>\n<p>The published samples made it possible to follow the ITA path and discover the<br \/>symbolic names of many calls by simply comparing the asm code with available<br \/>C++ sources:<\/p>\n<p>&#8220;`<br \/>.text:000000018018E40D lea rax, [rcx+0B8h].text:000000018018E414 mov rcx, rax<br \/>.text:000000018018E417 mov [rbp+20h+var_98], rax<br \/>.text:000000018018E41B call agsr_CloneSample ; IF_FAILED_GOTO( _CloneSample( pSample, &amp;spSample ) );<br \/>.text:000000018018E420 mov rsi, qword ptr [rsp+120h+var_E8] ; spSample<br \/>.text:000000018018E425 mov r14d, eax<br \/>.text:000000018018E428 test eax, eax<br \/>.text:000000018018E42A js loc_18018EAD4 ; -&gt; error<br \/>.text:000000018018E430 mov rax, [rsi].text:000000018018E433 mov rbx, [rax+148h].text:000000018018E43A mov rcx, rbx<br \/>.text:000000018018E43D call cs:__guard_check_icall_fptr ; addr 7ffbba0ee443 (18e443) -&gt; target 7ffbf4c55400 (3acf5400) 180015400 ; __int64 __fastcall CMFSample::ConvertToContiguousBuffer(CMFSample *__hidden this, struct IMFMediaBuffer **)<br \/>.text:000000018018E43D ;<br \/>.text:000000018018E43D ; IF_FAILED_GOTO( spSample-&gt;ConvertToContiguousBuffer( &amp;spOutputBuffer ) );<br \/>.text:000000018018E443 lea rdx, [rsp+120h+var_C8] ; ComPtr&lt;IMFMediaBuffer&gt; spOutputBuffer;<br \/>.text:000000018018E448 mov rcx, rsi<br \/>.text:000000018018E44B call rbx<br \/>.text:000000018018E44D mov r14d, eax<br \/>.text:000000018018E450 test eax, eax<br \/>.text:000000018018E452 js loc_18018EAD4 ; -&gt; error<br \/>.text:000000018018E458 mov rbx, [rsp+120h+var_C8] ; spOutputBuffer<br \/>.text:000000018018E45D mov rax, [rbx].text:000000018018E460 mov r14, [rax+18h].text:000000018018E464 mov rcx, r14<br \/>.text:000000018018E467 call cs:__guard_check_icall_fptr ; addr 7ffbba0ee46d (18e46d) -&gt; target 7ffbba172150 (212150)<br \/>.text:000000018018E467 ;<br \/>.text:000000018018E467 ; IF_FAILED_GOTO( spOutputBuffer-&gt;Lock( &amp;pbOutputWeakRef, nullptr, &amp;cbOutputWeakRef ) ); ???<br \/>.text:000000018018E467 ;<br \/>.text:000000018018E467 ; Lock<br \/>.text:000000018018E467 ; (<br \/>.text:000000018018E467 ; [out] BYTE **ppbBuffer,<br \/>.text:000000018018E467 ; [out] DWORD *pcbMaxLength,<br \/>.text:000000018018E467 ; [out] DWORD *pcbCurrentLength<br \/>.text:000000018018E467 ; )<br \/>.text:000000018018E46D lea r9, [rsp+120h+var_F0] ; cbOutputWeakRef<br \/>.text:000000018018E472 xor r8d, r8d ; null<br \/>.text:000000018018E475 lea rdx, [rsp+120h+var_B8] ; pbOutputWeakRef<br \/>.text:000000018018E47A mov rcx, rbx<br \/>.text:000000018018E47D call r14 ; -&gt; 180212150<br \/>&#8220;`<\/p>\n<p>### CONTENT KEY EXTRACTION<br \/>WB syscall hijack (`NtQuerySystemInformation` IAT entry hijack) made it <br \/>possible to investigate WB calls (callstack), their arguments, stack and <br \/>memory content of PMP process during license acquisition (runtime PlayReady <br \/>operation in arbitrary VOD service environment such as Canal+ Online,<br \/>Netflix, HBO Max, Amazon Prime or Sky Showtime).<\/p>\n<p>Data and logs retrieved could be both tuned and compared with decrypted static<br \/>binary image.<\/p>\n<p>As a result, WB call location has been selected as including the following<br \/>information on the stack:<br \/>&#8211; XMR license blob<br \/>&#8211; license blob signature<br \/>&#8211; content key<\/p>\n<p>This is illustrated below (callstack layout done with respect to sub `0x215600`:<\/p>\n<p>&#8220;`<br \/>7e f5 18 92 41 cf ba fb 47 a0 5b c2 97 b7 4c 6d -&gt; var_5B0 (content key blob, size 0x20)<br \/>3e b5 52 69 e8 78 ae bf 99 5d bd 89 76 f4 a2 e3 <br \/>00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00<br \/>72 07 00 00 00 00 00 00 ad e3 a6 29 fa 7f 00 00<br \/>30 d7 d7 be 72 00 00 00 e8 dc d7 be 72 00 00 00<br \/>a6 93 88 c4 dc 01 00 00 10 00 00 00 00 00 00 00 &lt;&#8211; license signature data (addr, size), <br \/>24 92 88 c4 dc 01 00 00 76 01 00 00 00 00 00 00 &lt;&#8211; license xmr data (addr,size)<br \/>90 d7 d7 be 72 00 00 00 8f 03 00 00 00 00 00 00<br \/>30 a0 88 c4 dc 01 00 00 3d 74 6d ed f9 7f 00 00<br \/>f7 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00<br \/>00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00<br \/>00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00<br \/>e0 dc d7 be 72 00 00 00 4f 03 00 00 00 00 00 00<br \/>a0 5d 00 00 00 00 00 00 80 b9 88 c4 dc 01 00 00<br \/>00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00<br \/>a0 5d 00 00 00 00 00 00 20 a0 88 c4 dc 01 00 00<\/p>\n<p>8c d1 ae 6c c3 c6 8e fe 9f 02 5b 3c cd 7d 4b 4b -&gt; white-box AES rounds (expand key)<br \/>7a 36 9a 99 d8 1a dc 1e 66 9f e8 2c b1 d4 13 95<br \/>8e 18 01 c0 f3 6b f0 3f 18 b3 6b ed 98 8f 08 33<br \/>e2 9d 50 36 97 70 26 8f 09 45 cb e4 17 4c 45 51<br \/>f8 94 fc 29 e9 62 5c 20 66 a1 11 42 f7 6b d2 95<br \/>08 96 3c 4e 67 72 e6 e8 87 55 71 2c f6 b8 25 3f<br \/>8f f8 88 eb 6e 0c e8 85 6f df 1f 2f 1f e1 bc 96<br \/>b6 49 9d 0d 5e c3 f3 0e b7 9a 6a a7 2e fd 50 b7<br \/>27 21 13 80 ff 64 66 08 ce 78 8a 29 66 03 5c 18<br \/>bc f6 f6 02 92 8f 8c 92 d6 b4 47 e1 ac a2 d1 f8<br \/>46 4d 3c b2 4e 00 92 b4 a1 af eb 68 4b ee a4 6e<\/p>\n<p>70 00 00 00 00 00 00 00 7f 00 00 00 00 00 00 00<br \/>a0 10 00 00 00 00 00 00 20 a0 88 c4 dc 01 00 00<br \/>00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00<br \/>7f 00 00 00 00 00 00 00 a0 5d 00 00 00 00 00 00<\/p>\n<p>a7 c1 73 1b 23 ad b4 07 20 95 c4 62 9d 2b 57 6f -&gt; license blob data (var_3C0)<br \/>41 e3 e0 c6 33 2d af 65 68 aa ae 3b b0 56 7f c5<br \/>2b 1e 8e f9 5e 0b 2b aa 18 8b d3 bc 48 a1 e3 e3<br \/>11 7c 77 15 5a 25 39 2a 49 b9 12 ce d8 31 1b ad<br \/>fe 6a 9d 21 36 9b 6d 8e 69 25 af c2 d5 45 32 90<br \/>54 5a e8 c2 04 e5 57 5d e1 8c 16 9b 34 21 4d 6c<br \/>9b 9c 0b 65 fc 74 4f d2 2b 69 13 19 49 d0 bb d8<br \/>69 8b c7 8a ff 3f 77 91 a8 b1 3d 88 b6 9d b6 40<\/p>\n<p>98 44 cb cf bf cf b0 64 15 fe ae 24 b0 eb b4 c7 -&gt; encrypted prkf key (var_340)<br \/>28 0c 98 98 22 16 bc 8a a8 28 bd 36 1d 7a ef 9a<br \/>0b a0 e8 a2 e8 58 26 c2 6c fe 53 fe 69 19 cb fa<br \/>f6 38 4e e0 0d 4b f5 cf 24 f5 21 18 6f 27 15 8d<br \/>8d 00 00 00 00 00 00 00 a1 5b a7 29 fa 7f 00 00<br \/>&#8220;`<\/p>\n<p>The above callstack corresponded to the WB segment decryption call invoked from<br \/>the following location:<\/p>\n<p>&#8220;`<br \/>.text:0000000180215D2C loc_180215D2C: ; CODE XREF: agsr_get_license_decryptor+720\u2191j<br \/>.text:0000000180215D2C cmp [rbp+5A0h+arg_10], 0 ; =0 (ustawiane wyzej)<br \/>.text:0000000180215D33 jnz short loc_180215D9B ; u mnie 0<br \/>.text:0000000180215D35 lea rax, agsr_decrypt_segment ; TUTAJ MOGE ZROBIC HIJACK CALL&#8217;A!!!!<br \/>.text:0000000180215D35 ; aby odczytac ECC key<br \/>.text:0000000180215D35 ;<br \/>.text:0000000180215D35 ; decrypt<br \/>.text:0000000180215D35 ; &#8211; wb segment<br \/>.text:0000000180215D35 ; desc_va: 0x6d8100 size 100<br \/>.text:0000000180215D35 ; relocs_va: 0x9436f0 num 0x5197<br \/>.text:0000000180215D35 ; segments (num 1, data size 415):<br \/>.text:0000000180215D35 ; * [0000] encr_va 6b3a40 size 00019f section .text<br \/>.text:0000000180215D3C add rax, r14<br \/>.text:0000000180215D3F call rax<br \/>.text:0000000180215D41 test eax, eax<br \/>.text:0000000180215D43 js loc_180215C89 ; -&gt; error<br \/>.text:0000000180215D49 mov ebx, [rsp+6A0h+var_650] ; u mnie 0<br \/>.text:0000000180215D4D lea rax, [rbp+5A0h+var_5B0] ; plaintext key (ECC secret) ? rbp-10h ??<br \/>.text:0000000180215D51 mov r9d, dword ptr [rbp+5A0h+var_550+8] ; arg4 = license xmr size ?<br \/>.text:0000000180215D55 xor ecx, ecx<br \/>.text:0000000180215D57 mov r8, qword ptr [rbp+5A0h+var_550] ; arg3 = license xmr ?<br \/>.text:0000000180215D5B cmp r13w, 5 ; decryptor type ?<br \/>.text:0000000180215D60 mov [rsp+6A0h+var_670], ebx ; arg7 = u mnie 0<br \/>.text:0000000180215D64 mov [rsp+6A0h+var_678], rax ; arg6 = plaintext key (ECC secret) ?<br \/>&#8220;`<\/p>\n<p>The actual call to WB syscall has been conducted (hijacked) at this location:<\/p>\n<p>&#8220;`<br \/>.text:00000001800EBD1C mov eax, cs:dword_18098F428<br \/>.text:00000001800EBD22 lea rdx, [r11-48h] ; SystemInformation<br \/>.text:00000001800EBD26 mov [r11-28h], rcx<br \/>.text:00000001800EBD2A mov ecx, 0B9h ; SystemInformationClass<br \/>.text:00000001800EBD2F mov [r11-20h], rax<br \/>.text:00000001800EBD33 call cs:__imp_NtQuerySystemInformation<br \/>.text:00000001800EBD39 bts eax, 1Ch<br \/>.text:00000001800EBD3D<br \/>.text:00000001800EBD3D loc_1800EBD3D: ; CODE XREF: agsr_decrypt_segment+21\u2191j<br \/>&#8220;`<\/p>\n<p>Initially, the hijacking proceeded by comparing the low 16-bit value of the <br \/>return address:<\/p>\n<p>&#8220;`<br \/>is_decrypt_location:<br \/>; mov rdx,qword ptr [rsp+SAVED_REG_RIP+8]; and edx,0ffffh<br \/>; cmp edx,0bd39h<br \/>&#8220;`<\/p>\n<p>In order to make sniffer code portable across various PlayReady library <br \/>versions and Windows 10 \/ 11 systems, another, completely automatic and <br \/>address independent approach has been used that relied on the following:<br \/>&#8211; heuristic check for rbp &#8220;validity&#8221;<br \/>&#8211; match of wb call args size<br \/>&#8211; match of wb call type<br \/>&#8211; some stack content verification as expected to be present at target call <br \/>location (license xmr \/ signature ptrs, signature size)<\/p>\n<p>The above was sufficient to match on a target WB call. Upon matching, the <br \/>sniffer proceeds with arbitrary data dump of data contained on the callstack <br \/>comprising of license, signature and content keys.<\/p>\n<p>It also takes into account potential shift of the data on the stack occurring<br \/>on some systems, which is due to different local variable layout (it does so<br \/>with respect to content key blob).<\/p>\n<p>## HW DRM DISABLING<br \/>On Windows platforms with HW DRM capability, the attack (content key sniffing) <br \/>can still proceed as this feature can be easily disabled by setting the <br \/>following registry entry:<\/p>\n<p>&#8220;`<br \/>HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\PlayReady\\Troubleshooter<br \/>&#8220;`<\/p>\n<p>with value `DisableHWDRM` DWORD val = `1`<\/p>\n<p>Upon HWDRM disabling, PlayReady DRM proceeds as in Windows 10 without HW <br \/>support. More specifically, client identities get created and are maintained <br \/>in local file system (`*.bla` files).<\/p>\n<p>## MAGIC XOR KEYS DISCOVERY<br \/>The analysis conducted with respect to license blob decryption provided strong<br \/>hints that content key acquired by the sniffer (and denoted by `var_5B0` above)<br \/>is actually the content key blob.<\/p>\n<p>Investigation of code paths using that blob revealed that decryptor instance <br \/>used it to build AES rounds table. The AES rounds table was further used by <br \/>the core AES decryptor subroutine for MPEG A\/V segments decryption.<\/p>\n<p>Yet, the content key blob in its form present in memory and acquired by the <br \/>sniffer didn&#8217;t work (we failed to decrypt target MPEG movies with it, we tried <br \/>various combinations, byte reversals, etc.).<\/p>\n<p>We started to suspect that content key blob is maintained in PMP in some other<br \/>form. It could be permutation, affine \/ algebraic transform or some obfuscation.<\/p>\n<p>We didn&#8217;t know the real content key value, which could provide some hints with<br \/>respect to how content key blob bytes corresponds to it.<\/p>\n<p>Thinking more about it we figured out that we didn&#8217;t need any real content key <br \/>values at all. We could generate these on our own.<\/p>\n<p>PlayReady identities stored in Windows CDM carry public components for both <br \/>signing and encryption key. One could use the public encryption component to <br \/>build a custom license carrying a specific content key.<\/p>\n<p>This is what we did.<\/p>\n<p>So, we generated arbitrary license blob for a given PlayReady identity (using <br \/>its public encryption key), so that a content key was known to have byte 0 at <br \/>pos 0:<\/p>\n<p>&#8220;`<br \/>msprcp&gt; genlicense -i 0C86330B0E98CD7C586F336088DAFA0E -v 0 -p 0<br \/>pubkey<br \/>0000: cb 27 6f 9f 9f 76 46 64 54 23 19 ef 9c c7 69 0f .&#8217;o..vFdT#&#8230;.i.<br \/>0010: 9c 3b e3 75 8b d3 78 2a 8d 03 fb a8 bf 9e 1c 6d .;.u..x*&#8230;&#8230;.m<br \/>0020: f7 10 1c 69 94 2c 4d 07 d9 68 8b 61 09 85 bb d3 &#8230;i.,M..h.a&#8230;.<br \/>0030: 4e e8 58 20 e2 0c c9 bc a9 a8 1e b7 f6 59 65 7d N.X&#8230;&#8230;&#8230;.Ye}<br \/>content key<br \/>0000: 19 3f fc 9e 78 e2 35 05 9c 2f 5e 15 f1 6a 3a ed .?..x.5..\/^..j:.<br \/>0010: 00 1a 31 8d ea 33 7a c3 67 57 e7 e6 26 4a bd 00 ..1..3z.gW..&amp;J..<br \/>license<br \/>0000: be 70 c2 2b 49 ff 69 b4 b4 e5 ec db f4 93 2d d8 .p.+I.i&#8230;&#8230;.-.<br \/>0010: a2 42 de 3a 8e 20 38 bd 9e be 00 d5 12 b2 7d 14 .B.:..8&#8230;&#8230;.}.<br \/>0020: d4 18 30 ab 3b 96 7e 23 23 fd 81 fd c6 f8 98 7e ..0.;..##&#8230;&#8230;.<br \/>0030: 90 7b 78 02 22 1a 21 6b c6 00 13 a1 6a 5c 85 c5 .{x.&#8221;.!k&#8230;.j&#8230;<br \/>0040: 19 8d aa 46 4e 58 d8 a1 c1 6b 9c 6b 9b 02 6e 1e &#8230;FNX&#8230;k.k..n.<br \/>0050: 83 b4 e4 32 0a 4f 18 28 93 2d 32 0d 8a 04 b8 63 &#8230;2.O.(.-2&#8230;.c<br \/>0060: 85 aa bf 2e af a3 ae 36 9f 8b db c5 06 8f bf 0d &#8230;&#8230;.6&#8230;&#8230;..<br \/>0070: 8a 8e 95 be 49 db ab 95 e6 c5 55 18 f2 08 c5 6d &#8230;.I&#8230;..U&#8230;.m<br \/>stored to license.blob<br \/>&#8220;`<\/p>\n<p>We then tried to decrypt the license blob with the use the key subroutine we<br \/>located for that purpose (decrypt license blob indicated by `prsubs` cmd):<\/p>\n<p>&#8220;`<br \/>wret&gt; declicense ..\\mspr_toolkit\\license.blob -k 0C86330B0E98CD7C586F336088DAFA0E.enc.prv<br \/>DecLicenseCmd::run<br \/>DECRYPT LICENSE<br \/>keydata<br \/>0x00000000: 4c 33 c6 8e 0e f1 b6 f1 0c d5 31 6b 40 94 aa 68 L3&#8230;&#8230;..1k@..h<br \/>0x00000010: 32 cc 68 1b 00 3b fc 65 8b c4 3c e3 cb 62 de fc 2.h..;.e..&lt;..b..<br \/>0x00000020: 11 ef 51 7b 92 73 a1 84 24 ac 71 33 cf 76 d3 05 ..Q{.s..$.q3.v..<br \/>0x00000030: 44 2d 4e 12 79 3f 3f 09 7a 4e 4d 51 ac 78 a7 3c D-N.y??.zNMQ.x.&lt;<br \/>0x00000040: 6b k<br \/>license<br \/>0x00000000: be 70 c2 2b 49 ff 69 b4 b4 e5 ec db f4 93 2d d8 .p.+I.i&#8230;&#8230;.-.<br \/>0x00000010: a2 42 de 3a 8e 20 38 bd 9e be 00 d5 12 b2 7d 14 .B.:. 8&#8230;&#8230;.}.<br \/>0x00000020: d4 18 30 ab 3b 96 7e 23 23 fd 81 fd c6 f8 98 7e ..0.;.~##&#8230;&#8230;~<br \/>0x00000030: 90 7b 78 02 22 1a 21 6b c6 00 13 a1 6a 5c 85 c5 .{x.&#8221;.!k&#8230;.j\\..<br \/>0x00000040: 19 8d aa 46 4e 58 d8 a1 c1 6b 9c 6b 9b 02 6e 1e &#8230;FNX&#8230;k.k..n.<br \/>0x00000050: 83 b4 e4 32 0a 4f 18 28 93 2d 32 0d 8a 04 b8 63 &#8230;2.O.(.-2&#8230;.c<br \/>0x00000060: 85 aa bf 2e af a3 ae 36 9f 8b db c5 06 8f bf 0d &#8230;&#8230;.6&#8230;&#8230;..<br \/>0x00000070: 8a 8e 95 be 49 db ab 95 e6 c5 55 18 f2 08 c5 6d &#8230;.I&#8230;..U&#8230;.m<br \/>target 7ffd3d1728a0<br \/>wb res: 0 call res 0<br \/>output key<br \/>0x00000000: be 44 10 7a e9 4a 4b b9 44 8d 98 3d a7 db 5f 5e .D.z.JK.D..=.._^<br \/>0x00000010: 3a c8 66 a1 12 b8 a0 20 ba 90 7c 0e 30 f4 8c 85 :.f&#8230;. ..|.0&#8230;<br \/>&#8220;`<\/p>\n<p>The output indicated content key value 0x3a for pos 0.<\/p>\n<p>We did the same for other values corresponding to pos 0 such as 1:<\/p>\n<p>&#8220;`<br \/>genlicense -i 0C86330B0E98CD7C586F336088DAFA0E -v 1 -p 0<br \/>&#8220;`<\/p>\n<p>The output indicated content key value 0x3b at pos 0.<\/p>\n<p>&#8220;`<br \/>wret&gt; declicense ..\\mspr_toolkit\\license.blob -k 0C86330B0E98CD7C586F336088DAFA0E.enc.prv<br \/>DecLicenseCmd::run<br \/>DECRYPT LICENSE<br \/>keydata<br \/>0x00000000: 4c 33 c6 8e 0e f1 b6 f1 0c d5 31 6b 40 94 aa 68 L3&#8230;&#8230;..1k@..h<br \/>0x00000010: 32 cc 68 1b 00 3b fc 65 8b c4 3c e3 cb 62 de fc 2.h..;.e..&lt;..b..<br \/>0x00000020: 11 ef 51 7b 92 73 a1 84 24 ac 71 33 cf 76 d3 05 ..Q{.s..$.q3.v..<br \/>0x00000030: 44 2d 4e 12 79 3f 3f 09 7a 4e 4d 51 ac 78 a7 3c D-N.y??.zNMQ.x.&lt;<br \/>0x00000040: 6b k<br \/>license<br \/>0x00000000: 2c b2 7a 58 fa a0 b2 67 ba 1e 72 9d 54 2f 75 bc ,.zX&#8230;g..r.T\/u.<br \/>0x00000010: 93 82 66 4e 02 92 16 07 0b 16 c6 04 de 32 a5 94 ..fN&#8230;&#8230;&#8230;2..<br \/>0x00000020: 29 52 e6 8f 1b 08 65 e2 ad 16 9a 8c d5 a3 42 df )R&#8230;.e&#8230;&#8230;.B.<br \/>0x00000030: b0 6e c6 61 f6 46 c5 b9 31 13 d3 a4 a9 7d 55 a5 .n.a.F..1&#8230;.}U.<br \/>0x00000040: 79 c2 65 b4 9f 21 4b 53 85 35 4c 26 fc 4d 87 81 y.e..!KS.5L&amp;.M..<br \/>0x00000050: 9f fb 55 3e f6 3d 40 6d 74 85 cf b3 f6 c1 2e 8d ..U&gt;.=@mt&#8230;&#8230;.<br \/>0x00000060: 57 32 3e c9 77 c8 53 d6 74 b8 df ba 02 45 9d c2 W2&gt;.w.S.t&#8230;.E..<br \/>0x00000070: ee 5d 52 b7 f7 23 81 dd 23 bd dc 5b ba d1 16 d0 .]R..#..#..[&#8230;.<br \/>target 7ffd3c6528a0<br \/>wb res: 0 call res 0<br \/>output key<br \/>0x00000000: 56 1b e7 af 17 c1 ca 55 bb b8 f7 dc 1a d2 c5 97 V&#8230;&#8230;U&#8230;&#8230;..<br \/>0x00000010: 3b c0 85 03 97 db 75 a5 b5 83 73 19 13 4d 16 24 ;&#8230;..u&#8230;s..M.$<br \/>&#8220;`<\/p>\n<p>We tested other values and here is where things started to gen interesting. No<br \/>matter what content key value was used for pos 0, the output obtained indicated<br \/>it was simply a result of a XOR operation with value 0x3a.<\/p>\n<p>The other interesting observation was that the function used to calculate the<br \/>output byte for a content key at pos 0 was a bijection (XOR).<\/p>\n<p>So, we tested values at other positions:<\/p>\n<p>&#8220;`<br \/>genlicense -i 0C86330B0E98CD7C586F336088DAFA0E -v 0 -p 1<br \/>genlicense -i 0C86330B0E98CD7C586F336088DAFA0E -v 1 -p 1<br \/>genlicense -i 0C86330B0E98CD7C586F336088DAFA0E -v 2 -p 1<br \/>&#8230;<br \/>genlicense -i 0C86330B0E98CD7C586F336088DAFA0E -v 0 -p 2<br \/>genlicense -i 0C86330B0E98CD7C586F336088DAFA0E -v 1 -p 2<br \/>&#8230;<br \/>&#8220;`<\/p>\n<p>For all positions, we ended up with a bijection and a XOR function with some<br \/>constant value. The only difference was in a constant value &#8211; it was different<br \/>for every key pos.<\/p>\n<p>So, finally we generated a license with a content key having all bytes set to<br \/>zero:<\/p>\n<p>&#8220;`<br \/>msprcp&gt; genzlicense -i 0C86330B0E98CD7C586F336088DAFA0E -p decdata\\key_zero.dat<br \/>pubkey<br \/>0000: cb 27 6f 9f 9f 76 46 64 54 23 19 ef 9c c7 69 0f .&#8217;o..vFdT#&#8230;.i.<br \/>0010: 9c 3b e3 75 8b d3 78 2a 8d 03 fb a8 bf 9e 1c 6d .;.u..x*&#8230;&#8230;.m<br \/>0020: f7 10 1c 69 94 2c 4d 07 d9 68 8b 61 09 85 bb d3 &#8230;i.,M..h.a&#8230;.<br \/>0030: 4e e8 58 20 e2 0c c9 bc a9 a8 1e b7 f6 59 65 7d N.X&#8230;&#8230;&#8230;.Ye}<br \/>content key<br \/>0000: 48 45 4c 4c 4f 20 4d 49 43 52 4f 53 4f 46 54 21 HELLO.MICROSOFT!<br \/>0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 &#8230;&#8230;&#8230;&#8230;&#8230;.<br \/>license<br \/>0000: fe c1 77 07 03 01 0c d1 3b 75 54 24 3f d2 cf 53 ..w&#8230;..;uT$?..S<br \/>0010: c7 21 68 a6 da f9 72 ab 6c 9d b5 9a 0b 10 88 40 .!h&#8230;r.l&#8230;&#8230;@<br \/>0020: fe 91 90 55 12 0f 0c 80 04 88 c1 80 87 67 68 3c &#8230;U&#8230;&#8230;&#8230;gh&lt;<br \/>0030: 32 4f 1b 46 1d eb 6c e1 d1 f0 9e ea 99 09 30 49 2O.F..l&#8230;&#8230;.0I<br \/>0040: 59 3c 6e a9 72 2f 8e 4c 9c 0b e0 f2 62 85 37 33 Y&lt;n.r\/.L&#8230;.b.73<br \/>0050: 4a b1 fe 09 0c 2f ad 70 b6 b6 4a 0f 77 7a c0 24 J&#8230;.\/.p..J.wz.$<br \/>0060: fc c7 1c f6 aa ec 6b 1d b1 ef dc 95 ec cb 73 42 &#8230;&#8230;k&#8230;&#8230;.sB<br \/>0070: af 99 9f d7 b1 3c 23 4f 5f 8d 50 60 7e 17 4d 74 &#8230;..&lt;#O_.P&#8230;Mt<br \/>stored to zlicense.blob<br \/>&#8220;`<\/p>\n<p>It&#8217;s decryption revealed XOR values associated with content key:<\/p>\n<p>&#8220;`<br \/>wret&gt; declicense ..\\mspr_toolkit\\zlicense.blob -k 0C86330B0E98CD7C586F336088DAFA0E.enc.prv<br \/>DecLicenseCmd::run<br \/>DECRYPT LICENSE<br \/>keydata<br \/>0x00000000: 4c 33 c6 8e 0e f1 b6 f1 0c d5 31 6b 40 94 aa 68 L3&#8230;&#8230;..1k@..h<br \/>0x00000010: 32 cc 68 1b 00 3b fc 65 8b c4 3c e3 cb 62 de fc 2.h..;.e..&lt;..b..<br \/>0x00000020: 11 ef 51 7b 92 73 a1 84 24 ac 71 33 cf 76 d3 05 ..Q{.s..$.q3.v..<br \/>0x00000030: 44 2d 4e 12 79 3f 3f 09 7a 4e 4d 51 ac 78 a7 3c D-N.y??.zNMQ.x.&lt;<br \/>0x00000040: 6b k<br \/>license<br \/>0x00000000: fe c1 77 07 03 01 0c d1 3b 75 54 24 3f d2 cf 53 ..w&#8230;..;uT$?..S<br \/>0x00000010: c7 21 68 a6 da f9 72 ab 6c 9d b5 9a 0b 10 88 40 .!h&#8230;r.l&#8230;&#8230;@<br \/>0x00000020: fe 91 90 55 12 0f 0c 80 04 88 c1 80 87 67 68 3c &#8230;U&#8230;&#8230;&#8230;gh&lt;<br \/>0x00000030: 32 4f 1b 46 1d eb 6c e1 d1 f0 9e ea 99 09 30 49 2O.F..l&#8230;&#8230;.0I<br \/>0x00000040: 59 3c 6e a9 72 2f 8e 4c 9c 0b e0 f2 62 85 37 33 Y&lt;n.r\/.L&#8230;.b.73<br \/>0x00000050: 4a b1 fe 09 0c 2f ad 70 b6 b6 4a 0f 77 7a c0 24 J&#8230;.\/.p..J.wz.$<br \/>0x00000060: fc c7 1c f6 aa ec 6b 1d b1 ef dc 95 ec cb 73 42 &#8230;&#8230;k&#8230;&#8230;.sB<br \/>0x00000070: af 99 9f d7 b1 3c 23 4f 5f 8d 50 60 7e 17 4d 74 &#8230;..&lt;#O_.P`~.Mt<br \/>target 7ffd3c6528a0<br \/>wb res: 0 call res 0<br \/>output key<br \/>0x00000000: ef 3e a0 a8 de 88 33 f5 9b f0 89 7b 19 f7 31 92 .&gt;&#8230;.3&#8230;.{..1.<br \/>0x00000010: 3a d2 57 2c f8 8b da e3 dd c7 9b e8 16 be 31 85 :.W,&#8230;&#8230;&#8230;.1.<br \/>&#8220;`<\/p>\n<p>The following XOR key value (magic sequence) was obtained:<\/p>\n<p>&#8220;`<br \/>3a d2 57 2c f8 8b da e3 dd c7 9b e8 16 be 31 85<br \/>&#8220;`<\/p>\n<p>By following the same approach across various PlayReady library and Windows OS<br \/>versions (10 and 11), we found out that there are only two such magic key <br \/>sequences used across Windows OS versions released since 2022 (one for Windows <br \/>10, the other for Windows 11).<\/p>\n<p>The sequence for Windows 11 was the following:<\/p>\n<p>&#8220;`<br \/>81 73 8a db 5b 4b b4 22 3f aa d8 9c bb 45 9b ec<br \/>&#8220;`<\/p>\n<p>The script `genxorkey.bat` located in `tests\\xor_key` directory can discover<br \/>XOR key values automatically for various versions of PlayReady libraries present<br \/>in Windows 10 \/ 11 x64 systems across various builds from late 2022 till Nov<br \/>2024. The name of the output file is `xorkey.txt&#8217;. Corresponding `gentest.bat` <br \/>script decrypts the license containing special signature \/ content key sequence<br \/>(`decdata\\key_zero.dat`). It produces `test.txt` output file for each library.<\/p>\n<p>### CONTENT KEYS \/ MAGIC XOR KEYS VERIFICATION<br \/>Knowing that content key blob obtained by the license sniffer was a bijection<br \/>(XOR functions with a predefined constant for each byte pos), we could now try <br \/>to obtain the plaintext value of the actual content key.<\/p>\n<p>We imported sniffed data for arbitrary movie from Canal+ Online VOD collection:<\/p>\n<p>&#8220;`<br \/>msprcp&gt; implicdata ..\\..\\tests\\sniffdata\\vod\\w11\\canalp_xxxxxxxxxxxxx.dat<\/p>\n<p>LICENSE<br \/>* key id: d4710165a17e7f4ab6f0973e9bb8c723<br \/>* content key: 04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br \/>* key SHA256: c37da9b9500501a429ed038a6bf037df079a69aefdbcee873f2414089d51882b<br \/>&#8220;`<\/p>\n<p>We exported its license blob to file:<\/p>\n<p>&#8220;`<br \/>msprcp&gt; licdata -k d4710165a17e7f4ab6f0973e9bb8c723 -e sniff.blob<br \/>license blob<br \/>0000: 3f a0 a4 df 3a a5 4b 9b 21 19 4e 76 04 c3 49 4c ?&#8230;:.K.!.Nv..IL<br \/>0010: 6d 56 e6 8a b8 b1 5c 4d 93 83 06 70 95 19 e2 de mV&#8230;..M&#8230;p&#8230;.<br \/>0020: 65 22 f3 40 37 7e 4a 3e 60 56 ff 46 be 85 37 6e e&#8221;.@7.J&gt;.V.F..7n<br \/>0030: 04 6b 8d af 37 e2 6b f2 fb fd 00 b9 cc cf a8 33 .k..7.k&#8230;&#8230;..3<br \/>0040: e8 07 bb 65 d3 3b 58 99 42 38 a1 7a 74 b6 39 f6 &#8230;e.;X.B8.zt.9.<br \/>0050: 4e 13 53 ae 71 ec 44 67 59 7b 10 99 6a 10 a0 f8 N.S.q.DgY{..j&#8230;<br \/>0060: 2b 78 a0 b0 85 19 9e c7 d9 ed b9 54 e4 e1 b4 2e +x&#8230;&#8230;&#8230;T&#8230;.<br \/>0070: 9c 70 0e 87 ac 86 7b a1 7c 03 a6 f7 99 5b d4 45 .p&#8230;.{.|&#8230;.[.E<br \/>stored to sniff.blob<br \/>msprcp&gt;<br \/>&#8220;`<\/p>\n<p>The license data indicated it was encrypted to the identity key starting with<br \/>`34 87 66 73` bytes:<\/p>\n<p>&#8220;`<br \/>msprcp&gt; licdata -k d4710165a17e7f4ab6f0973e9bb8c723 -v<br \/>&#8230;<br \/>attr: 002a ECCDeviceKey<br \/>data<br \/>0000: 00 01 00 40 34 87 66 73 af da 57 db 05 60 7a 56 &#8230;@4.fs..W&#8230;zV<br \/>0010: 93 31 05 44 b9 79 c2 4d 20 95 b3 5f 99 da d5 8d .1.D.y.M&#8230;_&#8230;.<br \/>0020: f4 ae e5 af e7 60 33 06 ec 03 84 dc 90 74 ec e6 &#8230;&#8230;3&#8230;&#8230;t..<br \/>0030: 05 08 01 ea b2 b8 bd 47 2a ba c0 6c a8 98 14 6b &#8230;&#8230;.G*..l&#8230;k<br \/>0040: ed 29 40 ab .)@.<br \/>&#8230;<br \/>&#8220;`<\/p>\n<p>So, in order to decrypt the license we needed to use the W11 CDM identity that<br \/>has this key:<\/p>\n<p>&#8220;`<br \/>msprcp&gt; set CDM_DIR cdm\\w11<br \/>msprcp&gt; identity<br \/>318780A3793C675A09F6871E67DA5817<br \/>49CB836D40F3C68E496382B6F26B035D<br \/>5B75F8180A95751793D99A4E3BCF1E28<br \/>92B7A487F4BBA29A87CC845EA86CCBAC<br \/>9D83557F9740AAFD4F8F594279D970F1<\/p>\n<p>msprcp&gt; identity 5B75F8180A95751793D99A4E3BCF1E28<br \/>[5B75F8180A95751793D99A4E3BCF1E28]SIGN<br \/>IdentityInfo<br \/>pubkey<br \/>0000: 78 1f a2 d6 09 98 78 88 07 ce bd 27 c8 f5 83 60 x&#8230;..x&#8230;.&#8217;&#8230;.<br \/>0010: 0b 1c 7b 46 ad ee f1 db b3 4e 15 88 f6 40 cc 31 ..{F&#8230;..N&#8230;@.1<br \/>0020: ea 3c ea 06 5d 5f 59 74 f1 34 4c e2 ae a6 b0 bb .&lt;..]_Yt.4L&#8230;..<br \/>0030: ea 96 70 ea 0a c5 3f 94 a1 c1 b8 ec 9e d6 93 1c ..p&#8230;?&#8230;&#8230;&#8230;<br \/>prvkey<br \/>0000: 97 f2 1f bf e9 eb 6f 1b 56 45 61 45 20 63 3e af &#8230;&#8230;o.VEaE.c&gt;.<br \/>0010: af 40 ca 47 10 b2 34 3e 60 49 0d 1f 18 b2 56 33 .@.G..4&gt;.I&#8230;.V3<br \/>ENCRYPT<br \/>IdentityInfo<br \/>pubkey<br \/>0000: 34 87 66 73 af da 57 db 05 60 7a 56 93 31 05 44 4.fs..W&#8230;zV.1.D<br \/>0010: b9 79 c2 4d 20 95 b3 5f 99 da d5 8d f4 ae e5 af .y.M&#8230;_&#8230;&#8230;..<br \/>0020: e7 60 33 06 ec 03 84 dc 90 74 ec e6 05 08 01 ea ..3&#8230;&#8230;t&#8230;&#8230;<br \/>0030: b2 b8 bd 47 2a ba c0 6c a8 98 14 6b ed 29 40 ab &#8230;G*..l&#8230;k.)@.<br \/>prvkey<br \/>0000: 5d ae 07 b2 02 c1 28 0b d5 86 04 9f 2e ee a9 00 ]&#8230;..(&#8230;&#8230;&#8230;<br \/>0010: 42 de 00 b3 25 ae 82 1d b6 22 c9 42 80 af 5f ed B&#8230;%&#8230;.&#8221;.B.._.<br \/>0020: 08 85 aa 4b 33 cd b9 2c 69 a5 2b dc 2f 72 1c 6a &#8230;K3..,i.+.\/r.j<br \/>0030: 99 b9 76 cc 57 99 93 70 c4 eb 10 af 50 e4 ba 09 ..v.W..p&#8230;.P&#8230;<br \/>0040: 92 .<br \/>&#8220;`<\/p>\n<p>After exporting the identity keys:<\/p>\n<p>&#8220;`<br \/>msprcp&gt; identity -e 5B75F8180A95751793D99A4E3BCF1E28<br \/>5B75F8180A95751793D99A4E3BCF1E28.enc.pub (public encryption key)<br \/>5B75F8180A95751793D99A4E3BCF1E28.enc.prv (private encryption key)<br \/>5B75F8180A95751793D99A4E3BCF1E28.sig.pub (public signing key)<br \/>5B75F8180A95751793D99A4E3BCF1E28.sig.prv (private signing key)<br \/>&#8220;`<\/p>\n<p>we tried to decrypt the sniffed license blob, this time we needed to use some<br \/>PlayReady library corresponding to Windows 11 OS:<\/p>\n<p>&#8220;`<br \/>wret&gt; prlib w11_oct24.dll<br \/>PRLib::run<br \/>wret&gt; wbsetup -r<br \/>WBSetupCmd::run<br \/>Warbird encrypted binary<br \/>### setting up Warbird for runtime analysis<br \/>&#8211; scanning for heap exec descriptors<br \/>found: 985<br \/>&#8211; scanning for segment descriptors<br \/>found: 37<br \/>&#8211; decrypting heapexec descriptors<br \/>&#8211; decrypting segment descriptors<br \/>&#8211; locating ret syscalls<br \/>total: 1042<br \/>&#8211; locating heapexec syscalls<br \/>total: 919 (WB_MOV10_B9 892, WB_MOV10_REG 7, WB_LEA10 5, WB_SHARED 15)<br \/>&#8211; adjusting code refs<br \/>total: 1487<br \/>&#8211; adjusting data refs<br \/>total: 2<br \/>&#8211; patching self LEAs<br \/>total: 985<br \/>&#8211; patching ret syscalls<br \/>&#8211; patching heapexec syscalls<br \/>&#8211; patching wb call<br \/>&#8211; patching antidebug call<br \/>&#8211; patching App Policy<br \/>&#8211; Dll init Windows.Media.dll<br \/>size 7145640 bytes<br \/>base 180000000<br \/>&#8211; patching App Model<br \/>&#8211; Dll init Windows.Storage.ApplicationData.dll<br \/>size 435248 bytes<br \/>base 180000000<br \/>&#8211; disabling thread library calls<br \/>wret&gt;<br \/>wret&gt;<br \/>wret&gt; declicense ..\\mspr_toolkit\\sniff.blob -k 5B75F8180A95751793D99A4E3BCF1E28.enc.prv<br \/>DecLicenseCmd::run<br \/>DECRYPT LICENSE<br \/>main obj lea at 2bde10<br \/>main obj vtable 43b4d8<br \/>lea at 2bdfa7<br \/>lea va 2c1920<br \/>lea at 2c1995<br \/>lea va 2c0050<br \/>syscall va 2c04a7<br \/>lea candidate at 2c0479<br \/>lea candidate va 2cba40<br \/>syscall va 2c061e<br \/>lea candidate at 2c05d6<br \/>lea candidate va 2cc170<br \/>sub_candidate e9600 size 63cf<br \/>keydata<br \/>0x00000000: 5d ae 07 b2 02 c1 28 0b d5 86 04 9f 2e ee a9 00 ]&#8230;..(&#8230;&#8230;&#8230;<br \/>0x00000010: 42 de 00 b3 25 ae 82 1d b6 22 c9 42 80 af 5f ed B&#8230;%&#8230;.&#8221;.B.._.<br \/>0x00000020: 08 85 aa 4b 33 cd b9 2c 69 a5 2b dc 2f 72 1c 6a &#8230;K3..,i.+.\/r.j<br \/>0x00000030: 99 b9 76 cc 57 99 93 70 c4 eb 10 af 50 e4 ba 09 ..v.W..p&#8230;.P&#8230;<br \/>0x00000040: 92 .<br \/>license<br \/>0x00000000: 3f a0 a4 df 3a a5 4b 9b 21 19 4e 76 04 c3 49 4c ?&#8230;:.K.!.Nv..IL<br \/>0x00000010: 6d 56 e6 8a b8 b1 5c 4d 93 83 06 70 95 19 e2 de mV&#8230;.\\M&#8230;p&#8230;.<br \/>0x00000020: 65 22 f3 40 37 7e 4a 3e 60 56 ff 46 be 85 37 6e e&#8221;.@7~J&gt;`V.F..7n<br \/>0x00000030: 04 6b 8d af 37 e2 6b f2 fb fd 00 b9 cc cf a8 33 .k..7.k&#8230;&#8230;..3<br \/>0x00000040: e8 07 bb 65 d3 3b 58 99 42 38 a1 7a 74 b6 39 f6 &#8230;e.;X.B8.zt.9.<br \/>0x00000050: 4e 13 53 ae 71 ec 44 67 59 7b 10 99 6a 10 a0 f8 N.S.q.DgY{..j&#8230;<br \/>0x00000060: 2b 78 a0 b0 85 19 9e c7 d9 ed b9 54 e4 e1 b4 2e +x&#8230;&#8230;&#8230;T&#8230;.<br \/>0x00000070: 9c 70 0e 87 ac 86 7b a1 7c 03 a6 f7 99 5b d4 45 .p&#8230;.{.|&#8230;.[.E<br \/>target 7ffd3c9cc170<br \/>wb res: 0 call res 0<br \/>output key<br \/>0x00000000: 24 5a 65 ef a8 c8 07 9c 08 1e 4e 8f 85 97 6d 85 $Ze&#8230;&#8230;.N&#8230;m.<br \/>0x00000010: 85 14 8f 9e 4b b8 c0 7e 7b 30 fe fd db 0f 08 8a &#8230;.K..~{0&#8230;&#8230;<br \/>XOR(ed) signature key<br \/>0x00000000: 24 5a 65 ef a8 c8 07 9c 08 1e 4e 8f 85 97 6d 85 $Ze&#8230;&#8230;.N&#8230;m.<br \/>XOR(ed) content key<br \/>0x00000000: 85 14 8f 9e 4b b8 c0 7e 7b 30 fe fd db 0f 08 8a &#8230;.K..~{0&#8230;&#8230;<br \/>&#8220;`<\/p>\n<p>The output key `85 14 8f 9e` visible above is the content key blob. In order <br \/>to find the actual content key value, we need to set the XOR key, which is<br \/>zero by default:<\/p>\n<p>&#8220;`<br \/>wret&gt; xorkey<br \/>XORKeyCmd::run<br \/>[none]0x00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 &#8230;&#8230;&#8230;&#8230;&#8230;.<br \/>0x00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 &#8230;&#8230;&#8230;&#8230;&#8230;.<br \/>&#8220;`<br \/>So, we set the magic XOR key value to correspond to Windows 11 OS:<\/p>\n<p>&#8220;`<br \/>wret&gt; xorkey w11<br \/>XORKeyCmd::run<br \/>[w11]0x00000000: c3 cd 57 69 1e ff 15 6c 95 d9 46 d5 34 12 1d fb ..Wi&#8230;l..F.4&#8230;<br \/>0x00000010: 81 73 8a db 5b 4b b4 22 3f aa d8 9c bb 45 9b ec .s..[K.&#8221;?&#8230;.E..<br \/>&#8220;`<\/p>\n<p>and decrypt the license again:<\/p>\n<p>&#8220;`<br \/>wret&gt; declicense ..\\mspr_toolkit\\sniff.blob -k 5B75F8180A95751793D99A4E3BCF1E28.enc.prv<br \/>DecLicenseCmd::run<br \/>DECRYPT LICENSE<br \/>keydata<br \/>0x00000000: 5d ae 07 b2 02 c1 28 0b d5 86 04 9f 2e ee a9 00 ]&#8230;..(&#8230;&#8230;&#8230;<br \/>0x00000010: 42 de 00 b3 25 ae 82 1d b6 22 c9 42 80 af 5f ed B&#8230;%&#8230;.&#8221;.B.._.<br \/>0x00000020: 08 85 aa 4b 33 cd b9 2c 69 a5 2b dc 2f 72 1c 6a &#8230;K3..,i.+.\/r.j<br \/>0x00000030: 99 b9 76 cc 57 99 93 70 c4 eb 10 af 50 e4 ba 09 ..v.W..p&#8230;.P&#8230;<br \/>0x00000040: 92 .<br \/>license<br \/>0x00000000: 3f a0 a4 df 3a a5 4b 9b 21 19 4e 76 04 c3 49 4c ?&#8230;:.K.!.Nv..IL<br \/>0x00000010: 6d 56 e6 8a b8 b1 5c 4d 93 83 06 70 95 19 e2 de mV&#8230;.\\M&#8230;p&#8230;.<br \/>0x00000020: 65 22 f3 40 37 7e 4a 3e 60 56 ff 46 be 85 37 6e e&#8221;.@7~J&gt;`V.F..7n<br \/>0x00000030: 04 6b 8d af 37 e2 6b f2 fb fd 00 b9 cc cf a8 33 .k..7.k&#8230;&#8230;..3<br \/>0x00000040: e8 07 bb 65 d3 3b 58 99 42 38 a1 7a 74 b6 39 f6 &#8230;e.;X.B8.zt.9.<br \/>0x00000050: 4e 13 53 ae 71 ec 44 67 59 7b 10 99 6a 10 a0 f8 N.S.q.DgY{..j&#8230;<br \/>0x00000060: 2b 78 a0 b0 85 19 9e c7 d9 ed b9 54 e4 e1 b4 2e +x&#8230;&#8230;&#8230;T&#8230;.<br \/>0x00000070: 9c 70 0e 87 ac 86 7b a1 7c 03 a6 f7 99 5b d4 45 .p&#8230;.{.|&#8230;.[.E<br \/>target 7ffd3c9cc170<br \/>wb res: 0 call res 0<br \/>output key<br \/>0x00000000: 24 5a 65 ef a8 c8 07 9c 08 1e 4e 8f 85 97 6d 85 $Ze&#8230;&#8230;.N&#8230;m.<br \/>0x00000010: 85 14 8f 9e 4b b8 c0 7e 7b 30 fe fd db 0f 08 8a &#8230;.K..~{0&#8230;&#8230;<br \/>signature key<br \/>0x00000000: e7 97 32 86 b6 37 12 f0 9d c7 08 5a b1 85 70 7e ..2..7&#8230;..Z..p~<br \/>content key<br \/>0x00000000: 04 67 05 45 10 f3 74 5c 44 9a 26 61 60 4a 93 66 .g.E..t\\D.&amp;a`J.f<br \/>wret&gt;<br \/>&#8220;`<\/p>\n<p>Now, the real content key is revealed (`04 67 05 45`&#8230;).<\/p>\n<p>A proof and confirmation that this key is real is available in tests directory<br \/>(`win_sniffed_key_for_canalp_vod`). It contains a log that showcases the import<br \/>of a license data (and content key) contained by the sniffer to MSPR toolkit<br \/>for a successful movie sequence download and decryption.<\/p>\n<p>The other confirmation that sniffed content keys are real ones can be obtained<br \/>with the help of license signatures and crypto:<\/p>\n<p>&#8220;`<br \/>msprcp&gt; licdata -k d4710165a17e7f4ab6f0973e9bb8c723 -v<br \/>LICENSE<br \/>* key id: d4710165a17e7f4ab6f0973e9bb8c723<br \/>* signature key: e7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br \/>* key SHA256: ab7ea6600bee5e9d150051bbcf74ae0f64971d23f0d8fa102a261860c473a36e<br \/>* content key: 04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br \/>* key SHA256: c37da9b9500501a429ed038a6bf037df079a69aefdbcee873f2414089d51882b<br \/>&#8230;<br \/>XMR SIGNATURE<br \/>0000: ed 47 b2 51 7d 1a 5f 25 9e f1 59 d7 7a 91 58 f8 .G.Q}._%..Y.z.X.<br \/>calculated cmac<br \/>0000: ed 47 b2 51 7d 1a 5f 25 9e f1 59 d7 7a 91 58 f8 .G.Q}._%..Y.z.X.<br \/>##### SIGNATURE KEY \/ AES-CMAC OK<br \/>msprcp&gt;<br \/>&#8220;`<\/p>\n<p>The crypto check works as following:<br \/>&#8211; plaintext value of a digital signature key encrypted through ECC is extracted <br \/>from a Protected Media Path process (this explains several versions of the<br \/>sniffer .dat files),<br \/>&#8211; the extracted signature key is used to calculate the AES-CMAC value of a <br \/>binary license XMR blob<br \/>&#8211; the calculated signature value is checked against the signature appended at<br \/>the end of the issued license<br \/>&#8211; correct AES-CMAC value implicates correct signature key (and correct content <br \/>key)<\/p>\n<p>The above mechanism is also used by Microsoft to verify the correctness of <br \/>decrypted content keys received from a license server. It relies on the fact <br \/>that signature key is part of the same encrypted license blob as content key. <br \/>Thus, successful extraction of a signature key implicates successful extraction<br \/>of a content key.<\/p>\n<p>Finally, it is worth to note that sniffer tool has been updated to &#8220;deobfuscate&#8221;<br \/>the content key blobs harvested from the stack with the use of a magic XOR key <br \/>values (provided as config in `sniffer\\decdata\\xorkey.w10` and <br \/>`sniffer\\decdata\\xorkey.w11`). As such, the sniffer should be deemed as <br \/>extracting actual (plaintext) content key values.<\/p>\n<p>## WHITEBOX CRYPTO BYPASS<br \/>The initial (XOR key) attack, is limited to the narrow time window. The sniffer <br \/>needs to extract content key from the stack at a given code execution context.<br \/>The content keys gets cleared upon return from a target subroutine.<\/p>\n<p>There is however a data structure, which is not imposed by said limits. These <br \/>are the white-box data structures in a form of AES round keys. They need to be <br \/>present for the whole time of a movie decryption \/ playback (core AES <br \/>decryption subroutine uses these).<\/p>\n<p>PlayReady creates a license decryptor object as a follow up of a license blob<br \/>decryption and successful signature verification. The license blob decryptor<br \/>doesn&#8217;t hold the content key (AES key) in clear form &#8211; it maintains it through<br \/>the form of white-box crypto AES round keys. The goal of white-box crypto is<br \/>to make reconstruction of a secret-key difficult \/ not possible.<\/p>\n<p>This is illustrated by the constructor code below:<\/p>\n<p>&#8220;`<br \/>.text:0000000180222D50 sub_180222D50 proc near ; DATA XREF: agsr_construct_license_decryptor_internal+CE\u2193o<br \/>.text:0000000180222D50 ; .pdata:00000001809AF98C\u2193o<br \/>.text:0000000180222D50<br \/>.text:0000000180222D50 arg_0 = qword ptr 8<br \/>.text:0000000180222D50 arg_8 = qword ptr 10h<br \/>.text:0000000180222D50 arg_10 = qword ptr 18h<br \/>.text:0000000180222D50 arg_18 = qword ptr 20h<br \/>.text:0000000180222D50<br \/>.text:0000000180222D50 lea rdx, unk_180998298<br \/>.text:0000000180222D57 mov rcx, [rsp+0].text:0000000180222D5B mov [rsp+arg_0], rbx<br \/>.text:0000000180222D60 mov [rsp+arg_8], rbp<br \/>.text:0000000180222D65 mov [rsp+arg_10], rsi<br \/>.text:0000000180222D6A mov [rsp+arg_18], rdi<br \/>.text:0000000180222D6F push r14<br \/>.text:0000000180222D71 push r15<br \/>.text:0000000180222D73 movzx ebp, word ptr [rcx+10h] ; decryptor type<br \/>.text:0000000180222D77 mov rsi, rdx<br \/>.text:0000000180222D7A mov r9, [rcx+1Eh].text:0000000180222D7E mov r10d, ebp ; decryptor type<br \/>.text:0000000180222D81 mov r11d, [rcx+1Ah].text:0000000180222D85 mov r8, [rcx+12h] ; src data to copy into decryptor<br \/>.text:0000000180222D89 mov rbx, [rcx+8] ; this<br \/>&#8230;<br \/>.text:0000000180222E02 mov edx, 0B0h ; size 0xb0<br \/>.text:0000000180222E07 lea r9, [rbx+14h] ; target to copy data to<br \/>.text:0000000180222E07 ; decryptor offset+0x14, data size 0xb0 (AES round keys)<br \/>.text:0000000180222E0B nop dword ptr [rax+rax+00h].text:0000000180222E10<br \/>.text:0000000180222E10 loc_180222E10: ; CODE XREF: sub_180222D50+D2\u2193j<br \/>.text:0000000180222E10 movzx eax, byte ptr [r8].text:0000000180222E14 lea r8, [r8+1].text:0000000180222E18 mov [r9], al<br \/>.text:0000000180222E1B lea r9, [r9+1].text:0000000180222E1F add edx, 0FFFFFFFFh<br \/>.text:0000000180222E22 jnz short loc_180222E10<\/p>\n<p>&#8220;`<\/p>\n<p>The white-box crypto attack becomes fairly easy to implement upon the knowledge<br \/>of the actual content key and resulting AES round keys.<\/p>\n<p>The AES round keys can be obtained with the use of a prsubs call to &#8220;expand key&#8221;<br \/>code.<\/p>\n<p>We found out the relation between content key bytes and initial white-boxed AES<br \/>round key (key round 0) by verifying what value is generated by the expand key<br \/>subroutine for a given pos as a response to given content key byte (at the same<br \/>pos).<\/p>\n<p>Finding out the answer to the above required to run &#8220;expand key&#8221; subroutine <br \/>16*256 times (16 is the length of AES content key, 256 is the possible value <br \/>count for each pos).<\/p>\n<p>As a result, we obtained 16 tables, each was 256 bytes in size. These can be <br \/>seen with the use of `ektable` (expand key table) command:<\/p>\n<p>&#8220;`<br \/>wret&gt; ektable -v<br \/>EKTableCmd::run<br \/>[w11_oct24.dll]* pos 0<br \/>0x00000000: 60 d4 13 a7 86 32 f5 41 b7 03 c4 70 51 e5 22 96 `&#8230;.2.A&#8230;pQ.&#8221;.<br \/>0x00000010: d5 61 a6 12 33 87 40 f4 02 b6 71 c5 e4 50 97 23 .a..3.@&#8230;q..P.#<br \/>0x00000020: 11 a5 62 d6 f7 43 84 30 c6 72 b5 01 20 94 53 e7 ..b..C.0.r.. .S.<br \/>0x00000030: a4 10 d7 63 42 f6 31 85 73 c7 00 b4 95 21 e6 52 &#8230;cB.1.s&#8230;.!.R<br \/>0x00000040: 82 36 f1 45 64 d0 17 a3 55 e1 26 92 b3 07 c0 74 .6.Ed&#8230;U.&amp;&#8230;.t<br \/>0x00000050: 37 83 44 f0 d1 65 a2 16 e0 54 93 27 06 b2 75 c1 7.D..e&#8230;T.&#8217;..u.<br \/>0x00000060: f3 47 80 34 15 a1 66 d2 24 90 57 e3 c2 76 b1 05 .G.4..f.$.W..v..<br \/>0x00000070: 46 f2 35 81 a0 14 d3 67 91 25 e2 56 77 c3 04 b0 F.5&#8230;.g.%.Vw&#8230;<br \/>0x00000080: bf 0b cc 78 59 ed 2a 9e 68 dc 1b af 8e 3a fd 49 &#8230;xY.*.h&#8230;.:.I<br \/>0x00000090: 0a be 79 cd ec 58 9f 2b dd 69 ae 1a 3b 8f 48 fc ..y..X.+.i..;.H.<br \/>0x000000a0: ce 7a bd 09 28 9c 5b ef 19 ad 6a de ff 4b 8c 38 .z..(.[&#8230;j..K.8<br \/>0x000000b0: 7b cf 08 bc 9d 29 ee 5a ac 18 df 6b 4a fe 39 8d {&#8230;.).Z&#8230;kJ.9.<br \/>0x000000c0: 5d e9 2e 9a bb 0f c8 7c 8a 3e f9 4d 6c d8 1f ab ]&#8230;&#8230;|.&gt;.Ml&#8230;<br \/>0x000000d0: e8 5c 9b 2f 0e ba 7d c9 3f 8b 4c f8 d9 6d aa 1e .\\.\/..}.?.L..m..<br \/>0x000000e0: 2c 98 5f eb ca 7e b9 0d fb 4f 88 3c 1d a9 6e da ,._..~&#8230;O.&lt;..n.<br \/>0x000000f0: 99 2d ea 5e 7f cb 0c b8 4e fa 3d 89 a8 1c db 6f .-.^&#8230;.N.=&#8230;.o<br \/>* pos 1<br \/>0x00000000: ec 58 9f 2b 0a be 79 cd 3b 8f 48 fc dd 69 ae 1a .X.+..y.;.H..i..<br \/>0x00000010: 59 ed 2a 9e bf 0b cc 78 8e 3a fd 49 68 dc 1b af Y.*&#8230;.x.:.Ih&#8230;<br \/>0x00000020: 9d 29 ee 5a 7b cf 08 bc 4a fe 39 8d ac 18 df 6b .).Z{&#8230;J.9&#8230;.k<br \/>0x00000030: 28 9c 5b ef ce 7a bd 09 ff 4b 8c 38 19 ad 6a de (.[..z&#8230;K.8..j.<br \/>0x00000040: 0e ba 7d c9 e8 5c 9b 2f d9 6d aa 1e 3f 8b 4c f8 ..}..\\.\/.m..?.L.<br \/>0x00000050: bb 0f c8 7c 5d e9 2e 9a 6c d8 1f ab 8a 3e f9 4d &#8230;|]&#8230;l&#8230;.&gt;.M<br \/>0x00000060: 7f cb 0c b8 99 2d ea 5e a8 1c db 6f 4e fa 3d 89 &#8230;..-.^&#8230;oN.=.<br \/>0x00000070: ca 7e b9 0d 2c 98 5f eb 1d a9 6e da fb 4f 88 3c .~..,._&#8230;n..O.&lt;<br \/>0x00000080: 33 87 40 f4 d5 61 a6 12 e4 50 97 23 02 b6 71 c5 3.@..a&#8230;P.#..q.<br \/>0x00000090: 86 32 f5 41 60 d4 13 a7 51 e5 22 96 b7 03 c4 70 .2.A`&#8230;Q.&#8221;&#8230;.p<br \/>0x000000a0: 42 f6 31 85 a4 10 d7 63 95 21 e6 52 73 c7 00 b4 B.1&#8230;.c.!.Rs&#8230;<br \/>0x000000b0: f7 43 84 30 11 a5 62 d6 20 94 53 e7 c6 72 b5 01 .C.0..b. .S..r..<br \/>0x000000c0: d1 65 a2 16 37 83 44 f0 06 b2 75 c1 e0 54 93 27 .e..7.D&#8230;u..T.&#8217;<br \/>0x000000d0: 64 d0 17 a3 82 36 f1 45 b3 07 c0 74 55 e1 26 92 d&#8230;.6.E&#8230;tU.&amp;.<br \/>0x000000e0: a0 14 d3 67 46 f2 35 81 77 c3 04 b0 91 25 e2 56 &#8230;gF.5.w&#8230;.%.V<br \/>0x000000f0: 15 a1 66 d2 f3 47 80 34 c2 76 b1 05 24 90 57 e3 ..f..G.4.v..$.W.<br \/>&#8230;<br \/>&#8220;`<\/p>\n<p>While white-box crypto in use by PlayReady doesn&#8217;t rely on a simple XOR as it <br \/>was the case for content key obfuscation, it is still of questionable strength.<br \/>The problem lies in the fact that tables obtained for each content key pos <br \/>(ektables) are all bijections (1 to 1 mappings, each table contains unique <br \/>256 values).<\/p>\n<p>This makes discovery of a content key from white-box structures maintained by <br \/>the decryptor straightforward.<\/p>\n<p>One just needs the first white-box AES round key value and reverse lookup table<br \/>built from ektables described above.<\/p>\n<p>The illustration of content key discovery from white-box crypto structures is<br \/>shown below.<\/p>\n<p>We first decrypt arbitrary sniffed license, but this time also issue a call to<br \/>expand key in order to obtain the corresponding white-box AES crypto structure:<\/p>\n<p>&#8220;`<br \/>wret&gt; declicense ..\\mspr_toolkit\\sniff.blob -k 5B75F8180A95751793D99A4E3BCF1E28.enc.prv -e -o ekey.dat<br \/>&#8230;<br \/>target 7ffd3d2ec170<br \/>wb res: 0 call res 0<br \/>output key<br \/>0x00000000: 24 5a 65 ef a8 c8 07 9c 08 1e 4e 8f 85 97 6d 85 $Ze&#8230;&#8230;.N&#8230;m.<br \/>0x00000010: 85 14 8f 9e 4b b8 c0 7e 7b 30 fe fd db 0f 08 8a &#8230;.K..~{0&#8230;&#8230;<br \/>signature key<br \/>0x00000000: e7 97 32 86 b6 37 12 f0 9d c7 08 5a b1 85 70 7e ..2..7&#8230;..Z..p~<br \/>content key<br \/>0x00000000: 04 67 05 45 10 f3 74 5c 44 9a 26 61 60 4a 93 66 .g.E..t\\D.&amp;a`J.f<br \/>7ffd3d109600<br \/>expand key<br \/>expandkey res: 0<br \/>expanded key<br \/>0x00000000: 7e e3 f8 8f 31 f4 d8 1d 1e c1 6a 92 4c be 7a e5 ~&#8230;1&#8230;..j.L.z.<br \/>0x00000010: 0d 50 a6 75 53 be 04 e9 26 dc 96 d2 cc 05 92 23 .P.uS&#8230;&amp;&#8230;&#8230;#<br \/>0x00000020: a7 be 78 39 64 c8 37 c3 be 4f c8 ae d2 a1 c1 23 ..x9d.7..O&#8230;..#<br \/>0x00000030: 1d 8b 2a 7a 61 5b 05 a1 c7 0c d5 17 0d b5 0c 2c ..*za[&#8230;&#8230;&#8230;,<br \/>0x00000040: 88 32 d7 b6 f1 71 ca 0f 2e 65 07 00 3b c8 13 34 .2&#8230;q&#8230;e..;..4<br \/>0x00000050: c1 67 90 e0 28 0e 42 f7 1e 73 5d ef 3d a3 56 c3 .g..(.B..s].=.V.<br \/>0x00000060: a1 88 ad a4 91 9e f7 4b 97 f5 b2 bc b2 4e fc 67 &#8230;&#8230;.K&#8230;..N.g<br \/>0x00000070: 16 ec 8b 98 9f 6a 64 cb 10 87 ce 6f ba d1 2a 10 &#8230;..jd&#8230;.o..*.<br \/>0x00000080: 03 09 52 12 84 7b 2e c1 8c e4 f8 b6 2e 2d ca be ..R..{&#8230;&#8230;.-..<br \/>0x00000090: be 9b 9b f8 71 de b0 71 a0 5f 78 ea 8b 7f e4 6f &#8230;.q..q._x&#8230;.o<br \/>0x000000a0: 2a 21 ec 62 d4 9a 40 66 dd d3 5d de c7 62 b0 7a *!.b..@f..]..b.z<br \/>saving to ekey.dat file<br \/>wret&gt;<br \/>&#8220;`<\/p>\n<p>Then, we make use of the predefined ektable in order to obtain the content key<br \/>value corresponding to the initial round key (EK key):<\/p>\n<p>&#8220;`<br \/>wret&gt; ekkey ekey.dat<br \/>EKKeyCmd::run<br \/>EK key<br \/>0x00000000: 7e e3 f8 8f 31 f4 d8 1d 1e c1 6a 92 4c be 7a e5 ~&#8230;1&#8230;..j.L.z.<br \/>7ffd3d109600<br \/>plaintext key<br \/>0x00000000: 04 67 05 45 10 f3 74 5c 44 9a 26 61 60 4a 93 66 .g.E..t\\D.&amp;a`J.f<br \/>wret&gt;<br \/>&#8220;`<\/p>\n<p>One can notice that value `7e` treated as index of EK table (pos 0) yields<br \/>value `04`. Similarly value `e3` for EK table (pos 1) yields `67`.<\/p>\n<p>This confirms the ability to discover plaintext value of a content key from AES<br \/>white-box crypto (more specifically from AES round key 0).<\/p>\n<p>What&#8217;s worth to mention is that the white-box crypto attack seems to work for<br \/>PlayReady binaries corresponding to same Windows OS version (not all binaries<br \/>were tested, but Win 11 binaries from Sep 2024, Aug 2024 and Oct 2024 all<br \/>worked fine though).<\/p>\n<p>Finally, we can check if the white-box AES key extracted by the sniffer can be<br \/>deciphered.<\/p>\n<p>This time, we do the test on Windows 10. The test is conducted with respect to<br \/>license data obtained for the same movie available in CANAL+ VOD (`XXXXX <br \/>XXXXXXXX`).<\/p>\n<p>First, we import the sniffed license data to the MSPR toolkit:<\/p>\n<p>&#8220;`<br \/>msprcp&gt; implicdata ..\\..\\tests\\sniffdata\\vod\\w10\\canalp_xxxxxxxxxxxxx.dat<\/p>\n<p>LICENSE<br \/>* key id: d4710165a17e7f4ab6f0973e9bb8c723<br \/>* content key: 04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br \/>* key SHA256: c37da9b9500501a429ed038a6bf037df079a69aefdbcee873f2414089d51882b<br \/>&#8220;`<\/p>\n<p>Then we import the AES rounds harvested by the sniffer to output file:<\/p>\n<p>&#8220;`<br \/>msprcp&gt; licdata -k d4710165a17e7f4ab6f0973e9bb8c723 -r rounds.dat<br \/>rounds key<br \/>0000: 8c d1 ae 6c c3 c6 8e fe 9f 02 5b 3c cd 7d 4b 4b &#8230;l&#8230;&#8230;[&lt;.}KK<br \/>0010: 7a 36 9a 99 d8 1a dc 1e 66 9f e8 2c b1 d4 13 95 z6&#8230;&#8230;f..,&#8230;.<br \/>0020: 8e 18 01 c0 f3 6b f0 3f 18 b3 6b ed 98 8f 08 33 &#8230;..k.?..k&#8230;.3<br \/>0030: e2 9d 50 36 97 70 26 8f 09 45 cb e4 17 4c 45 51 ..P6.p&amp;..E&#8230;LEQ<br \/>0040: f8 94 fc 29 e9 62 5c 20 66 a1 11 42 f7 6b d2 95 &#8230;).b..f..B.k..<br \/>0050: 08 96 3c 4e 67 72 e6 e8 87 55 71 2c f6 b8 25 3f ..&lt;Ngr&#8230;Uq,..%?<br \/>0060: 8f f8 88 eb 6e 0c e8 85 6f df 1f 2f 1f e1 bc 96 &#8230;.n&#8230;o..\/&#8230;.<br \/>0070: b6 49 9d 0d 5e c3 f3 0e b7 9a 6a a7 2e fd 50 b7 .I..^&#8230;..j&#8230;P.<br \/>0080: 27 21 13 80 ff 64 66 08 ce 78 8a 29 66 03 5c 18 &#8216;!&#8230;df..x.)f&#8230;<br \/>0090: bc f6 f6 02 92 8f 8c 92 d6 b4 47 e1 ac a2 d1 f8 &#8230;&#8230;&#8230;.G&#8230;..<br \/>00a0: 46 4d 3c b2 4e 00 92 b4 a1 af eb 68 4b ee a4 6e FM&lt;.N&#8230;&#8230;hK..n<br \/>stored to rounds.dat<br \/>&#8220;`<\/p>\n<p>Finally, we use the WRET toolkit and setup it with some PlayReady library <br \/>corresponding to Windows 10 system:<\/p>\n<p>&#8220;`<br \/>wret&gt; image w10_prlib.dll<br \/>ImageCmd::run<br \/>&#8211; Dll init w10_prlib.dll<br \/>size 10347408 bytes<br \/>base 180000000<br \/>wret&gt; prlib w10_prlib.dll<br \/>PRLib::run<br \/>wret&gt; wbsetup -r<br \/>WBSetupCmd::run<br \/>Warbird encrypted binary<br \/>### setting up Warbird for runtime analysys<br \/>&#8211; scanning for heap exec descriptors<br \/>found: 981<br \/>&#8211; scanning for segment descriptors<br \/>found: 37<br \/>&#8211; decrypting heapexec descriptors<br \/>&#8211; decrypting segment descriptors<br \/>&#8211; locating ret syscalls<br \/>total: 1023<br \/>&#8211; locating heapexec syscalls<br \/>total: 911 (WB_MOV10_B9 880, WB_MOV10_REG 10, WB_LEA10 3, WB_SHARED 18)<br \/>&#8211; adjusting code refs<br \/>total: 1470<br \/>&#8211; adjusting data refs<br \/>total: 2<br \/>&#8211; patching self LEAs<br \/>total: 981<br \/>&#8211; patching ret syscalls<br \/>&#8211; patching heapexec syscalls<br \/>&#8211; patching wb call<br \/>&#8211; patching antidebug call<br \/>&#8211; patching App Policy<br \/>&#8211; Dll init Windows.Media.dll<br \/>size 7145640 bytes<br \/>base 180000000<br \/>&#8211; patching App Model<br \/>&#8211; Dll init Windows.Storage.ApplicationData.dll<br \/>size 435248 bytes<br \/>base 180000000<br \/>&#8211; disabling thread library calls<br \/>wret&gt;<br \/>&#8220;`<br \/>We setup the XOR key for Windows 10 system too prior to setting up EK tables:<\/p>\n<p>&#8220;`<br \/>wret&gt; xorkey w10<br \/>XORKeyCmd::run<br \/>[w10]0x00000000: a7 7b ec e4 91 a8 7e bc d8 a2 c6 28 56 b1 65 b3 .{&#8230;.~&#8230;.(V.e.<br \/>0x00000010: 3a d2 57 2c f8 8b da e3 dd c7 9b e8 16 be 31 85 :.W,&#8230;&#8230;&#8230;.1.<br \/>&#8220;`<\/p>\n<p>We use `ekkey` command to obtain the plaintext value of the content key from <br \/>the whitebox crypto data file:<\/p>\n<p>&#8220;`<br \/>wret&gt; ekkey ..\\mspr_toolkit\\rounds.dat<br \/>EKKeyCmd::run<br \/>EK key<br \/>0x00000000: 8c d1 ae 6c c3 c6 8e fe 9f 02 5b 3c cd 7d 4b 4b &#8230;l&#8230;&#8230;[&lt;.}KK<br \/>plaintext key<br \/>0x00000000: 04 67 05 45 10 f3 74 5c 44 9a 26 61 60 4a 93 66 .g.E..t\\D.&amp;a`J.f<br \/>&#8220;`<\/p>\n<p>We obtained the content key, which was verified through movie decryption and<br \/>XMR crypto signature to be the correct one.<\/p>\n<p>It&#8217;s worth to mention that ektable bijection made the algebraic transformation<br \/>in use by the core AES decryption subroutine irrelevant (no need to analyse) <br \/>too:<\/p>\n<p>&#8220;`<br \/>.text:00000001806B3F55 pcmpgtb xmm5, xmm3 ; ######################<br \/>.text:00000001806B3F55 ; ##### GMUL 2<br \/>.text:00000001806B3F55 ; ######################<br \/>.text:00000001806B3F55 ;<br \/>.text:00000001806B3F55 ; xmm5 ffffffff000000ffffffff000000<br \/>.text:00000001806B3F59 pand xmm5, xmm6 ; xmm5 1b1b1b1b0000001b1b1b1b000000<br \/>.text:00000001806B3F5D paddb xmm3, xmm3 ; xmm3 ca8e4206da9e5216dc985410cc8844<br \/>.text:00000001806B3F61 test r13, r13 ; obfuscated content key<br \/>.text:00000001806B3F64 pxor xmm4, xmm4 ; =0<br \/>.text:00000001806B3F68 pxor xmm3, xmm5 ; xmm3 d195591dda9e5216c7834f0bcc8844<br \/>.text:00000001806B3F68 ; xmm3 = iv[0-f] gmul 2 #2<br \/>.text:00000001806B3F6C jz loc_1806B498E ; -&gt; error<br \/>.text:00000001806B3F72 pcmpgtb xmm4, xmm3 ; ######################<br \/>.text:00000001806B3F72 ; ##### GMUL 2<br \/>.text:00000001806B3F72 ; ######################<br \/>.text:00000001806B3F72 ;<br \/>.text:00000001806B3F72 ; xmm4 ffff0000ffff00ffff0000ffff00<br \/>.text:00000001806B3F76 xor eax, eax ; =0<br \/>.text:00000001806B3F78 pand xmm4, xmm6 ; xmm4 1b1b00001b1b001b1b00001b1b00<br \/>.text:00000001806B3F7C paddb xmm3, xmm3 ; xmm3 a22ab23ab43ca42c8e069e16981088<br \/>.text:00000001806B3F80 movdqu xmm0, xmmword ptr [r13+0A0h] ; obfuscated content key<br \/>.text:00000001806B3F80 ; round key 10 ???<br \/>.text:00000001806B3F80 ; key[a0-af].text:00000001806B3F89 pxor xmm5, xmm5 ; =0<br \/>.text:00000001806B3F8D inc eax ; =1<br \/>.text:00000001806B3F8F pxor xmm3, xmm4 ; xmm3 b931b23aaf27a42c951d9e16830b88<br \/>.text:00000001806B3F8F ; xmm3 = iv[0-f] gmul 2 #3<br \/>.text:00000001806B3F93 movd xmm7, eax ; =1 (iv increment)<br \/>.text:00000001806B3F97 pcmpgtb xmm5, xmm3 ; ######################<br \/>.text:00000001806B3F97 ; ##### GMUL 2<br \/>.text:00000001806B3F97 ; ######################<br \/>.text:00000001806B3F97 ;<br \/>&#8220;`<\/p>\n<p>### OFFLINE IMPLEMENTATION<br \/>We verified the content of ektables used across PlayReady binaries available<br \/>for Windows 10 and 11 (`tests\\xor_key\\genektable.bat` script). The tests<br \/>revealed that there are only two such tables in use, one for Windows 10 and<br \/>the other for Windows 11 systems identified by the following hashes:<\/p>\n<p>* EK table sha256 (Windows 10)<br \/>0x00000000: dd c2 07 9b d3 4d 62 87 01 19 54 7a 56 5c 0c cc &#8230;..Mb&#8230;TzV\\..<br \/>0x00000010: 35 b3 70 95 1b 5b 3a 4b 0e f8 19 d8 20 65 72 ec 5.p..[:K&#8230;. er.<\/p>\n<p>* EK table sha256 (Windows 11)<br \/>0x00000000: 43 50 4b aa dd 25 53 09 74 fb 17 c0 c6 fd 69 34 CPK..%S.t&#8230;..i4<br \/>0x00000010: 40 59 fb 1f a9 1f 28 ab d2 2e 68 26 7b 78 25 7d @Y&#8230;.(&#8230;h&amp;{x%}<\/p>\n<p>This implicates potential offline implementation of white-box AES key rounds<br \/>decryption upon their harvesting from PMP process memory (implementation that<br \/>uses two ektables, each 16*256 in size and that does not require dedicated tools<br \/>such as WRET toolkit &#8211; Warbird protection is irrelevant for the attack).<\/p>\n<p>## PRIVATE ENCRYPTION KEY DISCOVERY<br \/>So far, it has been shown that content keys could be successfully extracted <br \/>from the Protected Media Path (memory of the PMP process) either directly <br \/>(magic XOR key value) or indirectly (white-box crypto AES rounds).<\/p>\n<p>The only secret that remained unbroken so far was a private encryption identity<br \/>key.<\/p>\n<p>Below, details regarding its successful compromise are given.<\/p>\n<p>### LICENSE BLOB DECRYPTION IMPLEMENTATION<br \/>Tthe main license decryption subroutine is denoted by prsubs command (&#8220;decrypt<br \/>license blob&#8221;).<\/p>\n<p>It is a WB heap execute subroutine that takes the following arguments (accessed<br \/>through `rcx`):<\/p>\n<p>&#8220;`<br \/>.text:00000001802228CE ; args:<br \/>.text:00000001802228CE ; 1) op &#8211; 0 in our case (1 is for decryptor type==5?)<br \/>.text:00000001802228CE ; 2) ptr to encrypted key (ECC encryption key)<br \/>.text:00000001802228CE ; 3) encrypted key size (0x41 size)<br \/>.text:00000001802228CE ; 4) binary license blob (0x80 size)<br \/>.text:00000001802228CE ; 5) output for plaintext secret key ? (size 0x20)<br \/>&#8220;`<\/p>\n<p>The subroutine operation could be described as following:<br \/>1) the private ECC decryption is deobfuscated, as a result &#8220;obfuscated ECC<br \/>key&#8221; form is obtained of size 0x20<br \/>2) the nibbles of a &#8220;obfuscated ECC key&#8221; (0x40 of them) are used as index<br \/>to a &#8220;bijection&#8221; table, as a result each nibble is mapped to a new value<br \/>(from table) and &#8220;mapped ECC key&#8221; is obtained<br \/>3) the actual license decryption relies on some obfuscated ECC crypto<br \/>implementation, instead of using the private ECC key for MULTIPLY OP,<br \/>the nibble of the mapped ECC key is used as an index to ECC precalc<br \/>table that contains some precalc ECC data<\/p>\n<p>The above process is even more complex as there are multiple subroutines and<br \/>multiple bijection tables, which can be used to process the key in step 2:<\/p>\n<p>&#8220;`<br \/>wret&gt; pkdata<br \/>PKDataCmd::run<br \/>[PrvKey data]* internal decrypt 2b7cd0 bijection 8828a0<br \/>* internal decrypt 3375b0 bijection 8a2620<br \/>* internal decrypt 3b3be0 bijection 8c1ff0<br \/>* internal decrypt 3b7910 bijection 8e2190<br \/>* internal decrypt 523390 bijection 9023b0<br \/>* internal decrypt 5289d0 bijection 922430<br \/>&#8220;`<\/p>\n<p>Decision which subroutine \/ bijection table is used depends on the format of<br \/>encrypted ECC private key. The bijection is applied to each nibble of the key<br \/>in a separate way (there are separate bijection subtables for each of the 0x40<br \/>nibbles). Finally, the bijection tables were just helper tables (they were not<br \/>used directly as a map table).<\/p>\n<p>Sample ECC point precalc tables used in step 3) can be inspected with the use<br \/>of the `eccpctab` command:<\/p>\n<p>&#8220;`<br \/>wret&gt; eccpctab ..\\mspr_toolkit\\sniff.blob<br \/>ECCPrecalcTabCmd::run<br \/>[ECC point]0x00000000: 3f a0 a4 df 3a a5 4b 9b 21 19 4e 76 04 c3 49 4c ?&#8230;:.K.!.Nv..IL<br \/>0x00000010: 6d 56 e6 8a b8 b1 5c 4d 93 83 06 70 95 19 e2 de mV&#8230;.\\M&#8230;p&#8230;.<br \/>0x00000020: 65 22 f3 40 37 7e 4a 3e 60 56 ff 46 be 85 37 6e e&#8221;.@7~J&gt;`V.F..7n<br \/>0x00000030: 04 6b 8d af 37 e2 6b f2 fb fd 00 b9 cc cf a8 33 .k..7.k&#8230;&#8230;..3<br \/>[precalc point 0]0x00000000: 07 c7 29 6f 15 99 3b b1 bb 9e 44 5a 44 c8 72 0b ..)o..;&#8230;DZD.r.<br \/>0x00000010: 83 ac 30 a7 f8 13 df c9 9d 03 15 0d 8c d2 bd b9 ..0&#8230;&#8230;&#8230;&#8230;.<br \/>0x00000020: 00 00 00 00 2f 3a 24 6c 63 bc 09 e0 dd b1 73 62 &#8230;.\/:$lc&#8230;..sb<br \/>0x00000030: 36 6a 4f c6 d9 89 f5 e6 25 38 a4 6d f5 ae 44 80 6jO&#8230;..%8.m..D.<br \/>0x00000040: 17 7a 7a 41 00 00 00 00 .zzA&#8230;.<\/p>\n[precalc point 1]0x00000000: 8d b8 f5 93 a7 1c 76 d4 03 5d d8 17 5d 20 1c f7 &#8230;&#8230;v..]..] ..<br \/>0x00000010: ed ab 24 81 af 0f a6 55 b1 03 e9 09 22 34 7f 02 ..$&#8230;.U&#8230;.&#8221;4..<br \/>0x00000020: 00 00 00 00 0f 55 4a 1b 46 a6 9d 0e 8c 43 d9 54 &#8230;..UJ.F&#8230;.C.T<br \/>0x00000030: 3b 50 c9 44 19 d4 95 2b 58 1f 5c 58 13 d5 2e 1f ;P.D&#8230;+X.\\X&#8230;.<br \/>0x00000040: 3c 8b e5 e2 00 00 00 00 &lt;&#8230;&#8230;.<br \/>&#8230;<br \/>&#8220;`<\/p>\n<p>To make things even more obscure, what looked like ECC points weren&#8217;t them (or<br \/>were not available in a plaintext form):<\/p>\n<p>&#8220;`<br \/>msprcp&gt; oncurve pcpoint.dat<br \/>ECC point: pcpoint.dat<br \/>X: 7c7296f15993bb1bb9e445a44c8720b83ac30a7f813dfc99d03150d8cd2bdb9<br \/>Y: 2f3a246c63bc09e0ddb17362366a4fc6d989f5e62538a46df5ae4480177a7a41<br \/>ERROR: point is not on curve!<br \/>&#8220;`<\/p>\n<p>### KEY DISCOVERY<br \/>At first, we though that dissecting the high level ECC ops (such as add and <br \/>multiply) along their use with respect to the encrypted license blob and <br \/>precalc point is to reveal details of the crypto algorithm in use and <br \/>potentially match is with the ECC decrypt operations such as the one in use <br \/>by our code:<\/p>\n<p>&#8220;`<br \/>public static ECPoint decrypt(ECPoint encrypted[],BigInteger prv) {<br \/>ECPoint point1=encrypted[0];<br \/>ECPoint point2=encrypted[1];<\/p>\n<p>ECPoint tmp=point1.op_multiply(prv);<br \/>ECPoint negpoint=tmp.op_neg();<br \/>ECPoint plaintext=point2.op_add(negpoint);<\/p>\n<p>return plaintext;<br \/>}<br \/>&#8220;`<\/p>\n<p>But, we started to obtain some nested expressions during the analysis process:<\/p>\n<p>&#8220;`<br \/>.text:0000000180335C5D test r15d, r15d<br \/>.text:0000000180335C60 jz short loc_180335CB6<br \/>.text:0000000180335C62 lea rax, [rbp+610h+var_C0] ; OP2( OP( OP2 ( OP(P0), OP(P0) )), OP2( OP( OP2 ( OP(P0), OP(P0) )), OP2( OP(P0), OP2( OP(P0), OP( OP2 ( OP(P0), OP(P0) ))))))<br \/>.text:0000000180335C69 mov [rsp+710h+var_6C8], rax<br \/>.text:0000000180335C6E lea r9, unk_1808A56E0<br \/>.text:0000000180335C75 mov [rsp+710h+var_6D0], rbx<br \/>.text:0000000180335C7A lea rax, unk_1808A2B10<\/p>\n<p>OP2( OP( OP2 ( OP(P0), OP(P0) )), OP2( OP( OP2 ( OP(P0), OP(P0) )), OP2( OP(P0), OP2( OP(P0), OP( OP2 ( OP(P0), OP(P0) ))))))<\/p>\n<p>change OP(P) = 2P<br \/>OP2(P,Q) = P+Q<br \/>&#8220;`<\/p>\n<p>This didn&#8217;t look like a good \/ promising approach, it looked quite tedious too.<\/p>\n<p>Instead of diving \/ analyzing highly obfuscated code, bijections and custom ECC<br \/>crypto, we decided to take another approach. The following key observations <br \/>were used for that: <\/p>\n<p>1) a nibble of the &#8220;mapped ECC key&#8221; tells, which precalc ECC point to use<br \/>as a starting point for license blob processing (ECC decryption),<br \/>2) while the bijection transformation was done separately for each nibble of <br \/>the &#8220;mapped ECC key&#8221;, the output nibble value was always in the range 0-f<br \/>3) discovering plaintext representation of the &#8220;mapped ECC key&#8221; required<br \/>the knowledge of the nibble mapping.<\/p>\n<p>In order to find the nibble mapping, we decided to do the following:<\/p>\n<p>We generated ECC key with a private key denoted by a constant value set for<br \/>all of its nibbles:<\/p>\n<p>&#8220;`<br \/>msprcp&gt; genfixedkey key_01.pub 0x01<\/p>\n<p>ECC key<br \/>&#8211; prv: 1111111111111111111111111111111111111111111111111111111111111111<br \/>&#8211; pub:<br \/>X: 217e617f0b6443928278f96999e69a23a4f2c152bdf6d6cdf66e5b80282d4ed<br \/>Y: 194a7debcb97712d2dda3ca85aa8765a56f45fc758599652f2897c65306e5794<br \/>stored to key_01.pub<br \/>&#8220;`<\/p>\n<p>We then generated a license blob with a special signature \/ content key<br \/>sequence (easy to spot \/ detect):<\/p>\n<p>&#8220;`<br \/>msprcp&gt; genzlicense -k key_01.pub -o lic_key_01.blob<br \/>pubkey<br \/>0000: 02 17 e6 17 f0 b6 44 39 28 27 8f 96 99 9e 69 a2 &#8230;&#8230;D9(&#8216;&#8230;.i.<br \/>0010: 3a 4f 2c 15 2b df 6d 6c df 66 e5 b8 02 82 d4 ed :O,.+.ml.f&#8230;&#8230;<br \/>0020: 19 4a 7d eb cb 97 71 2d 2d da 3c a8 5a a8 76 5a .J}&#8230;q&#8211;.&lt;.Z.vZ<br \/>0030: 56 f4 5f c7 58 59 96 52 f2 89 7c 65 30 6e 57 94 V._.XY.R..|e0nW.<br \/>content key<br \/>0000: 48 45 4c 4c 4f 20 4d 49 43 52 4f 53 4f 46 54 21 HELLO.MICROSOFT!<br \/>0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 &#8230;&#8230;&#8230;&#8230;&#8230;.<br \/>license<br \/>0000: fb 38 63 c4 7d 48 33 83 76 25 a7 32 fe 77 6a 06 .8c.}H3.v%.2.wj.<br \/>0010: bf be b7 c2 f2 75 de 6c 8e 04 6a 3e 94 1d bb de &#8230;..u.l..j&gt;&#8230;.<br \/>0020: d6 31 aa ee a8 9b 51 ed e0 8c 26 19 6d bd 51 65 .1&#8230;.Q&#8230;&amp;.m.Qe<br \/>0030: ab 1a 3c 57 ba 2a 00 3c fd 67 57 16 46 a6 18 f6 ..&lt;W.*.&lt;.gW.F&#8230;<br \/>0040: c1 3f 41 85 3e e5 15 b9 59 53 f1 1f 75 e1 0a 07 .?A.&gt;&#8230;YS..u&#8230;<br \/>0050: ec c6 4a 24 6e 48 71 c2 35 af 3e 47 83 7d db c1 ..J$nHq.5.&gt;G.}..<br \/>0060: c8 0b 58 0e 88 2e 13 54 b6 50 e1 e5 73 d5 90 de ..X&#8230;.T.P..s&#8230;<br \/>0070: 23 e3 94 01 dc 65 3d 11 db 80 63 85 04 2f 2f 03 #&#8230;.e=&#8230;c..\/\/.<br \/>stored to lic_key_01.blob<br \/>&#8220;`<\/p>\n<p>In the next step, we setup the bijection tables, so that they implicated the<br \/>use of ECC precalc point at given predefined index (0 in our case):<\/p>\n<p>&#8220;`<br \/>wret&gt; pkdata -k 0x00<br \/>PKDataCmd::run<br \/>setting up bijection for key:<br \/>0x00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 &#8230;&#8230;&#8230;&#8230;&#8230;.<br \/>0x00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 &#8230;&#8230;&#8230;&#8230;&#8230;.<br \/>&#8220;`<\/p>\n<p>Finally, we tried to decrypt the known license blob with the use of an unknown<br \/>private key:<\/p>\n<p>&#8220;`<br \/>wret&gt; declicense decdata\\lic_key_01.blob -k 0C86330B0E98CD7C586F336088DAFA0E.enc.prv<br \/>DecLicenseCmd::run<br \/>DECRYPT LICENSE<br \/>keydata<br \/>0x00000000: 4c 33 c6 8e 0e f1 b6 f1 0c d5 31 6b 40 94 aa 68 L3&#8230;&#8230;..1k@..h<br \/>0x00000010: 32 cc 68 1b 00 3b fc 65 8b c4 3c e3 cb 62 de fc 2.h..;.e..&lt;..b..<br \/>0x00000020: 11 ef 51 7b 92 73 a1 84 24 ac 71 33 cf 76 d3 05 ..Q{.s..$.q3.v..<br \/>0x00000030: 44 2d 4e 12 79 3f 3f 09 7a 4e 4d 51 ac 78 a7 3c D-N.y??.zNMQ.x.&lt;<br \/>0x00000040: 6b k<br \/>license<br \/>0x00000000: fb 38 63 c4 7d 48 33 83 76 25 a7 32 fe 77 6a 06 .8c.}H3.v%.2.wj.<br \/>0x00000010: bf be b7 c2 f2 75 de 6c 8e 04 6a 3e 94 1d bb de &#8230;..u.l..j&gt;&#8230;.<br \/>0x00000020: d6 31 aa ee a8 9b 51 ed e0 8c 26 19 6d bd 51 65 .1&#8230;.Q&#8230;&amp;.m.Qe<br \/>0x00000030: ab 1a 3c 57 ba 2a 00 3c fd 67 57 16 46 a6 18 f6 ..&lt;W.*.&lt;.gW.F&#8230;<br \/>0x00000040: c1 3f 41 85 3e e5 15 b9 59 53 f1 1f 75 e1 0a 07 .?A.&gt;&#8230;YS..u&#8230;<br \/>0x00000050: ec c6 4a 24 6e 48 71 c2 35 af 3e 47 83 7d db c1 ..J$nHq.5.&gt;G.}..<br \/>0x00000060: c8 0b 58 0e 88 2e 13 54 b6 50 e1 e5 73 d5 90 de ..X&#8230;.T.P..s&#8230;<br \/>0x00000070: 23 e3 94 01 dc 65 3d 11 db 80 63 85 04 2f 2f 03 #&#8230;.e=&#8230;c..\/\/.<br \/>target 7ffd3c6528a0<br \/>wb res: 0 call res 0<br \/>output key<br \/>0x00000000: ef 3e a0 a8 de 88 33 f5 9b f0 89 7b 19 f7 31 92 .&gt;&#8230;.3&#8230;.{..1.<br \/>0x00000010: 3a d2 57 2c f8 8b da e3 dd c7 9b e8 16 be 31 85 :.W,&#8230;&#8230;&#8230;.1.<br \/>signature key<br \/>0x00000000: 48 45 4c 4c 4f 20 4d 49 43 52 4f 53 4f 46 54 21 HELLO MICROSOFT!<br \/>content key<br \/>0x00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 &#8230;&#8230;&#8230;&#8230;&#8230;.<br \/>&#8220;`<\/p>\n<p>The decryption worked fine (special signature \/ content key sequence was <br \/>obtained).<\/p>\n<p>The side effect of the above was the knowledge that:<br \/>&#8211; value 0x0 of the &#8220;mapped ECC key&#8221; nibble corresponded to real nibble value <br \/>0x01<\/p>\n<p>The more interesting case to illustrate is for ECC key generated with a private<br \/>key denoting nibble value 0x2:<\/p>\n<p>&#8220;`<br \/>msprcp&gt; genfixedkey key_02.pub 0x02<br \/>ECC key<br \/>&#8211; prv: 2222222222222222222222222222222222222222222222222222222222222222<br \/>&#8211; pub:<br \/>X: d65a93977caa3d1b081852ff57a79e465f1660577304baead505dd3a48589cf3<br \/>Y: 50185e895372df6221ea3a137557e473fddb6755f05bd507c3c533fce9c91285<br \/>stored to key_02.pub<br \/>&#8220;`<\/p>\n<p>Again, license blob with a special signature \/ content key sequence was<br \/>generated:<\/p>\n<p>&#8220;`<br \/>msprcp&gt; genzlicense -k key_02.pub -o lic_key_02.blob<br \/>pubkey<br \/>0000: d6 5a 93 97 7c aa 3d 1b 08 18 52 ff 57 a7 9e 46 .Z..|.=&#8230;R.W..F<br \/>0010: 5f 16 60 57 73 04 ba ea d5 05 dd 3a 48 58 9c f3 _..Ws&#8230;&#8230;:HX..<br \/>0020: 50 18 5e 89 53 72 df 62 21 ea 3a 13 75 57 e4 73 P.^.Sr.b!.:.uW.s<br \/>0030: fd db 67 55 f0 5b d5 07 c3 c5 33 fc e9 c9 12 85 ..gU.[&#8230;.3&#8230;..<br \/>content key<br \/>0000: 48 45 4c 4c 4f 20 4d 49 43 52 4f 53 4f 46 54 21 HELLO.MICROSOFT!<br \/>0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 &#8230;&#8230;&#8230;&#8230;&#8230;.<br \/>license<br \/>0000: 01 61 c3 d6 9a 09 33 14 7d 11 b9 85 53 7e ca f9 .a&#8230;.3.}&#8230;S&#8230;<br \/>0010: e2 cf 69 46 d3 08 a8 67 0b 01 66 83 3b 22 34 3c ..iF&#8230;g..f.;&#8221;4&lt;<br \/>0020: e8 74 56 ad 7e f9 bd 1a 3d 85 6f 4a a7 be 47 2c .tV&#8230;..=.oJ..G,<br \/>0030: ae 2d 85 5b 6c 1f 36 8e 36 c1 b4 1d ba 6d 7a 58 .-.[l.6.6&#8230;.mzX<br \/>0040: c4 a1 29 fc 6b 59 28 4a 6e a2 f2 c5 58 13 04 60 ..).kY(Jn&#8230;X&#8230;<br \/>0050: 52 b6 b2 f5 72 61 62 90 f3 f9 2b 99 ae d1 6a 40 R&#8230;rab&#8230;+&#8230;j@<br \/>0060: e0 48 0e d3 06 f9 c5 16 f7 e9 21 69 93 f1 cc 69 .H&#8230;&#8230;..!i&#8230;i<br \/>0070: d4 f6 25 29 e1 d1 6f 8b de 3a e9 44 ca 6b f3 7b ..%)..o..:.D.k.{<br \/>stored to lic_key_02.blob<br \/>&#8220;`<\/p>\n<p>And an attempt to decrypt the known license blob with the use of an unknown<br \/>private key was made starting with a bijection table implicating the use of<br \/>ECC precalc point at index 1:<\/p>\n<p>&#8220;`<br \/>wret&gt; pkdata -k 0x01<br \/>PKDataCmd::run<br \/>setting up bijection for key:<br \/>0x00000000: 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 &#8230;&#8230;&#8230;&#8230;&#8230;.<br \/>0x00000010: 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 &#8230;&#8230;&#8230;&#8230;&#8230;.<\/p>\n<p>wret&gt; declicense decdata\\lic_key_02.blob -k 0C86330B0E98CD7C586F336088DAFA0E.enc.prv<br \/>DecLicenseCmd::run<br \/>DECRYPT LICENSE<br \/>keydata<br \/>0x00000000: 4c 33 c6 8e 0e f1 b6 f1 0c d5 31 6b 40 94 aa 68 L3&#8230;&#8230;..1k@..h<br \/>0x00000010: 32 cc 68 1b 00 3b fc 65 8b c4 3c e3 cb 62 de fc 2.h..;.e..&lt;..b..<br \/>0x00000020: 11 ef 51 7b 92 73 a1 84 24 ac 71 33 cf 76 d3 05 ..Q{.s..$.q3.v..<br \/>0x00000030: 44 2d 4e 12 79 3f 3f 09 7a 4e 4d 51 ac 78 a7 3c D-N.y??.zNMQ.x.&lt;<br \/>0x00000040: 6b k<br \/>license<br \/>0x00000000: 01 61 c3 d6 9a 09 33 14 7d 11 b9 85 53 7e ca f9 .a&#8230;.3.}&#8230;S~..<br \/>0x00000010: e2 cf 69 46 d3 08 a8 67 0b 01 66 83 3b 22 34 3c ..iF&#8230;g..f.;&#8221;4&lt;<br \/>0x00000020: e8 74 56 ad 7e f9 bd 1a 3d 85 6f 4a a7 be 47 2c .tV.~&#8230;=.oJ..G,<br \/>0x00000030: ae 2d 85 5b 6c 1f 36 8e 36 c1 b4 1d ba 6d 7a 58 .-.[l.6.6&#8230;.mzX<br \/>0x00000040: c4 a1 29 fc 6b 59 28 4a 6e a2 f2 c5 58 13 04 60 ..).kY(Jn&#8230;X..`<br \/>0x00000050: 52 b6 b2 f5 72 61 62 90 f3 f9 2b 99 ae d1 6a 40 R&#8230;rab&#8230;+&#8230;j@<br \/>0x00000060: e0 48 0e d3 06 f9 c5 16 f7 e9 21 69 93 f1 cc 69 .H&#8230;&#8230;..!i&#8230;i<br \/>0x00000070: d4 f6 25 29 e1 d1 6f 8b de 3a e9 44 ca 6b f3 7b ..%)..o..:.D.k.{<br \/>target 7ffd3c6528a0<br \/>wb res: 0 call res 0<br \/>output key<br \/>0x00000000: e3 f4 3b d3 77 18 ec 2f 75 88 94 a4 df 00 06 61 ..;.w..\/u&#8230;&#8230;a<br \/>0x00000010: d9 47 af 25 07 90 23 b7 9c 8c 30 fa e3 61 4a 73 .G.%..#&#8230;0..aJs<br \/>signature key<br \/>0x00000000: 44 8f d7 37 e6 b0 92 93 ad 2a 52 8c 89 b1 63 d2 D..7&#8230;..*R&#8230;c.<br \/>content key<br \/>0x00000000: e3 95 f8 09 ff 1b f9 54 41 4b ab 12 f5 df 7b f6 &#8230;&#8230;.TAK&#8230;.{.<br \/>&#8220;`<\/p>\n<p>This time, the special sequence hasn&#8217;t been reveled. This means, that value<br \/>0x1 of the &#8220;mapped ECC key&#8221; nibble did not correspond to real nibble value 0x1.<\/p>\n<p>This should be expected (we found out that real nibble value 0x1 corresponds to<br \/>nibble 0x0 present in obfuscated private key).<\/p>\n<p>The tests conducted for bijections set to nibble values 2-d (through `pkdata` <br \/>command) were not successful. But, value 0xe triggered the match:<\/p>\n<p>&#8220;`<br \/>wret&gt; pkdata -k 0x0e<br \/>PKDataCmd::run<br \/>setting up bijection for key:<br \/>0x00000000: ee ee ee ee ee ee ee ee ee ee ee ee ee ee ee ee &#8230;&#8230;&#8230;&#8230;&#8230;.<br \/>0x00000010: ee ee ee ee ee ee ee ee ee ee ee ee ee ee ee ee &#8230;&#8230;&#8230;&#8230;&#8230;.<\/p>\n<p>wret&gt; declicense decdata\\lic_key_02.blob -k 0C86330B0E98CD7C586F336088DAFA0E.enc.prv<br \/>DecLicenseCmd::run<br \/>DECRYPT LICENSE<br \/>keydata<br \/>0x00000000: 4c 33 c6 8e 0e f1 b6 f1 0c d5 31 6b 40 94 aa 68 L3&#8230;&#8230;..1k@..h<br \/>0x00000010: 32 cc 68 1b 00 3b fc 65 8b c4 3c e3 cb 62 de fc 2.h..;.e..&lt;..b..<br \/>0x00000020: 11 ef 51 7b 92 73 a1 84 24 ac 71 33 cf 76 d3 05 ..Q{.s..$.q3.v..<br \/>0x00000030: 44 2d 4e 12 79 3f 3f 09 7a 4e 4d 51 ac 78 a7 3c D-N.y??.zNMQ.x.&lt;<br \/>0x00000040: 6b k<br \/>license<br \/>0x00000000: 01 61 c3 d6 9a 09 33 14 7d 11 b9 85 53 7e ca f9 .a&#8230;.3.}&#8230;S~..<br \/>0x00000010: e2 cf 69 46 d3 08 a8 67 0b 01 66 83 3b 22 34 3c ..iF&#8230;g..f.;&#8221;4&lt;<br \/>0x00000020: e8 74 56 ad 7e f9 bd 1a 3d 85 6f 4a a7 be 47 2c .tV.~&#8230;=.oJ..G,<br \/>0x00000030: ae 2d 85 5b 6c 1f 36 8e 36 c1 b4 1d ba 6d 7a 58 .-.[l.6.6&#8230;.mzX<br \/>0x00000040: c4 a1 29 fc 6b 59 28 4a 6e a2 f2 c5 58 13 04 60 ..).kY(Jn&#8230;X..`<br \/>0x00000050: 52 b6 b2 f5 72 61 62 90 f3 f9 2b 99 ae d1 6a 40 R&#8230;rab&#8230;+&#8230;j@<br \/>0x00000060: e0 48 0e d3 06 f9 c5 16 f7 e9 21 69 93 f1 cc 69 .H&#8230;&#8230;..!i&#8230;i<br \/>0x00000070: d4 f6 25 29 e1 d1 6f 8b de 3a e9 44 ca 6b f3 7b ..%)..o..:.D.k.{<br \/>target 7ffd3c6528a0<br \/>wb res: 0 call res 0<br \/>output key<br \/>0x00000000: ef 3e a0 a8 de 88 33 f5 9b f0 89 7b 19 f7 31 92 .&gt;&#8230;.3&#8230;.{..1.<br \/>0x00000010: 3a d2 57 2c f8 8b da e3 dd c7 9b e8 16 be 31 85 :.W,&#8230;&#8230;&#8230;.1.<br \/>signature key<br \/>0x00000000: 48 45 4c 4c 4f 20 4d 49 43 52 4f 53 4f 46 54 21 HELLO MICROSOFT!<br \/>content key<br \/>0x00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 &#8230;&#8230;&#8230;&#8230;&#8230;.<br \/>&#8220;`<\/p>\n<p>Our knowledge of the nibble mapping could be updated with a new value:<br \/>&#8211; &#8220;deobfuscated ECC key&#8221; nibble 0x0 -&gt; real nibble 0x1<br \/>&#8211; &#8220;deobfuscated ECC key&#8221; nibble 0xe -&gt; real nibble 0x2<\/p>\n<p>The whole process has been repeated for the remaining nibble values. The <br \/>following presents the final nibble mapping obtained (Windows 10 OS case):<br \/>&#8220;`<br \/>01 -&gt; 00<br \/>02 -&gt; 0e<br \/>03 -&gt; 0c<br \/>04 -&gt; 07<br \/>05 -&gt; 0b<br \/>06 -&gt; 05<br \/>07 -&gt; 09<br \/>08 -&gt; 06<br \/>09 -&gt; 04<br \/>0a -&gt; 02<br \/>0b -&gt; 0d<br \/>0c -&gt; 03<br \/>0d -&gt; 0a<br \/>0e -&gt; 0f<br \/>0f -&gt; 08<br \/>&#8220;`<\/p>\n<p>There is one mapping missing from it for value 0, which is due to the inability<br \/>to generate ECC point for a zero private key. It can be easily figured out <br \/>though:<\/p>\n<p>&#8220;`<br \/>00 -&gt; 01 (the remaining \/ unused value)<br \/>&#8220;`<\/p>\n<p>Now, we can replace nibbles of the &#8220;mapped ECC key&#8221; with real values (run it <br \/>through the mapping):<\/p>\n<p>&#8220;`<br \/>obfuscated ECC key<br \/>0x00000000: f9 dc f8 b9 5a c4 5e 4e 35 c3 b3 46 4c af d9 8a &#8230;.Z.^N5..FL&#8230;<br \/>0x00000010: 1a f5 ed 9e ab ea 5b 5a 8f 49 71 82 70 e7 0d 52 &#8230;&#8230;[Z.Iq.p..R<br \/>mapped ECC key<br \/>0x00000000: 99 17 ad 43 0c 16 69 a5 12 e3 50 d0 64 0d d2 a5 &#8230;C..i&#8230;P.d&#8230;<br \/>0x00000010: 75 95 d2 b5 8f 07 58 35 f3 3e df 44 c3 03 db ca u&#8230;..X5.&gt;.D&#8230;.<br \/>plaintext ECC key<br \/>0x00000000: 77 04 db 9c 13 08 87 d6 0a 2c 61 b1 89 1b ba d6 w&#8230;&#8230;..,a&#8230;..<br \/>0x00000010: 46 76 ba 56 fe 14 6f c6 ec c2 be 99 3c 1c b5 3d Fv.V..o&#8230;..&lt;..=<br \/>&#8220;`<\/p>\n<p>The obtained plaintext value of private ECC encryption key is correct as <br \/>indicated by the crypto check:<\/p>\n<p>&#8220;`<br \/>msprcp&gt; checkkeypair ..\\wret_toolkit\\0C86330B0E98CD7C586F336088DAFA0E.enc.plain ..\\wret_toolkit\\0C86330B0E98CD7C586F336088DAFA0E.enc.pub<br \/>KEY CHECK:<\/p>\n<p>&#8211; prv: 7704db9c130887d60a2c61b1891bbad64676ba56fe146fc6ecc2be993c1cb53d<br \/>&#8211; pub:<br \/>X: cb276f9f9f764664542319ef9cc7690f9c3be3758bd3782a8d03fba8bf9e1c6d<br \/>Y: f7101c69942c4d07d9688b610985bbd34ee85820e20cc9bca9a81eb7f659657d<br \/>KEY CHECK OK<br \/>&#8220;`<\/p>\n<p>The WRET toolkit makes it possible to automatically find the value of a private<br \/>encryption key with the use of the technique depicted above. The following<br \/>commands can be issued to accomplish that:<\/p>\n<p>&#8220;`<br \/>wret&gt; image w10_prlib.dll<br \/>wret&gt; prlib w10_prlib.dll<br \/>wret&gt; wbsetup -r<br \/>wret&gt; xorkey w10<br \/>wret&gt; handler -a prvkeyextractor<br \/>wret&gt; decenckey 0C86330B0E98CD7C586F336088DAFA0E.enc.prv -o 0C86330B0E98CD7C586F336088DAFA0E.enc.plain<br \/>&#8230;<br \/>DECRYPT LICENSE<br \/>keydata<br \/>0x00000000: 4c 33 c6 8e 0e f1 b6 f1 0c d5 31 6b 40 94 aa 68 L3&#8230;&#8230;..1k@..h<br \/>0x00000010: 32 cc 68 1b 00 3b fc 65 8b c4 3c e3 cb 62 de fc 2.h..;.e..&lt;..b..<br \/>0x00000020: 11 ef 51 7b 92 73 a1 84 24 ac 71 33 cf 76 d3 05 ..Q{.s..$.q3.v..<br \/>0x00000030: 44 2d 4e 12 79 3f 3f 09 7a 4e 4d 51 ac 78 a7 3c D-N.y??.zNMQ.x.&lt;<br \/>0x00000040: 6b k<br \/>license<br \/>0x00000000: 51 20 b1 9b 2d 44 ff 68 87 6d eb 04 97 24 7f 0f Q ..-D.h.m&#8230;$..<br \/>0x00000010: 36 6c 78 27 fb 78 f6 05 4c 0c a5 11 e5 26 1b f3 6lx&#8217;.x..L&#8230;.&amp;..<br \/>0x00000020: 6c b5 90 63 68 25 d2 5b 7a 34 29 4f e2 ec dd 29 l..ch%.[z4)O&#8230;)<br \/>0x00000030: dc 55 fe 57 ce 4b 23 9f da a0 c9 0c 00 41 95 b4 .U.W.K#&#8230;&#8230;A..<br \/>0x00000040: 08 23 fb c9 91 e1 d6 3e 10 1f 3e 52 81 85 dc 83 .#&#8230;..&gt;..&gt;R&#8230;.<br \/>0x00000050: c2 17 99 4d 5a 7d 51 bf 03 3c ce 87 9f 01 80 e2 &#8230;MZ}Q..&lt;&#8230;&#8230;<br \/>0x00000060: 9e ef c1 98 ac a6 15 1f 9f 8c dc ee 5e 99 21 27 &#8230;&#8230;&#8230;&#8230;^.!&#8217;<br \/>0x00000070: 95 76 de b4 a5 ba 5e d6 ae d7 94 48 8c 73 15 4e .v&#8230;.^&#8230;.H.s.N<br \/>target 7ffd3c6528a0<br \/>PrvKeyExtractor: decsub va 3375b0<br \/>wb res: 0 call res 0<br \/>output key<br \/>0x00000000: ef 3e a0 a8 de 88 33 f5 9b f0 89 7b 19 f7 31 92 .&gt;&#8230;.3&#8230;.{..1.<br \/>0x00000010: 3a d2 57 2c f8 8b da e3 dd c7 9b e8 16 be 31 85 :.W,&#8230;&#8230;&#8230;.1.<br \/>signature key<br \/>0x00000000: 48 45 4c 4c 4f 20 4d 49 43 52 4f 53 4f 46 54 21 HELLO MICROSOFT!<br \/>content key<br \/>0x00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 &#8230;&#8230;&#8230;&#8230;&#8230;.<br \/>[BIJECTION]1 -&gt; 0<br \/>0 -&gt; 1<br \/>a -&gt; 2<br \/>c -&gt; 3<br \/>9 -&gt; 4<br \/>6 -&gt; 5<br \/>8 -&gt; 6<br \/>4 -&gt; 7<br \/>f -&gt; 8<br \/>7 -&gt; 9<br \/>d -&gt; a<br \/>5 -&gt; b<br \/>3 -&gt; c<br \/>b -&gt; d<br \/>2 -&gt; e<br \/>e -&gt; f<br \/>[MAP]0 -&gt; 1<br \/>1 -&gt; 0<br \/>2 -&gt; a<br \/>3 -&gt; c<br \/>4 -&gt; 9<br \/>5 -&gt; 6<br \/>6 -&gt; 8<br \/>7 -&gt; 4<br \/>8 -&gt; f<br \/>9 -&gt; 7<br \/>a -&gt; d<br \/>b -&gt; 5<br \/>c -&gt; 3<br \/>d -&gt; b<br \/>e -&gt; 2<br \/>f -&gt; e<br \/>restoring original bijections<br \/>DECRYPT LICENSE<br \/>keydata<br \/>0x00000000: 4c 33 c6 8e 0e f1 b6 f1 0c d5 31 6b 40 94 aa 68 L3&#8230;&#8230;..1k@..h<br \/>0x00000010: 32 cc 68 1b 00 3b fc 65 8b c4 3c e3 cb 62 de fc 2.h..;.e..&lt;..b..<br \/>0x00000020: 11 ef 51 7b 92 73 a1 84 24 ac 71 33 cf 76 d3 05 ..Q{.s..$.q3.v..<br \/>0x00000030: 44 2d 4e 12 79 3f 3f 09 7a 4e 4d 51 ac 78 a7 3c D-N.y??.zNMQ.x.&lt;<br \/>0x00000040: 6b k<br \/>license<br \/>0x00000000: 51 20 b1 9b 2d 44 ff 68 87 6d eb 04 97 24 7f 0f Q ..-D.h.m&#8230;$..<br \/>0x00000010: 36 6c 78 27 fb 78 f6 05 4c 0c a5 11 e5 26 1b f3 6lx&#8217;.x..L&#8230;.&amp;..<br \/>0x00000020: 6c b5 90 63 68 25 d2 5b 7a 34 29 4f e2 ec dd 29 l..ch%.[z4)O&#8230;)<br \/>0x00000030: dc 55 fe 57 ce 4b 23 9f da a0 c9 0c 00 41 95 b4 .U.W.K#&#8230;&#8230;A..<br \/>0x00000040: 08 23 fb c9 91 e1 d6 3e 10 1f 3e 52 81 85 dc 83 .#&#8230;..&gt;..&gt;R&#8230;.<br \/>0x00000050: c2 17 99 4d 5a 7d 51 bf 03 3c ce 87 9f 01 80 e2 &#8230;MZ}Q..&lt;&#8230;&#8230;<br \/>0x00000060: 9e ef c1 98 ac a6 15 1f 9f 8c dc ee 5e 99 21 27 &#8230;&#8230;&#8230;&#8230;^.!&#8217;<br \/>0x00000070: 95 76 de b4 a5 ba 5e d6 ae d7 94 48 8c 73 15 4e .v&#8230;.^&#8230;.H.s.N<br \/>target 7ffd3c6528a0<br \/>PrvKeyExtractor: decsub va 3375b0<br \/>wb res: 0 call res 0<br \/>output key<br \/>0x00000000: 24 18 81 4e 85 b3 05 88 20 26 6e ba d9 cd 3b af $..N&#8230;. &amp;n&#8230;;.<br \/>0x00000010: 12 4f 4e 16 6d af 41 de 46 2e 74 0a 0a 17 71 7d .ON.m.A.F.t&#8230;q}<br \/>signature key<br \/>0x00000000: 83 63 6d aa 14 1b 7b 34 f8 84 a8 92 8f 7c 5e 1c .cm&#8230;{4&#8230;..|^.<br \/>content key<br \/>0x00000000: 28 9d 19 3a 95 24 9b 3d 9b e9 ef e2 1c a9 40 f8 (..:.$.=&#8230;&#8230;@.<br \/>obfuscated ECC key<br \/>0x00000000: f9 dc f8 b9 5a c4 5e 4e 35 c3 b3 46 4c af d9 8a &#8230;.Z.^N5..FL&#8230;<br \/>0x00000010: 1a f5 ed 9e ab ea 5b 5a 8f 49 71 82 70 e7 0d 52 &#8230;&#8230;[Z.Iq.p..R<br \/>mapped ECC key<br \/>0x00000000: 99 17 ad 43 0c 16 69 a5 12 e3 50 d0 64 0d d2 a5 &#8230;C..i&#8230;P.d&#8230;<br \/>0x00000010: 75 95 d2 b5 8f 07 58 35 f3 3e df 44 c3 03 db ca u&#8230;..X5.&gt;.D&#8230;.<br \/>plaintext ECC key<br \/>0x00000000: 77 04 db 9c 13 08 87 d6 0a 2c 61 b1 89 1b ba d6 w&#8230;&#8230;..,a&#8230;..<br \/>0x00000010: 46 76 ba 56 fe 14 6f c6 ec c2 be 99 3c 1c b5 3d Fv.V..o&#8230;..&lt;..=<br \/>saving to 0C86330B0E98CD7C586F336088DAFA0E.enc.plain file<br \/>&#8220;`<\/p>\n<p>It&#8217;s worth to mention that a use of a brute force approach to find out the <br \/>nibble mapping would require checking 16! (20922789888000) combinations as<br \/>there are 16 possible values for the nibble that are to be mapped (ordered):<\/p>\n","protected":false},"excerpt":{"rendered":"<p>&#8220;`# Microsoft Warbird and PMP security research# (c) Security Explorations 2008-2024 Poland# (c) AG Security Research 2019-2024 Poland&#8220;` &#8220;&#8230;If you decide to make it public&#8230;, stress the fact that it is not asecurity issue in PlayReady or any Microsoft technology; it&#8217;s a securityissue in the STB&#8221; &#8211; Microsoft, Nov 2022 # INTRODUCITONThis package presents security &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-60595","post","type-post","status-publish","format-standard","hentry","category-vulnerability"],"_links":{"self":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/60595","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/comments?post=60595"}],"version-history":[{"count":0,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/60595\/revisions"}],"wp:attachment":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/media?parent=60595"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/categories?post=60595"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/tags?post=60595"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}