<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Elastic Security Labs - Articles by Christophe Alladoum</title>
        <link>https://www.elastic.co/security-labs</link>
        <description>Trusted security news &amp; research from the team at Elastic.</description>
        <lastBuildDate>Fri, 07 Aug 2026 18:22:44 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Elastic Security Labs - Articles by Christophe Alladoum</title>
            <url>https://www.elastic.co/security-labs/assets/security-labs-thumbnail.png</url>
            <link>https://www.elastic.co/security-labs</link>
        </image>
        <copyright>© 2026. elasticsearch B.V. All Rights Reserved</copyright>
        <item>
            <title><![CDATA[Ransomware in the honeypot: how we capture keys with sticky canary files]]></title>
            <link>https://www.elastic.co/security-labs/ransomware-in-the-honeypot-how-we-capture-keys</link>
            <guid>ransomware-in-the-honeypot-how-we-capture-keys</guid>
            <pubDate>Fri, 23 Feb 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[This article describes the process of capturing encryption keys from ransomware using Elastic Defend ransomware protection.]]></description>
            <content:encoded><![CDATA[<h2>TL;DR</h2>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image12.png" alt="Source: https://twitter.com/DebugPrivilege/status/1716890625864564796" /></p>
<p>At Elastic, we have bi-annual ON Weeks, where engineers break into “hack-a-thon” teams to tackle a technical challenge voted on by the team. This article presents the outcome of <a href="https://www.elastic.co/security-labs/deep-dive-into-the-ttd-ecosystem">yet another</a> Elastic ON Week, where we delved into an innovative application of Elastic Endpoint ransomware protection. Our research used our existing ransomware canary protection, deployed since 7.14, to generate memory snapshots (i.e., data collections that record process information) of the process(es) identified as ransomware. Through analysis of these snapshots, our research illustrated how we could recover critical information for the forensics process and even encryption keys, allowing for complete decryption.</p>
<p>This process memory snapshotting mechanism was added starting with Elastic Defend 8.11, allowing DFIR teams to find memory dumps of ransomware flagged by our ransomware protection, all within Elastic Endpoint’s secure installation folder (by default, <code>$ElasticInstallPath\Endpoint\cache\RansomwareDumps</code>).</p>
<h2>Introduction</h2>
<p>In 2024, we don't need to explain what ransomware is or the multibillion-dollar industry it's become or explain how even companies with unlimited budgets struggle to contain or prevent it. These adversaries are mature and efficient, often outpacing security functions like forensic and malware analysis.</p>
<h3>Current state of protection</h3>
<p>Thankfully, over the years, AVs/EDRs have become increasingly better at detecting and preventing ransomware. Among the most common existing mitigations, we find:</p>
<ul>
<li>Static and dynamic detection by signatures: this is usually performed at various levels (through hashes at a file or ELF/PE section level) and file activity (write access to files with high entropy changes) has the advantage of being easily and rapidly implemented, but are also likely to generate false positives</li>
<li>Reverse engineering: Reversing binaries can expose new ways to interfere with execution, as malware authors implement OS-level fail-safes (for instance, through Mutant objects) and/or network fail-safes (like WANNACRY)</li>
<li>Recovery backups: These are not always thoroughly tested, and even if they’re working there is a risk of data loss between the last backup and the moment of infection</li>
<li>Shadow copies: Somewhat similar to recovery backups, ransomware usually actively locates and attempts to destroy them prior to encrypting files on a system</li>
<li>High entropy and rapid file change: This approach is purely experimental and attempts to detect drastic changes in the file content as an indicator of encryption, however, this is also very false positive (FP) prone</li>
<li>Last cryptography weakness: By far the most complex mitigation, as it requires reverse engineering and cryptographic knowledge, but also luck as adversaries hope that the author rolls their own crypto API (see Elastic's Mark Mager <a href="https://youtu.be/0TF9NLsGCHA">2019 DEFCON talk</a> for some examples); this approach can’t work against modern OS native cryptographic APIs as long as they’re properly implemented according to documentation</li>
</ul>
<h3>How ransomware (usually) works, and why it matters</h3>
<p>It is imperative that we know both what we're protecting against and how it internally operates to be effective. This diverse nature underlines that there may never be a universal solution to combat all ransomware strains. Understanding this diversity also emphasizes the importance of our technique, which provides significant insights about ransomware.</p>
<p>From a high level, the sequence of actions that ransomware executes is usually summarized as such:</p>
<ol>
<li><strong>Delivery</strong>: this can be done in several ways, from social engineering to 0-day/1-day vulnerability exploitation. This approach can also rely on weak passwords to remotely infect targets.</li>
<li><strong>C2 Communication</strong>: once the execution starts, the ransomware may communicate with the C2 to exchange configuration and share information about the victim. This step can also leave room for the C2 to have a kill switch in place, preventing further infection</li>
<li><strong>Encryption</strong>: after establishing a cryptographic context, the process recursively browses the file system, looks for files with specific extensions, and encrypts them.</li>
<li><strong>Extortion</strong>: after sharing the decryption keys with the C2, the ransomware will drop a ransom note and (usually very visibly) notify the infected user of its actions and ways to obtain the decryption key. At that point, all cryptographic context allowing recovery may already be lost</li>
<li><strong>Propagation</strong>: if possible, the ransomware may try to infect more systems automatically.</li>
</ol>
<p>However, looking at it at a lower level reveals that ransomware operates quite uniquely: for example, focusing on the delivery step, the notorious <a href="https://www.cisa.gov/sites/default/files/FactSheets/NCCIC%20ICS_FactSheet_WannaCry_Ransomware_S508C.pdf">WANNACRY ransomware</a> spread via a vulnerability in the Windows operating system, known as <a href="https://arstechnica.com/information-technology/2017/04/nsa-leaking-shadow-brokers-just-dumped-its-most-damaging-release-yet/">EternalBlue</a>; whereas <a href="https://malpedia.caad.fkie.fraunhofer.de/details/win.lockbit">LOCKBIT</a> variants tend to infect using phishing emails, exploit kits, or by leveraging compromised Remote Desktop Protocol (RDP) credentials.</p>
<p>During this research, it was mostly the 3rd step that interested us as it is usually where detection and prevention can be most effective, such as with our canary protection.</p>
<h3>Understanding the Canary files feature in Elastic Endpoint</h3>
<p>Originating in Elastic 7.14, Elastic Endpoint ransomware protection uses <a href="https://www.elastic.co/blog/deterring-ransomware-for-state-and-local-government">canary files</a> with the purpose of attempting to honeypot ransomware by (over-)writing some specific files. This provides a high confidence indicator that the culprit process is attempting to encrypt all files.</p>
<p>A canary file acts and looks exactly like any other file - it can have valid content (DOCX, PDF, etc.), hidden, or marked as a system file to avoid user tampering. However, canary files cannot be “fingerprinted” and avoided by ransomware. All of these factors lead to a robust indicator for ransomware access.</p>
<p>Even though canary files are very successful in providing indicators for ransomware, it is hard to be certain on Windows systems that no file has been encrypted <em>before</em> the detection (and, if wanted, termination) occurs. This is not a product defect, it is due to the very structure of how MiniFilters work on Windows. Therefore, even though the attack is thwarted, some files may have been encrypted. Worse, if the process is terminated, the possibility of retrieving the original content may be completely lost.</p>
<p>And this is where our ON Week research began…</p>
<h2>Extending our canary protection to generate process snapshots</h2>
<h3>The basic underlying concept</h3>
<p>The idea behind this first research was as follows:</p>
<ul>
<li>At the kernel level, detect write access attempts to a file with a specific name (our canary)</li>
<li>From userland, generate a process dump of the culprit process attempting the write operation and signal the driver to continue execution as designed</li>
<li>Analyze process dumps</li>
</ul>
<p>With ON Week being limited to one week, this is the initial time frame we had for developing a prototype.</p>
<h3>Implementation</h3>
<h4>In kernel land</h4>
<p>Developing a MiniFilter driver to monitor write access to files with specific names went relatively easily following the well-documented <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/_ifsk/">MiniFilter API documentation</a>:</p>
<ol>
<li>Declare the filter table containing the callbacks we want to install, one for write access when invoking <code>NtWriteFile()</code>, and another for when attempting to write to a mapped section</li>
</ol>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image19.png" alt="Registering MiniFilter callbacks for file and section writes" /></p>
<ol start="2">
<li>Create and register the filter, including the file name pattern to monitor and start filtering:
<img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image5.png" alt="Declaring a filename pattern to inspect for the MiniFilter driver" /></li>
</ol>
<p><em>Image 2: Declaring a filename pattern to inspect for the MiniFilter driver</em>
Once our filter is registered to the Filter Manager, write accesses will go through our driver’s callbacks when specific syscalls are triggered: by <code>NtWriteFile</code> when a process attempts to write a buffer to a file, or by <code>NtCreateSection()</code> when a process to create a section with file-backed mappings with write access (<code>SECTION_MAP_WRITE</code>)</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image16.png" alt="Suspending process upon write access detection to the canary file" /></p>
<p>As we can see either action will result in the invoking process being suspended (call to our function <code>SuspendProcessById</code>) allowing a userland process to snapshot its memory. The following video summarizes all those steps:</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image2.png" alt="Source: https://youtu.be/U0vCHzN-69w" /></p>
<h3>In user land</h3>
<p>Generating memory dumps is a robust mechanism well anchored into Windows and a significant part of its Error Reporting mechanism - or <a href="https://learn.microsoft.com/en-us/windows/win32/wer/windows-error-reporting">WER</a>. Through simple and explicit API calls, like <a href="https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/nf-minidumpapiset-minidumpwritedump"><code>MiniDumpWriteDump</code></a> any user or program may dump (if permission permits) the complete memory layout and content of a target process, along with more information depending on flags passed during invocation such as:</p>
<ul>
<li>handle information</li>
<li>thread information</li>
<li>unloaded module details and more</li>
</ul>
<p>A complete reference list of available types can be consulted <a href="https://github.com/Skulltrail192/One-Core-Api/blob/76729f2108c2afca24d89efc92b814a07b92a62e/dll/win32/dbghelp/compat.h#L914-L931">here</a>.</p>
<p>We decided to use memory dumps, designed for debugging software, to extend our ransomware protection feature's existing canary file capabilities. When ransomware is detected, we generate a complete memory dump before the process is terminated. Using memory dumps against malware has tremendous advantages, including:</p>
<ul>
<li>Revealing the process memory layout, which is particularly useful when packing has obscured the memory regions</li>
<li>Disclosing all memory contents of the process as it is running, including unwiped memory regions since Windows does not immediately erase memory for performance reasons</li>
<li>Providing stable and safe ways to experiment against malware through emulation</li>
</ul>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image11.png" alt="Creating the memory dump from the user-mode process" /></p>
<p>Very quickly, we had a stable and reliable way to detect canary write access and generate complete memory dumps of the ransomware triggering them. Due to time constraints, we selected two popular families to test the analysis phase of our project: NOTPETYA and WANNACRY.</p>
<p>The prototype code can be found <a href="https://github.com/calladoum-elastic/canary-driver">here</a> and is not intended for production use. Please experiment at your own risk, using non-production systems.</p>
<h3>Real-life examples</h3>
<h4>Recovering keys from process runtime: the case of NOTPETYA</h4>
<p>Why NOTPETYA? It was a good first candidate because it encrypts all files with one random session key. It also uses strong cryptography:</p>
<ul>
<li>RSA-1024 for the host-level asymmetric encryption key</li>
<li>A unique AES-128 CBC key used for encrypting the files</li>
</ul>
<p>Using the driver and agent crafted above, we could easily have NOTPETYA (SHA1 <a href="https://www.virustotal.com/gui/file/027cc450ef5f8c5f653329641ec1fed91f694e0d229928963b30f6b0d7d3a745"><code>027cc450ef5f8c5f653329641ec1fed91f694e0d229928963b30f6b0d7d3a745</code></a>) run in a contained environment and get a process minidump at a very predictable runtime location.</p>
<p>Our current design causes the driver to capture the writes synchronously, so we know exactly where we are in the process runtime when analyzing dump files. However, we still needed some reverse engineering to learn exactly how the session keys were generated.</p>
<p>Reversing this NOTPETYA DLL proved to be straightforward, which helped us move quickly:</p>
<ul>
<li>After some initial checks, the DLL attempts to iterate through all the possible drive letters, and for each match (i.e., the letter - such as <code>C:\</code> exists) a <code>0x20</code> thread context will be created to proceed with the encryption</li>
</ul>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image4.png" alt="Reversing NOTPETYA encryption steps" /></p>
<ul>
<li>Each thread initializes its own cryptographic context using the Microsoft CryptoAPI; we  note the use of AES-CBC 128 bits</li>
</ul>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image14.png" alt="Confirming NOTPETYA’s use of AES 128 CBC" /></p>
<ul>
<li>Encrypts the files recursively (with a maximum recursion level of 15), dropping the ransom message and destroying the cryptographic context</li>
</ul>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image10.png" alt="NOTPETYA recursive cryptographic context mechanism" /></p>
<ul>
<li>The file encryption itself is performed using file-backed mappings to overwrite files of specifically targeted extensions:</li>
</ul>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image20.png" alt="NOTPETYA file encryption using key from the global context" /></p>
<p>This leaves us with a very basic stack-based structure for the context:</p>
<pre><code>c
struct _THREAD_CONTEXT { /* sizeof=0x20, align=0x4, mappedto_50) */
  /* 00000000 */ WORD lpswzRootPathName[4];
  /* 00000008 */ HANDLE hProvider;
  /* 0000000C */ PVOID field_C;
  /* 00000010 */ LPVOID pBase64Data;
  /* 00000014 */ HCRYPTPROV hKey;
  /* 00000018 */ DWORD field_18;
  /* 0000001C */ HANDLE hFile;
};
</code></pre>
<p>Equipped with that knowledge, we could explore further in the dump. Since we know write accesses were made using <code>kernel32!CreateFileMapping</code>, this means <code>ntdll!NtCreateSection</code> is called, and we can isolate the active thread that triggered the syscall to the canary file:</p>
<pre><code>dx @$curprocess.Threads.Where( t =&gt; t.Stack.Frames.First().ToDisplayString().Contains(&quot;NtCreateSection&quot;) )
</code></pre>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image7.png" alt="Retrieving the active thread in the memory dump" /></p>
<p>As explained previously, we’ve isolated the context session and know it’s located in the stack. From the base pointer to the session context, we can retrieve the cryptographic context from the context structure member <code>_THREAD_CONTEXT.hKey</code> located at offset 0x14.</p>
<pre><code>0:007:x86&gt; dx @$curthread.Stack.Frames[3].Attributes.FrameOffset + 0x10
@$curthread.Stack.Frames[3].Attributes.FrameOffset + 0x10 : 0x518d210
0:007:x86&gt; dps poi(0x518d210) l6
004859a0  003a0043
004859a4  0000005c
004859a8  00538418
004859ac  00000000
004859b0  04060550 
004859b4  0048fc48   &lt;&lt;&lt; hKey
0:007:x86&gt; dps 0048fc48 
0048fc48  74a850c0 rsaenh!CPGenKey
0048fc4c  74a9ad90 rsaenh!CPDeriveKey
0048fc50  74a886c0 rsaenh!CPDestroyKey
0048fc54  74a9c770 rsaenh!CPSetKeyParam
0048fc58  74a898c0 rsaenh!CPGetKeyParam
0048fc5c  74a84c40 rsaenh!CPExportKey
0048fc60  74a86290 rsaenh!CPImportKey
0048fc64  74a99880 rsaenh!CPEncrypt
0048fc68  74a8a500 rsaenh!CPDecrypt
0048fc6c  74a9b5c0 rsaenh!CPDuplicateKey
0048fc70  00538418 
0048fc74  e3155764 &lt;&lt;&lt; hCryptKey
0048fc78  22222222
[...]
</code></pre>
<p>The crypto context structures are not made publicly accessible by Microsoft but have been <a href="https://forums.codeguru.com/showthread.php?79163-Structure-of-HCRYPTKEY-Data&amp;s=b0a1fb3f896437fc13727105e44628d6&amp;p=2234957#post2234957">reverse-engineered</a></p>
<pre><code>struct HCRYPTKEY
{
    void* CPGenKey;
    void* CPDeriveKey;
    void* CPDestroyKey;
    void* CPSetKeyParam;
    void* CPGetKeyParam;
    void* CPExportKey;
    void* CPImportKey;
    void* CPEncrypt;
    void* CPDecrypt;
    void* CPDuplicateKey;
    HCRYPTPROV hCryptProv;
    magic_s *magic; // XOR-ed
};
struct magic_s
{
    key_data_s *key_data;
};

struct key_data_s
{
    void *unknown; // XOR-ed pointer
    uint32_t alg;
    uint32_t flags;
    uint32_t key_size;
    void* key_bytes;
};
</code></pre>
<p>From this context, we can extract and decode the location of the AES structure, as the key is known to be <a href="https://forums.codeguru.com/showthread.php?79163-Structure-of-HCRYPTKEY-Data&amp;s=b0a1fb3f896437fc13727105e44628d6&amp;p=2234957#post2234957"><code>0xE35A172C</code></a> for 32-bit processes:</p>
<pre><code>0:007:x86&gt; ? e3155764^ 0xE35A172C
Evaluate expression: 5193800 = 004f4048

0:007:x86&gt; dps poi(004f4048 ) l5
0053cdd0  e3152844   // /* +0 */ unknown
0053cdd4  0000660e   // /* +4 */ alg
0053cdd8  00000001   // /* +8 */ flags
0053cddc  00000010   // /* +c */ key_size
0053cde0  0053ce70   // /* +10 */ key_bytes
</code></pre>
<p>From the dump, we also know the type (AES-CBC), location in memory (<code>0x053ce70</code>), and size (<code>0x10</code>) of the key. The session key can be successfully retrieved!</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image1.png" alt="NOTPETYA file encryption key, extracted from the session context in the memory dump" /></p>
<p>Not only does this allow complete decryption of all encrypted files for this process, but the astute observer would have noticed that all <a href="https://gist.github.com/calladoum-elastic/8a142ad8b20de048a0edb2ec6fde2660">those steps can be automated</a>, allowing us to create decryptors using just the generated memory dump!</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image18.png" alt="Automatically extracting session key" /></p>
<p>To see this process fully, you can watch the <a href="https://youtu.be/UCZFAV9EveQ">demo</a> and check out the <a href="https://gist.github.com/calladoum-elastic/3b733b023c237a6017b399d4c4f18d27#file-notpetya_extract_key_from_dump-py">code</a> on GitHub.</p>
<p>We can even create scripts for decryption that would apply to all machines infected with the same variant. Even though WinDbg is the tool of choice, all those steps can be completely automated, making this approach very scalable.</p>
<h3>Predicting encryption keys from the process runtime: the case of WANNACRY</h3>
<p>WANNACRY is another ransomware family we felt qualified for this experiment, as it is well-known and – most importantly for this research – used a more complex logic for file encryption:</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image8.png" alt="A description of WANNACRY’s encryption from Akbanov et al. (JTIT 2019)" /></p>
<h4>Deep dive into Windows (Pseudo-)random Number generation</h4>
<p>To encrypt files, WANNACRY uses Windows' encryption library and generates one random AES key per file by means of the high-level API function <a href="https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom"><code>advapi32!CryptGenRandom</code></a>. Each key is associated with the corresponding file, then RSA-encrypted and submitted to its C2. By design, the approach we used against NOTPETYA will not work here. WANNACRY offered us a different challenge, once again demonstrating that having a complete memory dump provides other invaluable resources.</p>
<p>Random number generation is often less random than most people think. Generating a truly random number is both challenging and expensive, and this challenge is at the heart of any cryptographic algorithm.</p>
<p>Windows (not unlike other OS) generates <a href="https://en.wikipedia.org/wiki/Pseudorandom_number_generator">random numbers in a pseudo-random way</a>. This means the random number generator derives an initial state (called a seed) with a cryptographic function (for instance, XorShift or Mersenne-Twister). One of the logical consequences of using PRNG is that knowing the state of the random generator at a moment T allows us to know precisely all random values at T+1, T+2, etc. Note that this is not a weakness as randomness is a highly complex and performance-costly operation; this approach is a great trade-off.</p>
<p>We will be taking advantage of this property to defeat WANNACRY. Knowing that WANNACRY repeatedly will call CryptGenRandom to generate the AES encryption for each file, if we have a way to know those values strictly through emulation of the minidump file, then we will also know the possible AES keys. This looks promising but may conceal several roadblocks.</p>
<p>Taking a step back, what is CryptGenRandom in the first place – what does it do? The <a href="https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom">MSDN</a> informs us that this (deprecated) function fills up a buffer with random content using a <a href="https://learn.microsoft.com/en-us/windows/desktop/SecGloss/c-gly">Cryptographic Service Provider</a>(HCRYPTPROV). Setting a breakpoint to CryptGenRandom  allows us to look under the hood with WinDbg on a Windows 11 x64. We can then easily traverse the high-level APIs and observe that <code>advapi32!CryptGenRandom</code> is a wrapper to <code>cryptsp!CryptGenRandom</code>, which in turn leads us to the <code>CPGenRandom</code> function in <code>rsaenh.dll</code>.</p>
<pre><code>0:000&gt; g
Breakpoint 9 hit
CRYPTSP!CryptGenRandom+0x29:
00007ffc`990c1699 488b8be0000000  mov     rcx,qword ptr [rbx+0E0h] ds:000001e1`38ade010=e35a16cde1cff7d0
0:000&gt; dps @rbx
000001e1`38addf30  00007ffc`987956d0 rsaenh!CPAcquireContext
000001e1`38addf38  00007ffc`987951e0 rsaenh!CPReleaseContext
000001e1`38addf40  00007ffc`98791140 rsaenh!CPGenKey
000001e1`38addf48  00007ffc`987a8f80 rsaenh!CPDeriveKey
000001e1`38addf50  00007ffc`987948a0 rsaenh!CPDestroyKey
000001e1`38addf58  00007ffc`987aaac0 rsaenh!CPSetKeyParam
[...]

0:000&gt; t
CRYPTSP!CryptGenRandom+0x3c:
00007ffc`990c16ac ff1506c50000    call    qword ptr [CRYPTSP!_guard_dispatch_icall_fptr (00007ffc`990cdbb8)] ds:00007ffc`990cdbb8={CRYPTSP!guard_dispatch_icall_nop (00007ffc`990c4d30)}

0:000&gt; r rax, rcx,rdx ,r8
rax=00007ffc987954d0 rcx=e35a16cde1cff7d0 rdx=0000000000000010 r8=00000065859bfe70

0:000&gt; .printf &quot;%y\n&quot;, @rax
rsaenh!CPGenRandom (00007ffc`987954d0)
</code></pre>
<p>When <code>CRYPTSP!CryptGenRandom</code> is invoked, the RCX register holds the pointer to the encoded crypto provider, which is XOR encoded with the magic constant <code>0xE35A172CD96214A0</code> (Remember the <code>0xE35A172C</code> magic constant we used earlier? This is its 64-bit version counterpart). Looking at <code>rsaenh!CPGenRandom</code> in IDA made clear the cryptographic provider handle serves only as a check to determine the correct validity of the context passed to the function but has no real implication about the randomness generation.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image17.png" alt="Reversing rsaenh.dll to understand the random number generation" /></p>
<p>The entire randomness generation logic is deported to the function <code>cryptbase!SystemFunction036</code>, which simply takes two arguments: the buffer to receive the random data and its length. This was great news because random number generation had no external factor an attacker could use at runtime to make the generation more complex. Going farther into the rabbit hole, we realized that <code>cryptbase!SystemFunction036</code> itself is nothing more than a light wrapper for <code>bcryptprimitives!ProcessPrng</code>, which – by the name of the function – seems to match our expectations.</p>
<p>The <code>bcryptprimitives</code> DLL is part of the <a href="https://learn.microsoft.com/en-us/windows/win32/seccng/cng-portal">Cryptographic Next Generation API</a> (CNG) and is quite complex. Fully reversing would be out-of-scope for this research, so we only focused on the parts we're interested in. First, we observed that once loaded in the process, the library initializes the process seed - either from the <a href="https://www.felixcloutier.com/x86/rdrand"><code>rdrand</code></a> instruction or from a VTL1 call to the <a href="https://learn.microsoft.com/en-us/windows/win32/procthread/isolated-user-mode--ium--processes"><code>IumKernelState</code></a> trustlet in the explicitly named <code>InitUmRootRngState</code> function. Then, it populates a random number generator state table and updates the RNG seed version state in the <code>ntdll!_KUSER_SHARED_DATA::RNGSeedVersion</code>.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image9.png" alt="Validating the cryptographic seed initialization from WinDbg" /></p>
<p>When <code>ProcessPrng</code> is invoked, the generation of the next pseudo-random number is determined by a CPU-specific state. To be precise, the processor number on which the current thread is running is used as an index to load and generate the next number. We’ll explain more later, but this will be challenging in the future. Using this state information, the next number is produced by invoking <code>AesRNGState_generate</code>, storing the result inside the buffer given in an argument.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image6.png" alt="Next random buffer generation in cryptbase.dll" /></p>
<p>This is a non-negligible issue for what we’re trying to accomplish. On multiprocessor-aware Windows (which all modern PCs are), it is hard to consistently know the processor number the thread is running on, making generation prediction impossible. However, Windows provides ways to affect the scheduler, as shown below.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image13.png" alt="Retrieving current CPU number to determine the PNRG state table entry to use" /></p>
<h4>Pseudo-Random number prediction through user-mode emulation of the memory dump</h4>
<p>Keeping in sight that to defeat WANNACRY, we need to be able to execute the function <code>cryptbase!SystemFunction036</code> directly from the memory dump. We can make this with an emulator (like QEMU or Bochs) by mapping the execution context (populating the memory layout, restoring the TEB/PEB etc.) gathered from the memory dump of the ransomware, which we did following these steps:</p>
<ol>
<li>Parse the user-mode dump to <a href="https://gist.github.com/calladoum-elastic/3b733b023c237a6017b399d4c4f18d27#file-emulate_cryptrandomgen-py-L272-L287">extract and map all the memory layout</a>; for this step, we used the Python bindings of the <a href="https://github.com/0vercl0k/udmp-parser">udmp-parser</a> library</li>
<li>Fully <a href="https://gist.github.com/calladoum-elastic/3b733b023c237a6017b399d4c4f18d27#file-emulate_cryptrandomgen-py-L291-L322">reconstruct a working memory layout in an emulator</a>, for which <a href="https://github.com/yrp604/bochscpu">bochscpu</a> along with its <a href="https://github.com/hugsy/bochscpu-python">Python bindings</a> were used</li>
<li><a href="https://gist.github.com/calladoum-elastic/3b733b023c237a6017b399d4c4f18d27#file-emulate_cryptrandomgen-py-L354-L370">Rebuild a valid thread context</a> by finding the function <code>cryptbase!SystemFunction036</code> and emulating the runtime</li>
</ol>
<p>However, we still lack the ability to predict on which CPU the thread invoking <code>cryptbase!SystemFunction036</code> will be running on, therefore, cannot accurately predict the following values returned by the function. On a single-core machine, this is not a problem as our PRNG state table will only hold one entry, and this approach was tested to work perfectly out-of-the-box. However, it fails on multi-core systems, as only the first call to <code>cryptbase!SystemFunction036</code> would return the correct random values.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image15.png" alt="Execution of cryptbase!SystemFunction036 on single core VM: different outputs" /></p>
<p>To have accurate emulation on multi-core machines, we need to know the processor number on which the next thread calling <code>cryptbase!SystemFunction036</code> will be called at runtime is fairly impossible. Two possible approaches were tested:</p>
<ol>
<li>From the dump, we have knowledge of the entire PRNG state table. Because of this, we can make the emulation script hook the function <code>ntdll!RtlGetCurrentProcessorNumberEx</code> and use it to determine the index in the random table, then have it generate all the values for a specific core. This approach proved successful but extremely tedious, especially at scale as automation would generate exponential possibilities to retrieve the correctly generated sequence.</li>
<li>Another option happens during the canary detection itself. Once the canary confirms it is ransomware, we can enforce the culprit process CPU affinity to only one CPU, whose index we can choose freely. This can be done from kernel or user mode as long as the targeted process is opened with the <a href="https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setprocessaffinitymask"><code>PROCESS_SET_INFORMATION</code></a> access right. This processor index will determine the entry taken in the <code>AesStateTable</code> array, and doing so allows us to reliably predict all future values of the PNRG via emulation.</li>
</ol>
<p><img src="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/image3.png" alt="EExecution of cryptbase!SystemFunction036 on multi-core VM with forced affinity: same output" /></p>
<p>To see the WANNACRY process in full, you can watch the <a href="https://youtu.be/uXqI0ZSqZhI">demo</a>. We also have the <a href="https://gist.github.com/calladoum-elastic/3b733b023c237a6017b399d4c4f18d27">code</a> available for review on GitHub.</p>
<p>Testing both techniques showed that it is possible to predict the future value of the PRNG with the minidump at our disposal. This would be immensely helpful against ransomware like WANNACRY, which uses Windows PRNG to generate unique AES keys for each encrypted file.</p>
<h2>Incorporating this research into the Elastic Endpoint</h2>
<p>ON Week at Elastic is a place for experimenting without constraints and often leads to great improvements for the existing Elastic solutions.</p>
<p>Process snapshot generation was added to Elastic Security in version <a href="https://www.elastic.co/blog/whats-new-elastic-security-8-11-0">8.11</a>. With protection enabled, should ransomware be detected, the endpoint will generate a complete memory process dump before resuming execution, likely leading to the ransomware process termination. We hope this simple addition can assist DFIR teams further by providing better insight into what the ransomware was attempting.</p>
<p>Recent news has shown that process memory dumps can leak a <a href="https://www.msn.com/en-us/news/technology/microsoft-china-stole-secret-key-that-unlocked-us-govt-email-from-crash-debug-dump/ar-AA1glLPJ">great amount of valuable private information</a> if made available publicly. Therefore, it must be stressed that no memory dump is ever submitted to Elastic, even with the feature enabled. The dump file is generated (and compressed) by the endpoint locally, and the resulting file is stored within Elastic’s secure installation folder (by default, <code>$ElasticInstallPath\Endpoint\cache\RansomwareDumps</code>). This way, the dump files cannot be easily tampered with by attackers but are easily accessible to forensics and incident response teams to assist them in the recovery process.</p>
<p>Let’s demonstrate this feature in action on a fresh Elastic 8.11 against NOTPETYA: <a href="https://youtu.be/d16yKWUf3dI">watch the demo</a></p>
<h2>Closing remarks</h2>
<p>This concluded our ON Week research with quite a positive outcome. Did we come up with a bulletproof solution against all ransomware? No, and such a thing likely won’t ever exist. As we’ve underlined in the introduction, ransomware exists in so many types and varieties that it probably seems impossible to have one solution for all.</p>
<p>What this research found, however, was that this approach offers a great trade-off between FP risk, system requirements, and potential outcome. There is very little risk of snapshotting the process memory should it be flagged as ransomware by the canary feature. In the case of a false positive, the computer would simply end up with a dump file in a protected location (and ZIP compression would drastically reduce the footprint on disk).</p>
<p>While this is not the perfect ransomware solution, offering a memory dump of the ransomware can boost forensic work and potentially allow teams to recover or even predict session encryption keys. Complete memory dumps can be an amazing ally in debugging and forensics because they provide an exhaustive view of how things happen at runtime. And thanks to emulation, we can confidently retrace some of the steps that lead to a compromise and hopefully fix it.</p>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/security-labs/assets/images/ransomware-in-the-honeypot-how-we-capture-keys/photo-edited-07.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Deep dive into the TTD ecosystem]]></title>
            <link>https://www.elastic.co/security-labs/deep-dive-into-the-ttd-ecosystem</link>
            <guid>deep-dive-into-the-ttd-ecosystem</guid>
            <pubDate>Wed, 30 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[This is the first in a series focused on the Time Travel Debugging (TTD) technology developed by Microsoft that was explored in detail during a recent independent research period.]]></description>
            <content:encoded><![CDATA[<p>Several times a year, Elastic Security Labs researchers get the freedom to choose and dig into projects of their liking — either alone or as a team. This time is internally referred to as “On-Week” projects. This is the first in a series focused on the <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/time-travel-debugging-overview">Time Travel Debugging</a> (TTD) technology developed by Microsoft that was explored in detail during a recent On-Week session.</p>
<p>Despite being made public for several years, awareness of TTD and its potential are greatly underrated within the infosec community. We hope this two-part series can help shed some light on how TTD can be useful for program debugging, vulnerability research and exploitation, and malware analysis.</p>
<p>This research involved first understanding the inner workings of TTD and then assessing some interesting applicable uses that can be made out of it. This post will focus on how researchers dive deep into TTD, sharing their methodology along with some interesting findings. The second part will detail the applicable use of TTD for the purpose of malware analysis and integration with Elastic Security.</p>
<h1>Background</h1>
<p><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/time-travel-debugging-overview">Time Travel Debugging</a> is a tool developed by Microsoft Research that allows users to record execution and navigate freely into the user-mode runtime of a binary. TTD itself relies on two technologies: Nirvana for the binary translation, and iDNA for the trace reading/writing process. Available since Windows 7, TTD internals were first detailed <a href="https://www.usenix.org/legacy/events/vee06/full_papers/p154-bhansali.pdf">in a publicly available paper</a>. Since then, both <a href="https://www.youtube.com/watch?v=l1YJTg_A914&amp;">Microsoft</a> and <a href="https://infocondb.org/con/recon/recon-2015/hooking-nirvana-stealthy-instrumentation-techniques-for-windows-10">independent researchers</a> have covered it in great detail. For this reason, we won’t explore the internals of both technologies in depth. Instead, Elastic researchers investigated the ecosystem — or the executables, DLLs, and drivers — that make the TTD implementation work. This led to some interesting findings about TTD, but also Windows itself, as TTD leverages some (undocumented) techniques to work as intended in special cases, such as <a href="https://docs.microsoft.com/en-us/windows/win32/services/protecting-anti-malware-services-#system-protected-process">Protected Processes</a>.</p>
<p>But why investigate TTD at all? Aside from pure curiosity, it is likely that one of the possible intended uses for the technology would be discovering bugs in production environments. When bugs are hard to trigger or reproduce, having a “record-once-replay-always” type of environment helps compensate for that difficulty, which is exactly what TTD implements when coupled with WinDbg.</p>
<p>Debugging tools such as <a href="https://apps.microsoft.com/store/detail/9PGJGD53TN86">WinDbg</a> have always been an immense source of information when reversing Windows components, as they provide additional comprehensible information, usually in plain text. Debugging tools (especially debuggers) must cooperate with the underlying operating system, which could involve debugging interfaces and/or previously undisclosed capabilities from the OS. TTD conforms to that pattern.</p>
<h1>High-level overview</h1>
<p>TTD works by first creating a recording that tracks every instruction executed by an application and stores it in a database (suffixed with .run). Recorded traces can be replayed at will using the WinDbg debugger, which on first access will index the .run file, allowing for faster navigation through the database. To be able to track execution of arbitrary processes, TTD injects a DLL responsible for recording activity on-demand which allows it to record processes by spawning them, but also may attach to an already-running process.</p>
<p>TTD is freely <a href="https://apps.microsoft.com/store/detail/9PGJGD53TN86">downloadable</a> as part of the WinDbg Preview package in the MS Store. It can be used directly from WinDbg Preview (aka WinDbgX), but is a standalone component that is located in <code>C:\Program Files\WindowsApps\Microsoft.WinDbg_&lt;version&gt;&lt;/version&gt;_&lt;arch&gt;__8wekyb3d8bbwe\amd64\ttd</code> for the x64 architecture, which we will focus on in this post. x86 and arm64 versions are also available for download in the MS Store.</p>
<p>The package consists of two EXE files (TTD.exe and TTDInject.exe) and a handful of DLLs. This research focuses on the major DLL responsible for everything not related to Nirvana/iDNA (i.e. responsible for the session management, driver communication, DLL injection, and more): ttdrecord.dll</p>
<p>_Note: Most of this research was made using two versions of the ttdrecord DLL: mostly on a 2018 version (1.9.106.0 SHA256=aca1786a1f9c96bbe1ea9cef0810c4d164abbf2c80c9ecaf0a1ab91600da6630), and early 2022 version (10.0.19041.1 SHA256=1FF7F54A4C865E4FBD63057D5127A73DA30248C1FF28B99FF1A43238071CBB5C). The older versions were found to have more symbols, which helped speed up the reverse engineering process. We then re-adapted structures and function names to the most recent version. Therefore, some of the structures explained here might not be the same if you’re trying to reproduce on more recent versions. _</p>
<h1>Examining TTD features</h1>
<h2>Command line parameters</h2>
<p>Readers should note that TTD.exe acts essentially as a wrapper to ttdrecord!ExecuteTTTracerCommandLine:</p>
<pre><code>HRESULT wmain()
{
v28 = 0xFFFFFFFFFFFFFFFEui64;
hRes = CoInitializeEx(0i64, 0);
if ( hRes &gt;= 0 )
{
ModuleHandleW = GetModuleHandleW(L&quot;TTDRecord.dll&quot;);
[...]
TTD::DiagnosticsSink::DiagnosticsSink(DiagnosticsSink, &amp;v22);
CommandLineW = GetCommandLineW();
lpDiagnosticsSink = Microsoft::WRL::Details::Make&lt;TTD::CppToComDiagnosticsSink,TTD::DiagnosticsSink&gt;(&amp;v31, DiagnosticsSink);
hRes = ExecuteTTTracerCommandLine(*lpDiagnosticsSink, CommandLineW, 2i64);
[...]
</code></pre>
<p>The final line of the code excerpt above shows a call to ExecuteTTTracerCommandLine , which takes an integer as the last argument. This argument corresponds to the desired tracing modes, which are: - 0 -&gt; FullTracingMode, - 1 -&gt; UnrestrictedTracing and - 2 -&gt; Standalone (the hardcoded mode for the public version of TTD.exe)</p>
<p>Forcing TTD to run in full-tracing mode reveals available options, which include some hidden capabilities such as process reparenting (-parent) and automatic tracing until reboot (-onLaunch) for programs and services.</p>
<p><a href="https://gist.github.com/calladoum-elastic/4666dafc789a273c35a4aedf2ed9cd9e">Dumping the complete option set</a> of TTDRecord.dll revealed interesting hidden command line options such as:</p>
<pre><code>-persistent Trace programs or services each time they are started (forever). You must specify a full path to the output location with -out.
-delete Stop future tracing of a program previously specified with -onLaunch or -persistent. Does not stop current tracing. For -plm apps you can only specify the package (-delete &lt;package&gt;) and all apps within that package will be removed from future tracing
-initialize Manually initialize your system for tracing. You can trace without administrator privileges after the system is initialized.
</code></pre>
<p>The process of setting up Nirvana requires TTD to set up the InstrumentationCallback field in the target _EPROCESS. This is achieved through the (undocumented but <a href="https://www.codeproject.com/Articles/543542/Windows-x64-System-Service-Hooks-and-Advanced-Debu">known</a>) NtSetInformationProcess(ProcessInstrumentationCallback) syscall (ProcessInstrumentationCallback, which has a value of 40). Due to the potential security implication, invoking this syscall requires elevated privileges. Interestingly, the -initialize flag also hinted that TTD could be deployed as a Windows service. Such service would be responsible for proxying tracing requests to arbitrary processes. This can be confirmed by executing it and seeing the resulting error message:</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image13.jpg" alt="Deducing TTDService.exe" /></p>
<p>Even though it <a href="https://www.virustotal.com/gui/search/TTDService.exe/files">is easy</a>to find evidence confirming the existence of TTDService.exe , the file was not provided as part of the public package, so aside from noting that TTD can run as a service, we will not cover it in this post.</p>
<h2>TTD process injection</h2>
<p>As explained, a TTD trace file can either be created from the standalone binary TTD.exe or through a service TTDService.exe (private), both of which must be run in a privileged context. However, those are just launchers and injecting the recording DLL (named TTDRecordCPU.dll) is the job of another process: TTDInject.exe.</p>
<p>TTDInject.exe is another executable noticeably larger than TTD.exe, but with a pretty simple objective: prepare the tracing session. In an overly simplified view, TTD.exe will first start the process to be recorded in a suspended state. It will then spawn TTDInject.exe, passing it all the necessary arguments to prepare the session. Note that TTDInject can also spawn the process directly depending on the tracing mode we mentioned earlier — therefore, we are describing the most common behavior (i.e. when spawned from TTD.exe).</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image17.jpg" alt="TTD.exe process metadata" /></p>
<p>TTDInject will create a thread to execute TTDLoader!InjectThread in the recorded process, which after various validations will in turn load the library responsible for recording all process activity, TTDRecordCPU.dll.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image6.jpg" alt="Using TTD to trace Notepad.exe" /></p>
<p>From that point onward, all instructions, memory accesses, exceptions triggered, or CPU states encountered during the execution will be recorded.</p>
<p>Once the general workflow of TTD was understood, it became clear that little to no manipulation is possible after the session initialization. Thus, further attention was paid to the arguments supported by ttdrecord.dll. Thanks to the C++ mangling function format, a lot of critical information can be retrieved from the function names themselves, which makes analyzing the command line argument parser relatively simple. One interesting flag that was discovered was PplDebuggingToken. That flag is hidden and only available in Unrestricted Mode.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image19.jpg" alt="Discovering PplDebuggingToken method" /></p>
<p>The existence of this flag immediately raised questions: TTD was architected first around Windows 7 and 8, and on Windows 8.1+. The concept of Protection Level was added to processes, dictating that processes can only open handles to a process with a <a href="https://www.elastic.co/blog/protecting-windows-protected-processes#Protected%20process%20light:%7E:text=a%20kernel%20driver.-,Protected%20process%20light,-">Protection Level</a> that is equal or inferior. It is a simple byte in the _EPROCESS structure in the kernel, and thus not directly modifiable from user mode.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image11.jpg" alt="Binary diff comparing TTD on Windows 8 with Windows 8.1" /></p>
<p>The values of the Protection Level byte are well known and are summarized in the table below.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image20.png" alt="Protection Level value mappings" /></p>
<p>The Local Security Authority subsystem (lsass.exe) on Windows <a href="https://docs.microsoft.com/en-us/windows-server/security/credentials-protection-and-management/configuring-additional-lsa-protection">can be configured</a> to run as Protected Process Light, which aims to limit the reach of an intruder who gains maximum privileges on a host. By acting at the kernel level, no user-mode process can open a handle to lsass, no matter how privileged.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image2.jpg" alt="Verifying LSASS protection level" /></p>
<p>But the PplDebuggingToken flag appears to suggest otherwise. If such a flag existed, it would be the dream of any pentester/red teamer: a (magic) token that would allow them to inject into protected processes and record them, dump their memory or more. The command line parser seems to imply that the content of the command flag is a mere wide-string. Could this be a PPL backdoor?</p>
<h3>Chasing after the PPL debugging token</h3>
<p>Returning to ttdrecord.dll, the PplDebuggingToken command line option is parsed and stored in a context structure along with all of the options required to create the TTD session. The value can be traced down to several locations, with an interesting one being within TTD::InitializeForAttach, whose behavior is simplified in the following pseudo-code:</p>
<pre><code>ErrorCode TTD::InitializeForAttach(TtdSession *ctx)
{
  [...]
  EnableDebugPrivilege(GetCurrentProcess()); // [1]
  HANDLE hProcess = OpenProcess(0x101040u, 0, ctx-&gt;dwProcessId);
  if(hProcess == INVALID_HANDLE_VALUE)
 {
    goto Exit;
  }
  [...]
  HMODULE ModuleHandleW = GetModuleHandleW(L&quot;crypt32.dll&quot;);
  if ( ModuleHandleW )
  pfnCryptStringToBinaryW = GetProcAddress(ModuleHandleW, &quot;CryptStringToBinaryW&quot;); // [2]

  if ( ctx-&gt;ProcessDebugInformationLength ) // [3]
  {
DecodedProcessInformationLength = ctx-&gt;ProcessDebugInformationLength;
DecodedProcessInformation = std::vector&lt;unsigned char&gt;(DecodedProcessInformationLength);
wchar_t* b64PplDebuggingTokenArg = ctx-&gt;CmdLine_PplDebugToken;
if ( *pfnCryptStringToBinaryW )
{
  if( ERROR_SUCCESS == pfnCryptStringToBinaryW( // [4]
                      b64PplDebuggingTokenArg,
                      DecodedProcessInformationLength,
                      CRYPT_STRING_BASE64,
                      DecodedProcessInformation.get(),
                      &amp;DecodedProcessInformationLength,
                      0, 0))
  {
    Status = NtSetInformationProcess( // [5]
               NtGetCurrentProcess(),
               ProcessDebugAuthInformation,
               DecodedProcessInformation.get(),
               DecodedProcessInformationLength);
  }
[...]
</code></pre>
<p>After enabling the SeDebugPrivilege flag for the current process ([1]) and obtaining a handle to the process to attach to ([2]), the function resolves an exported generic function used to perform string operations: crypt32!CryptStringToBinaryW. In this instance, it is used for decoding the base64-encoded value of the PplDebuggingToken context option if it was provided by the command line( [3], [4]). The decoded value is then used to invoke the syscall NtSetInformationProcess(ProcessDebugAuthInformation) ([5]). The token doesn’t seem to be used anywhere else, which made us scrutinize that syscall.</p>
<p>The process information class ProcessDebugAuthInformation was added in <a href="https://en.wikipedia.org/wiki/Windows_10_version_1803">RS4</a>. A quick look at ntoskrnl shows that this syscall simply passes the buffer to CiSetInformationProcess located in ci.dll, which is the Code Integrity driver DLL. The buffer is then passed to ci!CiSetDebugAuthInformation with fully controlled arguments.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image8.jpg" alt="ProcessDebugAuthInformation class" /></p>
<p>The following diagram summarizes at a high level where this happens in the execution flow of TTD.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image24.png" alt="TTD execution flow diagram" /></p>
<p>The execution flow in CiSetDebugAuthInformation is simple enough: the buffer with the base64-decoded PplDebuggingToken and its length are passed as arguments for parsing and validation to ci!SbValidateAndParseDebugAuthToken. Should the validation succeed, and after some extra validation, a handle to the process performing the syscall (remember that we’re still handling the syscall nt!NtSetInformationProcess) will be inserted in a process debug information object then stored in a global list entry.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image16.jpg" alt="SbValidateAndParseDebugAuthToken method" /></p>
<p>But how is that interesting? Because this list is only accessed in a single location: in ci!CiCheckProcessDebugAccessPolicy, and this function is reached during a NtOpenProcess syscall. And, as the name of the newly discovered flag suggested earlier, any process whose PID is located in that list would bypass the Protection Level enforcement. This was confirmed practically in a <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugging-using-kd-and-ntkd">KD</a> session by setting an access breakpoint on that list (on our version of ci.dll this was located at ci+364d8). We also <a href="https://docs.microsoft.com/en-us/windows-server/security/credentials-protection-and-management/configuring-additional-lsa-protection">enabled PPL on LSASS</a> and wrote a simple PowerShell script that would trigger a NtOpenProcess syscall:</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image12.jpg" alt="KD session output" /></p>
<p>By breaking at the call to nt!PsTestProtectedProcessIncompatibility in nt!PspProcessOpen, we can confirm that our PowerShell process attempts to target lsass.exe, which is a PPL process:</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image4.jpg" alt="Confirming our PowerShell process targets a PPL process in LSASS" /></p>
<p>Now to confirm the initial theory of what the PplDebuggingToken argument would do by forcing the return value of the call to nt!PsTestProtectedProcessIncompatibility:</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image23.jpg" alt="Testing TTD with PowerShell" /></p>
<p>We break at the instruction following the call to nt!PsTestProtectedProcessIncompatibility (which only calls CI!CiCheckProcessDebugAccessPolicy), and force the return value to 0 (as mentioned earlier a value of 1 means incompatible):</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image5.jpg" alt="Obtaining a handle to LSASS" /></p>
<p>Success! We obtained a handle to LSASS despite it being PPL, confirming our theory. Summarizing, if we can find a “valid value” (we’ll dig into that soon) it will pass the check of SbValidateAndParseDebugAuthToken() in ci!CiSetDebugAuthInformation(), and we would have a universal PPL bypass. If this sounds too good to be true, that’s mostly because it is — but confirming it requires developing a better understanding of what CI.dll is doing.</p>
<h3>Understanding Code Integrity policies</h3>
<p>Restrictions based on code integrity, such as those used by AppLocker, can be enforced through policies, which in their human readable form are XML files. There are two types of policies: base and supplemental. Examples of what base policies look like can be found in their XML format in &quot;C:\Windows\schemas\CodeIntegrity\ExamplePolicies&quot;. This is what a Base Policy looks like in its XML form (taken from &quot;C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml&quot;), which reveals most of the details we’re interested in clearly in plaintext.</p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;SiPolicy xmlns=&quot;urn:schemas-microsoft-com:sipolicy&quot;&gt;
&lt;VersionEx&gt;1.0.1.0&lt;/VersionEx&gt;
&lt;PolicyID&gt;{A244370E-44C9-4C06-B551-F6016E563076}&lt;/PolicyID&gt;
&lt;BasePolicyID&gt;{A244370E-44C9-4C06-B551-F6016E563076}&lt;/BasePolicyID&gt;
&lt;PlatformID&gt;{2E07F7E4-194C-4D20-B7C9-6F44A6C5A234}&lt;/PlatformID&gt;
&lt;Rules&gt;
&lt;Rule&gt;&lt;Option&gt;Enabled:Unsigned System Integrity Policy&lt;/Option&gt;&lt;/Rule&gt;
&lt;Rule&gt;&lt;Option&gt;Enabled:Advanced Boot Options Menu&lt;/Option&gt;&lt;/Rule&gt;
&lt;Rule&gt;&lt;Option&gt;Enabled:UMCI&lt;/Option&gt;&lt;/Rule&gt;
&lt;Rule&gt;&lt;Option&gt;Enabled:Update Policy No Reboot&lt;/Option&gt;&lt;/Rule&gt;
&lt;/Rules&gt;
&lt;!--EKUS-- &gt;
&lt;EKUs /&gt;
&lt;!--File Rules-- &gt;
&lt;FileRules&gt;
&lt;Allow ID=&quot;ID_ALLOW_A_1&quot; FileName=&quot;*&quot; /&gt;
&lt;Allow ID=&quot;ID_ALLOW_A_2&quot; FileName=&quot;*&quot; /&gt;
&lt;/FileRules&gt;
&lt;!--Signers-- &gt;
&lt;Signers /&gt;
&lt;!--Driver Signing Scenarios-- &gt;
&lt;SigningScenarios&gt;
&lt;SigningScenario Value=&quot;131&quot; ID=&quot;ID_SIGNINGSCENARIO_DRIVERS_1&quot; FriendlyName=&quot;Auto generated policy on 08-17-2015&quot;&gt;
  &lt;ProductSigners&gt;
    &lt;FileRulesRef&gt;&lt;FileRuleRef RuleID=&quot;ID_ALLOW_A_1&quot; /&gt;&lt;/FileRulesRef&gt;
  &lt;/ProductSigners&gt;
&lt;/SigningScenario&gt;
&lt;SigningScenario Value=&quot;12&quot; ID=&quot;ID_SIGNINGSCENARIO_WINDOWS&quot; FriendlyName=&quot;Auto generated policy on 08-17-2015&quot;&gt;
  &lt;ProductSigners&gt;
    &lt;FileRulesRef&gt;&lt;FileRuleRef RuleID=&quot;ID_ALLOW_A_2&quot; /&gt;&lt;/FileRulesRef&gt;
  &lt;/ProductSigners&gt;
&lt;/SigningScenario&gt;
&lt;/SigningScenarios&gt;
&lt;UpdatePolicySigners /&gt;
&lt;CiSigners /&gt;
&lt;HvciOptions&gt;0&lt;/HvciOptions&gt;
&lt;Settings&gt;
&lt;Setting Provider=&quot;PolicyInfo&quot; Key=&quot;Information&quot; ValueName=&quot;Name&quot;&gt;
  &lt;Value&gt;&lt;String&gt;AllowAll&lt;/String&gt;&lt;/Value&gt;
&lt;/Setting&gt;
&lt;Setting Provider=&quot;PolicyInfo&quot; Key=&quot;Information&quot; ValueName=&quot;Id&quot;&gt;
  &lt;Value&gt;&lt;String&gt;041417&lt;/String&gt;&lt;/Value&gt;
&lt;/Setting&gt;
&lt;/Settings&gt;
&lt;/SiPolicy&gt;
</code></pre>
<p>XML-formatted policies can be compiled to a binary format using the ConvertFrom-CiPolicy PowerShell cmdlet:</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image14.jpg" alt="Compiling XML-formatted policies" /></p>
<p>Base Policies allow for fine granularity, with the ability to restrict by name, path, hash, or signer (with or without specific <a href="https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-ppsec/651a90f3-e1f5-4087-8503-40d804429a88">EKU</a>); but also in their action mode (Audit or Enforced).</p>
<p>Supplemental Policies were designed as an extension of Base Policies to provide more flexibility allowing, for instance, policies to apply (or not) to a specific group of workstations or servers. Therefore, they are more specific, but can also be more permissive than the Base Policy should be. Interestingly, before 2016, supplemental policies <a href="https://openrt.gitbook.io/open-surfacert/common/boot-sequence/uefi/secure-boot/windows-bootmanager-exploit">were not bound to a specific device</a>, allowing otherwise mitigated bypasses fixed by <a href="https://docs.microsoft.com/en-us/security-updates/securitybulletins/2016/ms16-094">MS16-094</a> and <a href="https://docs.microsoft.com/en-us/security-updates/securitybulletins/2016/ms16-100">MS16-100</a> that were <a href="https://arstechnica.com/information-technology/2016/08/microsoft-secure-boot-firmware-snafu-leaks-golden-key">broadly covered</a> by the media.</p>
<p>Keeping that information in mind, it is possible to get back to ci!SbValidateAndParseDebugAuthToken with more clarity: the function essentially follows three steps: 1. Call ci!SbParseAndVerifySignedSupplementalPolicy to parse the input buffer from the syscall and determine if it’s a validly-signed Supplemental Policy 2. Call ci!SbIsSupplementalPolicyBoundToDevice to compare the DeviceUnlockId from the supplemental policy to that of the current system; such values can be easily retrieved using the syscall NtQuerySystemEnvironmentValueEx with the GUID <a href="https://oofhours.com/2019/09/02/geeking-out-with-uefi/"><code>{EAEC226F-C9A3-477A-A826-DDC716CDC0E3}</code></a>3. Finally, extract two variables from the policy: an integer (DWORD) which corresponds to the Protection Level, and a (UNICODE_STRING) Debug Authorization.</p>
<p>Since it is possible to craft policy files (via XML or PowerShell scripting), Step 3 is not a problem. Neither is Step 2, as the DeviceUnlockId can be forged with the syscall <code>NtSetSystemEnvironmentValueEx({EAEC226F-C9A3-477A-A826-DDC716CDC0E3})</code> as long as we have the SeSystemEnvironmentPrivilege privilege. However, it should be noted that the UnlockId is a volatile value that will be restored upon reboot.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image26.jpg" alt="Forging UnLockId" /></p>
<p>However, bypassing Step 1 is virtually impossible, as it requires : - to own the private key for a Microsoft-owned certificates with the particular <a href="http://oid-info.com/get/1.3.6.1.4.1.311.10.3.6">OID 1.3.6.1.4.1.311.10.3.6</a>(i.e. - MS NT5 Lab (szOID_NT5_CRYPTO)) - and that the aforementioned certificate must not be revoked or expired</p>
<p>So, where does that leave us? We have now confirmed that, contrary to conventional wisdom, PPL processes can be opened by another process without the extra step of loading a kernel driver. However, it should also be stressed that such a use case is niche, since only Microsoft (literally) holds the keys to using this technique for very targeted machines. Nevertheless, such a case is still a great example of an air gap use of CI for debugging purposes.</p>
<h2>Offensive TTD</h2>
<p><em>Note: As a reminder, TTD.exe requires elevated privileges which all of the techniques discussed below assume.</em></p>
<p>Throughout this research, we discovered some potentially interesting offensive and defensive use cases of TTD.</p>
<h3>Tracing != Debugging</h3>
<p>TTD is not a debugger! Therefore, it will work perfectly undetected for processes that perform a basic anti-debugging check, like using IsDebuggerPresent() (or any other way that depends on PEB.BeingDebugged). The following screenshot illustrates this detail by making TTD attach to a simple notepad process:</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image1.jpg" alt="Attaching to Notepad.exe with TTD" /></p>
<p>From a debugger we can check the BeingDebugged field located in the notepad PEB, which shows that the flag is not set:</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image21.jpg" alt="Verifying Notepad.exe BeingDebugged reports unset" /></p>
<h3>The curious case of ProcLaunchMon</h3>
<p>Another interesting trick made available by TTD is abusing the built-in Windows driver ProcLaunchMon.sys. When running as a service (i.e. TTDService.exe), ttdrecord.dll will create the service instance, load the driver, and communicate with the device available at .\com_microsoft_idna_ProcLaunchMon to register newly traced clients.</p>
<p>The driver itself will be used to monitor new processes created by the TTD service and then suspend those processes directly from the kernel, thus bypassing any protection that solely monitors process creation with the creation flag CREATE_SUSPENDED (as mentioned <a href="https://attack.mitre.org/techniques/T1055/012/#detection">here</a> for instance). We developed a basic Device Driver client for this research, which can be found <a href="https://gist.github.com/calladoum-elastic/328068f19e60a76b00f20cdb936cd078">here</a>.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image3.jpg" alt="Using ProcLaunchMon to monitor Notepad.exe" /></p>
<h3>CreateDump.exe</h3>
<p>Another fun fact: even though it is not strictly part of TTD, the WinDbgX package provides a .NET signed binary whose name perfectly summarizes its functionality: createdump.exe. This binary is located at &quot;C:\Program Files\WindowsApps\Microsoft.WinDbg_*\createdump.exe&quot;.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image9.jpg" alt="CreateDump.exe metadata" /></p>
<p>This binary can be used to snapshot and dump the context of a process provided as an argument, in the direct lineage of other <a href="https://lolbas-project.github.io">LOLBAS</a>.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image22.jpg" alt="Using CreateDump.exe to interact with LSASS" /></p>
<p>This once more highlights the need to avoid relying on static signatures and filename blocklist entries to protect against attacks such as credential dumping and favor more robust approaches such as <a href="https://docs.microsoft.com/en-us/windows-server/security/credentials-protection-and-management/configuring-additional-lsa-protection">RunAsPPL</a>, <a href="https://docs.microsoft.com/en-us/windows/security/identity-protection/credential-guard/credential-guard-manage">Credential Guard</a>, or <a href="https://www.elastic.co/guide/en/security/current/whats-new.html#_endpoint_enhancements">Elastic Endpoint’s Credential Hardening</a>.</p>
<h2>Defensive TTD</h2>
<h3>Blocking TTD</h3>
<p>Though TTD is an extremely useful feature, cases where it would be required to be enabled on non-development or test machines (such as production servers or workstations) are rare. Even though this seems largely undocumented at the time of this writing, ttdrecord.dll allows an early exit scenario by simply creating or updating a registry key located under &quot;HKEY_LOCAL_MACHINE\Software\Microsoft\TTD&quot;, and updating the DWORD32 value RecordingPolicy to 2. Further attempts to use any TTD service (TTD.exe, TTDInject.exe, TTDService.exe) will be stopped and an ETW event will be generated to track attempts.</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image15.jpg" alt="Using the Registry to interfere with TTD" /></p>
<h3>Detecting TTD</h3>
<p>Preventing the use of TTD might be too extreme for all environments — however, several indicators exist for detecting the use of TTD. A process being traced has the following properties:</p>
<ul>
<li>One thread will be running the code from TTDRecordCPU.dll, which can be verified using a simple built-in Windows command: tasklist /m TTDRecordCPU.dll</li>
<li>Even though this can be bypassed, the parent PID of the recorded process (or the first one, in case recursive tracing is enabled), would be TTD.exe itself:</li>
</ul>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image18.jpg" alt="Monitoring TTD" /></p>
<ul>
<li>Also, the _KPROCESS.InstrumentationCallback pointer would be set to land in the TTDRecordCPU.dll BSS section of the executable:</li>
</ul>
<p><img src="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/image25.jpg" alt="IntrementationCallback" /></p>
<p>Therefore, detecting tracing from TTD can be achieved through both User-Mode and Kernel-Mode methods.</p>
<h1>Conclusion</h1>
<p>This concludes the first part of this “On-Week” research focused on TTD. Digging into the internals of the TTD ecosystem revealed some very interesting, lesser-known mechanisms built-in to Windows, which are required to make TTD work for certain edge cases — such as the tracing of PPL processes.</p>
<p>Even though this research didn’t unveil a new secret backdoor for targeting PPL processes, it did show an unexplored technique built into Windows to do so. If anything, this research highlights the importance of a model based on strong cryptography (here through CI.dll), and how it can bring a lot of flexibility — while maintaining a high level of security — when implemented adequately.</p>
<p>The second part of this series will be less research-oriented and more hands-on with the release of a small tool we also developed as part of On-Week. This assists in the process of binary analysis through TTD, using the Windows Sandbox.</p>
<h2>Acknowledgement</h2>
<p>As this research was already concluded and the article in progress, the author became aware of research that covered a similar subject and findings regarding that very same technique (PPL debugging token). That research was performed by Lucas George (from the company Synacktiv), who presented his findings at <a href="https://www.sstic.org/2022/presentation/supreme_ttd_-_that_s_my_ppl/">SSTIC 2022</a>.</p>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/security-labs/assets/images/deep-dive-into-the-ttd-ecosystem/photo-edited-02-w.jpg" length="0" type="image/jpg"/>
        </item>
    </channel>
</rss>