<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://tulach.cc/feed.xml" rel="self" type="application/atom+xml" /><link href="https://tulach.cc/" rel="alternate" type="text/html" hreflang="en-US" /><updated>2026-02-08T16:45:16+01:00</updated><id>https://tulach.cc/feed.xml</id><title type="html">Samuel Tulach</title><subtitle>Security researcher with focus on game anti-cheat technologies and operating system internals.</subtitle><author><name>Samuel Tulach</name><email>noreply@tulach.cc</email></author><entry><title type="html">Protecting Unity’s IL2CPP builds</title><link href="https://tulach.cc/protecting-unity-s-il2cpp-builds/" rel="alternate" type="text/html" title="Protecting Unity’s IL2CPP builds" /><published>2025-01-12T07:10:00+01:00</published><updated>2025-01-12T07:10:00+01:00</updated><id>https://tulach.cc/protecting-unity-s-il2cpp-builds</id><content type="html" xml:base="https://tulach.cc/protecting-unity-s-il2cpp-builds/"><![CDATA[<p>When someone mentions <a href="https://unity.com/">Unity game engine</a>, the first thing that comes to most people’s mind is either the splash screen, <a href="https://en.wikipedia.org/wiki/Asset_flip">the plethora of asset flip mobile games</a>, or <a href="https://mancunion.com/2023/10/19/a-timeline-of-the-unity-controversy/">the recent licensing controversy</a>. If you are into game security though, Unity implies an easy target, and you most likely think of <a href="https://wiki.cheatengine.org/index.php?title=Mono">Cheat Engine’s Mono Dissector</a>, <a href="https://github.com/dnSpy/dnSpy">dnSpy</a>, and <a href="https://github.com/Perfare/Il2CppDumper">all sorts of IL2CPP tools</a>.</p>

<p><img src="/assets/img/posts/12_splashscreen.png" alt="splash" /></p>

<p><em>Unity’s splash screen</em></p>

<h2 id="mono">Mono</h2>
<p>Unity uses <a href="https://learn.microsoft.com/en-us/dotnet/csharp/">C#</a> as its primary language, and it has two <a href="https://docs.unity3d.com/6000.0/Documentation/Manual/scripting-backends.html">scripting backends</a> for you to choose from: <a href="https://docs.unity3d.com/6000.0/Documentation/Manual/scripting-backends-mono.html">Mono</a> and <a href="https://docs.unity3d.com/6000.0/Documentation/Manual/scripting-backends-il2cpp.html">IL2CPP</a>.</p>

<p>When Mono is used, the compilation process is <a href="https://dev.to/kcrnac/net-execution-process-explained-c-1b7a">exactly as you would expect for any other .NET project</a>: the scripts get compiled into .NET assemblies (IL), and then the Mono runtime is shipped with the game, which does the actual JIT machine code compilation and execution.</p>

<p>The thing about .NET assemblies, though, is that you can just open them in any .NET decompiler tool (like <a href="https://github.com/dnSpy/dnSpy">dnSpy</a>), and you will get almost 1:1 code reconstruction from the original. On top of that, you can also very easily patch the code by simply recompiling parts of it.</p>

<p><img src="/assets/img/posts/12_dnspy.png" alt="dnspy" /></p>

<p><em><a href="https://github.com/dnSpy/dnSpy">dnSpy</a> decompiling function from <a href="https://learn.unity.com/project/fps-template">sample FPS microgame</a></em></p>

<p>This is not really a problem if you are making a single-player game and you want to allow your players to mod the game to their heart’s desire. There are even <a href="https://github.com/LavaGang/MelonLoader">projects that streamline this process</a>, and <a href="https://rimworldwiki.com/wiki/Modding_Tutorials">some developers even opt to integrate them into their game builds directly</a>.</p>

<p>When it becomes a problem is when you are working on some sort of competitive multiplayer game. You definitely don’t want players to be modding that, and you want to preferably make it as hard as possible to reverse-engineer it.</p>

<p>So, what are your options? If you are using the Mono backend, then unfortunately not much. Some tricks I will show later will work there too, but realistically, your best bet is <a href="https://blog.ndepend.com/in-the-jungle-of-net-obfuscator-tools/">some off-the-shelf .NET obfuscator</a>. Also, due to the JIT compilation, there is no way for you to verify the code integrity once the game is running. A malicious actor can simply overwrite the JITed machine code, and you have no reference to compare it to. That’s not the focus of this article, though, so let’s move to IL2CPP.</p>

<h2 id="il2cpp">IL2CPP</h2>
<p>The <a href="https://docs.unity3d.com/6000.0/Documentation/Manual/scripting-backends-il2cpp.html">documentation</a> regarding IL2CPP states the following:</p>

<blockquote>
  <p>The IL2CPP (Intermediate Language To C++) scripting backend is an alternative to the Mono backend. IL2CPP provides better support for applications across a wider range of platforms. The IL2CPP backend converts MSIL (Microsoft Intermediate Language) code (for example, C# code in scripts) into C++ code, then uses the C++ code to create a native binary file (for example, .exe, .apk, or .xap) for your chosen platform.</p>
</blockquote>

<p>Well, that sounds like it completely solves the problem, no? Mono assemblies get somehow magically translated to C++ code, which is then compiled into a native binary. No .NET is involved past that point, so unless we ship <a href="https://learn.microsoft.com/en-us/windows/win32/dxtecharts/debugging-with-symbols">debug symbols</a> with the game, reverse engineering of the game should be significantly harder, and it should be impossible to recover any class, method, fields, or parameter names, <em>right?</em></p>

<p><strong>Unfortunately, no.</strong> IL2CPP was not designed as a system to better protect the game’s code and <a href="https://discussions.unity.com/t/expected-performance-of-new-coreclr-vs-mono-vs-il2cpp/910032/2">not even to improve performance</a> (as some people might think), but rather for Unity to be able to run on <a href="https://news.ycombinator.com/item?id=39947969">platforms where JIT compilation is not allowed or viable</a>.</p>

<p>To make matters worse, Unity uses a component-based architecture. In this system, individual <a href="https://docs.unity3d.com/6000.0/Documentation/Manual/creating-scripts.html">scripts</a> (C# code files) are attached to <a href="https://docs.unity3d.com/6000.0/Documentation/ScriptReference/GameObject.html">GameObjects</a>, which are organized into <a href="https://docs.unity3d.com/Manual/CreatingScenes.html">scenes</a>. Due to this, the compiled scenes (and other assets) include information about which scripts are attached to which GameObjects. This is done by referencing classes, methods, and fields <em>by name</em>, which in turn means that this information has to be retained by the scripting backend as well. For example, when the engine asks for <code class="language-plaintext highlighter-rouge">PlayerCharacterController</code> to be initialized for a GameObject, it needs to know which class it is.</p>

<p>As you can imagine, there are automated tools that can leverage this information. The most popular one is most likely <a href="https://github.com/Perfare/Il2CppDumper">Il2CppDumper</a>. Let’s check how it works.</p>

<p>I have created a new project using the <a href="https://learn.unity.com/project/fps-template">FPS microgame template</a> in Unity 2022.3.55f1, changed the scripting backend to IL2CPP and made a Windows build.</p>

<p><img src="/assets/img/posts/12_folder.png" alt="folder" /></p>

<p>The structure of the folder is as follows:</p>

<ul>
  <li>Two folders containing debug symbols and managed assemblies used by IL2CPP. These files are not intended to be published with the game build.</li>
  <li><code class="language-plaintext highlighter-rouge">TestGame_Data</code>: Contains compiled scenes, asset information, and IL2CPP metadata (we will cover this later).</li>
  <li><code class="language-plaintext highlighter-rouge">baselib.dll</code>: An IL2CPP base library providing platform-specific implementations for file I/O, memory allocation, networking, and threading.</li>
  <li><code class="language-plaintext highlighter-rouge">GameAssembly.dll</code>: Includes the actual game code compiled from the C++ generated by IL2CPP.</li>
  <li><code class="language-plaintext highlighter-rouge">TestGame.exe</code>: A loader stub responsible for loading <code class="language-plaintext highlighter-rouge">UnityEngine.dll</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">UnityEngine.dll</code>: Contains the game engine code.</li>
</ul>

<p>Now let’s try running the previously mentioned <a href="https://github.com/Perfare/Il2CppDumper">Il2CppDumper</a>. All we have to do is specify the correct files as command-line parameters and run it.</p>

<p><img src="/assets/img/posts/12_dumper.png" alt="dumper" /></p>

<p>This will generate several files. The main one is <code class="language-plaintext highlighter-rouge">dump.cs</code>, which contains information about all classes, including their fields and methods with their full names reconstructed. It also contains in-memory offsets to these elements, which makes it incredibly easy to modify them if you know where their class instance is in memory.</p>

<p>On top of that, there are also reconstructed .NET assemblies, which also include all the function names, parameters, fields, etc., except for the actual code logic in those functions.</p>

<p><img src="/assets/img/posts/12_dnspy_il2cpp.png" alt="dnspy" /></p>

<p>If you wanted to write a cheat that would overwrite the gravity in the game while it’s running, all you would have to do now is find <code class="language-plaintext highlighter-rouge">GameObjectManager</code> in <code class="language-plaintext highlighter-rouge">UnityEngine.dll</code> (which is quite trivial given that Unity has a <a href="https://docs.unity3d.com/560/Documentation/Manual/WindowsDebugging.html">public debug symbol server</a>), then use it to loop through the active game objects to find one with the <code class="language-plaintext highlighter-rouge">PlayerCharacterController</code> class, and then use the <code class="language-plaintext highlighter-rouge">GravityDownForce</code> offset you’ve just got from the dumper to overwrite the value.</p>

<h2 id="experimenting">Experimenting</h2>
<p>Congratulations 🎉, now that the mandatory introduction is out of the way, we can get to the fun stuff. Most of the information that the dumper is reading is not stored in <code class="language-plaintext highlighter-rouge">GameAssembly.dll</code>, as you might expect, but in <code class="language-plaintext highlighter-rouge">global-metadata.dat</code> located in the <code class="language-plaintext highlighter-rouge">TestGame_Data\il2cpp_data\Metadata</code> directory.</p>

<p>Let’s experiment a bit and mess around with a hex editor (<a href="https://mh-nexus.de/en/hxd/">HxD</a> in my case). What happens when we replace the previously mentioned <code class="language-plaintext highlighter-rouge">GravityDownForce</code> class field name with something else of the same length?</p>

<p><img src="/assets/img/posts/12_hex_editor.png" alt="hexeditor" /></p>

<p>Now let’s try running the game.</p>

<p><img src="/assets/img/posts/12_game.png" alt="game" /></p>

<p>Surprise, surprise, it runs perfectly fine. What if we run the dumper now?</p>

<p><img src="/assets/img/posts/12_dnspy_replaced.png" alt="dnspy" /></p>

<p>So it’s that easy? Just do string replacement in the file and voilà, names obfuscated? Well, I wouldn’t be writing this article if it was that easy, would I? Let’s replace something else, like the <code class="language-plaintext highlighter-rouge">PlayerCharacterController</code> class.</p>

<p><img src="/assets/img/posts/12_game_broken.png" alt="game" /></p>

<p>The game launches, but it’s broken. If we look into the log (<code class="language-plaintext highlighter-rouge">Player.log</code>), we can see many <a href="https://learn.microsoft.com/en-us/dotnet/api/system.nullreferenceexception?view=net-9.0">null reference exceptions</a> being thrown on each frame.</p>

<p><img src="/assets/img/posts/12_log.png" alt="log" /></p>

<p>This is due to the previously mentioned component-based architecture. The engine wants to associate the script with the name <code class="language-plaintext highlighter-rouge">PlayerCharacterController</code> to the player GameObject, but that fails since there is no class with this name anymore. Other scripts fail too, as they expect the class to be present on the GameObject as well.</p>

<h2 id="now-what">Now what?</h2>
<p>In theory, it should be possible to replace all the names in both the compiled scenes and assets and in <code class="language-plaintext highlighter-rouge">global-metadata.dat</code>, which would be optimal since the original names would not be present anywhere in the build. In practice, though, that would mean reverse-engineering and parsing those scenes and asset formats so that you could properly do the replacement. Basic text find and replace would work only if we had super specific names (like <code class="language-plaintext highlighter-rouge">OBFUSCATE_THIS_PlayerCharacterController</code>); otherwise, we might end up in a situation where our class is called just <code class="language-plaintext highlighter-rouge">Player</code>, and replacing this string would break the entire engine.</p>

<p>Lets try to take a safer, more minimal approach. We will only change type names in <code class="language-plaintext highlighter-rouge">global-metadata.dat</code> and keep the original ones in scenes and assets.</p>

<p>But how do we do that without breaking the game? Instead of using completely random names, we will create <a href="https://en.wikipedia.org/wiki/Hash_function">a hash</a> from them so that if we have the original name, we can easily get the corresponding hash, but not the other way around. We will then use <a href="https://en.wikipedia.org/wiki/Middleware">a middleware DLL library</a> that will act as <code class="language-plaintext highlighter-rouge">GameAssembly.dll</code>, redirecting most of the calls to the real library without modifying them. However, for calls that take type names as input, we will apply the hashing function to those inputs first before forwarding the call to the real library.</p>

<p><img src="/assets/img/posts/12_diagram.png" alt="diagram" /></p>

<h2 id="global-metadatadat">global-metadata.dat</h2>
<p>To parse <code class="language-plaintext highlighter-rouge">global-metadata.dat</code>, we can either look at the code of the previously mentioned <a href="https://github.com/Perfare/Il2CppDumper">Il2CppDumper</a>, <a href="https://docs.unity3d.com/560/Documentation/Manual/WindowsDebugging.html">Unity’s debug symbols</a>, or directly at the source code of <code class="language-plaintext highlighter-rouge">GameAssembly.dll</code>. To do that, we will build our test project again, but this time, check the option “Create Visual Studio Solution.”</p>

<p><img src="/assets/img/posts/12_solution_create.png" alt="solution" /></p>

<p>The created solution contains both the code generated by the <a href="https://unity.com/blog/engine-platform/an-introduction-to-ilcpp-internals">IL2CPP compiler</a> and all the <a href="https://unity.com/blog/engine-platform/an-introduction-to-ilcpp-internals">supporting code of the IL2CPP runtime library</a>.</p>

<p><img src="/assets/img/posts/12_solution_vs.png" alt="solution" /></p>

<h2 id="hold-on">Hold on</h2>
<p>So we can get the whole source code of the <code class="language-plaintext highlighter-rouge">GameAssembly.dll</code> file. Why would we then be messing with some additional DLL? Why not edit the source code directly and just compile it? Is there something stopping us from doing so?</p>

<p>Good question. <strong>Yes.</strong> The Unity licensing team. Unless you have an <a href="https://unity.com/products/unity-enterprise">enterprise license</a>, which can give you access to the engine source code, <a href="https://discussions.unity.com/t/is-it-permissible-to-modify-the-source-code-of-il2cpp-according-to-the-terms-of-use/919199/2">you are not allowed to modify it</a>.</p>

<p>If your game studio does happen to have permission to modify it, you can implement the middleware library code directly into it. Although, at that point, you could make much more advanced modifications to the engine build process itself.</p>

<h2 id="back-to-global-metadatadat">Back to global-metadata.dat</h2>
<p>If you are interested, you can go through the code to see exactly how <code class="language-plaintext highlighter-rouge">global-metadata.dat</code> is loaded and used. For our intents and purposes, though, all we need is a way to parse the file to hash selected type names.</p>

<p>The file structure is incredibly simple. It has a header with offsets to individual definitions and their count:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_Header</span>
<span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">Sanity</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">Version</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">StringLiteralOffset</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">StringLiteralSize</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">StringLiteralDataOffset</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">StringLiteralDataSize</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">StringOffset</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">StringSize</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">EventsOffset</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">EventsSize</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">PropertiesOffset</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">PropertiesSize</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">MethodsOffset</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">MethodsSize</span><span class="p">;</span>
    <span class="cm">/* ... */</span>
<span class="p">}</span> <span class="n">Header</span><span class="p">;</span>

<span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_FieldDefinition</span>
<span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">NameIndex</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">TypeIndex</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">Token</span><span class="p">;</span>
<span class="p">}</span> <span class="n">FieldDefinition</span><span class="p">;</span>

<span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_PropertyDefinition</span>
<span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">NameIndex</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">Get</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">Set</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">Attrs</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">Token</span><span class="p">;</span>
<span class="p">}</span> <span class="n">PropertyDefinition</span><span class="p">;</span>

<span class="cm">/* MethodDefinition, TypeDefinition, ... */</span>
</code></pre></div></div>

<p>Getting the individual definitions and strings by their index can then be done like so:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="kr">inline</span> <span class="n">T</span> <span class="nf">Offset</span><span class="p">(</span><span class="kt">size_t</span> <span class="n">sectionOffset</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">itemIndex</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">uint8_t</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">header</span><span class="p">)</span> <span class="o">+</span> <span class="n">sectionOffset</span><span class="p">)</span> <span class="o">+</span> <span class="n">itemIndex</span><span class="p">;</span>
<span class="p">}</span>

<span class="kr">inline</span> <span class="kt">char</span><span class="o">*</span> <span class="nf">GetStringFromIndex</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">index</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="n">Offset</span><span class="o">&lt;</span><span class="kt">char</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">header</span><span class="o">-&gt;</span><span class="n">StringOffset</span><span class="p">,</span> <span class="n">index</span><span class="p">);</span>
<span class="p">}</span>

<span class="kr">inline</span> <span class="n">TypeDefinition</span><span class="o">*</span> <span class="nf">GetTypeDefinitionFromIndex</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">index</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="n">Offset</span><span class="o">&lt;</span><span class="n">TypeDefinition</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">header</span><span class="o">-&gt;</span><span class="n">TypeDefinitionsOffset</span><span class="p">,</span> <span class="n">index</span><span class="p">);</span>
<span class="p">}</span>

<span class="cm">/* GetParameterDefinitionFromIndex, GetMethodDefinitionFromIndex, ... */</span>
</code></pre></div></div>

<p>Before we go through them and start hashing the names, we need to decide what hashing function we will use. The function should be as fast as possible and preferably able to run without any additional memory allocations.</p>

<p>After searching for roughly 3 seconds, I found <a href="https://xxhash.com/">xxHash</a>, which appears to be exactly what we need.</p>

<p>For the purposes of this article, I wanted to make it as simple as possible. To avoid moving the entire file around and adjusting all offsets, we are going to replace the names with their hashed counterparts but make the hash the same length.</p>

<p>To do this, we will use this helper function. It uses <a href="https://xxhash.com/">xxHash</a> to generate a 64-bit number from the buffer, then converts it into a hexadecimal string, which is either cut or repeated depending on the needed length. No standard library functions (such as <code class="language-plaintext highlighter-rouge">std::format</code>) are used to maximize performance and limit the aforementioned memory allocations, as we are going to reuse this function in the middleware DLL, so it will be called quite often.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">Hash</span><span class="o">::</span><span class="n">Run</span><span class="p">(</span><span class="kt">char</span><span class="o">*</span> <span class="n">buffer</span><span class="p">,</span> <span class="k">const</span> <span class="kt">size_t</span> <span class="n">size</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">constexpr</span> <span class="k">auto</span> <span class="n">seed</span> <span class="o">=</span> <span class="mh">0x59648347113887</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">XXH64_hash_t</span> <span class="n">hash</span> <span class="o">=</span> <span class="n">XXH64</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="n">size</span><span class="p">,</span> <span class="n">seed</span><span class="p">);</span>

    <span class="k">static</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">hexDigits</span> <span class="o">=</span> <span class="s">"0123456789abcdef"</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">hashString</span><span class="p">[</span><span class="mi">17</span><span class="p">];</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">8</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">hashString</span><span class="p">[</span><span class="n">i</span> <span class="o">*</span> <span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="n">hexDigits</span><span class="p">[(</span><span class="n">hash</span> <span class="o">&gt;&gt;</span> <span class="p">(</span><span class="mi">56</span> <span class="o">-</span> <span class="n">i</span> <span class="o">*</span> <span class="mi">8</span><span class="p">))</span> <span class="o">&amp;</span> <span class="mh">0xF</span><span class="p">];</span>
        <span class="n">hashString</span><span class="p">[</span><span class="n">i</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">hexDigits</span><span class="p">[(</span><span class="n">hash</span> <span class="o">&gt;&gt;</span> <span class="p">(</span><span class="mi">52</span> <span class="o">-</span> <span class="n">i</span> <span class="o">*</span> <span class="mi">8</span><span class="p">))</span> <span class="o">&amp;</span> <span class="mh">0xF</span><span class="p">];</span>
    <span class="p">}</span>
    <span class="n">hashString</span><span class="p">[</span><span class="mi">16</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'\0'</span><span class="p">;</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">size</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span>
        <span class="n">buffer</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">hashString</span><span class="p">[</span><span class="n">i</span> <span class="o">%</span> <span class="mi">16</span><span class="p">];</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">size</span> <span class="o">&lt;</span> <span class="mi">16</span><span class="p">)</span>
        <span class="n">buffer</span><span class="p">[</span><span class="n">size</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'\0'</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now let’s get to the hashing. We will first get all the types and then retrieve their fields, methods, and properties. We’ll also perform a sanity check to ensure that the file is indeed a metadata file.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">Metadata</span><span class="o">::</span><span class="n">ModifyType</span><span class="p">(</span><span class="n">TypeDefinition</span><span class="o">*</span> <span class="n">type</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">char</span><span class="o">*</span> <span class="n">namePtr</span> <span class="o">=</span> <span class="n">GetStringFromIndex</span><span class="p">(</span><span class="n">type</span><span class="o">-&gt;</span><span class="n">NameIndex</span><span class="p">);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">name</span><span class="p">(</span><span class="n">namePtr</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">IsInternalType</span><span class="p">(</span><span class="n">name</span><span class="p">))</span>
        <span class="k">return</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">Config</span><span class="o">::</span><span class="n">ShouldProtect</span><span class="p">(</span><span class="n">namePtr</span><span class="p">))</span>
        <span class="k">return</span><span class="p">;</span>

    <span class="n">Hash</span><span class="o">::</span><span class="n">Run</span><span class="p">(</span><span class="n">namePtr</span><span class="p">,</span> <span class="n">strlen</span><span class="p">(</span><span class="n">namePtr</span><span class="p">));</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%s =&gt; %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">name</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">namePtr</span><span class="p">);</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">uint32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">type</span><span class="o">-&gt;</span><span class="n">FieldCount</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">FieldDefinition</span><span class="o">*</span> <span class="n">field</span> <span class="o">=</span> <span class="n">GetFieldDefinitionFromIndex</span><span class="p">(</span><span class="n">type</span><span class="o">-&gt;</span><span class="n">FieldStart</span> <span class="o">+</span> <span class="n">i</span><span class="p">);</span>
        <span class="n">ModifyField</span><span class="p">(</span><span class="n">field</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">uint32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">type</span><span class="o">-&gt;</span><span class="n">MethodCount</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">MethodDefinition</span><span class="o">*</span> <span class="n">method</span> <span class="o">=</span> <span class="n">GetMethodDefinitionFromIndex</span><span class="p">(</span><span class="n">type</span><span class="o">-&gt;</span><span class="n">MethodStart</span> <span class="o">+</span> <span class="n">i</span><span class="p">);</span>
        <span class="n">ModifyMethod</span><span class="p">(</span><span class="n">method</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">uint32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">type</span><span class="o">-&gt;</span><span class="n">PropertyCount</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">PropertyDefinition</span><span class="o">*</span> <span class="n">property</span> <span class="o">=</span> <span class="n">GetPropertyDefinitionFromIndex</span><span class="p">(</span><span class="n">type</span><span class="o">-&gt;</span><span class="n">PropertyStart</span> <span class="o">+</span> <span class="n">i</span><span class="p">);</span>
        <span class="n">ModifyProperty</span><span class="p">(</span><span class="n">property</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kt">bool</span> <span class="n">Metadata</span><span class="o">::</span><span class="n">Process</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">uint8_t</span><span class="o">&gt;&amp;</span> <span class="n">buffer</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">header</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">Header</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">buffer</span><span class="p">.</span><span class="n">data</span><span class="p">());</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">header</span><span class="o">-&gt;</span><span class="n">Sanity</span> <span class="o">!=</span> <span class="mh">0xFAB11BAF</span><span class="p">)</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">"Version: %u</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">header</span><span class="o">-&gt;</span><span class="n">Version</span><span class="p">);</span>

    <span class="n">typeCount</span> <span class="o">=</span> <span class="n">header</span><span class="o">-&gt;</span><span class="n">TypeDefinitionsSize</span> <span class="o">/</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">TypeDefinition</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Types: %u</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">typeCount</span><span class="p">);</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">uint32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">typeCount</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">TypeDefinition</span><span class="o">*</span> <span class="n">type</span> <span class="o">=</span> <span class="n">GetTypeDefinitionFromIndex</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
        <span class="n">ModifyType</span><span class="p">(</span><span class="n">type</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Notice how there is the <code class="language-plaintext highlighter-rouge">IsInternalType()</code> and <code class="language-plaintext highlighter-rouge">Config::ShouldProtect()</code> check. IL2CPP has some internal types and methods that it references by name, so we cannot change those. Additionally, a config file containing target class names is used to further make the obfuscation a bit more targeted. In theory, we could switch this around and instead obfuscate everything except the internal types and methods, but this list would be very long (although finite, so it’s possible).</p>

<p>Here is the rest of the code. <code class="language-plaintext highlighter-rouge">IsInternalType()</code> also checks for method names used by <code class="language-plaintext highlighter-rouge">MonoBehaviour</code> (the <code class="language-plaintext highlighter-rouge">GameObject</code> base class), such as <code class="language-plaintext highlighter-rouge">Awake()</code>, <code class="language-plaintext highlighter-rouge">Start()</code>, and <code class="language-plaintext highlighter-rouge">Update()</code>.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">Metadata</span><span class="o">::</span><span class="n">ModifyField</span><span class="p">(</span><span class="n">FieldDefinition</span><span class="o">*</span> <span class="n">field</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">char</span><span class="o">*</span> <span class="n">namePtr</span> <span class="o">=</span> <span class="n">GetStringFromIndex</span><span class="p">(</span><span class="n">field</span><span class="o">-&gt;</span><span class="n">NameIndex</span><span class="p">);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">name</span><span class="p">(</span><span class="n">namePtr</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">IsInternalType</span><span class="p">(</span><span class="n">name</span><span class="p">))</span>
        <span class="k">return</span><span class="p">;</span>

    <span class="n">Hash</span><span class="o">::</span><span class="n">Run</span><span class="p">(</span><span class="n">namePtr</span><span class="p">,</span> <span class="n">strlen</span><span class="p">(</span><span class="n">namePtr</span><span class="p">));</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">" - [field] %s =&gt; %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">name</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">namePtr</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">Metadata</span><span class="o">::</span><span class="n">ModifyParameter</span><span class="p">(</span><span class="n">ParameterDefinition</span><span class="o">*</span> <span class="n">parameter</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">char</span><span class="o">*</span> <span class="n">namePtr</span> <span class="o">=</span> <span class="n">GetStringFromIndex</span><span class="p">(</span><span class="n">parameter</span><span class="o">-&gt;</span><span class="n">NameIndex</span><span class="p">);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">name</span><span class="p">(</span><span class="n">namePtr</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">IsInternalType</span><span class="p">(</span><span class="n">name</span><span class="p">))</span>
        <span class="k">return</span><span class="p">;</span>

    <span class="n">Hash</span><span class="o">::</span><span class="n">Run</span><span class="p">(</span><span class="n">namePtr</span><span class="p">,</span> <span class="n">strlen</span><span class="p">(</span><span class="n">namePtr</span><span class="p">));</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">" - [param] %s =&gt; %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">name</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">namePtr</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">Metadata</span><span class="o">::</span><span class="n">ModifyProperty</span><span class="p">(</span><span class="n">PropertyDefinition</span><span class="o">*</span> <span class="n">property</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">char</span><span class="o">*</span> <span class="n">namePtr</span> <span class="o">=</span> <span class="n">GetStringFromIndex</span><span class="p">(</span><span class="n">property</span><span class="o">-&gt;</span><span class="n">NameIndex</span><span class="p">);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">name</span><span class="p">(</span><span class="n">namePtr</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">IsInternalType</span><span class="p">(</span><span class="n">name</span><span class="p">))</span>
        <span class="k">return</span><span class="p">;</span>

    <span class="n">Hash</span><span class="o">::</span><span class="n">Run</span><span class="p">(</span><span class="n">namePtr</span><span class="p">,</span> <span class="n">strlen</span><span class="p">(</span><span class="n">namePtr</span><span class="p">));</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">" - [prop] %s =&gt; %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">name</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">namePtr</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">Metadata</span><span class="o">::</span><span class="n">ModifyMethod</span><span class="p">(</span><span class="n">MethodDefinition</span><span class="o">*</span> <span class="n">method</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">char</span><span class="o">*</span> <span class="n">namePtr</span> <span class="o">=</span> <span class="n">GetStringFromIndex</span><span class="p">(</span><span class="n">method</span><span class="o">-&gt;</span><span class="n">NameIndex</span><span class="p">);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">name</span><span class="p">(</span><span class="n">namePtr</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">IsInternalType</span><span class="p">(</span><span class="n">name</span><span class="p">))</span>
        <span class="k">return</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">name</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="sc">'.'</span><span class="p">)</span> <span class="o">!=</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">npos</span><span class="p">)</span>
        <span class="k">return</span><span class="p">;</span>

    <span class="n">Hash</span><span class="o">::</span><span class="n">Run</span><span class="p">(</span><span class="n">namePtr</span><span class="p">,</span> <span class="n">strlen</span><span class="p">(</span><span class="n">namePtr</span><span class="p">));</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">" - [method] %s() =&gt; %s()</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">name</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">namePtr</span><span class="p">);</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">uint32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">method</span><span class="o">-&gt;</span><span class="n">ParameterCount</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">ParameterDefinition</span><span class="o">*</span> <span class="n">parameter</span> <span class="o">=</span> <span class="n">GetParameterDefinitionFromIndex</span><span class="p">(</span><span class="n">method</span><span class="o">-&gt;</span><span class="n">ParameterStart</span> <span class="o">+</span> <span class="n">i</span><span class="p">);</span>
        <span class="n">ModifyParameter</span><span class="p">(</span><span class="n">parameter</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now let’s run it and save the modified buffer back into the <code class="language-plaintext highlighter-rouge">global-metadata.dat</code> file.</p>

<p><img src="/assets/img/posts/12_metadata_run.png" alt="run" /></p>

<p>If we now run the dumper and check the generated output, we will see something like this:</p>

<p><img src="/assets/img/posts/12_dnspy_after.png" alt="dnspy" /></p>

<p>If we try to run the game at this point, it will either be broken or crash right away.</p>

<h2 id="middleware">Middleware</h2>
<p>Writing the middleware DLL is pretty straightforward. All we need to do is pass through the vast majority of functions to the original DLL. This can be done through <a href="https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#export-address-table">forwarded exports</a> or by manually writing function wrappers that will then call the original.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">OnDllAttach</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">Console</span><span class="o">::</span><span class="n">Init</span><span class="p">();</span>
    <span class="n">Console</span><span class="o">::</span><span class="n">Print</span><span class="p">(</span><span class="s">"Loading module..."</span><span class="p">);</span>
    <span class="n">Global</span><span class="o">::</span><span class="n">GameAssembly</span> <span class="o">=</span> <span class="n">LoadLibraryA</span><span class="p">(</span><span class="s">"GameAssembly.Real.dll"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">Global</span><span class="o">::</span><span class="n">GameAssembly</span> <span class="o">||</span> <span class="n">Global</span><span class="o">::</span><span class="n">GameAssembly</span> <span class="o">==</span> <span class="n">INVALID_HANDLE_VALUE</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">Console</span><span class="o">::</span><span class="n">Print</span><span class="p">(</span><span class="s">"Failed to load module"</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">Console</span><span class="o">::</span><span class="n">Print</span><span class="p">(</span><span class="s">"Module loaded at 0x%p"</span><span class="p">,</span> <span class="n">Global</span><span class="o">::</span><span class="n">GameAssembly</span><span class="p">);</span>
<span class="p">}</span>

<span class="cp">#define DEFINE_PASSTHROUGH_FUNCTION(return_type, func_name, arg_types, arg_names)        \
    EXTERN_C DLL_EXPORT return_type func_name arg_types                                  \
    {                                                                                    \
        static return_type (*target_func) arg_types = nullptr;                           \
        if (!target_func)                                                                \
        {                                                                                \
            target_func = reinterpret_cast&lt;return_type (*) arg_types&gt;(                   \
                GetProcAddress(Global::GameAssembly, #func_name));                       \
            if (!target_func)                                                            \
            {                                                                            \
                Console::Print("Failed to resolve %s()", #func_name);                    \
                return return_type();                                                    \
            }                                                                            \
        }                                                                                \
        return target_func arg_names;                                                    \
    }
</span>

<span class="n">DEFINE_PASSTHROUGH_FUNCTION</span><span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="n">il2cpp_init</span><span class="p">,</span> <span class="p">(</span><span class="n">PVOID</span> <span class="n">domain_name</span><span class="p">),</span> <span class="p">(</span><span class="n">domain_name</span><span class="p">));</span>
<span class="n">DEFINE_PASSTHROUGH_FUNCTION</span><span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="n">il2cpp_init_utf16</span><span class="p">,</span> <span class="p">(</span><span class="n">PVOID</span> <span class="n">domain_name</span><span class="p">),</span> <span class="p">(</span><span class="n">domain_name</span><span class="p">));</span>
<span class="n">DEFINE_PASSTHROUGH_FUNCTION</span><span class="p">(</span><span class="kt">void</span><span class="p">,</span> <span class="n">il2cpp_shutdown</span><span class="p">,</span> <span class="p">(),</span> <span class="p">());</span>
<span class="n">DEFINE_PASSTHROUGH_FUNCTION</span><span class="p">(</span><span class="kt">void</span><span class="p">,</span> <span class="n">il2cpp_set_config_dir</span><span class="p">,</span> <span class="p">(</span><span class="n">PVOID</span> <span class="n">config_path</span><span class="p">),</span> <span class="p">(</span><span class="n">config_path</span><span class="p">));</span>
<span class="cm">/* ... */</span>
</code></pre></div></div>

<p>Then, in functions where a type or method name is passed in, we first try to execute the original function without hashing anything and if it fails, we use the same hash function that we used previously:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">EXTERN_C</span> <span class="n">DLL_EXPORT</span> <span class="n">PVOID</span> <span class="nf">il2cpp_class_from_name</span><span class="p">(</span><span class="k">const</span> <span class="n">PVOID</span> <span class="n">image</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">namespaze</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">static</span> <span class="n">PVOID</span><span class="p">(</span><span class="o">*</span><span class="n">target_func</span><span class="p">)(</span><span class="k">const</span> <span class="n">PVOID</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">)</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">target_func</span><span class="p">)</span>
        <span class="n">target_func</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">PVOID</span><span class="p">(</span><span class="o">*</span><span class="p">)(</span><span class="k">const</span> <span class="n">PVOID</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="o">&gt;</span><span class="p">(</span><span class="n">GetProcAddress</span><span class="p">(</span><span class="n">Global</span><span class="o">::</span><span class="n">GameAssembly</span><span class="p">,</span> <span class="s">"il2cpp_class_from_name"</span><span class="p">));</span>

    <span class="n">Console</span><span class="o">::</span><span class="n">Print</span><span class="p">(</span><span class="s">"il2cpp_class_from_name(): %s.%s"</span><span class="p">,</span> <span class="n">namespaze</span><span class="p">,</span> <span class="n">name</span><span class="p">);</span>

    <span class="n">PVOID</span> <span class="n">result</span> <span class="o">=</span> <span class="n">target_func</span><span class="p">(</span><span class="n">image</span><span class="p">,</span> <span class="n">namespaze</span><span class="p">,</span> <span class="n">name</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">result</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">result</span><span class="p">;</span>

    <span class="k">const</span> <span class="kt">size_t</span> <span class="n">s1</span> <span class="o">=</span> <span class="n">strlen</span><span class="p">(</span><span class="n">namespaze</span><span class="p">);</span>
    <span class="k">const</span> <span class="kt">size_t</span> <span class="n">s2</span> <span class="o">=</span> <span class="n">strlen</span><span class="p">(</span><span class="n">name</span><span class="p">);</span>

    <span class="kt">char</span> <span class="n">t1</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span>
    <span class="kt">char</span> <span class="n">t2</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">s1</span> <span class="o">&gt;=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">t1</span><span class="p">)</span> <span class="o">||</span> <span class="n">s2</span> <span class="o">&gt;=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">t2</span><span class="p">))</span>
    <span class="p">{</span>
        <span class="n">Console</span><span class="o">::</span><span class="n">Print</span><span class="p">(</span><span class="s">"Buffer too small"</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">memcpy</span><span class="p">(</span><span class="n">t1</span><span class="p">,</span> <span class="n">namespaze</span><span class="p">,</span> <span class="n">s1</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
    <span class="n">memcpy</span><span class="p">(</span><span class="n">t2</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">s2</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>

    <span class="n">Hash</span><span class="o">::</span><span class="n">Run</span><span class="p">(</span><span class="n">t1</span><span class="p">,</span> <span class="n">s1</span><span class="p">);</span>
    <span class="n">Hash</span><span class="o">::</span><span class="n">Run</span><span class="p">(</span><span class="n">t2</span><span class="p">,</span> <span class="n">s2</span><span class="p">);</span>

    <span class="n">Console</span><span class="o">::</span><span class="n">Print</span><span class="p">(</span><span class="s">"-&gt; %s.%s"</span><span class="p">,</span> <span class="n">t1</span><span class="p">,</span> <span class="n">t2</span><span class="p">);</span>

    <span class="n">result</span> <span class="o">=</span> <span class="n">target_func</span><span class="p">(</span><span class="n">image</span><span class="p">,</span> <span class="n">t1</span><span class="p">,</span> <span class="n">t2</span><span class="p">);</span>

    <span class="n">Console</span><span class="o">::</span><span class="n">Print</span><span class="p">(</span><span class="s">"-&gt; 0x%p"</span><span class="p">,</span> <span class="n">result</span><span class="p">);</span>

    <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And that’s it. If we now run the game, it will start and work just fine. We can also verify that no weird stuff is going on by <a href="https://docs.unity3d.com/6000.0/Documentation/Manual/log-files.html">checking the logs again</a>.</p>

<p><img src="/assets/img/posts/12_game_and_console.png" alt="game" /></p>

<h2 id="possible-issues">Possible issues</h2>
<p>In my proof of concept, I implemented the hashing only into <code class="language-plaintext highlighter-rouge">il2cpp_class_from_name()</code>, as that appeared to be all that was needed. As far as I can tell, more complex projects might require adding hashing to additional functions, such as <code class="language-plaintext highlighter-rouge">il2cpp_class_get_field_from_name()</code> or <code class="language-plaintext highlighter-rouge">il2cpp_class_get_method_from_name()</code>. That should be relatively easy to implement, though.</p>

<p>Another issue that will take some time to solve, but should be quite straightforward to work on, is the possibility of a <a href="https://en.wikipedia.org/wiki/Hash_collision">hash collision</a>. <a href="https://xxhash.com/">xxHash</a> should be quite robust, but as I mentioned previously, to save time, I did name replacement with strings of the same length. This means that for very short names with 1-3 letters, the hash will also have just 1-3 letters, increasing the possibility of a collision. To solve this, a fixed length able to store at least a 64-bit number should be used, which requires shifting the entire file around and recalculating all the offsets.</p>

<p>While the names are hashed in <code class="language-plaintext highlighter-rouge">global-metadata.dat</code> and therefore will be returned like that when using <a href="https://github.com/Perfare/Il2CppDumper">off-the-shelf tools to dump them</a>, some of them can still be restored through manual analysis. This can be done by <a href="https://github.com/mafaca/UtinyRipper">parsing the scene and asset files</a>, extracting the names from them, finding the hash function, running those extracted names through it, and then associating those names with the hashes returned by the dumper.</p>

<p>Performance can also be an issue, although from my testing, the <code class="language-plaintext highlighter-rouge">il2cpp_*_from_name()</code> exports don’t get called very often. The engine usually calls them once when initializing the class/type and then uses a pointer to reference them. However, there might be some cases where overhead can be introduced, such as when you need to instantiate many classes at once (e.g., AI or particles). Even then, the overhead should be minimal if memory allocations and frees are kept down.</p>

<p>So far, the biggest problem I have noticed is that you can use the same class, field, property, and method name as something internal in Unity. For example, you can create a class called <code class="language-plaintext highlighter-rouge">AudioSource</code> in your own namespace, which is perfectly fine. The issue with that is that <code class="language-plaintext highlighter-rouge">global-metadata.dat</code> does not store duplicate strings multiple times, so if you replace your own class name with something else, you are replacing all references to <code class="language-plaintext highlighter-rouge">AudioSource</code>. This can be solved by tediously going over all the definitions and checking whether multiple of them reference the same string.</p>

<p>And finally, stability. I did all of my testing on the <a href="https://learn.unity.com/project/fps-template">example projects that Unity provides</a>. Attempting to do something similar on projects with extremely large and complex codebases might introduce issues I had no idea about, but I am sure that with enough time and effort something similar can be ironed out to work well.</p>

<h2 id="other-tricks">Other tricks</h2>
<p>Those tricks are not <a href="https://docs.unity3d.com/6000.0/Documentation/Manual/scripting-backends-il2cpp.html">IL2CPP</a>-specific and can also be applied to projects using <a href="https://docs.unity3d.com/6000.0/Documentation/Manual/scripting-backends-mono.html">Mono</a>. In some cases, the same concepts can even be adapted for use with entirely different game engines.</p>

<p><strong>Export/Import Obfuscation</strong></p>

<p>This is straightforward to implement. All we need to do is replace the <a href="https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#export-address-table">export names</a> and make corresponding replacements for the associated <a href="https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#import-directory-table">import names</a>. For example, in our case, we would replace the export names in <code class="language-plaintext highlighter-rouge">GameAssembly.dll</code> by modifying its PE headers, and then perform string replacement in <code class="language-plaintext highlighter-rouge">UnityEngine.dll</code>, as it dynamically resolves the required imports.</p>

<p>If we wanted to get particularly creative, instead of using random names or a hashing function, we could simply swap the imports around. For example, the function <code class="language-plaintext highlighter-rouge">il2cpp_class_from_name</code> could be renamed to <code class="language-plaintext highlighter-rouge">il2cpp_array_new</code>, and <code class="language-plaintext highlighter-rouge">il2cpp_array_new</code> could be renamed to <code class="language-plaintext highlighter-rouge">il2cpp_class_from_name</code>.</p>

<p><img src="/assets/img/posts/12_exports.png" alt="pe" /></p>

<p><strong>Loader stub</strong></p>

<p>We can write a custom loader which, on Windows, would mean replacing the main <code class="language-plaintext highlighter-rouge">.exe</code> file (all we need to do is call <code class="language-plaintext highlighter-rouge">UnityMain()</code> in <code class="language-plaintext highlighter-rouge">UnityEngine.dll</code>). This loader would contain <code class="language-plaintext highlighter-rouge">UnityEngine.dll</code>, <code class="language-plaintext highlighter-rouge">GameAssembly.dll</code>, <code class="language-plaintext highlighter-rouge">global-metadata.dat</code>, and possibly other files as encrypted resources. These resources would only get decrypted once the loader starts, and they would only reside in memory. However, this would require hooking functions such as <a href="https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea"><code class="language-plaintext highlighter-rouge">CreateFile()</code></a> and <a href="https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile"><code class="language-plaintext highlighter-rouge">ReadFile()</code></a> since Unity expects these files to exist on disk.</p>

<p>Alternatively, we could use a commercial protector tool like <a href="https://vmpsoft.com/">VMProtect</a>, which includes <a href="https://vmpsoft.com/vmprotect/user-manual#files-section">a virtual file system</a> that works exactly like that.</p>

<p><strong>Fake engine version</strong></p>

<p>We can patch out the engine version from the files which will confuse automatic tools. For example <code class="language-plaintext highlighter-rouge">global-metadata.dat</code> has also it’s own version depending on which Unity version is used. For Unity 2022.3.55f1 that is version 31. This version is only checked once in <code class="language-plaintext highlighter-rouge">GameAssembly.dll</code> and the check can be easilly patched too.</p>

<p>The engine version can also be changed in other places, but that might require mode modifications.</p>

<p><img src="/assets/img/posts/12_version_change.png" alt="version_change" /></p>

<h2 id="conclusion">Conclusion</h2>
<p>Obviously, the code presented here does not handle all sorts of edge cases (as noted in the issues section). However, given that it took me roughly a few hours to write it, including this article, I’d say it should not be <em>that big</em> of a deal for larger studios to write a proper toolset.</p>

<p>I am not aware of the <a href="https://unity.com/legal/editor-source-code-terms">Unity Enterprise source code access for modification</a> pricing, but unless it’s so extremely expensive that it’s not viable even for large studios, then none of this should really be needed. They could just directly integrate obfuscation into the build process itself and, for good measure, perhaps even change the <code class="language-plaintext highlighter-rouge">global-metadata.dat</code> format.</p>

<p>What I don’t really understand though, is why Unity themselves don’t try to make reverse engineering of builds a bit harder. Imagine if you could just check a checkbox in the build settings, and all of the names would be hashed with a random seed, including the ones in scenes and assets. You would then get a file containing a list of the original and hashed names, so in case something goes wrong, you could look up the originals, as with any decent obfuscator.</p>

<p>Anyway, I hope that this article can spark a discussion about possible obfuscation methods a little bit and possibly even inspire someone to write more polished tools.</p>

<p>In case you want to experiment yourself, all of the code used here is in <a href="https://github.com/SamuelTulach/Occulto">this repository</a>.</p>

<p>Thanks for reading.</p>]]></content><author><name>Samuel Tulach</name><email>noreply@tulach.cc</email></author><category term="blog" /><category term="windows" /><category term="game-engine" /><category term="unity-engine" /><category term="il2cpp" /><summary type="html"><![CDATA[Writing a bootkit to manipulate VBS enclave's memory]]></summary></entry><entry><title type="html">From firmware to VBS enclave: bootkitting Hyper-V</title><link href="https://tulach.cc/from-firmware-to-vbs-enclave-bootkitting-hyper-v/" rel="alternate" type="text/html" title="From firmware to VBS enclave: bootkitting Hyper-V" /><published>2024-12-08T18:34:00+01:00</published><updated>2024-12-08T18:34:00+01:00</updated><id>https://tulach.cc/from-firmware-to-vbs-enclave-bootkitting-hyper-v</id><content type="html" xml:base="https://tulach.cc/from-firmware-to-vbs-enclave-bootkitting-hyper-v/"><![CDATA[<p>In my <a href="https://tulach.cc/using-vbs-enclaves-for-anti-cheat-purposes/">previous article</a>, I wrote about the possible use case of <a href="https://learn.microsoft.com/en-us/windows/win32/trusted-execution/vbs-enclaves">VBS enclaves</a> for anti-cheat purposes. I created a simple 2D game called <a href="https://github.com/SamuelTulach/SecureGame">SecureGame</a>, which utilized a VBS enclave to run the game logic and isolate its game data from the rest of the system.</p>

<p>I also noted that while, on paper, such isolation seems like a perfect idea that could even potentially completely eliminate the need for <a href="http://tulach.cc/the-issue-of-anti-cheat-on-linux/">kernel-mode anti-cheat solutions</a>, in practice, at least in its current state, it is not really viable. Any experienced developer will still be able to get around it due to complete control over the system’s early boot stages.</p>

<p>To prove my point and to have something that can then be used for writing detections, I decided to challenge myself to develop a project that could be used to modify the game’s memory. I called the project <a href="https://github.com/SamuelTulach/SecureHack">SecureHack</a> (very creative, I know).</p>

<p>If you haven’t read the <a href="https://tulach.cc/using-vbs-enclaves-for-anti-cheat-purposes/">previous article</a> and have no idea what <a href="https://learn.microsoft.com/en-us/windows/win32/trusted-execution/vbs-enclaves">VBS enclaves</a> are, please do so now, as I am going to assume you have read it.</p>

<h2 id="setting-a-goal">Setting a goal</h2>
<p>It seems like the goal is incredibly straightforward, just <em>somehow</em> gain access to the enclave’s memory. While it is the goal, just by itself, it would make an extremely short and boring article. For example, we could enable <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/install/the-testsigning-boot-configuration-option">test signing mode</a> and patch both the host process and the enclave to enable its debugging. Does this allow us to access its memory? Yes, but it’s not really what we are looking for, is it?</p>

<p>So, to make it more interesting, for the purposes of this article, let’s imagine that <a href="https://github.com/SamuelTulach/SecureGame">SecureGame</a> actually has some anti-cheat system that:</p>

<ul>
  <li>Protects the integrity of the host process (no 3rd party code execution, no patching, signature verification)</li>
  <li>Verifies that the enclave is loaded as an actual enclave (no API hooks that would cause the enclave to be loaded as a normal module)</li>
  <li>Checks that enclave debugging is disabled (no simple <code class="language-plaintext highlighter-rouge">securekernel.exe</code> patches or process flags override)</li>
</ul>

<p>And to make it more fun, the final goal will be to modify the score of one of the players in the game.</p>

<p><img src="/assets/img/posts/11_game.png" alt="game" /></p>

<p><em><a href="https://github.com/SamuelTulach/SecureGame">SecureGame</a> running without any modifications</em></p>

<h2 id="boot-process">Boot process</h2>
<p>Since <a href="https://en.wikipedia.org/wiki/Hyper-V">Hyper-V</a> won’t allow us to access its internals from within the system once it’s booted up and virtualized, we will need to get a bit creative. The most direct approach would be to essentially write a bootkit that will hook into the boot process. The latest versions of <a href="https://www.microsoft.com/en-us/windows">Microsoft Windows</a> officially support only <a href="https://en.wikipedia.org/wiki/UEFI">UEFI boot</a>.</p>

<p>When <a href="https://en.wikipedia.org/wiki/Hyper-V">Hyper-V</a> is not enabled, the boot process is very simple. First, the firmware loads <code class="language-plaintext highlighter-rouge">bootx64.efi</code> in the <code class="language-plaintext highlighter-rouge">\EFI\Boot</code> directory on the <a href="https://en.wikipedia.org/wiki/EFI_system_partition">EFI partition</a>, which is an <a href="https://tianocore-docs.github.io/edk2-ModuleWriteGuide/draft/4_uefi_applications/">EFI application</a> and is the actual executable implementing the boot manager. You can also find <code class="language-plaintext highlighter-rouge">bootmgfw.efi</code> in <code class="language-plaintext highlighter-rouge">\EFI\Microsoft</code>, which is a copy of it. You can launch both of those manually through the <a href="https://github.com/tianocore/edk2-archive/tree/master/ShellBinPkg/UefiShell">EFI shell</a> or programmatically with <a href="https://uefi.org/specs/UEFI/2.9_A/07_Services_Boot_Services.html#id37"><code class="language-plaintext highlighter-rouge">LoadImage()/StartImage()</code></a>.</p>

<p><img src="/assets/img/posts/11_boot_menu.png" alt="game" /></p>

<p><em>Boot manager (<code class="language-plaintext highlighter-rouge">bootmgfw.efi</code>) showing boot options</em></p>

<p>The boot manager then loads the next stage, which is <code class="language-plaintext highlighter-rouge">winload.efi</code>. That is located on the main <a href="https://en.wikipedia.org/wiki/NTFS">NTFS</a> partition in <code class="language-plaintext highlighter-rouge">C:\Windows\System32</code>. Don’t be misled by the <code class="language-plaintext highlighter-rouge">.efi</code> extension, though. It’s not an <a href="https://tianocore-docs.github.io/edk2-ModuleWriteGuide/draft/4_uefi_applications/">EFI application</a>, but instead, it has its custom <a href="https://learn.microsoft.com/en-us/windows/win32/debug/pe-format">PE subsystem</a> <code class="language-plaintext highlighter-rouge">WINDOWS_BOOT_APPLICATION</code> (value 0x10).</p>

<p>This stage then loads core drivers (disk, filesystem, chipset, TPM) into memory and finally loads the OS kernel (<code class="language-plaintext highlighter-rouge">ntoskrnl.exe</code>, again from <code class="language-plaintext highlighter-rouge">C:\Windows\System32</code>). It then passes execution to the kernel, which completes the loading of those core drivers, starts loading other boot-time drivers, begins execution on all cores, and finishes the boot process.</p>

<p><img src="/assets/img/posts/11_boot_process.png" alt="boot-nohyperv" /></p>

<p>When <a href="https://en.wikipedia.org/wiki/Hyper-V">Hyper-V</a> and <a href="https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/oem-vbs">VBS</a> are enabled, the boot process changes significantly. Before <code class="language-plaintext highlighter-rouge">winload.efi</code> starts loading the kernel, Hyper-V is initialized, which virtualizes the system. This is done by loading and starting the main Hyper-V module (<code class="language-plaintext highlighter-rouge">hvax64.exe</code> for AMD and <code class="language-plaintext highlighter-rouge">hvix64.exe</code> for Intel systems; technically, this is handled through <code class="language-plaintext highlighter-rouge">hvloader.dll</code>, which then then calls back to <code class="language-plaintext highlighter-rouge">winload.efi</code> to load the actual module, more on that later).</p>

<p>The boot process then continues, but as a Hyper-V guest. Microsoft refers to this as <a href="https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/vsm">virtual secure mode (VSM)</a>, which leverages hypervisor capabilities to isolate system components from each other. Microsoft also defines <a href="https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/vsm#virtual-trust-level-vtl">virtual trust levels (VTLs)</a>, where a VTL with a higher number represents a higher privilege level. In this setup, the standard kernel <code class="language-plaintext highlighter-rouge">ntoskrnl.exe</code> and user-mode processes run in VTL0, while <code class="language-plaintext highlighter-rouge">securekernel.exe</code> and its processes (including enclaves) run in VTL1.</p>

<p><img src="/assets/img/posts/11_boot_hyperv.png" alt="boot-hyperv" /></p>

<p>This means that we need to somehow intercept the loading of <code class="language-plaintext highlighter-rouge">hvax64.exe</code> or <code class="language-plaintext highlighter-rouge">hvix64.exe</code> so that we can patch it. Doing it later is simply not possible because the system will already be virtualized with isolation in place.</p>

<h2 id="déjà-vu">Déjà vu?</h2>
<p>Back in 2021, <a href="https://blog.back.engineering/researchers/_xeroxz/">@IDontCode</a> released the <a href="https://git.back.engineering/IDontCode/Voyager">Voyager</a> project, which implemented Hyper-V VM exit hooking for both AMD and Intel platforms and, more importantly, memory management that allowed copying memory from and to the guest as well as between different running processes. <a href="https://blog.back.engineering/20/04/2021/">Here is the related blog article</a>. Additionally, all the way back in 2017, <a href="https://github.com/Cr4sh">@Cr4sh</a> released <a href="https://github.com/Cr4sh/s6_pcie_microblaze/tree/master/python/payloads/DmaBackdoorHv">Hyper-V Backdoor</a> as one of the possible DMA payloads.</p>

<p>This means that much of the actual hard work has already been done, so let’s take inspiration from those two projects and work toward the goal of our VBS enclave memory manipulation.</p>

<h2 id="patching-hyper-v">Patching Hyper-V</h2>
<p>So let’s finally start with the fun stuff. The two previous projects used signature based scans and quite tedious hook chain to intercept <a href="https://en.wikipedia.org/wiki/Hyper-V">Hyper-V</a> load. Since my goal is not to support older versions of Windows and I only have AMD based system, I will focus on that.</p>

<p>Conveniently, in later versions of Windows, <code class="language-plaintext highlighter-rouge">winload.efi</code> exports both <code class="language-plaintext highlighter-rouge">BlLdrLoadImage()</code>, which is used to load the Hyper-V module, and <code class="language-plaintext highlighter-rouge">BlImgAllocateImageBuffer()</code>, which is used to allocate its memory. The idea is as follows: similar to the previous projects, we will intercept this module load and add a new section to it using an <a href="https://tianocore-docs.github.io/edk2-ModuleWriteGuide/draft/4_uefi_applications/">EFI application</a> or <a href="https://tianocore-docs.github.io/edk2-ModuleWriteGuide/draft/5_uefi_drivers/">driver</a>. Instead of adding a dedicated payload, however, we will just remap the entire EFI executable into it, which will double as both the Hyper-V payload and the initial patcher.</p>

<p>To achieve this without any complex hook chains or signature based scans, we can simply hook the <a href="https://uefi.org/specs/UEFI/2.10/08_Services_Runtime_Services.html">EFI runtime service</a> function <a href="https://uefi.org/specs/UEFI/2.10/08_Services_Runtime_Services.html#getvariable"><code class="language-plaintext highlighter-rouge">GetVariable()</code></a>. This function is called by <code class="language-plaintext highlighter-rouge">winload.efi</code> very early after it starts, to check whether <a href="https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/oem-secure-boot">secure boot</a> is enabled or not.</p>

<p><img src="/assets/img/posts/11_sb_check.png" alt="securebootcheck" /></p>

<p>To hook <a href="https://uefi.org/specs/UEFI/2.10/08_Services_Runtime_Services.html#getvariable"><code class="language-plaintext highlighter-rouge">GetVariable()</code></a>, all we need to do is swap the pointer in the <a href="https://uefi.org/specs/UEFI/2.10/04_EFI_System_Table.html#efi-runtime-services">EFI runtime services table</a> and recalculate the table’s <a href="https://en.wikipedia.org/wiki/Cyclic_redundancy_check">CRC</a>.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">VOID</span><span class="o">*</span> <span class="nf">SetServicePointer</span><span class="p">(</span><span class="n">EFI_TABLE_HEADER</span><span class="o">*</span> <span class="n">serviceTableHeader</span><span class="p">,</span> <span class="n">VOID</span><span class="o">**</span> <span class="n">serviceTableFunction</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">newFunction</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">CONST</span> <span class="n">EFI_TPL</span> <span class="n">tpl</span> <span class="o">=</span> <span class="n">gBS</span><span class="o">-&gt;</span><span class="n">RaiseTPL</span><span class="p">(</span><span class="n">TPL_HIGH_LEVEL</span><span class="p">);</span>

    <span class="n">VOID</span><span class="o">*</span> <span class="n">originalFunction</span> <span class="o">=</span> <span class="o">*</span><span class="n">serviceTableFunction</span><span class="p">;</span>
    <span class="o">*</span><span class="n">serviceTableFunction</span> <span class="o">=</span> <span class="n">newFunction</span><span class="p">;</span>

    <span class="n">serviceTableHeader</span><span class="o">-&gt;</span><span class="n">CRC32</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">gBS</span><span class="o">-&gt;</span><span class="n">CalculateCrc32</span><span class="p">((</span><span class="n">UINT8</span><span class="o">*</span><span class="p">)</span><span class="n">serviceTableHeader</span><span class="p">,</span> <span class="n">serviceTableHeader</span><span class="o">-&gt;</span><span class="n">HeaderSize</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">serviceTableHeader</span><span class="o">-&gt;</span><span class="n">CRC32</span><span class="p">);</span>

    <span class="n">gBS</span><span class="o">-&gt;</span><span class="n">RestoreTPL</span><span class="p">(</span><span class="n">tpl</span><span class="p">);</span>

    <span class="k">return</span> <span class="n">originalFunction</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">EFI_STATUS</span> <span class="n">EFIAPI</span> <span class="nf">UefiMain</span><span class="p">(</span><span class="k">const</span> <span class="n">EFI_HANDLE</span> <span class="n">imageHandle</span><span class="p">,</span> <span class="n">EFI_SYSTEM_TABLE</span><span class="o">*</span> <span class="n">systemTable</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">DebugInit</span><span class="p">(</span><span class="n">COM1</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">"UefiMain() @ 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="p">(</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">UefiMain</span><span class="p">);</span>

    <span class="n">OriginalGetVariable</span> <span class="o">=</span> <span class="p">(</span><span class="n">EFI_GET_VARIABLE</span><span class="p">)</span><span class="n">SetServicePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">gST</span><span class="o">-&gt;</span><span class="n">Hdr</span><span class="p">,</span> <span class="p">(</span><span class="n">VOID</span><span class="o">**</span><span class="p">)</span><span class="o">&amp;</span><span class="n">gRT</span><span class="o">-&gt;</span><span class="n">GetVariable</span><span class="p">,</span> <span class="p">(</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">HookedGetVariable</span><span class="p">);</span>
    <span class="n">Print</span><span class="p">(</span><span class="s">L"GetVariable(): 0x%p -&gt; 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">OriginalGetVariable</span><span class="p">,</span> <span class="n">HookedGetVariable</span><span class="p">);</span>

    <span class="k">return</span> <span class="n">EFI_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Once it’s hooked, we check for <code class="language-plaintext highlighter-rouge">SetupMode</code> or <code class="language-plaintext highlighter-rouge">SecureBoot</code> variable reads. If one of those variables is being read, we obtain the calling function address (<a href="https://learn.microsoft.com/en-us/cpp/intrinsics/returnaddress?view=msvc-170"><code class="language-plaintext highlighter-rouge">_ReturnAddress()</code></a>) and scan the memory downwards from there to find <a href="https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#ms-dos-stub-image-only">PE image headers</a>. Once found, we verify that the image has exported functions <code class="language-plaintext highlighter-rouge">BlLdrLoadImage()</code> and <code class="language-plaintext highlighter-rouge">BlImgAllocateImageBuffer()</code>. If that is the case, the function is being called from within <code class="language-plaintext highlighter-rouge">winload.efi</code>, and we can proceed to hook those two functions. I have used a slightly modified version of the <a href="https://github.com/SamuelTulach/LightHook">LightHook library</a> for that.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">EFI_STATUS</span> <span class="n">EFIAPI</span> <span class="nf">HookedGetVariable</span><span class="p">(</span><span class="n">CHAR16</span><span class="o">*</span> <span class="n">variableName</span><span class="p">,</span> <span class="n">EFI_GUID</span><span class="o">*</span> <span class="n">vendorGuid</span><span class="p">,</span> <span class="n">UINT32</span><span class="o">*</span> <span class="n">attributes</span><span class="p">,</span> <span class="n">UINTN</span><span class="o">*</span> <span class="n">dataSize</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">data</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">StrCmp</span><span class="p">(</span><span class="n">variableName</span><span class="p">,</span> <span class="s">L"SetupMode"</span><span class="p">))</span>
        <span class="k">return</span> <span class="n">OriginalGetVariable</span><span class="p">(</span><span class="n">variableName</span><span class="p">,</span> <span class="n">vendorGuid</span><span class="p">,</span> <span class="n">attributes</span><span class="p">,</span> <span class="n">dataSize</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>

    <span class="n">UINT64</span> <span class="n">returnAddress</span> <span class="o">=</span> <span class="p">(</span><span class="n">UINT64</span><span class="p">)</span><span class="n">_ReturnAddress</span><span class="p">();</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">CompareMem</span><span class="p">((</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">returnAddress</span><span class="p">,</span> <span class="s">"This program cannot be run in DOS mode"</span><span class="p">,</span> <span class="mi">38</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">returnAddress</span><span class="o">--</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">moduleBase</span> <span class="o">=</span> <span class="n">returnAddress</span> <span class="o">-</span> <span class="mh">0x4E</span><span class="p">;</span>

    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">loadImage</span> <span class="o">=</span> <span class="n">GetExport</span><span class="p">((</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">moduleBase</span><span class="p">,</span> <span class="s">"BlLdrLoadImage"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">loadImage</span><span class="p">)</span>
        <span class="k">return</span> <span class="nf">OriginalGetVariable</span><span class="p">(</span><span class="n">variableName</span><span class="p">,</span> <span class="n">vendorGuid</span><span class="p">,</span> <span class="n">attributes</span><span class="p">,</span> <span class="n">dataSize</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>

    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">allocateImageBuffer</span> <span class="o">=</span> <span class="n">GetExport</span><span class="p">((</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">moduleBase</span><span class="p">,</span> <span class="s">"BlImgAllocateImageBuffer"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">allocateImageBuffer</span><span class="p">)</span>
        <span class="k">return</span> <span class="nf">OriginalGetVariable</span><span class="p">(</span><span class="n">variableName</span><span class="p">,</span> <span class="n">vendorGuid</span><span class="p">,</span> <span class="n">attributes</span><span class="p">,</span> <span class="n">dataSize</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">HooksInstalled</span><span class="p">)</span>
        <span class="k">return</span> <span class="nf">OriginalGetVariable</span><span class="p">(</span><span class="n">variableName</span><span class="p">,</span> <span class="n">vendorGuid</span><span class="p">,</span> <span class="n">attributes</span><span class="p">,</span> <span class="n">dataSize</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>

    <span class="n">gST</span><span class="o">-&gt;</span><span class="n">ConOut</span><span class="o">-&gt;</span><span class="n">SetAttribute</span><span class="p">(</span><span class="n">gST</span><span class="o">-&gt;</span><span class="n">ConOut</span><span class="p">,</span> <span class="n">EFI_RED</span> <span class="o">|</span> <span class="n">EFI_BACKGROUND_BLACK</span><span class="p">);</span>
    <span class="n">gST</span><span class="o">-&gt;</span><span class="n">ConOut</span><span class="o">-&gt;</span><span class="n">ClearScreen</span><span class="p">(</span><span class="n">gST</span><span class="o">-&gt;</span><span class="n">ConOut</span><span class="p">);</span>
    <span class="n">Print</span><span class="p">(</span><span class="s">L"SecureHack</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>

    <span class="n">gST</span><span class="o">-&gt;</span><span class="n">ConOut</span><span class="o">-&gt;</span><span class="n">SetAttribute</span><span class="p">(</span><span class="n">gST</span><span class="o">-&gt;</span><span class="n">ConOut</span><span class="p">,</span> <span class="n">EFI_LIGHTGRAY</span> <span class="o">|</span> <span class="n">EFI_BACKGROUND_BLACK</span><span class="p">);</span>
    <span class="n">Print</span><span class="p">(</span><span class="s">L"ReturnAddress                -&gt; (phys) 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">returnAddress</span><span class="p">);</span>
    <span class="n">Print</span><span class="p">(</span><span class="s">L"BlLdrLoadImage               -&gt; (phys) 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">loadImage</span><span class="p">);</span>
    <span class="n">Print</span><span class="p">(</span><span class="s">L"BlImgAllocateImageBuffer     -&gt; (phys) 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">allocateImageBuffer</span><span class="p">);</span>

    <span class="n">BlLdrLoadImageHook</span> <span class="o">=</span> <span class="n">CreateHook</span><span class="p">((</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">loadImage</span><span class="p">,</span> <span class="p">(</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">HookedBlLdrLoadImage</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">EnableHook</span><span class="p">(</span><span class="o">&amp;</span><span class="n">BlLdrLoadImageHook</span><span class="p">))</span>
    <span class="p">{</span>
        <span class="n">Print</span><span class="p">(</span><span class="s">L"Failed to hook BlLdrLoadImage</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="n">INFINITE_LOOP</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="n">BlImgAllocateImageBufferHook</span> <span class="o">=</span> <span class="n">CreateHook</span><span class="p">((</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">allocateImageBuffer</span><span class="p">,</span> <span class="p">(</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">HookedBlImgAllocateImageBuffer</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">EnableHook</span><span class="p">(</span><span class="o">&amp;</span><span class="n">BlImgAllocateImageBufferHook</span><span class="p">))</span>
    <span class="p">{</span>
        <span class="n">Print</span><span class="p">(</span><span class="s">L"Failed to hook BlImgAllocateImageBuffer</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="n">INFINITE_LOOP</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="n">HooksInstalled</span> <span class="o">=</span> <span class="n">TRUE</span><span class="p">;</span>

    <span class="n">Sleep</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>

    <span class="k">return</span> <span class="nf">OriginalGetVariable</span><span class="p">(</span><span class="n">variableName</span><span class="p">,</span> <span class="n">vendorGuid</span><span class="p">,</span> <span class="n">attributes</span><span class="p">,</span> <span class="n">dataSize</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The first function out of those two that will be be called is going to be <code class="language-plaintext highlighter-rouge">BlImgAllocateImageBuffer()</code>. We need to do two things:</p>
<ul>
  <li>Extend the allocation so that it will fit our new section</li>
  <li>Make sure that the allocated memory has <a href="https://en.wikipedia.org/wiki/Memory_protection">read-write-execute (RWX) permissions</a> since the new section will contain both data and code</li>
</ul>

<p><img src="/assets/img/posts/11_allocate_image_buffer.png" alt="allocateimagebuffer" /></p>

<p>There is a problem though. How do we tell which image is being loaded? Even though there is no parameter passed in that has the image path or image name, there is an attributes parameter. While I have no idea what those attributes mean, by debugging, I have figured that the Hyper-V image is going to have different attributes than the rest of the images that are being loaded, and the first allocation is going to be the one we are after.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UINT64</span> <span class="n">EFIAPI</span> <span class="nf">HookedBlImgAllocateImageBuffer</span><span class="p">(</span><span class="n">VOID</span><span class="o">**</span> <span class="n">imageBuffer</span><span class="p">,</span> <span class="n">UINTN</span> <span class="n">imageSize</span><span class="p">,</span> <span class="n">UINT32</span> <span class="n">memoryType</span><span class="p">,</span> <span class="k">const</span> <span class="n">UINT32</span> <span class="n">attributes</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">unknown1</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">unknown2</span><span class="p">)</span>
<span class="p">{</span>
    <span class="cm">/*
     * First one with this type is the actual allocation:
     * BlImgAllocateImageBuffer(): 0xFFFFF806387AA000 (4276224) (147456)
     * BlLdrLoadImage(): \WINDOWS\system32\hvax64.exe hv.exe 0xFFFFF806387AA000 (4276224)
     * BlImgAllocateImageBuffer(): 0xFFFFF80638BBE000 (77824) (147456)
     * BlLdrLoadImage(): \WINDOWS\system32\hv.exe hv.exe 0xFFFFF806387AA000 (4276224)
     */</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">attributes</span> <span class="o">==</span> <span class="n">ATTRIBUTE_HV_IMAGE</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">ExtendedSize</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">ExtendedSize</span> <span class="o">=</span> <span class="n">TRUE</span><span class="p">;</span>

        <span class="k">const</span> <span class="n">UINTN</span> <span class="n">originalSize</span> <span class="o">=</span> <span class="n">imageSize</span><span class="p">;</span>
        <span class="k">const</span> <span class="n">UINTN</span> <span class="n">currentImageSize</span> <span class="o">=</span> <span class="n">GetCurrentImageSize</span><span class="p">();</span>
        <span class="n">imageSize</span> <span class="o">+=</span> <span class="n">currentImageSize</span><span class="p">;</span>
        <span class="n">memoryType</span> <span class="o">=</span> <span class="n">MEMORY_ATTRIBUTE_RWX</span><span class="p">;</span>

        <span class="n">DebugFormat</span><span class="p">(</span><span class="s">"Extended allocation for 0x%p from %ld to %ld (+%ld)</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">imageBuffer</span><span class="p">,</span> <span class="n">originalSize</span><span class="p">,</span> <span class="n">imageSize</span><span class="p">,</span> <span class="n">currentImageSize</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">allocated</span> <span class="o">=</span> <span class="p">((</span><span class="n">UINT64</span><span class="p">(</span><span class="o">*</span><span class="p">)(</span><span class="n">VOID</span><span class="o">**</span><span class="p">,</span> <span class="n">UINTN</span><span class="p">,</span> <span class="n">UINT32</span><span class="p">,</span> <span class="n">UINT32</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">))</span><span class="n">BlImgAllocateImageBufferHook</span><span class="p">.</span><span class="n">Trampoline</span><span class="p">)(</span>
        <span class="n">imageBuffer</span><span class="p">,</span> <span class="n">imageSize</span><span class="p">,</span> <span class="n">memoryType</span><span class="p">,</span> <span class="n">attributes</span><span class="p">,</span> <span class="n">unknown1</span><span class="p">,</span> <span class="n">unknown2</span><span class="p">);</span>

    <span class="k">return</span> <span class="n">allocated</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>After that <code class="language-plaintext highlighter-rouge">BlLdrLoadImage()</code> is called. It is just a wrapper around <code class="language-plaintext highlighter-rouge">LdrpLoadImage()</code>. We don’t really care about any of the bazillion different function parameters except for the image path, name, and the structure holding information about the current module which is being loaded (its base address and size).</p>

<p><img src="/assets/img/posts/11_load_image.png" alt="loadimage" />
<img src="/assets/img/posts/11_load_image_2.png" alt="loadimage" /></p>

<p>The Hyper-V image is always going to have the name <code class="language-plaintext highlighter-rouge">hv.exe</code> no matter the platform. When the hook is called, we call the original to load the image and then, if it’s the Hyper-V image, we change the <code class="language-plaintext highlighter-rouge">SizeOfImage</code> property in the <a href="https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-windows-specific-fields-image-only">NT headers</a> of the image to match our extended allocation and proceed with the next step, which is to add the new section, copy ourselves into it, and hook VM exit.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">EFI_STATUS</span> <span class="nf">HookedBlLdrLoadImage</span><span class="p">(</span><span class="n">VOID</span><span class="o">*</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg2</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg3</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg4</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg5</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg6</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg7</span><span class="p">,</span>
    <span class="n">VOID</span><span class="o">*</span> <span class="n">arg8</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg9</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg10</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg11</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg12</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg13</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg14</span><span class="p">,</span>
    <span class="n">VOID</span><span class="o">*</span> <span class="n">arg15</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg16</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg17</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">const</span> <span class="n">EFI_STATUS</span> <span class="n">status</span> <span class="o">=</span> <span class="p">((</span><span class="n">EFI_STATUS</span><span class="p">(</span><span class="o">*</span><span class="p">)(</span><span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span><span class="p">))</span><span class="n">BlLdrLoadImageHook</span><span class="p">.</span><span class="n">Trampoline</span><span class="p">)(</span>
        <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">,</span> <span class="n">arg3</span><span class="p">,</span> <span class="n">arg4</span><span class="p">,</span> <span class="n">arg5</span><span class="p">,</span> <span class="n">arg6</span><span class="p">,</span> <span class="n">arg7</span><span class="p">,</span> <span class="n">arg8</span><span class="p">,</span> <span class="n">arg9</span><span class="p">,</span> <span class="n">arg10</span><span class="p">,</span> <span class="n">arg11</span><span class="p">,</span> <span class="n">arg12</span><span class="p">,</span> <span class="n">arg13</span><span class="p">,</span> <span class="n">arg14</span><span class="p">,</span> <span class="n">arg15</span><span class="p">,</span> <span class="n">arg16</span><span class="p">,</span> <span class="n">arg17</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">EFI_ERROR</span><span class="p">(</span><span class="n">status</span><span class="p">))</span>
        <span class="k">return</span> <span class="n">status</span><span class="p">;</span>

    <span class="n">CHAR16</span><span class="o">*</span> <span class="n">imagePath</span> <span class="o">=</span> <span class="p">(</span><span class="n">CHAR16</span><span class="o">*</span><span class="p">)</span><span class="n">arg3</span><span class="p">;</span>
    <span class="n">CHAR16</span><span class="o">*</span> <span class="n">imageName</span> <span class="o">=</span> <span class="p">(</span><span class="n">CHAR16</span><span class="o">*</span><span class="p">)</span><span class="n">arg4</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">imagePath</span> <span class="o">||</span> <span class="o">!</span><span class="n">imageName</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">status</span><span class="p">;</span>

    <span class="k">const</span> <span class="n">PLDR_DATA_TABLE_ENTRY</span> <span class="n">entry</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">PPLDR_DATA_TABLE_ENTRY</span><span class="p">)</span><span class="n">arg9</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">StrCmp</span><span class="p">(</span><span class="n">imageName</span><span class="p">,</span> <span class="s">L"hv.exe"</span><span class="p">))</span>
        <span class="k">return</span> <span class="n">status</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">PatchedHyperV</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">status</span><span class="p">;</span>

    <span class="n">PatchedHyperV</span> <span class="o">=</span> <span class="n">TRUE</span><span class="p">;</span>

    <span class="k">const</span> <span class="n">UINT32</span> <span class="n">newSize</span> <span class="o">=</span> <span class="p">(</span><span class="n">UINT32</span><span class="p">)(</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">SizeOfImage</span> <span class="o">+</span> <span class="n">GetCurrentImageSize</span><span class="p">());</span>

    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">"Image %S is being loaded:</span><span class="se">\n</span><span class="s"> - Path: %S</span><span class="se">\n</span><span class="s"> - Base: 0x%p</span><span class="se">\n</span><span class="s"> - Original size: %u</span><span class="se">\n</span><span class="s"> - Extended size: %u</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">imageName</span><span class="p">,</span> <span class="n">imagePath</span><span class="p">,</span> <span class="n">entry</span><span class="o">-&gt;</span><span class="n">ModuleBase</span><span class="p">,</span> <span class="n">entry</span><span class="o">-&gt;</span><span class="n">SizeOfImage</span><span class="p">,</span> <span class="n">newSize</span><span class="p">);</span>

    <span class="n">entry</span><span class="o">-&gt;</span><span class="n">SizeOfImage</span> <span class="o">=</span> <span class="n">newSize</span><span class="p">;</span>
    <span class="n">NT_HEADERS</span><span class="p">(</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">ModuleBase</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">OptionalHeader</span><span class="p">.</span><span class="n">SizeOfImage</span> <span class="o">=</span> <span class="n">newSize</span><span class="p">;</span>

    <span class="n">ProcessHvImage</span><span class="p">(</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">ModuleBase</span><span class="p">);</span>

    <span class="k">return</span> <span class="n">status</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We are creating this new section since, once Hyper-V virtualizes the system, from its execution context, our driver’s memory is not valid (it’s mapped at a different address and it’s not executable).</p>

<p>But now, let’s find the VM exit handler so we can hook it. A VM exit is an event where the guest stops execution and control is transferred back to the hypervisor to handle specific operations or events (like instruction intercepts or access violations). Having control over it basically gives us control over the entire hypervisor. Finding the handler is quite trivial since we can just search for the <code class="language-plaintext highlighter-rouge">VMRUN</code> instruction in the case of <a href="https://www.0x04.net/doc/amd/33047.pdf">AMD SVM</a>. There are multiple instances of it in <code class="language-plaintext highlighter-rouge">hvax64.exe</code>, so you might need to go through them to find the actual VM loop.</p>

<p><em>Just a heads up, for some reason IDA flags code containing the <code class="language-plaintext highlighter-rouge">VMRUN</code> instruction we are looking for as data, so do not search for it as text. Instead, search for the <code class="language-plaintext highlighter-rouge">0F 01 D8</code> hex sequence.</em></p>

<p><img src="/assets/img/posts/11_vmrun.png" alt="vmrun" /></p>

<p>Once we find it, right under it, there are two <code class="language-plaintext highlighter-rouge">CALL</code> instructions. The second one points to the actual function handling the exit. If we want to hook it, all we have to do is patch the <code class="language-plaintext highlighter-rouge">CALL</code> instruction to point to our function instead.</p>

<p><img src="/assets/img/posts/11_exit_handler_call.png" alt="exithandlercall" /></p>

<p>So in our driver, we will do a signature scan for the function call, then replace its relative offset and save the original function address, but not as an absolute value—instead as a relative offset to the hooked function, since the memory layout will change later. We will then copy our driver’s image in its entirety into the newly created section.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">extern</span> <span class="n">IMAGE_DOS_HEADER</span> <span class="n">__ImageBase</span><span class="p">;</span>
<span class="n">VOID</span> <span class="nf">ProcessHvImage</span><span class="p">(</span><span class="k">const</span> <span class="n">UINT64</span> <span class="n">imageBase</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">const</span> <span class="n">UINT32</span> <span class="n">currentImageSize</span> <span class="o">=</span> <span class="p">(</span><span class="n">UINT32</span><span class="p">)</span><span class="n">GetCurrentImageSize</span><span class="p">();</span>
    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">section</span> <span class="o">=</span> <span class="n">AddSection</span><span class="p">(</span><span class="n">imageBase</span><span class="p">,</span> <span class="s">".uwu"</span><span class="p">,</span> <span class="n">currentImageSize</span><span class="p">,</span> <span class="n">SECTION_RWX</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">"Added new section at 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">section</span><span class="p">);</span>

    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">scan</span> <span class="o">=</span> <span class="n">FindPatternImage</span><span class="p">((</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">imageBase</span><span class="p">,</span> <span class="s">"E8 ? ? ? ? 48 89 04 24 E9"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">scan</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">DebugFormat</span><span class="p">(</span><span class="s">"Failed to find pattern</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">currentImageBase</span> <span class="o">=</span> <span class="p">(</span><span class="n">UINT64</span><span class="p">)</span><span class="o">&amp;</span><span class="n">__ImageBase</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">targetFunction</span> <span class="o">=</span> <span class="p">(</span><span class="n">UINT64</span><span class="p">)</span><span class="n">HookedVmExitHandler</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">offset</span> <span class="o">=</span> <span class="n">targetFunction</span> <span class="o">-</span> <span class="n">currentImageBase</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">remoteFunction</span> <span class="o">=</span> <span class="n">section</span> <span class="o">+</span> <span class="n">offset</span><span class="p">;</span>

    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">originalBase</span> <span class="o">=</span> <span class="n">scan</span> <span class="o">+</span> <span class="mi">5</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">INT32</span> <span class="n">originalOffset</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">INT32</span><span class="o">*</span><span class="p">)(</span><span class="n">scan</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">originalFunction</span> <span class="o">=</span> <span class="n">originalBase</span> <span class="o">+</span> <span class="n">originalOffset</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">INT32</span> <span class="n">newOffset</span> <span class="o">=</span> <span class="p">(</span><span class="n">INT32</span><span class="p">)(</span><span class="n">remoteFunction</span> <span class="o">-</span> <span class="n">originalBase</span><span class="p">);</span>

    <span class="n">OriginalOffsetFromHook</span> <span class="o">=</span> <span class="p">(</span><span class="n">INT32</span><span class="p">)(</span><span class="n">originalFunction</span> <span class="o">-</span> <span class="n">remoteFunction</span><span class="p">);</span>

    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">"Found function call at 0x%p:</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">scan</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - Original offset: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">originalOffset</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - Original function: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">originalFunction</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - New offset: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">newOffset</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - New function: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">remoteFunction</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - Hook offset: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">OriginalOffsetFromHook</span><span class="p">);</span>

    <span class="o">*</span><span class="p">(</span><span class="n">INT32</span><span class="o">*</span><span class="p">)(</span><span class="n">scan</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">=</span> <span class="n">newOffset</span><span class="p">;</span>

    <span class="n">CopyMem</span><span class="p">((</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">section</span><span class="p">,</span> <span class="p">(</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">__ImageBase</span><span class="p">,</span> <span class="n">currentImageSize</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">"Remapped current image to 0x%p with size %u</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">section</span><span class="p">,</span> <span class="n">currentImageSize</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And voilà, we have just hooked the VM exit. What now?</p>

<h2 id="vm-exit">VM exit</h2>
<p>We now have a custom handler that gets called every time a VM exit happens. It’s not really that useful yet, though. We need to get information about each exit and have a way to access and modify the guest’s registers.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UINT64</span> <span class="nf">HookedVmExitHandler</span><span class="p">(</span><span class="n">VOID</span><span class="o">*</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg2</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg3</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="p">((</span><span class="n">OriginalVmExitHandler_t</span><span class="p">)((</span><span class="n">UINT64</span><span class="p">)</span><span class="n">HookedVmExitHandler</span> <span class="o">+</span> <span class="n">OriginalOffsetFromHook</span><span class="p">))(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">,</span> <span class="n">arg3</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>First, let’s resolve the issue of registers access. Usually, when writing a hypervisor, you would save the registers state into some sort of structure, which you would <a href="https://github.com/tandasat/SimpleSvm/blob/master/SimpleSvm/SimpleSvm.cpp#L739">pass to the exit handler</a> and then restore their state from this structure when entering the guest again. It’s exactly the same here. If we look just above the exit handler call, we can see that register values are being pushed into memory saved in the register <code class="language-plaintext highlighter-rouge">R8</code>. Since <a href="https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170">Microsoft ABI</a> is used, <code class="language-plaintext highlighter-rouge">R8</code> is the third parameter of the function.</p>

<p><img src="/assets/img/posts/11_registers.png" alt="registers" /></p>

<p>We can use this in our handler as follows:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="nc">__declspec</span><span class="p">(</span><span class="n">align</span><span class="p">(</span><span class="mi">16</span><span class="p">))</span> <span class="n">GUEST_CONTEXT</span>
<span class="p">{</span>
    <span class="n">UINT8</span> <span class="n">Reserved1</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>
    <span class="n">UINT64</span> <span class="n">Rcx</span><span class="p">;</span>
    <span class="n">UINT64</span> <span class="n">Rdx</span><span class="p">;</span>
    <span class="n">UINT64</span> <span class="n">Rbx</span><span class="p">;</span>
    <span class="n">UINT8</span> <span class="n">Reserved2</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>
    <span class="n">UINT64</span> <span class="n">Rbp</span><span class="p">;</span>
    <span class="n">UINT64</span> <span class="n">Rsi</span><span class="p">;</span>
    <span class="n">UINT64</span> <span class="n">Rdi</span><span class="p">;</span>
    <span class="n">UINT64</span> <span class="n">R8</span><span class="p">;</span>
    <span class="n">UINT64</span> <span class="n">R9</span><span class="p">;</span>
    <span class="n">UINT64</span> <span class="n">R10</span><span class="p">;</span>
    <span class="n">UINT64</span> <span class="n">R11</span><span class="p">;</span>
    <span class="n">UINT64</span> <span class="n">R12</span><span class="p">;</span>
    <span class="n">UINT64</span> <span class="n">R13</span><span class="p">;</span>
    <span class="n">UINT64</span> <span class="n">R14</span><span class="p">;</span>
    <span class="n">UINT64</span> <span class="n">R15</span><span class="p">;</span>
    <span class="cm">/* ... */</span>
<span class="p">}</span> <span class="n">GUEST_CONTEXT</span><span class="p">,</span> <span class="o">*</span> <span class="n">PGUEST_CONTEXT</span><span class="p">;</span>

<span class="n">UINT64</span> <span class="nf">HookedVmExitHandler</span><span class="p">(</span><span class="n">VOID</span><span class="o">*</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg2</span><span class="p">,</span> <span class="k">const</span> <span class="n">PGUEST_CONTEXT</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="p">((</span><span class="n">OriginalVmExitHandler_t</span><span class="p">)((</span><span class="n">UINT64</span><span class="p">)</span><span class="n">HookedVmExitHandler</span> <span class="o">+</span> <span class="n">OriginalOffsetFromHook</span><span class="p">))(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">,</span> <span class="n">context</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now we need to get the <a href="https://nskernel.gitbook.io/kernel-play-guide/kvm/amd-v-and-sev#vmcb">virtual machine control block (VMCB)</a> to obtain information about the exit reason and the current guest state. This is also quite easy. The VMCB holds the exit reason at offset <code class="language-plaintext highlighter-rouge">0x70</code>. Inside the original exit handler function, there is a huge switch-case structure to handle different exit reasons. Right above it, we can see the actual exit reason code being read from the VMCB at the previously mentioned offset.</p>

<p><img src="/assets/img/posts/11_switch.png" alt="switch" /></p>

<p>Then we can just look at how the VMCB value is assigned at the top of the function. There is some pointer arithmetic and dereferencing from the second argument. Let’s not overthink it and just directly copy it.</p>

<p><img src="/assets/img/posts/11_vmcb.png" alt="vmcb" /></p>

<p>Which will get us this beautiful piece of code:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_VMCB_CONTROL_AREA</span>
<span class="p">{</span>
    <span class="cm">/* ... */</span>
    <span class="n">UINT64</span> <span class="n">VIntr</span><span class="p">;</span>                       <span class="c1">// +0x060</span>
    <span class="n">UINT64</span> <span class="n">InterruptShadow</span><span class="p">;</span>             <span class="c1">// +0x068</span>
    <span class="n">UINT64</span> <span class="n">ExitCode</span><span class="p">;</span>                    <span class="c1">// +0x070</span>
    <span class="n">UINT64</span> <span class="n">ExitInfo1</span><span class="p">;</span>                   <span class="c1">// +0x078</span>
    <span class="n">UINT64</span> <span class="n">ExitInfo2</span><span class="p">;</span>                   <span class="c1">// +0x080</span>
    <span class="n">UINT64</span> <span class="n">ExitIntInfo</span><span class="p">;</span>                 <span class="c1">// +0x088</span>
    <span class="cm">/* ... */</span>
<span class="p">}</span> <span class="n">VMCB_CONTROL_AREA</span><span class="p">,</span> <span class="o">*</span> <span class="n">PVMCB_CONTROL_AREA</span><span class="p">;</span>

<span class="n">PVMCB_CONTROL_AREA</span> <span class="nf">GetVmcb</span><span class="p">(</span><span class="k">const</span> <span class="n">UINT64</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">v3</span> <span class="o">=</span> <span class="o">*</span><span class="p">((</span><span class="n">UINT64</span><span class="o">*</span><span class="p">)</span><span class="n">context</span> <span class="o">-</span> <span class="mi">384</span><span class="p">);</span>
    <span class="k">const</span> <span class="n">UINT64</span><span class="o">**</span> <span class="n">v7</span> <span class="o">=</span> <span class="p">(</span><span class="n">UINT64</span><span class="o">**</span><span class="p">)(</span><span class="n">v3</span> <span class="o">+</span> <span class="mi">5056</span><span class="p">);</span>
    <span class="k">return</span> <span class="p">(</span><span class="n">PVMCB_CONTROL_AREA</span><span class="p">)(</span><span class="o">**</span><span class="n">v7</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">UINT64</span> <span class="nf">HookedVmExitHandler</span><span class="p">(</span><span class="n">VOID</span><span class="o">*</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg2</span><span class="p">,</span> <span class="k">const</span> <span class="n">PGUEST_CONTEXT</span> <span class="n">context</span><span class="p">)</span> 
<span class="p">{</span>
    <span class="n">PVMCB_CONTROL_AREA</span> <span class="n">vmcb</span> <span class="o">=</span> <span class="n">GetVmcb</span><span class="p">((</span><span class="n">UINT64</span><span class="p">)</span><span class="n">arg2</span><span class="p">);</span>
    <span class="cm">/* ... */</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now the last thing that remains is to figure out how to continue execution if we want to intercept some exit but don’t call the original handler afterward (since that could overwrite our register changes, for example). The function returns a value at the <code class="language-plaintext highlighter-rouge">0x0</code> offset from the <code class="language-plaintext highlighter-rouge">GS</code> segment.</p>

<p><img src="/assets/img/posts/11_return1.png" alt="return" />
<img src="/assets/img/posts/11_return2.png" alt="return" /></p>

<p>Let’s again just do the same as the original handler. If we want to handle the exit without calling the original, we can then do something like this:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UINT64</span> <span class="nf">HookedVmExitHandler</span><span class="p">(</span><span class="n">VOID</span><span class="o">*</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg2</span><span class="p">,</span> <span class="k">const</span> <span class="n">PGUEST_CONTEXT</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">PVMCB_CONTROL_AREA</span> <span class="n">vmcb</span> <span class="o">=</span> <span class="n">GetVmcb</span><span class="p">((</span><span class="n">UINT64</span><span class="p">)</span><span class="n">arg2</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">ExitCode</span> <span class="o">==</span> <span class="n">VMEXIT_CPUID</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="cm">/* Custom CPUID handling code here */</span>

        <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Rip</span> <span class="o">=</span> <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">NRip</span><span class="p">;</span>
        <span class="k">return</span> <span class="n">__readgsqword</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="p">((</span><span class="n">OriginalVmExitHandler_t</span><span class="p">)((</span><span class="n">UINT64</span><span class="p">)</span><span class="n">HookedVmExitHandler</span> <span class="o">+</span> <span class="n">OriginalOffsetFromHook</span><span class="p">))(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">,</span> <span class="n">context</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Let’s test it out. In a normal user-mode program (in VTL0), we are going to execute the <code class="language-plaintext highlighter-rouge">CPUID</code> instruction, which will cause an exit. We can then compare the register values to figure out if it’s coming from our program, effectively adding a backdoor that we can use. Again, we are using the <a href="https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170">Microsoft ABI</a>, so we’ll just stick with that, which means that <code class="language-plaintext highlighter-rouge">RCX</code> contains the first function parameter, <code class="language-plaintext highlighter-rouge">RDX</code> contains the second, and <code class="language-plaintext highlighter-rouge">RAX</code> holds the return value. The first parameter is going to be a magic value to verify the call, and the second one is going to specify which command should be executed.</p>

<p>We can then implement a check to verify that our exit hook is in place as follows:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define CPUID_BACKDOOR 0xaabbccdd12345
#define CPUID_RETURN_VALUE 0x123456789
#define COMMAND_CHECK_PRESENCE 1
</span>
<span class="n">VOID</span> <span class="nf">HandleCPUID</span><span class="p">(</span><span class="k">const</span> <span class="n">PVMCB_CONTROL_AREA</span> <span class="n">vmcb</span><span class="p">,</span> <span class="k">const</span> <span class="n">PGUEST_CONTEXT</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">switch</span> <span class="p">(</span><span class="n">context</span><span class="o">-&gt;</span><span class="n">Rdx</span><span class="p">)</span>
    <span class="p">{</span>
    <span class="k">case</span> <span class="n">COMMAND_CHECK_PRESENCE</span><span class="p">:</span>
        <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Rax</span> <span class="o">=</span> <span class="n">CPUID_RETURN_VALUE</span><span class="p">;</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="nl">default:</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="n">UINT64</span> <span class="nf">HookedVmExitHandler</span><span class="p">(</span><span class="n">VOID</span><span class="o">*</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">VOID</span><span class="o">*</span> <span class="n">arg2</span><span class="p">,</span> <span class="k">const</span> <span class="n">PGUEST_CONTEXT</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="cm">/* ... */</span>

    <span class="n">PVMCB_CONTROL_AREA</span> <span class="n">vmcb</span> <span class="o">=</span> <span class="n">GetVmcb</span><span class="p">((</span><span class="n">UINT64</span><span class="p">)</span><span class="n">arg2</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">ExitCode</span> <span class="o">==</span> <span class="n">VMEXIT_CPUID</span> <span class="o">&amp;&amp;</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">Rcx</span> <span class="o">==</span> <span class="n">CPUID_BACKDOOR</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">HandleCPUID</span><span class="p">(</span><span class="n">vmcb</span><span class="p">,</span> <span class="n">context</span><span class="p">);</span>

        <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Rip</span> <span class="o">=</span> <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">NRip</span><span class="p">;</span>
        <span class="k">return</span> <span class="n">__readgsqword</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="cm">/* ... */</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And in the user-mode program:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ExecuteCPUID</span> <span class="n">proc</span>
    <span class="n">cpuid</span>
    <span class="n">ret</span>
<span class="n">ExecuteCPUID</span> <span class="n">endp</span>

<span class="kr">inline</span> <span class="kt">bool</span> <span class="nf">CheckPresence</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">const</span> <span class="k">auto</span> <span class="n">result</span> <span class="o">=</span> <span class="n">ExecuteCPUID</span><span class="p">(</span><span class="n">CPUID_BACKDOOR</span><span class="p">,</span> <span class="n">COMMAND_CHECK_PRESENCE</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">result</span> <span class="o">==</span> <span class="n">CPUID_RETURN_VALUE</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">Control</span><span class="o">::</span><span class="n">CheckPresence</span><span class="p">())</span>
<span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Test call did not return CPUID_RETURN_VALUE</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="k">return</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">printf</span><span class="p">(</span><span class="s">"Test call returned CPUID_RETURN_VALUE</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
</code></pre></div></div>

<p>After running the program with our hooks in place, we should get the custom return value.</p>

<p><img src="/assets/img/posts/11_test_call.png" alt="testcall" /></p>

<h2 id="memory">Memory</h2>
<p>We have successfully <a href="https://en.wikipedia.org/wiki/Hyperjacking">hyperjacked</a> Hyper-V, but it’s not very useful yet. We need to figure out how to read and write the host’s <a href="https://en.wikipedia.org/wiki/Virtual_memory">physical memory</a> so that we can copy data between our control (or cheat) process running in VTL0 and the game’s enclave in VTL1, since in the Hyper-V context, there are no <a href="https://en.wikipedia.org/wiki/Page_table">virtual memory</a> mappings for them.</p>

<p>Remember how I mentioned that the <a href="#déjà-vu">previous projects</a> already did the hard work? Well, the <a href="https://git.back.engineering/IDontCode/Voyager">Voyager</a> project uses a self-referencing PML4 entry. A self-referencing entry means that it points to the physical memory region where it itself and others are located, allowing you to freely modify the <a href="https://en.wikipedia.org/wiki/Page_table">page table entries</a>. If you want to learn how it was found, see <a href="https://blog.back.engineering/20/04/2021/#hyper-v-page-tables">this part of the related blog post</a>.</p>

<p>We will add custom entries in such a way that each CPU core will be able to map multiple pages (again, nothing new, same as in <a href="https://git.back.engineering/IDontCode/Voyager">Voyager</a>):</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">EFI_STATUS</span> <span class="nf">MemoryInit</span><span class="p">(</span><span class="n">VOID</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">"Initialization on core %d:</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">MemoryGetCoreIndex</span><span class="p">());</span>

    <span class="n">PdptPhysical</span> <span class="o">=</span> <span class="n">MemoryTranslate</span><span class="p">((</span><span class="n">UINT64</span><span class="p">)</span><span class="n">Pdpt</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - PDPT: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">PdptPhysical</span><span class="p">);</span>
    <span class="n">PdPhysical</span> <span class="o">=</span> <span class="n">MemoryTranslate</span><span class="p">((</span><span class="n">UINT64</span><span class="p">)</span><span class="n">Pd</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - PD: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">PdPhysical</span><span class="p">);</span>
    <span class="n">PtPhysical</span> <span class="o">=</span> <span class="n">MemoryTranslate</span><span class="p">((</span><span class="n">UINT64</span><span class="p">)</span><span class="n">Pt</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - PT: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">PtPhysical</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">PdptPhysical</span> <span class="o">||</span> <span class="o">!</span><span class="n">PdPhysical</span> <span class="o">||</span> <span class="o">!</span><span class="n">PtPhysical</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">EFI_INVALID_PARAMETER</span><span class="p">;</span>

    <span class="n">HyperVPml4</span><span class="p">[</span><span class="n">MAPPING_PML4_IDX</span><span class="p">].</span><span class="n">Present</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="n">HyperVPml4</span><span class="p">[</span><span class="n">MAPPING_PML4_IDX</span><span class="p">].</span><span class="n">PageFrameNumber</span> <span class="o">=</span> <span class="n">PdptPhysical</span> <span class="o">&gt;&gt;</span> <span class="mi">12</span><span class="p">;</span>
    <span class="n">HyperVPml4</span><span class="p">[</span><span class="n">MAPPING_PML4_IDX</span><span class="p">].</span><span class="n">Supervisor</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">HyperVPml4</span><span class="p">[</span><span class="n">MAPPING_PML4_IDX</span><span class="p">].</span><span class="n">Write</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>

    <span class="n">Pdpt</span><span class="p">[</span><span class="mi">511</span><span class="p">].</span><span class="n">Present</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="n">Pdpt</span><span class="p">[</span><span class="mi">511</span><span class="p">].</span><span class="n">PageFrameNumber</span> <span class="o">=</span> <span class="n">PdPhysical</span> <span class="o">&gt;&gt;</span> <span class="mi">12</span><span class="p">;</span>
    <span class="n">Pdpt</span><span class="p">[</span><span class="mi">511</span><span class="p">].</span><span class="n">Supervisor</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">Pdpt</span><span class="p">[</span><span class="mi">511</span><span class="p">].</span><span class="n">Write</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>

    <span class="n">Pd</span><span class="p">[</span><span class="mi">511</span><span class="p">].</span><span class="n">Present</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="n">Pd</span><span class="p">[</span><span class="mi">511</span><span class="p">].</span><span class="n">PageFrameNumber</span> <span class="o">=</span> <span class="n">PtPhysical</span> <span class="o">&gt;&gt;</span> <span class="mi">12</span><span class="p">;</span>
    <span class="n">Pd</span><span class="p">[</span><span class="mi">511</span><span class="p">].</span><span class="n">Supervisor</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">Pd</span><span class="p">[</span><span class="mi">511</span><span class="p">].</span><span class="n">Write</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>

    <span class="k">for</span> <span class="p">(</span><span class="n">UINT32</span> <span class="n">idx</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">idx</span> <span class="o">&lt;</span> <span class="mi">512</span><span class="p">;</span> <span class="n">idx</span><span class="o">++</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">Pt</span><span class="p">[</span><span class="n">idx</span><span class="p">].</span><span class="n">Present</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
        <span class="n">Pt</span><span class="p">[</span><span class="n">idx</span><span class="p">].</span><span class="n">Supervisor</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="n">Pt</span><span class="p">[</span><span class="n">idx</span><span class="p">].</span><span class="n">Write</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="cm">/* ... */</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And then walk the page tables and overwrite them to access the memory:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UINT64</span> <span class="nf">MemoryMapPage</span><span class="p">(</span><span class="k">const</span> <span class="n">UINT64</span> <span class="n">physicalAddress</span><span class="p">,</span> <span class="k">const</span> <span class="k">enum</span> <span class="n">MapType</span> <span class="n">type</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">CPUID_EAX_01</span> <span class="n">cpuidResult</span><span class="p">;</span>
    <span class="n">__cpuid</span><span class="p">((</span><span class="n">INT32</span><span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">cpuidResult</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>

    <span class="k">const</span> <span class="n">UINT32</span> <span class="n">index</span> <span class="o">=</span> <span class="n">MemoryGetCoreIndex</span><span class="p">()</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="p">(</span><span class="n">UINT32</span><span class="p">)</span><span class="n">type</span><span class="p">;</span>
    <span class="n">Pt</span><span class="p">[</span><span class="n">index</span><span class="p">].</span><span class="n">PageFrameNumber</span> <span class="o">=</span> <span class="n">physicalAddress</span> <span class="o">&gt;&gt;</span> <span class="mi">12</span><span class="p">;</span>

    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">mappedAddress</span> <span class="o">=</span> <span class="n">MemoryGetMapVirtual</span><span class="p">(</span><span class="n">physicalAddress</span> <span class="o">&amp;</span> <span class="n">PAGE_MASK</span><span class="p">,</span> <span class="n">type</span><span class="p">);</span>

    <span class="n">__invlpg</span><span class="p">((</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span><span class="n">mappedAddress</span><span class="p">);</span>

    <span class="k">return</span> <span class="n">mappedAddress</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">UINT64</span> <span class="nf">MemoryTranslateGuestVirtual</span><span class="p">(</span><span class="k">const</span> <span class="n">UINT64</span> <span class="n">directoryBase</span><span class="p">,</span> <span class="k">const</span> <span class="n">UINT64</span> <span class="n">guestVirtual</span><span class="p">,</span> <span class="k">const</span> <span class="k">enum</span> <span class="n">MapType</span> <span class="n">mapType</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">VIRTUAL_ADDRESS</span> <span class="n">virtualAddress</span><span class="p">;</span>
    <span class="n">virtualAddress</span><span class="p">.</span><span class="n">Value</span> <span class="o">=</span> <span class="n">guestVirtual</span><span class="p">;</span>

    <span class="n">PML4E_64</span><span class="o">*</span> <span class="n">pml4</span> <span class="o">=</span> <span class="p">(</span><span class="n">PML4E_64</span><span class="o">*</span><span class="p">)</span><span class="n">MemoryMapPage</span><span class="p">(</span><span class="n">directoryBase</span><span class="p">,</span> <span class="n">mapType</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pml4</span> <span class="o">||</span> <span class="o">!</span><span class="n">pml4</span><span class="p">[</span><span class="n">virtualAddress</span><span class="p">.</span><span class="n">Pml4Index</span><span class="p">].</span><span class="n">Present</span><span class="p">)</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

    <span class="n">PDPTE_64</span><span class="o">*</span> <span class="n">pdpt</span> <span class="o">=</span> <span class="p">(</span><span class="n">PDPTE_64</span><span class="o">*</span><span class="p">)</span><span class="n">MemoryMapPage</span><span class="p">(</span><span class="n">pml4</span><span class="p">[</span><span class="n">virtualAddress</span><span class="p">.</span><span class="n">Pml4Index</span><span class="p">].</span><span class="n">PageFrameNumber</span> <span class="o">&lt;&lt;</span> <span class="mi">12</span><span class="p">,</span> <span class="n">mapType</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pdpt</span> <span class="o">||</span> <span class="o">!</span><span class="n">pdpt</span><span class="p">[</span><span class="n">virtualAddress</span><span class="p">.</span><span class="n">PdptIndex</span><span class="p">].</span><span class="n">Present</span><span class="p">)</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

    <span class="c1">// 1GB large page</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">pdpt</span><span class="p">[</span><span class="n">virtualAddress</span><span class="p">.</span><span class="n">PdptIndex</span><span class="p">].</span><span class="n">LargePage</span><span class="p">)</span>
        <span class="k">return</span> <span class="p">(</span><span class="n">pdpt</span><span class="p">[</span><span class="n">virtualAddress</span><span class="p">.</span><span class="n">PdptIndex</span><span class="p">].</span><span class="n">PageFrameNumber</span> <span class="o">&lt;&lt;</span> <span class="mi">12</span><span class="p">)</span> <span class="o">+</span> <span class="n">virtualAddress</span><span class="p">.</span><span class="n">Offset</span><span class="p">;</span>

    <span class="n">PDE_64</span><span class="o">*</span> <span class="n">pd</span> <span class="o">=</span> <span class="p">(</span><span class="n">PDE_64</span><span class="o">*</span><span class="p">)</span><span class="n">MemoryMapPage</span><span class="p">(</span><span class="n">pdpt</span><span class="p">[</span><span class="n">virtualAddress</span><span class="p">.</span><span class="n">PdptIndex</span><span class="p">].</span><span class="n">PageFrameNumber</span> <span class="o">&lt;&lt;</span> <span class="mi">12</span><span class="p">,</span> <span class="n">mapType</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pd</span> <span class="o">||</span> <span class="o">!</span><span class="n">pd</span><span class="p">[</span><span class="n">virtualAddress</span><span class="p">.</span><span class="n">PdIndex</span><span class="p">].</span><span class="n">Present</span><span class="p">)</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

    <span class="c1">// 2MB large page</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">pd</span><span class="p">[</span><span class="n">virtualAddress</span><span class="p">.</span><span class="n">PdIndex</span><span class="p">].</span><span class="n">LargePage</span><span class="p">)</span>
        <span class="k">return</span> <span class="p">(</span><span class="n">pd</span><span class="p">[</span><span class="n">virtualAddress</span><span class="p">.</span><span class="n">PdIndex</span><span class="p">].</span><span class="n">PageFrameNumber</span> <span class="o">&lt;&lt;</span> <span class="mi">12</span><span class="p">)</span> <span class="o">+</span> <span class="n">virtualAddress</span><span class="p">.</span><span class="n">Offset</span><span class="p">;</span>

    <span class="n">PTE_64</span><span class="o">*</span> <span class="n">pt</span> <span class="o">=</span> <span class="p">(</span><span class="n">PTE_64</span><span class="o">*</span><span class="p">)</span><span class="n">MemoryMapPage</span><span class="p">(</span><span class="n">pd</span><span class="p">[</span><span class="n">virtualAddress</span><span class="p">.</span><span class="n">PdIndex</span><span class="p">].</span><span class="n">PageFrameNumber</span> <span class="o">&lt;&lt;</span> <span class="mi">12</span><span class="p">,</span> <span class="n">mapType</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pt</span> <span class="o">||</span> <span class="o">!</span><span class="n">pt</span><span class="p">[</span><span class="n">virtualAddress</span><span class="p">.</span><span class="n">PtIndex</span><span class="p">].</span><span class="n">Present</span><span class="p">)</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

    <span class="k">return</span> <span class="p">(</span><span class="n">pt</span><span class="p">[</span><span class="n">virtualAddress</span><span class="p">.</span><span class="n">PtIndex</span><span class="p">].</span><span class="n">PageFrameNumber</span> <span class="o">&lt;&lt;</span> <span class="mi">12</span><span class="p">)</span> <span class="o">+</span> <span class="n">virtualAddress</span><span class="p">.</span><span class="n">Offset</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">UINT64</span> <span class="nf">MemoryMapGuestVirtual</span><span class="p">(</span><span class="k">const</span> <span class="n">UINT64</span> <span class="n">directoryBase</span><span class="p">,</span> <span class="k">const</span> <span class="n">UINT64</span> <span class="n">virtualAddress</span><span class="p">,</span> <span class="k">const</span> <span class="k">enum</span> <span class="n">MapType</span> <span class="n">mapType</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">guestPhysical</span> <span class="o">=</span> <span class="n">MemoryTranslateGuestVirtual</span><span class="p">(</span><span class="n">directoryBase</span><span class="p">,</span> <span class="n">virtualAddress</span><span class="p">,</span> <span class="n">mapType</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">guestPhysical</span><span class="p">)</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

    <span class="k">return</span> <span class="n">MemoryMapPage</span><span class="p">(</span><span class="n">guestPhysical</span><span class="p">,</span> <span class="n">mapType</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">EFI_STATUS</span> <span class="nf">MemoryCopyGuestVirtual</span><span class="p">(</span><span class="k">const</span> <span class="n">UINT64</span> <span class="n">dirbaseSource</span><span class="p">,</span> <span class="n">UINT64</span> <span class="n">virtualSource</span><span class="p">,</span> <span class="k">const</span> <span class="n">UINT64</span> <span class="n">dirbaseDestination</span><span class="p">,</span> <span class="n">UINT64</span> <span class="n">virtualDestination</span><span class="p">,</span> <span class="n">UINT64</span> <span class="n">size</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">size</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">UINT64</span> <span class="n">destSize</span> <span class="o">=</span> <span class="n">PAGE_SIZE</span> <span class="o">-</span> <span class="p">(</span><span class="n">virtualDestination</span> <span class="o">&amp;</span> <span class="n">PAGE_MASK</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">size</span> <span class="o">&lt;</span> <span class="n">destSize</span><span class="p">)</span>
            <span class="n">destSize</span> <span class="o">=</span> <span class="n">size</span><span class="p">;</span>

        <span class="n">UINT64</span> <span class="n">srcSize</span> <span class="o">=</span> <span class="n">PAGE_SIZE</span> <span class="o">-</span> <span class="p">(</span><span class="n">virtualSource</span> <span class="o">&amp;</span> <span class="n">PAGE_MASK</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">size</span> <span class="o">&lt;</span> <span class="n">srcSize</span><span class="p">)</span>
            <span class="n">srcSize</span> <span class="o">=</span> <span class="n">size</span><span class="p">;</span>

        <span class="n">VOID</span><span class="o">*</span> <span class="n">mappedSrc</span> <span class="o">=</span> <span class="p">(</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">MemoryMapGuestVirtual</span><span class="p">(</span><span class="n">dirbaseSource</span><span class="p">,</span> <span class="n">virtualSource</span><span class="p">,</span> <span class="n">MapSource</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">mappedSrc</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">EFI_INVALID_PARAMETER</span><span class="p">;</span>

        <span class="n">VOID</span><span class="o">*</span> <span class="n">mappedDest</span> <span class="o">=</span> <span class="p">(</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">MemoryMapGuestVirtual</span><span class="p">(</span><span class="n">dirbaseDestination</span><span class="p">,</span> <span class="n">virtualDestination</span><span class="p">,</span> <span class="n">MapDestination</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">mappedDest</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">EFI_INVALID_PARAMETER</span><span class="p">;</span>

        <span class="k">const</span> <span class="n">UINT64</span> <span class="n">currentSize</span> <span class="o">=</span> <span class="p">(</span><span class="n">destSize</span> <span class="o">&lt;</span> <span class="n">srcSize</span><span class="p">)</span> <span class="o">?</span> <span class="n">destSize</span> <span class="o">:</span> <span class="n">srcSize</span><span class="p">;</span>
        <span class="n">InternalCopyMemory</span><span class="p">(</span><span class="n">mappedDest</span><span class="p">,</span> <span class="n">mappedSrc</span><span class="p">,</span> <span class="n">currentSize</span><span class="p">);</span>

        <span class="n">virtualSource</span> <span class="o">+=</span> <span class="n">currentSize</span><span class="p">;</span>
        <span class="n">virtualDestination</span> <span class="o">+=</span> <span class="n">currentSize</span><span class="p">;</span>
        <span class="n">size</span> <span class="o">-=</span> <span class="n">currentSize</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">EFI_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>But hold on a second, the function name has “guest” in it. Does it copy the guest memory then? How is that possible though? This code runs on the host in the Hyper-V context, and the directory table base address is going to be a <a href="https://projectacrn.github.io/latest/developer-guides/hld/hv-memmgt.html">guest physical address (GPA)</a> and not a <a href="https://projectacrn.github.io/latest/developer-guides/hld/hv-memmgt.html">host physical address (HPA)</a>.</p>

<p>Well, to put it simply, Hyper-V uses a 1:1 physical memory mapping for both VTL0 and VTL1. This means that if something is running on the system under Hyper-V in VTL0 and has data at physical address <code class="language-plaintext highlighter-rouge">0x20000</code>, it will correspond to the actual host physical address <code class="language-plaintext highlighter-rouge">0x20000</code>. This is for practical reasons, since Hyper-V, for example, needs to copy data between VTLs. Having the same physical address makes mapping the same physical memory region to both VTLs much more straightforward.</p>

<p>If you are wondering how this looks from the VTL0 perspective, if you write a kernel-mode driver that scans the whole physical memory, you will find that certain regions where Hyper-V components and <code class="language-plaintext highlighter-rouge">securekernel.exe</code> with its processes are located will just appear as empty and read-only, or they will be completely invalid (attempting to read them will crash the system).</p>

<p><img src="/assets/img/posts/11_memory_fail.png" alt="windbg" /></p>

<p><em><a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/">WinDbg</a> kernel-mode debugger attached to VTL0, trying to read <code class="language-plaintext highlighter-rouge">securekernel.exe</code> base physical address</em></p>

<p>This makes things <strong>significantly</strong> easier for us, since we can just get the directory table base from VTL0 or VTL1 and directly use it without worrying about where it comes from or doing any additional GPA -&gt; HPA translation.</p>

<h2 id="securekernelexe">securekernel.exe</h2>
<p>In theory, there are many ways we can now proceed to get to the game’s enclave, but having information about where <code class="language-plaintext highlighter-rouge">securekernel.exe</code> is loaded and its image size will help us a lot (as you’ll see later).</p>

<p>To get this information, we will use the fact that when <code class="language-plaintext highlighter-rouge">securekernel.exe</code> wants to <a href="https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/hypercalls/hvcallvtlreturn">return execution back to VTL0</a> (for example, after a call to load the enclave module) or to call some function in VTL0 directly, it will use a custom <a href="https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/hypercall-interface">hypercall</a> to do so.</p>

<p>We can see it in the <code class="language-plaintext highlighter-rouge">SkCallNormalMode()</code> function, where the variable <code class="language-plaintext highlighter-rouge">ShvlpVtlReturn</code> stores a function address that then performs the actual hypercall. Since the instruction to perform the hypercall depends on the platform (<code class="language-plaintext highlighter-rouge">VMCALL</code> for Intel, <code class="language-plaintext highlighter-rouge">VMMCALL</code> for AMD), this function is allocated at runtime and therefore does not directly reside in any of <code class="language-plaintext highlighter-rouge">securekernel.exe</code> sections.</p>

<p><img src="/assets/img/posts/11_vtlreturn.png" alt="vtlreturn" /></p>

<p>We can intercept this hypercall (in my case on AMD, the <code class="language-plaintext highlighter-rouge">VMMCALL</code> instruction) and then compare the <code class="language-plaintext highlighter-rouge">RCX</code> register value, which is used as the hypercall code. These codes are documented by Microsoft themselves in their <a href="https://github.com/MicrosoftDocs/Virtualization-Documentation/blob/main/tlfs/Hypervisor%20Top%20Level%20Functional%20Specification%20v5.0C.pdf">Hypervisor Top Level Functional Specification</a>. The call we are looking for is <code class="language-plaintext highlighter-rouge">HvCallVtlReturn</code> with call code <code class="language-plaintext highlighter-rouge">0x0012</code>.</p>

<p><img src="/assets/img/posts/11_hypercall_spec.png" alt="hypercallspec" /></p>

<p>Which means that the code can then look something like this:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define HvCallVtlReturn 0x0012
</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">CalledVtlReturn</span> <span class="o">&amp;&amp;</span> <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">ExitCode</span> <span class="o">==</span> <span class="n">VMEXIT_VMMCALL</span> <span class="o">&amp;&amp;</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">Rcx</span> <span class="o">==</span> <span class="n">HvCallVtlReturn</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">CalledVtlReturn</span> <span class="o">=</span> <span class="n">TRUE</span><span class="p">;</span>
    <span class="n">HandleVTL1ToVTL0</span><span class="p">(</span><span class="n">vmcb</span><span class="p">,</span> <span class="n">context</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now that we have intercepted it, we can get the <code class="language-plaintext highlighter-rouge">securekernel.exe</code> information by searching the memory downward for <a href="https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#ms-dos-stub-image-only">PE headers</a> (just as we did in the <code class="language-plaintext highlighter-rouge">GetVariable()</code> hook). But watch out—remember how I mentioned that the <code class="language-plaintext highlighter-rouge">ShvlpVtlReturn</code> value is not in any <code class="language-plaintext highlighter-rouge">securekernel.exe</code> section and is instead allocated at runtime? As such, we cannot just take the current execution location from <code class="language-plaintext highlighter-rouge">RIP</code>; instead, we have to read the return address from the stack (which is going to point somewhere in <code class="language-plaintext highlighter-rouge">SkCallNormalMode</code>, for example).</p>

<p>To do that, we will map the <code class="language-plaintext highlighter-rouge">RSP</code> register value, read the first value on top of the stack (which is going to be at <code class="language-plaintext highlighter-rouge">0x0</code>), and then use this value to iterate through memory until we find <code class="language-plaintext highlighter-rouge">securekernel.exe</code>’s headers.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">VOID</span> <span class="nf">HandleVTL1ToVTL0</span><span class="p">(</span><span class="k">const</span> <span class="n">PVMCB_CONTROL_AREA</span> <span class="n">vmcb</span><span class="p">,</span> <span class="n">PGUEST_CONTEXT</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">rspPhysical</span> <span class="o">=</span> <span class="n">MemoryTranslateGuestVirtual</span><span class="p">(</span><span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Cr3</span><span class="p">,</span> <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Rsp</span><span class="p">,</span> <span class="n">MapSource</span><span class="p">);</span>
    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">rspMapped</span> <span class="o">=</span> <span class="n">MemoryMapPage</span><span class="p">(</span><span class="n">rspPhysical</span><span class="p">,</span> <span class="n">MapSource</span><span class="p">);</span>

    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">returnAddress</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">UINT64</span><span class="o">*</span><span class="p">)</span><span class="n">rspMapped</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">returnPhysical</span> <span class="o">=</span> <span class="n">MemoryTranslateGuestVirtual</span><span class="p">(</span><span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Cr3</span><span class="p">,</span> <span class="n">returnAddress</span><span class="p">,</span> <span class="n">MapSource</span><span class="p">);</span>

    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">"VTL1 to VTL0 transition:</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - RIP: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Rip</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - RSP: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Rsp</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - CR3: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Cr3</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - Stack physical: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">rspPhysical</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - Stack mapped: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">rspMapped</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - Return virtual: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">returnAddress</span><span class="p">);</span>
    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - Return physical: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">returnPhysical</span><span class="p">);</span>

    <span class="n">UINT64</span> <span class="n">currentPagePhysical</span> <span class="o">=</span> <span class="n">returnPhysical</span> <span class="o">&amp;</span> <span class="o">~</span><span class="mh">0xFFF</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">searchLimit</span> <span class="o">=</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">64</span><span class="p">;</span> <span class="c1">// 64mb</span>
    <span class="n">UINT64</span> <span class="n">searchCount</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="k">while</span> <span class="p">(</span><span class="n">searchCount</span> <span class="o">&lt;</span> <span class="n">searchLimit</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">const</span> <span class="n">UINT64</span> <span class="n">currentPageMapped</span> <span class="o">=</span> <span class="n">MemoryMapPage</span><span class="p">(</span><span class="n">currentPagePhysical</span><span class="p">,</span> <span class="n">MapSource</span><span class="p">);</span>
        <span class="k">const</span> <span class="n">PIMAGE_DOS_HEADER</span> <span class="n">dosHeader</span> <span class="o">=</span> <span class="p">(</span><span class="n">PIMAGE_DOS_HEADER</span><span class="p">)</span><span class="n">currentPageMapped</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">dosHeader</span><span class="o">-&gt;</span><span class="n">e_magic</span> <span class="o">==</span> <span class="n">IMAGE_DOS_SIGNATURE</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">dosHeader</span><span class="o">-&gt;</span><span class="n">e_lfanew</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">dosHeader</span><span class="o">-&gt;</span><span class="n">e_lfanew</span> <span class="o">&lt;</span> <span class="mi">4096</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">IMAGE_NT_HEADERS</span><span class="p">))</span>
            <span class="p">{</span>
                <span class="k">const</span> <span class="n">PIMAGE_NT_HEADERS</span> <span class="n">ntHeaders</span> <span class="o">=</span> <span class="p">(</span><span class="n">PIMAGE_NT_HEADERS</span><span class="p">)((</span><span class="n">UINT64</span><span class="p">)</span><span class="n">dosHeader</span> <span class="o">+</span> <span class="n">dosHeader</span><span class="o">-&gt;</span><span class="n">e_lfanew</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">ntHeaders</span><span class="o">-&gt;</span><span class="n">Signature</span> <span class="o">==</span> <span class="n">IMAGE_NT_SIGNATURE</span><span class="p">)</span>
                <span class="p">{</span>
                    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">"Found securekernel.exe:</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
                    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - Base virtual: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ntHeaders</span><span class="o">-&gt;</span><span class="n">OptionalHeader</span><span class="p">.</span><span class="n">ImageBase</span><span class="p">);</span>
                    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - Base physical: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">currentPagePhysical</span><span class="p">);</span>
                    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - Checksum: 0x%x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ntHeaders</span><span class="o">-&gt;</span><span class="n">OptionalHeader</span><span class="p">.</span><span class="n">CheckSum</span><span class="p">);</span>
                    <span class="n">DebugFormat</span><span class="p">(</span><span class="s">" - Size of image: 0x%x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ntHeaders</span><span class="o">-&gt;</span><span class="n">OptionalHeader</span><span class="p">.</span><span class="n">SizeOfImage</span><span class="p">);</span>

                    <span class="n">SecureKernelInfo</span><span class="p">.</span><span class="n">BaseAddressVirtual</span> <span class="o">=</span> <span class="n">ntHeaders</span><span class="o">-&gt;</span><span class="n">OptionalHeader</span><span class="p">.</span><span class="n">ImageBase</span><span class="p">;</span>
                    <span class="n">SecureKernelInfo</span><span class="p">.</span><span class="n">BaseAddressPhysical</span> <span class="o">=</span> <span class="n">currentPagePhysical</span><span class="p">;</span>
                    <span class="n">SecureKernelInfo</span><span class="p">.</span><span class="n">Size</span> <span class="o">=</span> <span class="n">ntHeaders</span><span class="o">-&gt;</span><span class="n">OptionalHeader</span><span class="p">.</span><span class="n">SizeOfImage</span><span class="p">;</span>
                    <span class="n">SecureKernelInfo</span><span class="p">.</span><span class="n">CR3</span> <span class="o">=</span> <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Cr3</span><span class="p">;</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="n">currentPagePhysical</span> <span class="o">-=</span> <span class="mi">4096</span><span class="p">;</span>
        <span class="n">searchCount</span> <span class="o">+=</span> <span class="mi">4096</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">searchCount</span> <span class="o">&gt;=</span> <span class="n">searchLimit</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">DebugFormat</span><span class="p">(</span><span class="s">"No image was found</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="enclave">Enclave</h2>
<p>So, what do we actually need to access the enclave’s memory? We need to figure out where in memory it’s located. Either find its <a href="https://en.wikipedia.org/wiki/Virtual_memory">physical address</a> directly or find its <a href="https://en.wikipedia.org/wiki/Virtual_memory">virtual address</a> and <a href="https://en.wikipedia.org/wiki/Page_table">directory table base</a> so we can translate it to physical.</p>

<p>The first thing that could come to your mind is to find some structure in <code class="language-plaintext highlighter-rouge">securekernel.exe</code> that would hold that information. That is not super easy, though, since the internal process list <code class="language-plaintext highlighter-rouge">SkpsProcessList</code>, which is referenced in functions like <code class="language-plaintext highlighter-rouge">SkpsInitializeProcess()</code>, does not hold the enclave’s process information. Enclaves are loaded in the <code class="language-plaintext highlighter-rouge">securekernel.exe</code>’s system process (<code class="language-plaintext highlighter-rouge">PsIumSystemProcess</code>) and therefore are not in the process list at all. Theoretically, we could traverse some internal structures from the system process to find the enclave module, but that seems like a lot of work given that these structures are not documented.</p>

<p><img src="/assets/img/posts/11_process_list.png" alt="processlist" /></p>

<p>Another possibility would be to iterate the <a href="https://learn.microsoft.com/en-us/windows/win32/sysinfo/handles-and-objects">handle table</a> and search for objects with the type <code class="language-plaintext highlighter-rouge">SkmiEnclaveType</code>, but that would also mean traversing dozens of undocumented structures.</p>

<p><img src="/assets/img/posts/11_handle_type.png" alt="handletype" /></p>

<p>Fortunately, we don’t need to do any of that. Instead, we can use a similar trick as with <code class="language-plaintext highlighter-rouge">securekernel.exe</code>. The <a href="https://learn.microsoft.com/en-us/cpp/c-runtime-library/crt-library-features?view=msvc-170">C runtime (CRT)</a> used by the <a href="https://learn.microsoft.com/en-us/cpp/build/reference/compiler-options?view=msvc-170">MSVC compiler</a> (and other compilers as well) includes <code class="language-plaintext highlighter-rouge">CPUID</code> instruction calls to, for example, check which <a href="https://en.wikipedia.org/wiki/Advanced_Vector_Extensions">Advanced Vector Extensions (AVX) version</a> (if any) is available.</p>

<p>This means that we can assume that a VM exit due to <code class="language-plaintext highlighter-rouge">CPUID</code> will occur at least once from within the enclave when it gets loaded. How do we tell that we are in the enclave itself?</p>

<p>We are going to use the previously found <code class="language-plaintext highlighter-rouge">securekernel.exe</code> information to check the <code class="language-plaintext highlighter-rouge">LSTAR</code> value against. <code class="language-plaintext highlighter-rouge">LSTAR</code> contains the address of <a href="https://en.wikipedia.org/wiki/System_call">system calls</a> entry point, so in VTL0 it will be pointing somewhere into <code class="language-plaintext highlighter-rouge">ntoskrnl.exe</code> and in VTL1 to <code class="language-plaintext highlighter-rouge">securekernel.exe</code>. Then, we are just going to filter only user-mode intercepts by checking that the current execution address (in <code class="language-plaintext highlighter-rouge">RIP</code>) is a <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/gettingstarted/virtual-address-spaces">user-mode address</a> (below <code class="language-plaintext highlighter-rouge">0x7FFFFFFFFFFF</code>). If those conditions are met, we are just going to save the current execution address and the current <code class="language-plaintext highlighter-rouge">CR3</code> value for later use.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">ExitCode</span> <span class="o">==</span> <span class="n">VMEXIT_CPUID</span> <span class="o">&amp;&amp;</span> 
    <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Rip</span> <span class="o">&lt;</span> <span class="mh">0x7FFFFFFFFFFF</span> <span class="o">&amp;&amp;</span>
    <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">LStar</span> <span class="o">&gt;</span> <span class="n">SecureKernelInfo</span><span class="p">.</span><span class="n">BaseAddressVirtual</span> <span class="o">&amp;&amp;</span>
    <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">LStar</span> <span class="o">&lt;</span> <span class="n">SecureKernelInfo</span><span class="p">.</span><span class="n">BaseAddressVirtual</span> <span class="o">+</span> <span class="n">SecureKernelInfo</span><span class="p">.</span><span class="n">Size</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">EnclaveInfo</span><span class="p">.</span><span class="n">TotalCalls</span><span class="o">++</span><span class="p">;</span>
    <span class="n">EnclaveInfo</span><span class="p">.</span><span class="n">LastRip</span> <span class="o">=</span> <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Rip</span><span class="p">;</span>
    <span class="n">EnclaveInfo</span><span class="p">.</span><span class="n">LastCR3</span> <span class="o">=</span> <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Cr3</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now we are going to add more commands to our <code class="language-plaintext highlighter-rouge">CPUID</code> backdoor to initialize the paging structures, translate and copy memory, and retrieve the information we have gathered. Since we need to get and return more data, we will use a struct that will be read and then written back to the control process (<code class="language-plaintext highlighter-rouge">COMMAND_DATA</code>).</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">VOID</span> <span class="nf">HandleCPUID</span><span class="p">(</span><span class="k">const</span> <span class="n">PVMCB_CONTROL_AREA</span> <span class="n">vmcb</span><span class="p">,</span> <span class="k">const</span> <span class="n">PGUEST_CONTEXT</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">COMMAND_DATA</span> <span class="n">data</span><span class="p">;</span>
    <span class="k">switch</span> <span class="p">(</span><span class="n">context</span><span class="o">-&gt;</span><span class="n">Rdx</span><span class="p">)</span>
    <span class="p">{</span>
    <span class="k">case</span> <span class="n">COMMAND_CHECK_PRESENCE</span><span class="p">:</span>
        <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Rax</span> <span class="o">=</span> <span class="n">CPUID_RETURN_VALUE</span><span class="p">;</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="k">case</span> <span class="n">COMMAND_INIT_MEMORY</span><span class="p">:</span>
        <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Rax</span> <span class="o">=</span> <span class="n">MemoryInit</span><span class="p">();</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="k">case</span> <span class="n">COMMAND_GET_CR3</span><span class="p">:</span>
        <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Rax</span> <span class="o">=</span> <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Cr3</span><span class="p">;</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="k">case</span> <span class="n">COMMAND_VIRTUAL_MEMORY_COPY</span><span class="p">:</span>
        <span class="n">data</span> <span class="o">=</span> <span class="n">GetCommand</span><span class="p">(</span><span class="n">vmcb</span><span class="p">,</span> <span class="n">context</span><span class="p">);</span>
        <span class="n">vmcb</span><span class="o">-&gt;</span><span class="n">Rax</span> <span class="o">=</span> <span class="n">MemoryCopyGuestVirtual</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">VirtualMemoryCopy</span><span class="p">.</span><span class="n">SourceCr3</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">VirtualMemoryCopy</span><span class="p">.</span><span class="n">SourceAddress</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">VirtualMemoryCopy</span><span class="p">.</span><span class="n">DestinationCr3</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">VirtualMemoryCopy</span><span class="p">.</span><span class="n">DestinationAddress</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">VirtualMemoryCopy</span><span class="p">.</span><span class="n">Size</span><span class="p">);</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="k">case</span> <span class="n">COMMAND_SECUREKERNEL_INFO</span><span class="p">:</span>
        <span class="n">data</span><span class="p">.</span><span class="n">SecureKernelData</span><span class="p">.</span><span class="n">BaseVirtual</span> <span class="o">=</span> <span class="n">SecureKernelInfo</span><span class="p">.</span><span class="n">BaseAddressVirtual</span><span class="p">;</span>
        <span class="n">data</span><span class="p">.</span><span class="n">SecureKernelData</span><span class="p">.</span><span class="n">BasePhysical</span> <span class="o">=</span> <span class="n">SecureKernelInfo</span><span class="p">.</span><span class="n">BaseAddressPhysical</span><span class="p">;</span>
        <span class="n">data</span><span class="p">.</span><span class="n">SecureKernelData</span><span class="p">.</span><span class="n">Size</span> <span class="o">=</span> <span class="n">SecureKernelInfo</span><span class="p">.</span><span class="n">Size</span><span class="p">;</span>
        <span class="n">data</span><span class="p">.</span><span class="n">SecureKernelData</span><span class="p">.</span><span class="n">CR3</span> <span class="o">=</span> <span class="n">SecureKernelInfo</span><span class="p">.</span><span class="n">CR3</span><span class="p">;</span>
        <span class="n">SetCommand</span><span class="p">(</span><span class="n">vmcb</span><span class="p">,</span> <span class="n">context</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">data</span><span class="p">);</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="k">case</span> <span class="n">COMMAND_ENCLAVE_INFO</span><span class="p">:</span>
        <span class="n">data</span><span class="p">.</span><span class="n">EnclaveData</span><span class="p">.</span><span class="n">TotalCalls</span> <span class="o">=</span> <span class="n">EnclaveInfo</span><span class="p">.</span><span class="n">TotalCalls</span><span class="p">;</span>
        <span class="n">data</span><span class="p">.</span><span class="n">EnclaveData</span><span class="p">.</span><span class="n">LastRip</span> <span class="o">=</span> <span class="n">EnclaveInfo</span><span class="p">.</span><span class="n">LastRip</span><span class="p">;</span>
        <span class="n">data</span><span class="p">.</span><span class="n">EnclaveData</span><span class="p">.</span><span class="n">LastCR3</span> <span class="o">=</span> <span class="n">EnclaveInfo</span><span class="p">.</span><span class="n">LastCR3</span><span class="p">;</span>
        <span class="n">SetCommand</span><span class="p">(</span><span class="n">vmcb</span><span class="p">,</span> <span class="n">context</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">data</span><span class="p">);</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="nl">default:</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="cm">/* In the control process */</span>
<span class="kr">inline</span> <span class="n">UINT64</span> <span class="nf">CopyVirtual</span><span class="p">(</span><span class="k">const</span> <span class="n">UINT64</span> <span class="n">sourceCr3</span><span class="p">,</span> <span class="k">const</span> <span class="n">UINT64</span> <span class="n">sourceAddress</span><span class="p">,</span> <span class="k">const</span> <span class="n">UINT64</span> <span class="n">destinationCr3</span><span class="p">,</span> <span class="k">const</span> <span class="n">UINT64</span> <span class="n">destinationAddress</span><span class="p">,</span> <span class="k">const</span> <span class="n">UINT32</span> <span class="n">size</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">COMMAND_DATA</span> <span class="n">data</span><span class="p">;</span>
    <span class="n">data</span><span class="p">.</span><span class="n">VirtualMemoryCopy</span><span class="p">.</span><span class="n">SourceCr3</span> <span class="o">=</span> <span class="n">sourceCr3</span><span class="p">;</span>
    <span class="n">data</span><span class="p">.</span><span class="n">VirtualMemoryCopy</span><span class="p">.</span><span class="n">SourceAddress</span> <span class="o">=</span> <span class="n">sourceAddress</span><span class="p">;</span>
    <span class="n">data</span><span class="p">.</span><span class="n">VirtualMemoryCopy</span><span class="p">.</span><span class="n">DestinationCr3</span> <span class="o">=</span> <span class="n">destinationCr3</span><span class="p">;</span>
    <span class="n">data</span><span class="p">.</span><span class="n">VirtualMemoryCopy</span><span class="p">.</span><span class="n">DestinationAddress</span> <span class="o">=</span> <span class="n">destinationAddress</span><span class="p">;</span>
    <span class="n">data</span><span class="p">.</span><span class="n">VirtualMemoryCopy</span><span class="p">.</span><span class="n">Size</span> <span class="o">=</span> <span class="n">size</span><span class="p">;</span>

    <span class="k">return</span> <span class="n">ExecuteCPUID</span><span class="p">(</span><span class="n">CPUID_BACKDOOR</span><span class="p">,</span> <span class="n">COMMAND_VIRTUAL_MEMORY_COPY</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">data</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Let’s try to read the enclave’s memory. For now, just the PE headers information.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">Entry</span><span class="p">()</span>
<span class="p">{</span>
    <span class="cm">/* ... */</span>
    
    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">cr3</span> <span class="o">=</span> <span class="n">Control</span><span class="o">::</span><span class="n">GetCR3</span><span class="p">();</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"VM:</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">" - CR3: 0x%llX</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">cr3</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    
    <span class="cm">/* ... */</span>

    <span class="k">const</span> <span class="n">SECUREKERNEL_DATA</span> <span class="n">data</span> <span class="o">=</span> <span class="n">Control</span><span class="o">::</span><span class="n">GetSecureKernelInfo</span><span class="p">();</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"securekernel.exe:</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">" - Base physical: 0x%llX</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">BasePhysical</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">" - Base virtual: 0x%llX</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">BaseVirtual</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">" - Size: 0x%llX</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">Size</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">" - CR3: 0x%llX</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">CR3</span><span class="p">);</span>

    <span class="cm">/* ... */</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">"Waiting for SecureGame.exe process...</span><span class="se">\n\n</span><span class="s">"</span><span class="p">);</span>
    <span class="n">DWORD</span> <span class="n">pid</span><span class="p">;</span>
    <span class="k">do</span>
    <span class="p">{</span>
        <span class="n">pid</span> <span class="o">=</span> <span class="n">Utils</span><span class="o">::</span><span class="n">GetPidByName</span><span class="p">(</span><span class="s">L"SecureGame.exe"</span><span class="p">);</span>
        <span class="n">Sleep</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">pid</span><span class="p">);</span>

    <span class="n">Sleep</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>

    <span class="cm">/* ... */</span>

    <span class="k">const</span> <span class="n">ENCLAVE_DATA</span> <span class="n">enclave</span> <span class="o">=</span> <span class="n">Control</span><span class="o">::</span><span class="n">GetEnclaveInfo</span><span class="p">();</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">"Enclave:</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">" - Total calls: %llu</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">enclave</span><span class="p">.</span><span class="n">TotalCalls</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">" - Last RIP: 0x%llX</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">enclave</span><span class="p">.</span><span class="n">LastRip</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">" - Last CR3: 0x%llX</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">enclave</span><span class="p">.</span><span class="n">LastCR3</span><span class="p">);</span>

    <span class="k">const</span> <span class="n">UINT64</span> <span class="n">moduleBase</span> <span class="o">=</span> <span class="n">FindEnclaveBase</span><span class="p">(</span><span class="n">cr3</span><span class="p">,</span> <span class="n">enclave</span><span class="p">.</span><span class="n">LastCR3</span><span class="p">,</span> <span class="n">enclave</span><span class="p">.</span><span class="n">LastRip</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">moduleBase</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Failed to find module base!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">" - Headers: 0x%llX</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">moduleBase</span><span class="p">);</span>

    <span class="n">IMAGE_DOS_HEADER</span> <span class="n">dosHeader</span> <span class="o">=</span> <span class="p">{};</span>
    <span class="n">IMAGE_NT_HEADERS</span> <span class="n">ntHeader</span> <span class="o">=</span> <span class="p">{};</span>
    <span class="n">Control</span><span class="o">::</span><span class="n">CopyVirtual</span><span class="p">(</span><span class="n">enclave</span><span class="p">.</span><span class="n">LastCR3</span><span class="p">,</span> <span class="n">moduleBase</span><span class="p">,</span> <span class="n">cr3</span><span class="p">,</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">UINT64</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">dosHeader</span><span class="p">),</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dosHeader</span><span class="p">));</span>
    <span class="n">Control</span><span class="o">::</span><span class="n">CopyVirtual</span><span class="p">(</span><span class="n">enclave</span><span class="p">.</span><span class="n">LastCR3</span><span class="p">,</span> <span class="n">moduleBase</span> <span class="o">+</span> <span class="n">dosHeader</span><span class="p">.</span><span class="n">e_lfanew</span><span class="p">,</span> <span class="n">cr3</span><span class="p">,</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">UINT64</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ntHeader</span><span class="p">),</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ntHeader</span><span class="p">));</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">" - Image base: 0x%llX</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ntHeader</span><span class="p">.</span><span class="n">OptionalHeader</span><span class="p">.</span><span class="n">ImageBase</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">" - Checksum: 0x%X</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ntHeader</span><span class="p">.</span><span class="n">OptionalHeader</span><span class="p">.</span><span class="n">CheckSum</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">" - Size of image: 0x%X</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ntHeader</span><span class="p">.</span><span class="n">OptionalHeader</span><span class="p">.</span><span class="n">SizeOfImage</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">" - Timestamp: 0x%X</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ntHeader</span><span class="p">.</span><span class="n">FileHeader</span><span class="p">.</span><span class="n">TimeDateStamp</span><span class="p">);</span>

    <span class="cm">/* ... */</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And this is what we get:</p>

<p><img src="/assets/img/posts/11_enclave_headers.png" alt="enclaveheaders" /></p>

<p>You have probably noticed that there is a bit more information in the screenshot than I have described as needed. That’s because I was experimenting a bit, but I want to highlight one thing. As I mentioned in the <a href="https://tulach.cc/using-vbs-enclaves-for-anti-cheat-purposes/">previous article</a>, a copy of the enclave’s module is loaded in the host process as well. For some reason, I expected the base addresses to match, but as you can see, that’s not the case. Also, the module does not show up when you use the usual <a href="https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot"><code class="language-plaintext highlighter-rouge">CreateToolhelp32Snapshot()</code></a>/<a href="https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-module32first"><code class="language-plaintext highlighter-rouge">Module32First()</code></a> to iterate modules (so it’s not in <code class="language-plaintext highlighter-rouge">PEB</code> module list). The reason why <a href="https://systeminformer.com/">System Informer</a> sees it is because it also uses <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntqueryvirtualmemory"><code class="language-plaintext highlighter-rouge">NtQueryVirtualMemory()</code></a> with <a href="https://processhacker.sourceforge.io/doc/ntmmapi_8h.html#a14b86ef4016bb0d15128d87287e17c10aa4716df7403c096598752ebf8b657701"><code class="language-plaintext highlighter-rouge">MemoryMappedFilenameInformation</code></a> on memory regions with the type of <code class="language-plaintext highlighter-rouge">MEM_IMAGE</code>.</p>

<p><img src="/assets/img/posts/11_different_base.png" alt="differentbase" /></p>

<p>I haven’t really been able to look into this in-depth, though. The actual virtual address of the enclave is committed even in the host process, although it’s just a large region that includes the enclave as well and you can’t read it. So it’s possible that you <em>might</em> be able to get the enclave’s base address in VTL1 from VTL0 user-mode somehow, but I’m not sure.</p>

<h2 id="writing-the-cheat">Writing the cheat</h2>
<p>Now that we can access the memory of the enclave, it’s time to reach our goal:</p>

<blockquote>
  <p>And to make it more fun, the final goal will be to modify the score of one of the players in the game.</p>
</blockquote>

<p>Since the game is made by us and we have the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/symbols">debugging symbols</a>, all we have to do is open the enclave image <code class="language-plaintext highlighter-rouge">SecureCore.dll</code> in some disassembler, load the symbols, and search for the variables that store the player’s score.</p>

<p><img src="/assets/img/posts/11_score.png" alt="score" /></p>

<p>As you can see in the screenshot above, the offset from the enclave’s base address to the left player’s score is <code class="language-plaintext highlighter-rouge">0x67AC</code> and to the right player’s score is <code class="language-plaintext highlighter-rouge">0x67BC</code> (the image base IDA uses is <code class="language-plaintext highlighter-rouge">0x180000000</code>).</p>

<p>Let’s try reading the score first:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while</span> <span class="p">(</span><span class="nb">true</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">constexpr</span> <span class="n">UINT64</span> <span class="n">leftScoreOffset</span> <span class="o">=</span> <span class="mh">0x67ac</span><span class="p">;</span>
    <span class="k">constexpr</span> <span class="n">UINT64</span> <span class="n">rightScoreOffset</span> <span class="o">=</span> <span class="mh">0x67bc</span><span class="p">;</span>

    <span class="kt">int</span> <span class="n">leftScore</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">Control</span><span class="o">::</span><span class="n">CopyVirtual</span><span class="p">(</span><span class="n">enclave</span><span class="p">.</span><span class="n">LastCR3</span><span class="p">,</span> <span class="n">moduleBase</span> <span class="o">+</span> <span class="n">leftScoreOffset</span><span class="p">,</span> <span class="n">cr3</span><span class="p">,</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">UINT64</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">leftScore</span><span class="p">),</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">leftScore</span><span class="p">));</span>

    <span class="kt">int</span> <span class="n">rightScore</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">Control</span><span class="o">::</span><span class="n">CopyVirtual</span><span class="p">(</span><span class="n">enclave</span><span class="p">.</span><span class="n">LastCR3</span><span class="p">,</span> <span class="n">moduleBase</span> <span class="o">+</span> <span class="n">rightScoreOffset</span><span class="p">,</span> <span class="n">cr3</span><span class="p">,</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">UINT64</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rightScore</span><span class="p">),</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">rightScore</span><span class="p">));</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">"</span><span class="se">\r</span><span class="s"> - Left score: %i Right score: %i"</span><span class="p">,</span> <span class="n">leftScore</span><span class="p">,</span> <span class="n">rightScore</span><span class="p">);</span>

    <span class="n">Sleep</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><img src="/assets/img/posts/11_score_read.png" alt="scoreread" /></p>

<p>Seems to be working, so let’s try to overwrite it…</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while</span> <span class="p">(</span><span class="nb">true</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Press key to overwrite score...</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="n">getchar</span><span class="p">();</span>

    <span class="k">constexpr</span> <span class="n">UINT64</span> <span class="n">leftScoreOffset</span> <span class="o">=</span> <span class="mh">0x67ac</span><span class="p">;</span>
    <span class="k">constexpr</span> <span class="n">UINT64</span> <span class="n">rightScoreOffset</span> <span class="o">=</span> <span class="mh">0x67bc</span><span class="p">;</span>

    <span class="kt">int</span> <span class="n">leftScore</span> <span class="o">=</span> <span class="mi">999999</span><span class="p">;</span>
    <span class="n">Control</span><span class="o">::</span><span class="n">CopyVirtual</span><span class="p">(</span><span class="n">cr3</span><span class="p">,</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">UINT64</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">leftScore</span><span class="p">),</span> <span class="n">enclave</span><span class="p">.</span><span class="n">LastCR3</span><span class="p">,</span> <span class="n">moduleBase</span> <span class="o">+</span> <span class="n">leftScoreOffset</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">leftScore</span><span class="p">));</span>

    <span class="kt">int</span> <span class="n">rightScore</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">Control</span><span class="o">::</span><span class="n">CopyVirtual</span><span class="p">(</span><span class="n">cr3</span><span class="p">,</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">UINT64</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rightScore</span><span class="p">),</span> <span class="n">enclave</span><span class="p">.</span><span class="n">LastCR3</span><span class="p">,</span> <span class="n">moduleBase</span> <span class="o">+</span> <span class="n">rightScoreOffset</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">rightScore</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>

<p><img src="/assets/img/posts/11_score_write.png" alt="scoreread" /></p>

<p>And finally, it’s done. The complete project can be found <a href="https://github.com/SamuelTulach/SecureHack">here</a>.</p>

<h2 id="detection">Detection</h2>
<p>This might seem like a perfect way to cheat in some video game (regardless of enclave use), since even if there was some <a href="https://en.wikipedia.org/wiki/Category:Anti-cheat_software">anti-cheat</a>, it would have a really hard time figuring out what is going on. This is because it would be running either in user-mode or in kernel-mode but still in VTL0, and it would not have access to Hyper-V or higher VTLs… right?</p>

<p>While definitely stealthier than other methods, there are still quite a few ways you could detect this messing around with Hyper-V, but that’s something for a future article, since this one is already too long.</p>

<p>Just to give you an idea, extending the Hyper-V image will move every single allocation made right after it by its size, which you can then calculate and detect.</p>

<p><img src="/assets/img/posts/11_hv_extensions.png" alt="hvextensions" /></p>

<h2 id="final-words">Final words</h2>
<p>I think this is the longest article I have written yet. I still have trouble trying to balance the technicality of it so it’s not just technical jargon and a bunch of code, or so overly explanatory that it’s boring for the intended audience. If you have any tips for writing in the future, let me know.</p>

<p>Anyway, thanks for reading, and have a nice day.</p>]]></content><author><name>Samuel Tulach</name><email>noreply@tulach.cc</email></author><category term="blog" /><category term="amd64" /><category term="windows" /><category term="kernel" /><category term="anti-cheat" /><category term="hyper-v" /><category term="vbs" /><summary type="html"><![CDATA[Writing a bootkit to manipulate VBS enclave's memory]]></summary></entry><entry><title type="html">Using VBS enclaves for anti-cheat purposes</title><link href="https://tulach.cc/using-vbs-enclaves-for-anti-cheat-purposes/" rel="alternate" type="text/html" title="Using VBS enclaves for anti-cheat purposes" /><published>2024-11-09T23:07:00+01:00</published><updated>2024-11-09T23:07:00+01:00</updated><id>https://tulach.cc/using-vbs-enclaves-for-anti-cheat-purposes</id><content type="html" xml:base="https://tulach.cc/using-vbs-enclaves-for-anti-cheat-purposes/"><![CDATA[<p>A few months ago, when <a href="https://techcommunity.microsoft.com/blog/windowsosplatform/securely-design-your-applications-and-protect-your-sensitive-data-with-vbs-encla/4179543">Microsoft announced VBS (virtualization-based security) enclave functionality</a>, I started wondering whether it could be used for <a href="https://en.wikipedia.org/wiki/Category:Anti-cheat_software">game anti-cheating purposes</a>. While I was skeptical (later I will explain why), I decided to look into it and write <a href="https://github.com/SamuelTulach/SecureGame">a simple Pong-inspired game</a> which handles its entire game logic in such enclave.</p>

<h2 id="what-are-vbs-enclaves">What are VBS enclaves?</h2>
<p><a href="https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/about/">Hyper-V</a> is a <a href="https://learn.microsoft.com/en-us/windows-server/administration/performance-tuning/role/hyper-v-server/architecture">type-1 hypervisor</a>, which means that when enabled, the Windows installation it’s running on becomes <a href="https://www.techtarget.com/searchitoperations/definition/guest-OS-guest-operating-system">a guest</a>.</p>

<p><a href="https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/oem-vbs">Virtualization-based security (VBS)</a> is built on top of the Hyper-V platform. Since it operates at a higher privilege level than the kernel itself, it can help enforce <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/bringup/device-guard-and-credential-guard">strict code signing requirements (HVCI)</a> or <a href="https://learn.microsoft.com/en-us/windows/security/identity-protection/credential-guard/">completely isolate data even from code running at the kernel level</a>.</p>

<p><a href="https://learn.microsoft.com/en-us/windows/win32/trusted-execution/vbs-enclaves">VBS enclaves</a> allow developers to leverage VBS in their applications to isolate code and data from anything else running on the computer. Think of it as running Windows inside <a href="https://www.virtualbox.org/">VirtualBox</a> or <a href="https://www.vmware.com/products/desktop-hypervisor/workstation-and-fusion">VMware Player</a> as one VM, with this isolated environment as another. Neither can access the other unless the hosting application explicitly permits it.</p>

<p><img src="/assets/img/posts/10_trusted-execution-enclaves-diagram.png" alt="diagram" /></p>

<p><em>Trusted execution enclaves using VBS (<a href="https://learn.microsoft.com/en-us/windows/win32/trusted-execution/vbs-enclaves">source</a>)</em></p>

<h2 id="anti-cheat">Anti-cheat?</h2>
<p>Traditionally on Windows, anti-cheat software utilizes kernel-mode drivers to (among other things) prevent any user-mode programs from accessing the game’s memory. I have written about it in more detail <a href="https://tulach.cc/the-issue-of-anti-cheat-on-linux/">here</a>.</p>

<p>The idea of using VBS enclaves is that we could run parts of the game in this isolated environment. That would mean that even if cheat developers somehow got kernel-mode code execution, they would still not be able to manipulate the code and data in this enclave.</p>

<h2 id="a-bit-of-skepticism">A bit of skepticism</h2>
<p>While it sounds like a good idea on paper, there are currently a few problems with the use of VBS enclaves:</p>

<ul>
  <li><strong>Limited APIs</strong> - You can’t just take an existing program and tell Windows to run it in an enclave. It has to be a library (.DLL) specifically designed to run in such enclave which then needs a host process. There is a <a href="https://learn.microsoft.com/en-us/windows/win32/trusted-execution/available-in-enclaves">very limited set of APIs available</a> (<em>libvcruntime</em>, <em>vertdll</em>, <em>UCRT</em>, <em>bcrypt</em>).</li>
  <li><strong>Performance</strong> - Since virtualization is involved, there will always be some overhead, which in other use-cases is not that significant, but for a game trying to run logic or get data in/out of the enclave on each frame, this overhead is something that developers have to account for.</li>
  <li><strong>Security</strong> - While in theory, the enclave should be absolutely impenetrable, in practice, cheat developers will always have the edge unless the system becomes completely locked down including the firmware. While it’s true that the enclave is inaccessible to anything running <em>inside</em> the OS (since anything running inside the OS will run inside the virtualized environment), nothing is stopping cheat developers from writing a bootkit that will load before the OS even starts, therefore before any virtualization takes place. With a clever hook chain, they can gain complete control over the Hyper-V/VBS architecture itself (something I will be exploring in future blog posts).</li>
</ul>

<p>Regardless of these issues, I decided to write a simple proof-of-concept project to test this idea out and to have something to experiment with in the future.</p>

<h2 id="getting-started">Getting started</h2>
<p>Since VBS enclave functionality is quite new, the available sample projects and documentation are very sparse. In fact, I was not able to find any open-source project utilizing VBS enclaves, so the only starting point I could use was the <a href="https://learn.microsoft.com/en-us/windows/win32/trusted-execution/vbs-enclaves-dev-guide">official documentation</a> and <a href="https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/VbsEnclave">sample project</a>.</p>

<p>Development tools required:</p>
<ul>
  <li><a href="https://visualstudio.microsoft.com/">Visual Studio 2022</a> version 17.9 or later</li>
  <li><a href="https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/">Windows Software Development Kit (SDK)</a> version 10.0.22621.3233 or later</li>
</ul>

<p>Device/OS requirements:</p>
<ul>
  <li>Windows 11 or later or Windows Server 2019 or later</li>
  <li><a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/bringup/device-guard-and-credential-guard">VBS/HVCI</a> must be enabled</li>
  <li>For debugging and running enclaves without production signing, <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/install/the-testsigning-boot-configuration-option">test-signing must be enabled</a></li>
</ul>

<p>I used <a href="https://www.vmware.com/products/desktop-hypervisor/workstation-and-fusion">VMware Workstation</a> to run the latest version of Windows 11. Don’t forget to enable nested virtualization in setting if you also do so (Virtualize Intel VT-x/EPT or AMD-V/RVI).</p>

<p>Since I wanted something extremely simple, I decided to write a game inspired by the classic <a href="https://en.wikipedia.org/wiki/Pong">Pong</a>. The idea was to have a host process that would handle initialization, window creation, keyboard input and rendering, while the enclave would run the actual game logic.</p>

<p>I created a new solution with two projects: SecureGame, which would be the host process, and SecureCore, which would be the enclave itself.</p>

<p><img src="/assets/img/posts/10_project.png" alt="project" /></p>

<p>I used <a href="https://vcpkg.io/en/">vcpkg</a> to install <a href="https://www.libsdl.org/">SDL2</a> and then started implementing the game logic. There’s nothing special about it; I ended up with a game loop like this:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">Game</span><span class="o">::</span><span class="n">Loop</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">constexpr</span> <span class="kt">int</span> <span class="n">FPS</span> <span class="o">=</span> <span class="mi">240</span><span class="p">;</span>
    <span class="k">constexpr</span> <span class="kt">int</span> <span class="n">frameDelay</span> <span class="o">=</span> <span class="mi">1000</span> <span class="o">/</span> <span class="n">FPS</span><span class="p">;</span>

    <span class="kt">bool</span> <span class="n">running</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">running</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">const</span> <span class="n">Uint32</span> <span class="n">frameStart</span> <span class="o">=</span> <span class="n">SDL_GetTicks</span><span class="p">();</span>

        <span class="n">SDL_Event</span> <span class="n">event</span><span class="p">;</span>
        <span class="k">while</span> <span class="p">(</span><span class="n">SDL_PollEvent</span><span class="p">(</span><span class="o">&amp;</span><span class="n">event</span><span class="p">))</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">event</span><span class="p">.</span><span class="n">type</span> <span class="o">==</span> <span class="n">SDL_QUIT</span><span class="p">)</span>
                <span class="n">running</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">SDL_SetRenderDrawColor</span><span class="p">(</span><span class="n">m_Renderer</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">);</span>
        <span class="n">SDL_RenderClear</span><span class="p">(</span><span class="n">m_Renderer</span><span class="p">);</span>

        <span class="n">Tick</span><span class="p">();</span>

        <span class="n">SDL_RenderPresent</span><span class="p">(</span><span class="n">m_Renderer</span><span class="p">);</span>

        <span class="k">const</span> <span class="kt">int</span> <span class="n">frameTime</span> <span class="o">=</span> <span class="n">SDL_GetTicks</span><span class="p">()</span> <span class="o">-</span> <span class="n">frameStart</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">frameDelay</span> <span class="o">&gt;</span> <span class="n">frameTime</span><span class="p">)</span>
            <span class="n">SDL_Delay</span><span class="p">(</span><span class="n">frameDelay</span> <span class="o">-</span> <span class="n">frameTime</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="n">SDL_DestroyRenderer</span><span class="p">(</span><span class="n">m_Renderer</span><span class="p">);</span>
    <span class="n">SDL_DestroyWindow</span><span class="p">(</span><span class="n">m_Window</span><span class="p">);</span>
    <span class="n">SDL_Quit</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="the-interesting-stuff">The interesting stuff</h2>
<p>When the enclave is loaded, the host process can call any exported function from the enclave library using <code class="language-plaintext highlighter-rouge">CallEnclave()</code>.</p>

<p><img src="/assets/img/posts/10_vbs-enclaves-lifecycle-diagram.png" alt="lifecycle" /></p>

<p><em>Enclave lifecycle (<a href="https://learn.microsoft.com/en-us/windows/win32/trusted-execution/vbs-enclaves-dev-guide">source</a>)</em></p>

<p>There are several ways we could approach using it:</p>

<ol>
  <li>Use it only for save/load - Store and load data from the enclave, keeping the loaded data in host process memory for the least amount of time possible and only for the time they are needed.</li>
  <li>Supply all required input and run the entire game logic inside the enclave. The enclave then returns data needed for rendering, which is performed by the host process.</li>
  <li>Put everything in the enclave - The <code class="language-plaintext highlighter-rouge">CallEnclave()</code> function can also perform a “reverse call” back to the host process <a href="https://learn.microsoft.com/en-us/windows/win32/api/enclaveapi/nf-enclaveapi-callenclave">according to the documentation</a>. We could write a function that allows arbitrary function calls in the host process and then call this single function from within the enclave. This would allow us to put the entire game inside the enclave and work around its limitations.</li>
</ol>

<p>While option 3 seems the most interesting, I went with option 2 to keep things simple.</p>

<p>Initially, I wondered whether you could pass a pointer to host process data into the enclave and access it directly. The documentation doesn’t mention whether the host process memory is accessible to the enclave at all, and to make matters worse, the sample code only passes data values, never pointers.</p>

<p><img src="/assets/img/posts/10_hq_docs.png" alt="docs" /></p>

<p><em><code class="language-plaintext highlighter-rouge">CallEnclave()</code> (very helpful) documentation (<a href="https://learn.microsoft.com/en-us/windows/win32/api/enclaveapi/nf-enclaveapi-callenclave">source</a>)</em></p>

<p>By trying it out, I discovered that the enclave <strong>has access to the host process memory</strong>, so you can pass pointers to data structures inside the host process. With this knowledge, I created a structure to be shared between the enclave and the host process.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_TICK_DATA</span>
<span class="p">{</span>
    <span class="kt">float</span> <span class="n">DeltaTime</span><span class="p">;</span>

    <span class="kt">bool</span> <span class="n">KeyW</span><span class="p">;</span>
    <span class="kt">bool</span> <span class="n">KeyS</span><span class="p">;</span>
    <span class="kt">bool</span> <span class="n">KeyUp</span><span class="p">;</span>
    <span class="kt">bool</span> <span class="n">KeyDown</span><span class="p">;</span>

    <span class="k">struct</span>
    <span class="p">{</span>
        <span class="kt">float</span> <span class="n">X</span><span class="p">;</span>
        <span class="kt">float</span> <span class="n">Y</span><span class="p">;</span>
        <span class="kt">float</span> <span class="n">Width</span><span class="p">;</span>
        <span class="kt">float</span> <span class="n">Height</span><span class="p">;</span>
    <span class="p">}</span> <span class="n">LeftPaddle</span><span class="p">,</span> <span class="n">RightPaddle</span><span class="p">,</span> <span class="n">Ball</span><span class="p">;</span>

    <span class="kt">int</span> <span class="n">LeftScore</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">RightScore</span><span class="p">;</span>
<span class="p">}</span> <span class="n">TICK_DATA</span><span class="p">;</span>
</code></pre></div></div>

<p>This structure would hold information about which keys are pressed on the current tick, the time elapsed since the last frame rendered, and the output from the enclave containing game object positions and score.</p>

<p>The enclave would then implement the game logic like this (peak coding performance, don’t judge):</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">Reset</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">Data</span><span class="o">::</span><span class="n">BallPositionX</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">(</span><span class="n">WINDOW_WIDTH</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span> <span class="o">-</span> <span class="n">BALL_SIZE</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
    <span class="n">Data</span><span class="o">::</span><span class="n">BallPositionY</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">(</span><span class="n">WINDOW_HEIGHT</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span> <span class="o">-</span> <span class="n">BALL_SIZE</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>

    <span class="n">Data</span><span class="o">::</span><span class="n">BallVelocityX</span> <span class="o">=</span> <span class="p">(</span><span class="n">rand</span><span class="p">()</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="o">?</span> <span class="n">BALL_SPEED</span> <span class="o">:</span> <span class="o">-</span><span class="n">BALL_SPEED</span><span class="p">;</span>
    <span class="n">Data</span><span class="o">::</span><span class="n">BallVelocityY</span> <span class="o">=</span> <span class="p">(</span><span class="n">rand</span><span class="p">()</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="o">?</span> <span class="n">BALL_SPEED</span> <span class="o">:</span> <span class="o">-</span><span class="n">BALL_SPEED</span><span class="p">;</span>

    <span class="n">Data</span><span class="o">::</span><span class="n">State</span> <span class="o">=</span> <span class="n">Data</span><span class="o">::</span><span class="n">StateId</span><span class="o">::</span><span class="n">Running</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">Run</span><span class="p">(</span><span class="n">TICK_DATA</span><span class="o">*</span> <span class="n">currentTick</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">const</span> <span class="kt">float</span> <span class="n">deltaTime</span> <span class="o">=</span> <span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">DeltaTime</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">KeyW</span> <span class="o">&amp;&amp;</span> <span class="n">Data</span><span class="o">::</span><span class="n">LeftPaddleY</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">LeftPaddleY</span> <span class="o">-=</span> <span class="n">PADDLE_SPEED</span> <span class="o">*</span> <span class="n">deltaTime</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">KeyS</span> <span class="o">&amp;&amp;</span> <span class="n">Data</span><span class="o">::</span><span class="n">LeftPaddleY</span> <span class="o">&lt;</span> <span class="n">WINDOW_HEIGHT</span> <span class="o">-</span> <span class="n">PADDLE_HEIGHT</span><span class="p">)</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">LeftPaddleY</span> <span class="o">+=</span> <span class="n">PADDLE_SPEED</span> <span class="o">*</span> <span class="n">deltaTime</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">KeyUp</span> <span class="o">&amp;&amp;</span> <span class="n">Data</span><span class="o">::</span><span class="n">RightPaddleY</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">RightPaddleY</span> <span class="o">-=</span> <span class="n">PADDLE_SPEED</span> <span class="o">*</span> <span class="n">deltaTime</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">KeyDown</span> <span class="o">&amp;&amp;</span> <span class="n">Data</span><span class="o">::</span><span class="n">RightPaddleY</span> <span class="o">&lt;</span> <span class="n">WINDOW_HEIGHT</span> <span class="o">-</span> <span class="n">PADDLE_HEIGHT</span><span class="p">)</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">RightPaddleY</span> <span class="o">+=</span> <span class="n">PADDLE_SPEED</span> <span class="o">*</span> <span class="n">deltaTime</span><span class="p">;</span>

    <span class="n">Data</span><span class="o">::</span><span class="n">BallPositionX</span> <span class="o">+=</span> <span class="n">Data</span><span class="o">::</span><span class="n">BallVelocityX</span> <span class="o">*</span> <span class="n">deltaTime</span><span class="p">;</span>
    <span class="n">Data</span><span class="o">::</span><span class="n">BallPositionY</span> <span class="o">+=</span> <span class="n">Data</span><span class="o">::</span><span class="n">BallVelocityY</span> <span class="o">*</span> <span class="n">deltaTime</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">Data</span><span class="o">::</span><span class="n">BallPositionY</span> <span class="o">&lt;=</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">Data</span><span class="o">::</span><span class="n">BallPositionY</span> <span class="o">+</span> <span class="n">BALL_SIZE</span> <span class="o">&gt;=</span> <span class="n">WINDOW_HEIGHT</span><span class="p">)</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">BallVelocityY</span> <span class="o">=</span> <span class="o">-</span><span class="n">Data</span><span class="o">::</span><span class="n">BallVelocityY</span><span class="p">;</span>

    <span class="k">const</span> <span class="kt">bool</span> <span class="n">ballInLeftPaddleYRange</span> <span class="o">=</span> <span class="n">Data</span><span class="o">::</span><span class="n">BallPositionY</span> <span class="o">+</span> <span class="n">BALL_SIZE</span> <span class="o">&gt;=</span> <span class="n">Data</span><span class="o">::</span><span class="n">LeftPaddleY</span> <span class="o">&amp;&amp;</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">BallPositionY</span> <span class="o">&lt;=</span> <span class="n">Data</span><span class="o">::</span><span class="n">LeftPaddleY</span> <span class="o">+</span> <span class="n">PADDLE_HEIGHT</span><span class="p">;</span>
    <span class="k">const</span> <span class="kt">bool</span> <span class="n">ballInRightPaddleYRange</span> <span class="o">=</span> <span class="n">Data</span><span class="o">::</span><span class="n">BallPositionY</span> <span class="o">+</span> <span class="n">BALL_SIZE</span> <span class="o">&gt;=</span> <span class="n">Data</span><span class="o">::</span><span class="n">RightPaddleY</span> <span class="o">&amp;&amp;</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">BallPositionY</span> <span class="o">&lt;=</span> <span class="n">Data</span><span class="o">::</span><span class="n">RightPaddleY</span> <span class="o">+</span> <span class="n">PADDLE_HEIGHT</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">Data</span><span class="o">::</span><span class="n">BallPositionX</span> <span class="o">&lt;=</span> <span class="mi">20</span> <span class="o">+</span> <span class="n">PADDLE_WIDTH</span> <span class="o">&amp;&amp;</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">BallPositionX</span> <span class="o">&gt;=</span> <span class="mi">20</span> <span class="o">&amp;&amp;</span>
        <span class="n">ballInLeftPaddleYRange</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">BallPositionX</span> <span class="o">=</span> <span class="mi">20</span> <span class="o">+</span> <span class="n">PADDLE_WIDTH</span><span class="p">;</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">BallVelocityX</span> <span class="o">=</span> <span class="o">-</span><span class="n">Data</span><span class="o">::</span><span class="n">BallVelocityX</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">Data</span><span class="o">::</span><span class="n">BallPositionX</span> <span class="o">+</span> <span class="n">BALL_SIZE</span> <span class="o">&gt;=</span> <span class="n">WINDOW_WIDTH</span> <span class="o">-</span> <span class="mi">20</span> <span class="o">-</span> <span class="n">PADDLE_WIDTH</span> <span class="o">&amp;&amp;</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">BallPositionX</span> <span class="o">&lt;=</span> <span class="n">WINDOW_WIDTH</span> <span class="o">-</span> <span class="mi">20</span> <span class="o">&amp;&amp;</span>
        <span class="n">ballInRightPaddleYRange</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">BallPositionX</span> <span class="o">=</span> <span class="n">WINDOW_WIDTH</span> <span class="o">-</span> <span class="mi">20</span> <span class="o">-</span> <span class="n">PADDLE_WIDTH</span> <span class="o">-</span> <span class="n">BALL_SIZE</span><span class="p">;</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">BallVelocityX</span> <span class="o">=</span> <span class="o">-</span><span class="n">Data</span><span class="o">::</span><span class="n">BallVelocityX</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">Data</span><span class="o">::</span><span class="n">BallPositionX</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">RightScore</span><span class="o">++</span><span class="p">;</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">State</span> <span class="o">=</span> <span class="n">Data</span><span class="o">::</span><span class="n">StateId</span><span class="o">::</span><span class="n">Reset</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">Data</span><span class="o">::</span><span class="n">BallPositionX</span> <span class="o">+</span> <span class="n">BALL_SIZE</span> <span class="o">&gt;=</span> <span class="n">WINDOW_WIDTH</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">LeftScore</span><span class="o">++</span><span class="p">;</span>
        <span class="n">Data</span><span class="o">::</span><span class="n">State</span> <span class="o">=</span> <span class="n">Data</span><span class="o">::</span><span class="n">StateId</span><span class="o">::</span><span class="n">Reset</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">extern</span> <span class="s">"C"</span> <span class="kr">__declspec</span><span class="p">(</span><span class="n">dllexport</span><span class="p">)</span> <span class="kt">void</span><span class="o">*</span> <span class="n">CALLBACK</span> <span class="nf">GameTick</span><span class="p">(</span><span class="n">PVOID</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">TICK_DATA</span><span class="o">*</span> <span class="n">currentTick</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">TICK_DATA</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">context</span><span class="p">);</span>

    <span class="k">switch</span> <span class="p">(</span><span class="n">Data</span><span class="o">::</span><span class="n">State</span><span class="p">)</span>
    <span class="p">{</span>
    <span class="k">case</span> <span class="n">Data</span><span class="o">::</span><span class="n">StateId</span><span class="o">::</span><span class="n">Reset</span><span class="p">:</span>
        <span class="n">Reset</span><span class="p">();</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="k">case</span> <span class="n">Data</span><span class="o">::</span><span class="n">StateId</span><span class="o">::</span><span class="n">Running</span><span class="p">:</span>
        <span class="n">Run</span><span class="p">(</span><span class="n">currentTick</span><span class="p">);</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">LeftPaddle</span><span class="p">.</span><span class="n">X</span> <span class="o">=</span> <span class="mi">20</span><span class="p">;</span>
    <span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">LeftPaddle</span><span class="p">.</span><span class="n">Y</span> <span class="o">=</span> <span class="n">Data</span><span class="o">::</span><span class="n">LeftPaddleY</span><span class="p">;</span>
    <span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">LeftPaddle</span><span class="p">.</span><span class="n">Width</span> <span class="o">=</span> <span class="n">PADDLE_WIDTH</span><span class="p">;</span>
    <span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">LeftPaddle</span><span class="p">.</span><span class="n">Height</span> <span class="o">=</span> <span class="n">PADDLE_HEIGHT</span><span class="p">;</span>

    <span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">RightPaddle</span><span class="p">.</span><span class="n">X</span> <span class="o">=</span> <span class="n">WINDOW_WIDTH</span> <span class="o">-</span> <span class="mi">20</span> <span class="o">-</span> <span class="n">PADDLE_WIDTH</span><span class="p">;</span>
    <span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">RightPaddle</span><span class="p">.</span><span class="n">Y</span> <span class="o">=</span> <span class="n">Data</span><span class="o">::</span><span class="n">RightPaddleY</span><span class="p">;</span>
    <span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">RightPaddle</span><span class="p">.</span><span class="n">Width</span> <span class="o">=</span> <span class="n">PADDLE_WIDTH</span><span class="p">;</span>
    <span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">RightPaddle</span><span class="p">.</span><span class="n">Height</span> <span class="o">=</span> <span class="n">PADDLE_HEIGHT</span><span class="p">;</span>

    <span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">Ball</span><span class="p">.</span><span class="n">X</span> <span class="o">=</span> <span class="n">Data</span><span class="o">::</span><span class="n">BallPositionX</span><span class="p">;</span>
    <span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">Ball</span><span class="p">.</span><span class="n">Y</span> <span class="o">=</span> <span class="n">Data</span><span class="o">::</span><span class="n">BallPositionY</span><span class="p">;</span>
    <span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">Ball</span><span class="p">.</span><span class="n">Width</span> <span class="o">=</span> <span class="n">BALL_SIZE</span><span class="p">;</span>
    <span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">Ball</span><span class="p">.</span><span class="n">Height</span> <span class="o">=</span> <span class="n">BALL_SIZE</span><span class="p">;</span>

    <span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">LeftScore</span> <span class="o">=</span> <span class="n">Data</span><span class="o">::</span><span class="n">LeftScore</span><span class="p">;</span>
    <span class="n">currentTick</span><span class="o">-&gt;</span><span class="n">RightScore</span> <span class="o">=</span> <span class="n">Data</span><span class="o">::</span><span class="n">RightScore</span><span class="p">;</span>

    <span class="k">return</span> <span class="nb">nullptr</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And then the enclave function is called from within the game tick in the host process, and then the resulting game objects are rendered:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">Game</span><span class="o">::</span><span class="n">Tick</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">const</span> <span class="n">Uint8</span><span class="o">*</span> <span class="n">keystates</span> <span class="o">=</span> <span class="n">SDL_GetKeyboardState</span><span class="p">(</span><span class="nb">nullptr</span><span class="p">);</span>

    <span class="k">static</span> <span class="n">Uint32</span> <span class="n">lastTime</span> <span class="o">=</span> <span class="n">SDL_GetTicks</span><span class="p">();</span>
    <span class="k">const</span> <span class="n">Uint32</span> <span class="n">currentTime</span> <span class="o">=</span> <span class="n">SDL_GetTicks</span><span class="p">();</span>
    <span class="k">const</span> <span class="kt">float</span> <span class="n">deltaTime</span> <span class="o">=</span> <span class="p">(</span><span class="n">currentTime</span> <span class="o">-</span> <span class="n">lastTime</span><span class="p">)</span> <span class="o">/</span> <span class="mf">1000.0f</span><span class="p">;</span>
    <span class="n">lastTime</span> <span class="o">=</span> <span class="n">currentTime</span><span class="p">;</span>

    <span class="n">TICK_DATA</span> <span class="n">data</span><span class="p">;</span>
    <span class="n">data</span><span class="p">.</span><span class="n">DeltaTime</span> <span class="o">=</span> <span class="n">deltaTime</span><span class="p">;</span>
    <span class="n">data</span><span class="p">.</span><span class="n">KeyW</span> <span class="o">=</span> <span class="n">keystates</span><span class="p">[</span><span class="n">SDL_SCANCODE_W</span><span class="p">];</span>
    <span class="n">data</span><span class="p">.</span><span class="n">KeyS</span> <span class="o">=</span> <span class="n">keystates</span><span class="p">[</span><span class="n">SDL_SCANCODE_S</span><span class="p">];</span>
    <span class="n">data</span><span class="p">.</span><span class="n">KeyUp</span> <span class="o">=</span> <span class="n">keystates</span><span class="p">[</span><span class="n">SDL_SCANCODE_UP</span><span class="p">];</span>
    <span class="n">data</span><span class="p">.</span><span class="n">KeyDown</span> <span class="o">=</span> <span class="n">keystates</span><span class="p">[</span><span class="n">SDL_SCANCODE_DOWN</span><span class="p">];</span>

    <span class="n">PVOID</span> <span class="n">returnValue</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">CallEnclave</span><span class="p">(</span><span class="n">Global</span><span class="o">::</span><span class="n">TickRoutine</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">data</span><span class="p">,</span> <span class="nb">true</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">returnValue</span><span class="p">))</span>
    <span class="p">{</span>
        <span class="kt">char</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span>
        <span class="n">sprintf_s</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="s">"Failed to call enclave routine: %d"</span><span class="p">,</span> <span class="n">GetLastError</span><span class="p">());</span>
        <span class="n">MessageBoxA</span><span class="p">(</span><span class="nb">nullptr</span><span class="p">,</span> <span class="n">buffer</span><span class="p">,</span> <span class="s">"Error"</span><span class="p">,</span> <span class="n">MB_OK</span> <span class="o">|</span> <span class="n">MB_ICONERROR</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">SDL_SetRenderDrawColor</span><span class="p">(</span><span class="n">m_Renderer</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">);</span>

    <span class="k">const</span> <span class="n">SDL_Rect</span> <span class="n">leftPaddle</span> <span class="o">=</span>
    <span class="p">{</span>
        <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">LeftPaddle</span><span class="p">.</span><span class="n">X</span><span class="p">),</span>
        <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">LeftPaddle</span><span class="p">.</span><span class="n">Y</span><span class="p">),</span>
        <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">LeftPaddle</span><span class="p">.</span><span class="n">Width</span><span class="p">),</span>
        <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">LeftPaddle</span><span class="p">.</span><span class="n">Height</span><span class="p">)</span>
    <span class="p">};</span>
    <span class="n">SDL_RenderFillRect</span><span class="p">(</span><span class="n">m_Renderer</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">leftPaddle</span><span class="p">);</span>

    <span class="k">const</span> <span class="n">SDL_Rect</span> <span class="n">rightPaddle</span> <span class="o">=</span>
    <span class="p">{</span>
        <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">RightPaddle</span><span class="p">.</span><span class="n">X</span><span class="p">),</span>
        <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">RightPaddle</span><span class="p">.</span><span class="n">Y</span><span class="p">),</span>
        <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">RightPaddle</span><span class="p">.</span><span class="n">Width</span><span class="p">),</span>
        <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">RightPaddle</span><span class="p">.</span><span class="n">Height</span><span class="p">)</span>
    <span class="p">};</span>
    <span class="n">SDL_RenderFillRect</span><span class="p">(</span><span class="n">m_Renderer</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">rightPaddle</span><span class="p">);</span>

    <span class="k">const</span> <span class="n">SDL_Rect</span> <span class="n">ball</span> <span class="o">=</span>
    <span class="p">{</span>
        <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">Ball</span><span class="p">.</span><span class="n">X</span><span class="p">),</span>
        <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">Ball</span><span class="p">.</span><span class="n">Y</span><span class="p">),</span>
        <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">Ball</span><span class="p">.</span><span class="n">Width</span><span class="p">),</span>
        <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">Ball</span><span class="p">.</span><span class="n">Height</span><span class="p">)</span>
    <span class="p">};</span>
    <span class="n">SDL_RenderFillRect</span><span class="p">(</span><span class="n">m_Renderer</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ball</span><span class="p">);</span>

    <span class="kt">char</span> <span class="n">scoreText</span><span class="p">[</span><span class="mi">32</span><span class="p">];</span>
    <span class="n">sprintf_s</span><span class="p">(</span><span class="n">scoreText</span><span class="p">,</span> <span class="s">"%d - %d"</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">LeftScore</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">RightScore</span><span class="p">);</span>
    <span class="n">RenderText</span><span class="p">(</span><span class="n">scoreText</span><span class="p">,</span> <span class="n">WINDOW_WIDTH</span> <span class="o">/</span> <span class="mi">2</span> <span class="o">-</span> <span class="mi">40</span><span class="p">,</span> <span class="mi">20</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And that’s it, now let’s go test it out! Full source code available <a href="https://github.com/SamuelTulach/SecureGame">here</a>.</p>

<h2 id="testing">Testing</h2>
<p>When you start the game, nothing will seem out of the ordinary (apart from the fact that it won’t run without VBS enabled).</p>

<p><img src="/assets/img/posts/10_game.png" alt="game" /></p>

<p>Let’s try to mess with it. Trying to change the score values using <a href="https://www.cheatengine.org/">Cheat Engine</a> won’t work. The score isn’t in the host process memory at all (or it’s there only for a very brief moment when it’s rendered on screen, but changing it won’t affect the actual score counter).</p>

<p><img src="/assets/img/posts/10_cheat_engine.png" alt="cheatengine" /></p>

<p>Ok, so what about trying to edit the code directly? When we look at the loaded modules of the process, we can actually see the loaded enclave DLL.</p>

<p><img src="/assets/img/posts/10_module.png" alt="module" /></p>

<p>Does that mean we can just patch the module? Let’s open <code class="language-plaintext highlighter-rouge">SecureCore.dll</code> in IDA and copy over the first bytes of the <code class="language-plaintext highlighter-rouge">GameTick()</code> function.</p>

<p><img src="/assets/img/posts/10_ida.png" alt="ida" /></p>

<p>Then we can scan for the function using <a href="https://www.cheatengine.org/">Cheat Engine</a>.</p>

<p><img src="/assets/img/posts/10_function.png" alt="function" /></p>

<p>Let’s try to patch it by putting a return (0xC3) at the start of the function.</p>

<p><img src="/assets/img/posts/10_nothing.png" alt="nothing" /></p>

<p>Aaaaand nothing. So what’s going on? Well, first of all, let’s check the memory protection.</p>

<p><img src="/assets/img/posts/10_protection.png" alt="nothing" /></p>

<p>It’s actually just read-only. No execution permissions. That’s because this is just a dummy image (most likely to prevent memory address conflicts). There’s really no way we could mess around with the code or data in the enclave with anything we launch from within the OS.</p>

<p>Well, is there anything we <strong>can</strong> do then? Yes, obviously. Anything that’s outside of the enclave is easily accessible. We can hook into the host process and edit the data returned by or sent to the enclave. We can also just inject Cheat Engine’s speedhack DLL (which just hooks performance counters) and that will work too, since all the time calculations are done in the host process.</p>

<h2 id="conclusion">Conclusion</h2>
<p>Due to the limitations mentioned above, it would take incredible effort to implement VBS enclaves into an <a href="https://www.unrealengine.com/en-US">actual game engine</a> in a way that would be meaningful and not completely obliterate the game’s performance. As such, I don’t really see any game developers using them, not even accounting for the system requirements (Windows 11+, VBS enabled) since gamers might not be willing to reconfigure their systems just to play some game.</p>

<p>On top of that, while it would prevent any attempt to manipulate the game from programs or drivers loaded in the OS, experienced developers would have no issues getting around these restrictions by writing firmware apps that <a href="https://tulach.cc/bootkits-and-kernel-patching/">would manipulate the entire Windows bootchain</a>.</p>

<p>There are two things that Microsoft could do that would instantly eliminate the vast majority of game cheating:</p>

<ol>
  <li>Work with OEMs to enforce strict boot environment code signing policies. <a href="https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/oem-secure-boot">Secure Boot</a> is not sufficient (<a href="https://github.com/SamuelTulach/SecureFakePkg">1</a>, <a href="https://github.com/SamuelTulach/PatchBoot">2</a>). The system would have to verify everything from the moment it’s turned on until it’s turned off. If it allows loading any user-created firmware code, it’s over. However, being this strict would essentially mean locking the system down to Windows only, or at least making it very difficult to install alternative OSes (Linux), which no sane person can support.</li>
  <li>While this would require enormous effort, they could introduce whole “process enclaves” where an entire process could run in a separate virtualized environment while still having access to standard NT APIs. I’ve read several security-related blog posts from them and I feel this sort of containerization is something they’re aiming for, so we’ll see.</li>
</ol>

<p>Thanks for reading and have a nice day.</p>]]></content><author><name>Samuel Tulach</name><email>noreply@tulach.cc</email></author><category term="blog" /><category term="amd64" /><category term="windows" /><category term="kernel" /><category term="anti-cheat" /><category term="hyper-v" /><category term="vbs" /><summary type="html"><![CDATA[Exploring the possible use of VBS enclaves for anti-cheat purposes]]></summary></entry><entry><title type="html">The issue of anti-cheat on Linux</title><link href="https://tulach.cc/the-issue-of-anti-cheat-on-linux/" rel="alternate" type="text/html" title="The issue of anti-cheat on Linux" /><published>2024-09-10T13:12:00+02:00</published><updated>2024-09-10T13:12:00+02:00</updated><id>https://tulach.cc/the-issue-of-anti-cheat-on-linux</id><content type="html" xml:base="https://tulach.cc/the-issue-of-anti-cheat-on-linux/"><![CDATA[<p>The number of people choosing Linux as their primary operating system to play games has been slowly but steadily going up, at least <a href="https://www.gamingonlinux.com/2024/06/linux-user-share-on-steam-breaks-2pc-thanks-to-steam-deck/">according to the Steam hardware survey</a>. This is most likely because of the <a href="https://store.steampowered.com/steamdeck">Steam Deck release</a> and the <a href="https://www.theverge.com/2024/9/2/24233992/microsoft-recall-windows-11-uninstall-feature-bug">increasingly obnoxious features being added to Windows</a>.</p>

<p>If you switch to Linux today, you’ll probably be surprised by how many games run out of the box just fine (mostly due to the <a href="https://github.com/ValveSoftware/Proton">Windows compatibility layer Proton</a> built right into Steam), <em>except</em> for basically all competitive multiplayer games that utilize any sort of anti-cheat technology.</p>

<p>Just to name a few, here is a <a href="https://www.protondb.com/explore?sort=playerCount">list sorted by concurrent player count from ProtonDB</a>:</p>
<ul>
  <li><a href="https://www.protondb.com/app/578080">PUBG: BATTLEGROUNDS</a> - uses a combination of <a href="https://www.battleye.com/">BattlEye</a> and in-house AC, won’t launch at all.</li>
  <li><a href="https://www.protondb.com/app/1938090">Call of Duty</a> - uses RICOCHET AC, won’t launch at all.</li>
  <li><a href="https://www.protondb.com/app/252490">Rust</a> - uses <a href="https://www.easy.ac/en-US">Easy Anti-Cheat</a>, will launch, but you won’t be able to connect to any EAC-protected server (so basically any server except self-hosted ones).</li>
  <li><a href="https://www.protondb.com/app/359550">Tom Clancy’s Rainbow Six Siege</a> - uses <a href="https://www.battleye.com/">BattlEye</a>, won’t launch at all.</li>
  <li><a href="https://www.protondb.com/app/2195250">EA SPORTS FC 24</a> - uses <a href="https://help.ea.com/en/help/pc/ea-anticheat/">EAAC</a>, won’t launch at all.</li>
  <li><a href="https://www.protondb.com/app/1085660">Destiny 2</a> - uses <a href="https://www.battleye.com/">BattlEye</a>, won’t launch at all.</li>
</ul>

<p>Those are just games on Steam. Then there’s also <a href="https://playvalorant.com/en-gb/">Valorant</a> and <a href="https://www.leagueoflegends.com/cs-cz/">League of Legends</a>, which both now use <a href="https://support-valorant.riotgames.com/hc/en-us/articles/360046160933-What-is-Vanguard">Vanguard</a>, so they also won’t launch at all. While you can play <a href="https://www.counter-strike.net/cs2">Counter-Strike 2</a> on <a href="https://help.steampowered.com/en/faqs/view/571A-97DA-70E9-FF74">VAC-secured</a> servers natively on Linux, anyone trying to play more seriously will likely be playing on <a href="https://www.faceit.com/en">FACEIT</a> or <a href="https://play.esea.net/">ESEA</a>, which both also won’t work.</p>

<p>Now I can finally get to the point of the article… <em>Why am I writing this?</em> As someone who uses Linux daily, I would love to see these games support it, but I just don’t see that happening any time soon. Many people in the Linux community are frustrated by the fact that these anti-cheat solutions are stopping them from playing their favorite games. It also doesn’t help that some are <a href="https://youtu.be/_dOCtaBObg4?si=bxrl7H5Xl6FBafH5">fear-mongering about kernel-level anti-cheat solutions</a> and <a href="https://www.reddit.com/r/riotgames/comments/1cjq63h/vanguard_real_is/">spreading misinformation</a>.</p>

<p>In this article, I want to give you a high-level overview of how modern anti-cheat solutions work (which will hopefully be understandable even for non-technical people) and then explain why anti-cheat solutions in their current state just cannot work on Linux, as well as what the alternatives are.</p>

<h2 id="the-basics">The basics</h2>
<p><em>What is a videogame cheat?</em> We could talk for hours about whether all sorts of macros and exploits should be considered cheats, but the main thing that comes to people’s minds when talking about multiplayer games is an external program that somehow manipulates the game or reads information from the game to provide you with an advantage over others. A prime example of this would be a <a href="https://en.wikipedia.org/wiki/Cheating_in_online_games#Wallhacking">wallhack or aimbot</a>.</p>

<p>There are generally two ways you can go about this:</p>
<ol>
  <li>(<strong>External</strong>) Have a completely separate process that copies memory between itself and the game.</li>
  <li>(<strong>Internal</strong>) Force the game to load a <a href="https://learn.microsoft.com/en-us/troubleshoot/windows-client/setup-upgrade-and-drivers/dynamic-link-library">DLL file</a> (a <a href="https://en.wikipedia.org/wiki/Dynamic-link_library">shared library file</a> containing code) directly into the game, executing custom code from within the game.</li>
</ol>

<p>Unless you find some very niche way to load a DLL into the game, in both cases you will need the ability to read (and write) the game’s process memory.</p>

<h2 id="little-detour">Little detour</h2>
<p>If you are not a programmer (or <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ">you are a JavaScript developer</a>), you most likely don’t really know how memory management works on modern systems. Let’s imagine this situation: two programs are loaded in memory. What is stopping one program from directly accessing the memory of the other program?</p>

<p><img src="/assets/img/posts/9_address_space.png" alt="screenshot" /></p>

<p><em>Virtual address space in Windows (<a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/gettingstarted/virtual-address-spaces">source</a>).</em></p>

<p>While in the past it would have been perfectly possible to read (almost) any of the physical memory installed in the computer, nowadays OSes use <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/gettingstarted/virtual-address-spaces">virtual address spaces</a>. I don’t want to go into the details of how this is handled, but all you need to know is that each program is isolated in its own address space and cannot access other programs’ memory unless it uses functions provided by the operating system itself, like <a href="https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-readprocessmemory"><code class="language-plaintext highlighter-rouge">ReadProcessMemory</code></a> and <a href="https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory"><code class="language-plaintext highlighter-rouge">WriteProcessMemory</code></a>.</p>

<p>In order to use those two functions, you will need to open a <a href="https://learn.microsoft.com/en-us/windows/win32/sysinfo/handles-and-objects">handle</a> to the process you want to read or write memory from. This handle will be specific to your process and represent the access rights that you have relative to the object it represents (in this case, the game process). Remember this for later, as it will be important.</p>

<h2 id="anti-cheat">Anti-cheat</h2>
<p>Modern anti-cheat solutions have three main goals:</p>
<ol>
  <li>Block other processes from accessing the game’s memory whenever possible.</li>
  <li>Detect and ban anyone who tries to get around the blocking mentioned above.</li>
  <li>Once someone is banned, ensure that they cannot simply create a new game account and continue playing (HWID bans).</li>
</ol>

<p>This is usually achieved by multiple components working together. Let’s take a look at <a href="https://www.easy.ac/en-US">Easy Anti-Cheat</a> as an example:</p>
<ul>
  <li>Loader (usually <code class="language-plaintext highlighter-rouge">Game_EAC.exe</code> or <code class="language-plaintext highlighter-rouge">start_protected_game.exe</code>)</li>
  <li>Game library (<code class="language-plaintext highlighter-rouge">EasyAntiCheat_x64.dll</code> and “invisible” module)</li>
  <li>Service (<code class="language-plaintext highlighter-rouge">EasyAntiCheat.exe</code>)</li>
  <li>Kernel-mode driver (<code class="language-plaintext highlighter-rouge">EasyAntiCheat.sys</code>)</li>
</ul>

<p>Without a kernel-mode driver, there is no way to <em>effectively</em> block memory access into the game. With the kernel-mode driver, though, it’s incredibly simple. All that the driver needs to do is <a href="https://github.com/Microsoft/Windows-driver-samples/blob/main/general/obcallback/driver/callback.c">register a callback for handle creation</a>, filter out requests to open such handles to the game process, check the requested permissions, and if they allow memory access, either deny the request or lower the permissions. That way, no usermode process can now read or write the games memory. Same can be applied to module loading and file system access.</p>

<p><img src="/assets/img/posts/9_cheat_engine.png" alt="screenshot" /></p>

<p><em>Using open-source <a href="https://github.com/cheat-engine/cheat-engine">Cheat Engine</a> to try to read protected game’s memory (all reads fail).</em></p>

<p>So how can anyone get around it? They also <strong>somehow</strong> need to get their code into the kernel, which will open many ways for them to access the game memory.</p>

<p>Notice how I highlighted “somehow”? That’s because Windows is a closed system where Microsoft has the control to decide <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/install/kernel-mode-code-signing-requirements--windows-vista-and-later-">who should get access to the kernel</a>. All official kernel components are signed with Microsoft code signing certificates, so it’s trivial to verify their authenticity. All 3rd party drivers need to be signed with an <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/dashboard/code-signing-reqs">EV code signing certificate</a> (which can only be bought by companies) and then go through the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/dashboard/hardware-program-register">Hardware Developer Center</a> certification so they can even be loaded. I am not saying this is perfect; in fact, I will most likely be writing an article about how bad actors are still getting their stuff certified. However, when they do, it usually gets quickly revoked, and it’s so costly and complicated that most don’t even bother trying.</p>

<p>There is, of course, a way to get around it by using <a href="https://github.com/SamuelTulach/nullmap">all sorts of exploits</a> or by <a href="https://github.com/hfiref0x/KDU">using vulnerable drivers</a> (drivers that expose a programming interface to user-mode processes without any checks in place, which allows them to escalate their privileges and possibly even manipulate kernel components). This is where the second goal defined above comes in. The anti-cheat has to actively scan the system and try to find code that is not associated with any legitimate module (a module that was loaded properly, with all certs in place) and other modifications or patches that would otherwise not be there.</p>

<p>While most gamers are going to say that those anti-cheats are useless and that they see cheaters left and right, the truth is that they add a huge skill check, so not everyone is able to write a cheat and then not get banned. In fact, if done properly, the cheating problem can be basically eliminated this way (I’ll get to this later).</p>

<p>Another reason to run in the kernel is HWID (hardware identifier) banning (the 3rd point mentioned above). If a player is banned and creates a new account, playing on the same hardware will result in an immediate ban. Since the anti-cheat has a kernel component, it can directly talk to the hardware and read its serials that way. If it was running only as a user-mode process, it would be trivial to fake the serial reads. I am not personally a big fan of this since, as you can imagine, it can result in all sorts of unintended issues (people buying used hardware), but in reality, it’s not really a problem since those HWID bans usually expire after a few months (the game devs won’t tell you this though 😉).</p>

<h2 id="doing-it-properly">Doing it properly</h2>
<p>If I had to pick a game which handles cheating the best, then as of now in my humble opinion it would be <a href="https://playvalorant.com/en-gb/">Valorant</a> by <a href="https://www.riotgames.com/en">Riot Games</a>. Keep in mind the stuff that you’ve just read and let me explain:</p>
<ul>
  <li>The anti-cheat is loaded on boot. While scary for some, this allows them to block/detect the previously mentioned vulnerable drivers and exploits. This raises the skill required to write a cheat for the game even higher (usually, people resort to <a href="https://tulach.cc/bootkits-and-kernel-patching/">bootkits</a>).</li>
  <li>The kernel driver then doesn’t do anything apart from logging (locally). When the game is actually started, it goes through those logs and figures out if the game launch should be allowed or not and does all the kernel protection stuff mentioned above.</li>
  <li>More advanced methods to obtain HWID are used, such as reading <a href="https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/tpm-key-attestation">TPM EK</a>, which is very hard to spoof properly.</li>
</ul>

<p>But that’s not all. If that was all there was to it, other anti-cheats would be just as effective. The anti-cheat team closely works with the game development team as well. How? The anti-cheat introduces <a href="https://reversing.info/posts/guardedregions/">extra protection for certain memory regions of the game</a>. Some <a href="https://www.unknowncheats.me/forum/valorant/401729-valorant-1-01-names-decryption.html">game data are encrypted</a>, and the encryption keys change with every (even small) game update, making it really annoying for cheat developers. On top of all that, the team is very active in the cheating communities to get intel about what they are up to.</p>

<p>I have played Valorant quite extensively, all the way from Silver to Ascendant, and I have yet to meet a cheater.</p>

<h2 id="concerns">Concerns</h2>
<p>There are two main concerns that people have with those kernel-mode anti-cheats:</p>
<ol>
  <li>They are in the kernel doing in-depth scans; therefore, they must be vulnerable and a security issue.</li>
  <li>They are so deep in the system (and some start on system boot) that they can spy on us without us noticing.</li>
</ol>

<h3 id="security">Security</h3>
<p>Let me ask you a question. How many vulnerable drivers (yes, those that can be abused by bad actors to gain kernel access) do you think the average gamer has on their Windows install? I’ll start with my own system. This is what I can immediately think of:</p>
<ul>
  <li><a href="https://www.msi.com/Landing/afterburner/graphics-cards">MSI Afterburner</a> - <code class="language-plaintext highlighter-rouge">RTCore64.sys</code> driver (yes, even in the latest version) has a <a href="https://github.com/oakboat/RTCore64_Vulnerability/blob/main/RTCore64_Vulnerability/MemoryAccessor.cpp">vulnerability that allows any usermode process to read and write any kernel memory it wishes</a></li>
  <li><a href="https://www.cpuid.com/softwares/cpu-z.html">CPU-Z</a> - <code class="language-plaintext highlighter-rouge">cpuz142_x64.sys</code> driver has (again) <a href="https://github.com/shareef12/cpuz">kernel memory read/write vulnerability and MSR register read/write</a></li>
</ul>

<p>If I looked hard enough, I would most likely find more.</p>

<p>It would be really stupid of me to just point to random crap you could have on your computer and say “you have so much exploitable stuff, don’t even bother with security,” and that’s not what I am trying to say. Or maybe it is, but just a little bit… What I am trying to say is that there are many ways a malicious actor could do bad stuff with your system, but anti-cheat is very unlikely to have anything to do with it. In fact, I personally trust those anti-cheat developers much more than random vendors, since they are going to be very well aware of the possible abuse.</p>

<p>Furthermore, if you are using any commercial anti-malware solution, it’s definitely running its own kernel-mode driver (sometimes even multiple of them), and it’s most likely <a href="https://en.wikipedia.org/wiki/2024_CrowdStrike_incident">doing much more sketchy stuff to your system</a>.</p>

<p>Overall, the Windows driver ecosystem is a mess, but unfortunately, that is not going to change any time soon.</p>

<h3 id="spying">Spying</h3>
<p>As someone who is very well versed in Windows internals, I can tell you one thing, it doesn’t make sense. If you give the program administrative permissions (at least once), it can spy on you in the same way a kernel-mode driver could. There is absolutely no difference and it’s significantly easier to just write a standalone program. There are people who don’t want to play games because of their connection to <a href="https://www.tencent.com/">Tencent</a> (for example), but if it wasn’t for the kernel-mode anti-cheat, they would have no problem with it. Isn’t it a bit hypocritical? If the game company wanted to spy on you, they could have done so from the game process or the <a href="https://gamerant.com/stalker-2-drm/">service they have most likely installed for DRM purposes</a>.</p>

<p>Oh and just by the way, the vast majority of the data networked by those previously mentioned anti-cheats to their respective servers comes from their usermode component. The only thing that’s sent “by the kernel component” (in quotes since the usermode service requests the data from the driver and then networks it, drivers cannot directly network data) is the HWID mentioned multiple times above and then detections (something that’s out of the ordinary). There is really not some magic data grabbing happening that’s only possible in the kernel.</p>

<p>Another thing that is sometimes mentioned is that since it’s in the kernel, it would be harder for security researchers to debug and assess the possible spying. While technically true that it’s harder, it’s definitely not impossible or problematic for an experienced person, so trust me, security researchers and <em>especially</em> the entire cheating community keep a close eye on it, in the same way they do on the usermode components.</p>

<h2 id="linux">Linux</h2>
<p>Congratulations, you have successfully made it. You have read all of the stuff and now we can finally get to the Linux part of this post 🎉.</p>

<p>As you can probably already tell by the extensive rant above, I don’t have much good news. Linux is an open system. There is no central authority like on Windows that would tell you what you can and what you cannot do in the kernel. This obviously has countless advantages and it’s why so many people (and big corporations) love it, but is also the reason why anti-cheats cannot really function like they do on Windows.</p>

<p>There is no way for them to block or detect memory access into the game. Anything you could think of would just not work. Kernel module? Just recompile the kernel and change the functions it uses to hide the possible cheat and bypass all checks. Mandatory kernel patch? Same thing. What about usermode detections? Just run the game in a <a href="https://wiki.debian.org/FakeRoot">fakeroot environment</a> while the cheat runs with real root privileges, being hidden from the game completely… Mandatory custom kernel build? Entire Linux system dedicated to the anti-cheat? I mean… that could work, but at that point, you can just install Windows.</p>

<p>There have been attempts to get anti-cheat to work on Linux. <a href="https://www.easy.ac/en-US">Easy Anti-Cheat</a> is the most prominent one. Developers can <a href="https://www.gamingonlinux.com/2021/09/epic-games-announce-full-easy-anti-cheat-for-linux-including-wine-a-proton/">choose whether they want to allow it to run on Linux or not</a>. Linux gamers look at this and use it as an argument that anti-cheat on Linux does not face any issues, but the truth is that apart from the most basic sanity checks, EAC does absolutely nothing on Linux. It’s just a simple module that facilitates the server connection and data encryption/decryption for the game.</p>

<p>One of the games that allowed EAC to run under Wine/Proton is <a href="https://www.ea.com/games/apex-legends">Apex Legends</a>. I won’t be putting any links here, but if you search <a href="https://github.com/">GitHub</a> for cheats for this game, you will find many that work on Linux and there is absolutely no anti-cheat bypass required. It just works.</p>

<h2 id="all-hope-lost">All hope lost?</h2>
<p>As mentioned above, if you want to achieve the best results, you need to utilize both the <strong>active</strong> and <strong>passive</strong> measures. Active being the kernel component on Windows blocking memory access and trying to find possible discrepancies. Passive being the code virtualization, obfuscation, game data encryption as well as proper game networking and server-sided checks.</p>

<p>An example of how <strong>not</strong> to utilize kernel-mode anti-cheat would be <a href="https://www.fallguys.com/en-US">Fall Guys</a> (yes, that’s the game that one friend made you buy just so you could play it for 30 minutes and then never open again). This game is very specific. There would be no gain in having some sort of wallhack, there would be no gain in having any sort of aimbot (you don’t aim at stuff). All that people did was speedhacking and modifying the game in a way that allowed them to jump higher and generally change their movement. This game is a prime example of why you should write your network code properly. If the game had proper networking and server checks in place (tick-based system, actions performed on both the client and server, if there is a mismatch, the server is the authority and resets the player - that’s how <a href="https://en.wikipedia.org/wiki/Counter-Strike:_Global_Offensive">CS:GO</a> did it, and that’s why people were not flying over the map in that game or speedhacking, it had other issues though), there would be no need for anti-cheat. Not even a usermode one. Instead, they fixed absolutely nothing from their side and slapped <a href="https://www.easy.ac/en-US">Easy Anti-Cheat</a> on top of their game.</p>

<p>While it’s not really possible to do any of the previously mentioned active measures, there is nothing stopping you from utilizing the passive ones. So, if you are a game developer and want to limit cheating in your game on Linux:</p>
<ul>
  <li>Write proper networking code, verify data sent by the client so your game server does not blindly accept mach 8 as a walking speed.</li>
  <li>Use code obfuscation and virtualization as much as possible (be aware of the performance penalty, be smart about what parts of the code you protect), try to change it a bit with every update (commercial bin2bin obfuscators like <a href="https://vmpsoft.com/">VMProtect</a> or <a href="https://www.oreans.com/Themida.php">Themida</a> will produce different results on each run).</li>
  <li>If you have control over the game engine itself, try to keep sensitive information on the stack as much as possible.</li>
  <li>Do not ship debug symbols with your game, <a href="https://www.baeldung.com/linux/strip-executables">make sure the Linux binaries are stripped</a>.</li>
</ul>

<p>Thanks for reading.</p>]]></content><author><name>Samuel Tulach</name><email>noreply@tulach.cc</email></author><category term="blog" /><category term="linux" /><category term="kernel" /><category term="anticheat" /><summary type="html"><![CDATA[Why are developers so hesitant to bring anti-cheat solutions to Linux?]]></summary></entry><entry><title type="html">Detecting manually mapped drivers</title><link href="https://tulach.cc/detecting-manually-mapped-drivers/" rel="alternate" type="text/html" title="Detecting manually mapped drivers" /><published>2024-07-12T11:47:00+02:00</published><updated>2024-07-12T11:47:00+02:00</updated><id>https://tulach.cc/detecting-manually-mapped-drivers</id><content type="html" xml:base="https://tulach.cc/detecting-manually-mapped-drivers/"><![CDATA[<p>Manually mapping kernel mode drivers was always a common thing in the game hacking scene, but <em>some</em> anticheats were still not very mature. So, at least public projects usually focused on changing the value of <code class="language-plaintext highlighter-rouge">g_CiOptions</code> to disable driver signing enforcement, loading their unsigned driver, and then playing with some DKOM to make it harder to find.</p>

<p>In 2019, though, a nuclear bomb was dropped into the scene. That nuclear bomb was <a href="https://www.unknowncheats.me/forum/anti-cheat-bypass/320049-kdmapper-manual-map-driver-using-vulnerable-driver-intel.html">a project called kdmapper</a>.</p>

<p>If you have ever worked on anything remotely close to Windows internals, game hacking, or malware reversing, you most likely already know this project. Instead of using some vulnerable driver (a driver that exposes kernel memory r/w to user mode) to bypass DSE, it directly allocated memory in the kernel, copied the PE image into it, resolved its imports, and then called the entry point.</p>

<p>For the first few months (at least it felt like that at the time), it was a free-for-all game. People had been mapping random crap into the kernel without even understanding how any of it worked, and because most of the anticheat software at the time was not doing proper checks, they were getting away with it. After some time, though, anticheats started catching up, first by checking the most obvious things like IOCTL dispatch overwrites (they should always point to legitimate drivers’ memory regions), hooks by pointer swaps, running system threads with stack traces out of legitimate memory regions, etc. Later on, they started sending APCs or registering NMI callbacks, but most importantly, they also started proactively searching for those drivers in memory, and that’s what this article is about.</p>

<h2 id="where-do-we-start">Where do we start?</h2>
<p>If we want to scan the entire system, we pretty much have two options:</p>

<ol>
  <li>Walk <a href="https://wiki.osdev.org/Paging">page tables</a> to find all valid executable pages.</li>
  <li>Search all valid physical memory ranges.</li>
</ol>

<p>I picked the second option for my demonstration since it was quicker to write.</p>

<p>Here is the code that I will be using:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="n">PPHYSICAL_MEMORY_RANGE</span> <span class="n">physicalMemoryRanges</span> <span class="o">=</span> <span class="n">MmGetPhysicalMemoryRanges</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">physicalMemoryRanges</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">Log</span><span class="p">(</span><span class="s">"Failed to get physical memory ranges!"</span><span class="p">);</span>
    <span class="k">return</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">Log</span><span class="p">(</span><span class="s">"Starting memory scan (this can take a while)..."</span><span class="p">);</span>
<span class="k">constexpr</span> <span class="n">SIZE_T</span> <span class="n">CHUNK_SIZE</span> <span class="o">=</span> <span class="n">PAGE_SIZE</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">physicalMemoryRanges</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">BaseAddress</span><span class="p">.</span><span class="n">QuadPart</span> <span class="o">||</span> <span class="n">physicalMemoryRanges</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">NumberOfBytes</span><span class="p">.</span><span class="n">QuadPart</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">const</span> <span class="n">PHYSICAL_ADDRESS</span> <span class="n">start</span> <span class="o">=</span> <span class="n">physicalMemoryRanges</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">BaseAddress</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">SIZE_T</span> <span class="n">totalSize</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">SIZE_T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">physicalMemoryRanges</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">NumberOfBytes</span><span class="p">.</span><span class="n">QuadPart</span><span class="p">);</span>

    <span class="k">for</span> <span class="p">(</span><span class="n">SIZE_T</span> <span class="n">offset</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">offset</span> <span class="o">&lt;</span> <span class="n">totalSize</span><span class="p">;</span> <span class="n">offset</span> <span class="o">+=</span> <span class="n">CHUNK_SIZE</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">PHYSICAL_ADDRESS</span> <span class="n">chunkStart</span><span class="p">;</span>
        <span class="n">chunkStart</span><span class="p">.</span><span class="n">QuadPart</span> <span class="o">=</span> <span class="n">start</span><span class="p">.</span><span class="n">QuadPart</span> <span class="o">+</span> <span class="n">offset</span><span class="p">;</span>
        <span class="k">const</span> <span class="n">SIZE_T</span> <span class="n">chunkSize</span> <span class="o">=</span> <span class="n">min</span><span class="p">(</span><span class="n">CHUNK_SIZE</span><span class="p">,</span> <span class="n">totalSize</span> <span class="o">-</span> <span class="n">offset</span><span class="p">);</span>

        <span class="n">MM_COPY_ADDRESS</span> <span class="n">address</span><span class="p">;</span>
        <span class="n">address</span><span class="p">.</span><span class="n">PhysicalAddress</span> <span class="o">=</span> <span class="n">chunkStart</span><span class="p">;</span>

        <span class="n">SIZE_T</span> <span class="n">bytesRead</span><span class="p">;</span>
        <span class="n">status</span> <span class="o">=</span> <span class="n">MmCopyMemory</span><span class="p">(</span><span class="n">CheckBuffer</span><span class="p">,</span> <span class="n">address</span><span class="p">,</span> <span class="n">chunkSize</span><span class="p">,</span> <span class="n">MM_COPY_MEMORY_PHYSICAL</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">bytesRead</span><span class="p">);</span>

        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">NT_SUCCESS</span><span class="p">(</span><span class="n">status</span><span class="p">)</span> <span class="o">||</span> <span class="n">bytesRead</span> <span class="o">!=</span> <span class="n">chunkSize</span><span class="p">)</span>
            <span class="k">continue</span><span class="p">;</span>

        <span class="c1">// TODO</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="cool-now-what">Cool, now what?</h2>
<p>Now we have access to (almost) the entire memory and we can perform some heuristic scans on it, but what do we do? The first thing that probably comes to your mind is the DOS and NT headers that each PE executable has. Unfortunately (for us), though, literally every manual mapper ever in existence either zeroes them or does not copy them to the kernel at all. So, what do we search for then?</p>

<p>If the driver was compiled using MSVC with all sorts of exploit mitigations enabled, like <a href="https://learn.microsoft.com/en-us/windows/win32/secbp/control-flow-guard">Control Flow Guard</a>, <a href="https://learn.microsoft.com/en-us/cpp/build/reference/gs-buffer-security-check?view=msvc-170">Buffer Security Check/Security Cookie</a>, and <a href="https://devblogs.microsoft.com/cppblog/spectre-mitigations-in-msvc/">Spectre mitigations</a>, there will be compiler-generated code that is always going to be the same across any compiled image that we could search for.</p>

<p><code class="language-plaintext highlighter-rouge">__GSHandlerCheckCommon</code> - Security check handler</p>

<p><img src="/assets/img/posts/8_handler.png" alt="screenshot" /></p>

<p>There are even some standard library functions that will always be statically linked into the driver.</p>

<p><code class="language-plaintext highlighter-rouge">_cpu_features_init</code> - <code class="language-plaintext highlighter-rouge">memset</code> uses it to check if SSE or AVX is enabled</p>

<p><img src="/assets/img/posts/8_cpu_features.png" alt="screenshot" /></p>

<p><code class="language-plaintext highlighter-rouge">memset</code> - <a href="https://cplusplus.com/reference/cstring/memset/">Standard library function</a></p>

<p><img src="/assets/img/posts/8_memset.png" alt="screenshot" /></p>

<p>This looks like the optimal choice: create patterns out of them and search the memory. If you find the pattern outside of any legitimate module region, you have most likely found a manually mapped driver. The problem?</p>

<ol>
  <li>Each compiler version is going to produce different output, so you will need to check for lots of patterns to get actual matches.</li>
  <li>False flags are very likely due to UEFI firmware (<code class="language-plaintext highlighter-rouge">memset</code>, for example). You could filter out firmware entries, but that’s out of the scope of my little experiment.</li>
  <li>Certain compiler settings might change the output.</li>
</ol>

<p>I wanted something incredibly simple that would still have a high detection rate and a low false positive rate. I am going to assume that most of the drivers are compiled using <a href="https://visualstudio.microsoft.com/vs/features/cplusplus/">MSVC</a>. There is one thing about it specifically that we can use:</p>

<p><img src="/assets/img/posts/8_function_list.png" alt="screenshot" /></p>

<p>Do you see it already?</p>

<p>Even when you change all sorts of possible compiler options, it will always generate those wrapper functions around certain imports.</p>

<p><img src="/assets/img/posts/8_function_wrap.png" alt="screenshot" /></p>

<p>I’ve checked many drivers that I have made myself in the past and even ones that I was reversing, and to my surprise, all of them had those import wrappers. The only one that had none was (ironically) <a href="https://github.com/TheCruZ/kdmapper/tree/master/HelloWorld">kdmapper’s HelloWorld driver</a> (since it has only one import, it was just inlined).</p>

<h2 id="the-actual-detection">The actual detection</h2>
<p>Now that we have figured out what we are going to be searching for, we could finally go ahead and implement the search algorithm.</p>

<ol>
  <li>Find all absolute indirect jump instructions (<code class="language-plaintext highlighter-rouge">FF 25</code>).</li>
  <li>Check whether the instruction is within some kernel module address.</li>
  <li>If not, resolve the (assumed) import address.</li>
  <li>Read the import and check whether it points to the kernel (<code class="language-plaintext highlighter-rouge">ntoskrnl.exe</code>).</li>
</ol>

<p>If all those conditions are met, we have most likely found a manually mapped driver.</p>

<p><img src="/assets/img/posts/8_painting.png" alt="screenshot" />
<em>I heard good blog articles need fancy visual representations, so here is one.</em></p>

<p>Here is the code:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">bool</span> <span class="n">Detector</span><span class="o">::</span><span class="n">IsValidKernel</span><span class="p">(</span><span class="n">ULONG64</span> <span class="n">address</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">const</span> <span class="n">PRTL_PROCESS_MODULE_INFORMATION</span> <span class="n">ntoskrnl</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">SystemModules</span><span class="o">-&gt;</span><span class="n">Modules</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
    <span class="k">const</span> <span class="n">ULONG64</span> <span class="n">ntoskrnlStart</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">ULONG64</span><span class="o">&gt;</span><span class="p">(</span><span class="n">ntoskrnl</span><span class="o">-&gt;</span><span class="n">ImageBase</span><span class="p">);</span>
    <span class="k">const</span> <span class="n">ULONG64</span> <span class="n">ntoskrnlEnd</span> <span class="o">=</span> <span class="n">ntoskrnlStart</span> <span class="o">+</span> <span class="n">ntoskrnl</span><span class="o">-&gt;</span><span class="n">ImageSize</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">address</span> <span class="o">&gt;=</span> <span class="n">ntoskrnlStart</span> <span class="o">&amp;&amp;</span> <span class="n">address</span> <span class="o">&lt;</span> <span class="n">ntoskrnlEnd</span><span class="p">)</span>
        <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>

    <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">bool</span> <span class="n">Detector</span><span class="o">::</span><span class="n">IsValidAny</span><span class="p">(</span><span class="n">ULONG64</span> <span class="n">address</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">ULONG_PTR</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">SystemModules</span><span class="o">-&gt;</span><span class="n">NumberOfModules</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">const</span> <span class="n">PRTL_PROCESS_MODULE_INFORMATION</span> <span class="n">current</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">SystemModules</span><span class="o">-&gt;</span><span class="n">Modules</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
        <span class="k">const</span> <span class="n">ULONG64</span> <span class="n">moduleStart</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">ULONG64</span><span class="o">&gt;</span><span class="p">(</span><span class="n">current</span><span class="o">-&gt;</span><span class="n">ImageBase</span><span class="p">);</span>
        <span class="k">const</span> <span class="n">ULONG64</span> <span class="n">moduleEnd</span> <span class="o">=</span> <span class="n">moduleStart</span> <span class="o">+</span> <span class="n">current</span><span class="o">-&gt;</span><span class="n">ImageSize</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">address</span> <span class="o">&gt;=</span> <span class="n">moduleStart</span> <span class="o">&amp;&amp;</span> <span class="n">address</span> <span class="o">&lt;</span> <span class="n">moduleEnd</span><span class="p">)</span>
            <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// In the previous loop</span>
<span class="kt">bool</span> <span class="n">foundGadget</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">SIZE_T</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">chunkSize</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">CheckBuffer</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">==</span> <span class="mh">0xFF</span> <span class="o">&amp;&amp;</span> <span class="n">CheckBuffer</span><span class="p">[</span><span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="mh">0x25</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">ULONG64</span> <span class="n">physicalFound</span> <span class="o">=</span> <span class="n">chunkStart</span><span class="p">.</span><span class="n">QuadPart</span> <span class="o">+</span> <span class="n">j</span><span class="p">;</span>
        <span class="n">ULONG64</span> <span class="n">virtualFound</span> <span class="o">=</span> <span class="n">Utils</span><span class="o">::</span><span class="n">PhysicalToVirtual</span><span class="p">(</span><span class="n">physicalFound</span><span class="p">);</span>

        <span class="c1">// FF 25 XX XX XX XX</span>
        <span class="kt">int</span> <span class="n">instructionOffset</span><span class="p">;</span>
        <span class="n">Utils</span><span class="o">::</span><span class="n">ReadVirtualMemory</span><span class="p">(</span><span class="n">virtualFound</span> <span class="o">+</span> <span class="mi">2</span><span class="p">,</span> <span class="n">instructionOffset</span><span class="p">);</span>

        <span class="n">ULONG64</span> <span class="n">resolved</span><span class="p">;</span>
        <span class="n">Utils</span><span class="o">::</span><span class="n">ReadVirtualMemory</span><span class="p">(</span><span class="n">virtualFound</span> <span class="o">+</span> <span class="n">instructionOffset</span> <span class="o">+</span> <span class="mi">6</span><span class="p">,</span> <span class="n">resolved</span><span class="p">);</span>

        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">resolved</span><span class="p">)</span>
            <span class="k">continue</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">IsValidKernel</span><span class="p">(</span><span class="n">resolved</span><span class="p">))</span>
            <span class="k">continue</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">IsValidAny</span><span class="p">(</span><span class="n">virtualFound</span><span class="p">))</span>
            <span class="k">continue</span><span class="p">;</span>

        <span class="n">TotalGadgets</span><span class="o">++</span><span class="p">;</span>
        <span class="n">foundGadget</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>

        <span class="n">Log</span><span class="p">(</span><span class="s">"Found at 0x%llx (0x%llx)"</span><span class="p">,</span> <span class="n">virtualFound</span><span class="p">,</span> <span class="n">physicalFound</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now, let’s try to run the code on a system without any manually mapped drivers.</p>

<p><img src="/assets/img/posts/8_debug_1.png" alt="screenshot" /></p>

<pre><code class="language-txt">[MD] Starting main thread...
[MD] Initializing...
[MD] Caching system modules...
[MD] Starting memory scan (this can take a while)...
[MD] -&gt; Gadgets: 0
[MD] -&gt; Pages: 0
[MD] Finished
[MD] Unloading...
</code></pre>

<p>Now, lets try to map some random driver.</p>

<p><img src="/assets/img/posts/8_debug_2.png" alt="screenshot" /></p>

<pre><code class="language-txt">[MD] Starting main thread...
[MD] Initializing...
[MD] Caching system modules...
[MD] Starting memory scan (this can take a while)...
[MD] Found at 0xffffa97951701280 (0x23886b280)
[MD] -&gt; Gadgets: 1
[MD] -&gt; Pages: 1
[MD] Finished
</code></pre>

<p>As you can see, it has found the import wrapper in a region that is not associated with any loaded module. I have also tried this with several other drivers of mine and a few binaries that I have found around the internet.</p>

<h2 id="whats-the-point-though">What’s the point though?</h2>
<p>I am writing this article because there are still some people who believe they can just map their driver, swap some random pointer, and they are good to go. This is not true with practically any modern anticheat. As you could see in the example above, even I could, in a few hours, figure out how to (somewhat) reliably detect the presence of those drivers. Now imagine what modern anticheats do. Most of them will find the mapped driver, dump it, and send it to their servers for further analysis. If that driver then ever becomes flagged as a cheat, everyone who ever had it loaded will get banned retrospectively.</p>

<p>I would not be surprised if, in the future, anticheats decide to do some more crazy stuff, like making the page non-executable, then waiting for an exception to be thrown and utilizing <a href="http://tulach.cc/eac-and-its-cr3-shuffling/">HAL exception hooking</a> to check the stack trace.</p>]]></content><author><name>Samuel Tulach</name><email>noreply@tulach.cc</email></author><category term="blog" /><category term="amd64" /><category term="windows" /><category term="kernel" /><summary type="html"><![CDATA[Scanning system memory to detect manually mapped kernel mode drivers on Windows]]></summary></entry><entry><title type="html">Bootkits and kernel patching</title><link href="https://tulach.cc/bootkits-and-kernel-patching/" rel="alternate" type="text/html" title="Bootkits and kernel patching" /><published>2024-06-26T13:00:00+02:00</published><updated>2024-06-26T13:00:00+02:00</updated><id>https://tulach.cc/bootkits-and-kernel-patching</id><content type="html" xml:base="https://tulach.cc/bootkits-and-kernel-patching/"><![CDATA[<p>If you have at least a little bit of experience with Windows kernel development, then you know that patching any kernel code at runtime is a bad idea. Windows has had <a href="https://en.wikipedia.org/wiki/Kernel_Patch_Protection">KPP (PatchGuard)</a> since the Windows XP days. Even if you somehow disable it, anticheat software is going to have an easy time finding those patches by comparing the code in memory to the code on the disk. <em>Right?</em></p>

<h2 id="kernel-patch-protection-patchguard">Kernel Patch Protection, PatchGuard?</h2>
<p>If we were to just write something into the code section of the Windows kernel, the system would crash with bug check code <code class="language-plaintext highlighter-rouge">CRITICAL_STRUCTURE_CORRUPTION</code>. The kernel continuously checks for changes in protected memory regions (read-only code sections) and certain structures. If it finds something it’s not expecting, the system crashes.</p>

<p>I don’t really want to go into details on how it works, because all you need to know for this article to make sense (somewhat) is that its initialization happens when the kernel is started and it uses for its checks the state of the kernel image that is currently in memory. In other words, if you do any modification to the kernel code from within the boot sequence, when it’s already loaded in memory, but KPP is not yet initialized, the KPP will just take the modified code as the correct one.</p>

<h2 id="but-its-different-from-the-disk">But… it’s different from the disk!</h2>
<p>Let’s say that we have a bootkit. It patches the kernel before KPP is initialized and therefore the system runs just fine. How about anticheats? Surely they do check the kernel code against the files on the disk. After all, when the <a href="https://learn.microsoft.com/en-us/windows/win32/debug/pe-format">PE image</a> is loaded into the memory, the individual code sections (usually <code class="language-plaintext highlighter-rouge">.text</code>, kernel does not allow <a href="https://www.ired.team/offensive-security/defense-evasion/finding-all-rwx-protected-memory-regions">RWX sections</a>) should always correspond 1:1 to the ones on the disk. If they don’t, modifications were made.</p>

<p>Except there is a plot twist. <strong>They don’t.</strong></p>

<p>Since Windows 10, version 1809, Microsoft has <a href="https://techcommunity.microsoft.com/t5/windows-os-platform-blog/mitigating-spectre-variant-2-with-retpoline-on-windows/ba-p/295618">implemented mitigations against the Spectre vulnerability</a>. How does it work? <strong>It runtime patches kernel-mode drivers and parts of the kernel itself.</strong></p>

<p><img src="/assets/img/posts/6_runtime_imports.png" alt="screenshot" /></p>

<p><img src="/assets/img/posts/6_optimalization.png" alt="screenshot" /></p>

<p>As you can imagine, this is extremely abusable. Now those code sections are not the same as those in the file on disk. Writing huge shellcode or mapping a whole driver over those sections, though, would still be pretty easy to spot. Only imports are being replaced, which means that only a few bytes are modified at a time and the rest remains the same.</p>

<p><img src="/assets/img/posts/6_cringe_meme.jpg" alt="cringe meme" /></p>

<h2 id="what-can-we-do">What can we do?</h2>
<p>If we assume that anticheat software does not perform in-depth checks on those code sections, we can freely replace a few bytes without triggering a detection. Here is an example:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">EFI_STATUS</span> <span class="nf">HookedExitBootServices</span><span class="p">(</span><span class="n">EFI_HANDLE</span> <span class="n">imageHandle</span><span class="p">,</span> <span class="n">UINTN</span> <span class="n">mapKey</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">PROTECT_ULTRA</span><span class="p">();</span>
	<span class="n">gBS</span><span class="o">-&gt;</span><span class="n">ExitBootServices</span> <span class="o">=</span> <span class="n">OriginalExitBootServices</span><span class="p">;</span>

	<span class="n">UINT64</span> <span class="n">returnAddress</span> <span class="o">=</span> <span class="p">(</span><span class="n">UINT64</span><span class="p">)</span><span class="n">_ReturnAddress</span><span class="p">();</span>
	<span class="k">while</span> <span class="p">(</span><span class="n">CompareMem</span><span class="p">((</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">returnAddress</span><span class="p">,</span> <span class="s">"This program cannot be run in DOS mode"</span><span class="p">,</span> <span class="mi">38</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">returnAddress</span><span class="o">--</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">UINT64</span> <span class="n">moduleBase</span> <span class="o">=</span> <span class="n">returnAddress</span> <span class="o">-</span> <span class="mh">0x4E</span><span class="p">;</span>

	<span class="n">UINT64</span> <span class="n">loaderBlockScan</span> <span class="o">=</span> <span class="n">ScanForLoaderBlock</span><span class="p">((</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">moduleBase</span><span class="p">);</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">loaderBlockScan</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">Print</span><span class="p">(</span><span class="n">EW</span><span class="p">(</span><span class="s">L"Failed to find OslExecuteTransition!</span><span class="se">\n</span><span class="s">"</span><span class="p">));</span>
		<span class="n">INFINITE_LOOP</span><span class="p">();</span>
	<span class="p">}</span>

	<span class="n">UINT64</span> <span class="n">resolvedAddress</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">UINT64</span><span class="o">*</span><span class="p">)((</span><span class="n">loaderBlockScan</span> <span class="o">+</span> <span class="mi">7</span><span class="p">)</span> <span class="o">+</span> <span class="o">*</span><span class="p">(</span><span class="kt">int</span><span class="o">*</span><span class="p">)(</span><span class="n">loaderBlockScan</span> <span class="o">+</span> <span class="mi">3</span><span class="p">));</span>

	<span class="n">BlpArchSwitchContext</span> <span class="o">=</span> <span class="p">(</span><span class="n">BlpArchSwitchContext_t</span><span class="p">)</span><span class="n">Utils</span><span class="o">::</span><span class="n">FindPatternImage</span><span class="p">((</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">moduleBase</span><span class="p">,</span> <span class="n">EC</span><span class="p">(</span><span class="s">"40 53 48 83 EC 20 48 8B 15"</span><span class="p">));</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">BlpArchSwitchContext</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">Print</span><span class="p">(</span><span class="n">EW</span><span class="p">(</span><span class="s">L"Failed to find BlpArchSwitchContext!</span><span class="se">\n</span><span class="s">"</span><span class="p">));</span>
		<span class="n">INFINITE_LOOP</span><span class="p">();</span>
	<span class="p">}</span>

	<span class="n">BlpArchSwitchContext</span><span class="p">(</span><span class="n">ApplicationContext</span><span class="p">);</span>

	<span class="n">PLOADER_PARAMETER_BLOCK</span> <span class="n">loaderBlock</span> <span class="o">=</span> <span class="p">(</span><span class="n">PLOADER_PARAMETER_BLOCK</span><span class="p">)</span><span class="n">resolvedAddress</span><span class="p">;</span>

	<span class="n">KLDR_DATA_TABLE_ENTRY</span> <span class="n">kernelModule</span> <span class="o">=</span> <span class="n">Utils</span><span class="o">::</span><span class="n">GetModule</span><span class="p">(</span><span class="o">&amp;</span><span class="n">loaderBlock</span><span class="o">-&gt;</span><span class="n">LoadOrderListHead</span><span class="p">,</span> <span class="n">EW</span><span class="p">(</span><span class="s">L"ntoskrnl.exe"</span><span class="p">));</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">kernelModule</span><span class="p">.</span><span class="n">DllBase</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">ContextPrint</span><span class="p">(</span><span class="n">EW</span><span class="p">(</span><span class="s">L"Failed to find ntoskrnl.exe in OslLoaderBlock!</span><span class="se">\n</span><span class="s">"</span><span class="p">));</span>
		<span class="n">INFINITE_LOOP</span><span class="p">();</span>
	<span class="p">}</span>

	<span class="n">UINT64</span> <span class="n">functionScan</span> <span class="o">=</span> <span class="n">ScanForPatch1</span><span class="p">(</span><span class="n">kernelModule</span><span class="p">.</span><span class="n">DllBase</span><span class="p">);</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">functionScan</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">ContextPrint</span><span class="p">(</span><span class="n">EW</span><span class="p">(</span><span class="s">L"Failed to find target function (1)!</span><span class="se">\n</span><span class="s">"</span><span class="p">));</span>
		<span class="n">INFINITE_LOOP</span><span class="p">();</span>
	<span class="p">}</span>

	<span class="c1">// mov r11, [rsp+64]</span>
	<span class="c1">// jmp r11</span>
	<span class="n">UINT8</span> <span class="n">code</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span> <span class="mh">0x4C</span><span class="p">,</span> <span class="mh">0x8B</span><span class="p">,</span> <span class="mh">0x5C</span><span class="p">,</span> <span class="mh">0x24</span><span class="p">,</span> <span class="mh">0x40</span><span class="p">,</span> <span class="mh">0x41</span><span class="p">,</span> <span class="mh">0xFF</span><span class="p">,</span> <span class="mh">0xE3</span> <span class="p">};</span>
	<span class="n">Utils</span><span class="o">::</span><span class="n">CopyMemory</span><span class="p">((</span><span class="n">VOID</span><span class="o">*</span><span class="p">)</span><span class="n">functionScan</span><span class="p">,</span> <span class="n">code</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">code</span><span class="p">));</span>

	<span class="n">BlpArchSwitchContext</span><span class="p">(</span><span class="n">FirmwareContext</span><span class="p">);</span>

	<span class="c1">// mapKey can change if we call certain functions with internal allocations</span>
	<span class="c1">// for this reason we can either resolve the key manually, but that does not seem to work</span>
	<span class="c1">// on certain systems (gBS-&gt;GetMemoryMap fails) so we will just use the key</span>
	<span class="c1">// that was passed to this function but make sure there are no internal allocation</span>
	<span class="c1">// (no logo or extensive framebuffer printing)</span>

	<span class="n">PROTECT_END</span><span class="p">();</span>
	<span class="k">return</span> <span class="nf">OriginalExitBootServices</span><span class="p">(</span><span class="n">imageHandle</span><span class="p">,</span> <span class="n">mapKey</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Since the function has 8 arguments, arguments 5-8 are pushed onto the stack. <code class="language-plaintext highlighter-rouge">rsp+64</code> is then the location of the last argument. That allows us to call any kernel function while still having 7 arguments.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">inline</span> <span class="kt">uint64_t</span> <span class="nf">call_backdoor</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">function_address</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">param1</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">param2</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">param3</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">param4</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">param5</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">param6</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">param7</span> <span class="o">=</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">data</span><span class="o">::</span><span class="n">ntdll_handle</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">data</span><span class="o">::</span><span class="n">ntdll_handle</span> <span class="o">=</span> <span class="n">GetModuleHandleA</span><span class="p">(</span><span class="s">"ntdll.dll"</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">data</span><span class="o">::</span><span class="n">ntdll_handle</span><span class="p">)</span>
            <span class="k">throw</span> <span class="n">std</span><span class="o">::</span><span class="n">exception</span><span class="p">(</span><span class="s">"ntdll.dll handle could not be obtained"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">data</span><span class="o">::</span><span class="n">function_address</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">data</span><span class="o">::</span><span class="n">function_address</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">uint64_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">GetProcAddress</span><span class="p">(</span><span class="n">data</span><span class="o">::</span><span class="n">ntdll_handle</span><span class="p">,</span> <span class="s">"NtExerciseForTheReader"</span><span class="p">));</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">data</span><span class="o">::</span><span class="n">function_address</span><span class="p">)</span>
            <span class="k">throw</span> <span class="n">std</span><span class="o">::</span><span class="n">exception</span><span class="p">(</span><span class="s">"NtExerciseForTheReader export not located"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">typedef</span> <span class="nf">uint64_t</span><span class="p">(</span><span class="n">_stdcall</span><span class="o">*</span> <span class="n">function_t</span><span class="p">)(</span><span class="kt">uint64_t</span> <span class="n">first</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">second</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">third</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">fourth</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">fifth</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">sixth</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">seventh</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">target_function</span><span class="p">);</span>
    <span class="k">const</span> <span class="k">auto</span> <span class="n">target_function</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">function_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">data</span><span class="o">::</span><span class="n">function_address</span><span class="p">);</span>
    <span class="k">return</span> <span class="nf">target_function</span><span class="p">(</span><span class="n">param1</span><span class="p">,</span> <span class="n">param2</span><span class="p">,</span> <span class="n">param3</span><span class="p">,</span> <span class="n">param4</span><span class="p">,</span> <span class="n">param5</span><span class="p">,</span> <span class="n">param6</span><span class="p">,</span> <span class="n">param7</span><span class="p">,</span> <span class="n">function_address</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then we can just do the typical memory copy…</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">inline</span> <span class="kt">uint64_t</span> <span class="nf">overwrite_page</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">page_id</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">target_address</span><span class="p">,</span> <span class="n">defines</span><span class="o">::</span><span class="n">pte_t</span><span class="o">&amp;</span> <span class="n">original_value</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">page_offset</span> <span class="o">=</span> <span class="n">target_address</span> <span class="o">%</span> <span class="n">defines</span><span class="o">::</span><span class="n">page_size</span><span class="p">;</span>
    <span class="kt">uint64_t</span> <span class="n">page_start_physical</span> <span class="o">=</span> <span class="n">target_address</span> <span class="o">-</span> <span class="n">page_offset</span><span class="p">;</span>

    <span class="n">defines</span><span class="o">::</span><span class="n">pte_t</span> <span class="n">pte</span> <span class="o">=</span> <span class="p">{</span> <span class="mi">0</span> <span class="p">};</span>
    <span class="n">kernel_copy_memory</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">uint64_t</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pte</span><span class="p">),</span> <span class="n">data</span><span class="o">::</span><span class="n">swappable_pages</span><span class="p">[</span><span class="n">page_id</span><span class="p">].</span><span class="n">pte</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">defines</span><span class="o">::</span><span class="n">pte_t</span><span class="p">));</span>

    <span class="n">original_value</span> <span class="o">=</span> <span class="n">pte</span><span class="p">;</span>
    <span class="n">pte</span><span class="p">.</span><span class="n">page_frame</span> <span class="o">=</span> <span class="n">PAGE_TO_PFN</span><span class="p">(</span><span class="n">page_start_physical</span><span class="p">);</span>

    <span class="n">kernel_copy_memory</span><span class="p">(</span><span class="n">data</span><span class="o">::</span><span class="n">swappable_pages</span><span class="p">[</span><span class="n">page_id</span><span class="p">].</span><span class="n">pte</span><span class="p">,</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">uint64_t</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pte</span><span class="p">),</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">defines</span><span class="o">::</span><span class="n">pte_t</span><span class="p">));</span>

    <span class="n">kernel_flush_current_tb_immediately</span><span class="p">();</span>

    <span class="k">return</span> <span class="n">data</span><span class="o">::</span><span class="n">swappable_pages</span><span class="p">[</span><span class="n">page_id</span><span class="p">].</span><span class="n">virtual_address</span> <span class="o">+</span> <span class="n">page_offset</span><span class="p">;</span>
<span class="p">}</span>

<span class="kr">inline</span> <span class="kt">void</span> <span class="nf">read_physical_address</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">page_id</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">target_address</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">buffer</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">size</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">check_valid_physical_address</span><span class="p">(</span><span class="n">target_address</span><span class="p">))</span>
        <span class="k">return</span><span class="p">;</span>

    <span class="n">defines</span><span class="o">::</span><span class="n">pte_t</span> <span class="n">original</span><span class="p">;</span>
    <span class="kt">uint64_t</span> <span class="n">virtual_address</span> <span class="o">=</span> <span class="n">overwrite_page</span><span class="p">(</span><span class="n">page_id</span><span class="p">,</span> <span class="n">target_address</span><span class="p">,</span> <span class="n">original</span><span class="p">);</span>

    <span class="n">kernel_copy_memory</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="n">virtual_address</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>

    <span class="n">restore_page</span><span class="p">(</span><span class="n">page_id</span><span class="p">,</span> <span class="n">original</span><span class="p">);</span>
<span class="p">}</span>

<span class="kr">inline</span> <span class="kt">bool</span> <span class="nf">read_process_memory</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">page_id</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">process</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">address</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">buffer</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">size</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">address</span><span class="p">)</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

    <span class="kt">uint64_t</span> <span class="n">dirbase</span> <span class="o">=</span> <span class="n">get_process_directory_base</span><span class="p">(</span><span class="n">page_id</span><span class="p">,</span> <span class="n">process</span><span class="p">);</span>
    <span class="n">SIZE_T</span> <span class="n">current_offset</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">SIZE_T</span> <span class="n">total_size</span> <span class="o">=</span> <span class="n">size</span><span class="p">;</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">total_size</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">uint64_t</span> <span class="n">current_physical_address</span> <span class="o">=</span> <span class="n">translate_linear_address</span><span class="p">(</span><span class="n">page_id</span><span class="p">,</span> <span class="n">address</span> <span class="o">+</span> <span class="n">current_offset</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">current_physical_address</span><span class="p">)</span>
            <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

        <span class="kt">uint64_t</span> <span class="n">read_size</span> <span class="o">=</span> <span class="n">min</span><span class="p">(</span><span class="n">defines</span><span class="o">::</span><span class="n">page_size</span> <span class="o">-</span> <span class="p">(</span><span class="n">current_physical_address</span> <span class="o">&amp;</span> <span class="mh">0xFFF</span><span class="p">),</span> <span class="n">total_size</span><span class="p">);</span>

        <span class="n">read_physical_address</span><span class="p">(</span><span class="n">page_id</span><span class="p">,</span> <span class="n">current_physical_address</span><span class="p">,</span> <span class="n">buffer</span> <span class="o">+</span> <span class="n">current_offset</span><span class="p">,</span> <span class="n">read_size</span><span class="p">);</span>

        <span class="n">total_size</span> <span class="o">-=</span> <span class="n">read_size</span><span class="p">;</span>
        <span class="n">current_offset</span> <span class="o">+=</span> <span class="n">read_size</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">read_size</span><span class="p">)</span>
            <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="detection">Detection</h2>
<p>If we call kernel functions through our jumpout patch, the call stack is only going to contain memory locations associated with legitimate kernel modules. We can fully unload our EFI bootkit after the patch is done (it does not have to be a runtime driver).</p>

<p>The most obvious problem is then the patch. While anticheats still do compare code sections to the disk, they either just disregard the results (unless it’s a known public patch) or spam all differences to its servers (including the ones made by Windows itself). In both cases, the user will not receive a ban or even a kick from the game, although I have seen several P2C (pay-to-cheat) providers utilizing a similar method, so it’s very likely more in-depth checks are going to be implemented in the future.</p>]]></content><author><name>Samuel Tulach</name><email>noreply@tulach.cc</email></author><category term="blog" /><category term="amd64" /><category term="uefi" /><category term="bootkit" /><category term="windows" /><category term="kernel" /><summary type="html"><![CDATA[Patching kernel code at runtime is bad idea... or is it?]]></summary></entry><entry><title type="html">World’s fastest VM-exit</title><link href="https://tulach.cc/world-s-fastest-vm-exit/" rel="alternate" type="text/html" title="World’s fastest VM-exit" /><published>2024-02-09T17:47:00+01:00</published><updated>2024-02-09T17:47:00+01:00</updated><id>https://tulach.cc/world-s-fastest-vm-exit</id><content type="html" xml:base="https://tulach.cc/world-s-fastest-vm-exit/"><![CDATA[<p>The most common way to detect the presence of a hypervisor (apart from the CPUID HV presence bit) is to perform some kind of timing attack. All you have to do is measure time without instructions causing VM-exit and then measure it again with ones that do. A big difference between those times means that a hypervisor is most likely present. I won’t go into detail about how exactly that works and why, because there are already dozens of articles about it.</p>

<p>While thinking about this concept, I wondered about one thing: Is it possible to optimize VM-exit so much that the time difference will be within the margin of error? (Well, I knew it won’t be, but still wanted to know how much I can bring it down)</p>

<p>As an example, this is a simple test program that I am going to use. While is is not ideal since it is running in usermode and all sorts of things can happen to mess up the timings, most of the time it works fine and is good enough for the purpose of this article.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">printf</span><span class="p">(</span><span class="s">"Testing with no exit...</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="kt">int32_t</span> <span class="n">registers</span><span class="p">[</span><span class="mi">4</span><span class="p">];</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">uint64_t</span><span class="o">&gt;</span> <span class="n">no_exit_time</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">x</span> <span class="o">&lt;</span> <span class="n">CYCLES_PER_TEST</span><span class="p">;</span> <span class="n">x</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">uint64_t</span> <span class="n">time</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">100</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">auto</span> <span class="n">t1</span> <span class="o">=</span> <span class="n">__rdtsc</span><span class="p">();</span>
        <span class="k">auto</span> <span class="n">t2</span> <span class="o">=</span> <span class="n">__rdtsc</span><span class="p">();</span>
        <span class="n">time</span> <span class="o">+=</span> <span class="n">t2</span> <span class="o">-</span> <span class="n">t1</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">no_exit_time</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">time</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">printf</span><span class="p">(</span><span class="s">"No exit median: %llu</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">GetMedian</span><span class="p">(</span><span class="n">no_exit_time</span><span class="p">));</span>

<span class="n">printf</span><span class="p">(</span><span class="s">"Testing with exit...</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">uint64_t</span><span class="o">&gt;</span> <span class="n">exit_time_list</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">x</span> <span class="o">&lt;</span> <span class="n">CYCLES_PER_TEST</span><span class="p">;</span> <span class="n">x</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">uint64_t</span> <span class="n">time</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">100</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">auto</span> <span class="n">t1</span> <span class="o">=</span> <span class="n">__rdtsc</span><span class="p">();</span>
        <span class="n">__cpuid</span><span class="p">(</span><span class="n">registers</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
        <span class="k">auto</span> <span class="n">t2</span> <span class="o">=</span> <span class="n">__rdtsc</span><span class="p">();</span>
        <span class="n">time</span> <span class="o">+=</span> <span class="n">t2</span> <span class="o">-</span> <span class="n">t1</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">exit_time_list</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">time</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">printf</span><span class="p">(</span><span class="s">"With exit median: %llu</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">GetMedian</span><span class="p">(</span><span class="n">exit_time_list</span><span class="p">));</span>
</code></pre></div></div>

<p>This is what the program will output on bare metal with no hypervisor.</p>

<p><img src="/assets/img/posts/5_bare_metal.png" alt="screenshot" /></p>

<p>And this is what the program will output when custom made hypervisor with typical VM-exit implementation in C is loaded (note that <code class="language-plaintext highlighter-rouge">RDTSC</code> exiting is disabled).</p>

<p><img src="/assets/img/posts/5_hv_loaded.png" alt="screenshot" /></p>

<p>As you can see, when we use <code class="language-plaintext highlighter-rouge">CPUID</code> (which causes VM-exit unconditionally under Intel VT-x), the total time is significantly higher than on bare metal.</p>

<h2 id="optimalizations">Optimalizations</h2>
<p>I am using slightly modified <a href="https://github.com/ionescu007/SimpleVisor">SimpleVisor</a> project for this. The VM-exit handler looks something like this.</p>

<pre><code class="language-asm">AsmVtxEntry PROC
push    rcx
lea     rcx, [rsp+8h]

call    RtlCaptureContext

sub     rsp, 20h
jmp     VtxEntryHandler
AsmVtxEntry ENDP
</code></pre>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">VTX</span><span class="o">::</span><span class="n">VtxEntryHandler</span><span class="p">(</span><span class="n">PCONTEXT</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">context</span><span class="o">-&gt;</span><span class="n">Rcx</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">UINT64</span><span class="o">*&gt;</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">ULONG64</span><span class="o">&gt;</span><span class="p">(</span><span class="n">context</span><span class="p">)</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">context</span><span class="o">-&gt;</span><span class="n">Rcx</span><span class="p">));</span>

    <span class="k">const</span> <span class="n">PSHV_VP_DATA</span> <span class="n">vpData</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">PSHV_VP_DATA</span><span class="o">&gt;</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">ULONG64</span><span class="o">&gt;</span><span class="p">(</span><span class="n">context</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">-</span> <span class="n">KERNEL_STACK_SIZE</span><span class="p">);</span>

    <span class="n">SHV_VP_STATE</span> <span class="n">guestContext</span><span class="p">;</span>
    <span class="n">guestContext</span><span class="p">.</span><span class="n">GuestEFlags</span> <span class="o">=</span> <span class="n">VtxRead</span><span class="p">(</span><span class="n">GUEST_RFLAGS</span><span class="p">);</span>
    <span class="n">guestContext</span><span class="p">.</span><span class="n">GuestRip</span> <span class="o">=</span> <span class="n">VtxRead</span><span class="p">(</span><span class="n">GUEST_RIP</span><span class="p">);</span>
    <span class="n">guestContext</span><span class="p">.</span><span class="n">GuestRsp</span> <span class="o">=</span> <span class="n">VtxRead</span><span class="p">(</span><span class="n">GUEST_RSP</span><span class="p">);</span>
    <span class="n">guestContext</span><span class="p">.</span><span class="n">ExitReason</span> <span class="o">=</span> <span class="n">VtxRead</span><span class="p">(</span><span class="n">VM_EXIT_REASON</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xFFFF</span><span class="p">;</span>
    <span class="n">guestContext</span><span class="p">.</span><span class="n">VpRegs</span> <span class="o">=</span> <span class="n">context</span><span class="p">;</span>
    <span class="n">guestContext</span><span class="p">.</span><span class="n">ExitVm</span> <span class="o">=</span> <span class="n">FALSE</span><span class="p">;</span>
    <span class="n">guestContext</span><span class="p">.</span><span class="n">VpData</span> <span class="o">=</span> <span class="n">vpData</span><span class="p">;</span>
    <span class="n">guestContext</span><span class="p">.</span><span class="n">AdditionalData</span> <span class="o">=</span> <span class="n">GetProcessorAdditionalData</span><span class="p">(</span><span class="n">GetCurrentProcessorIndex</span><span class="p">(</span><span class="o">&amp;</span><span class="n">guestContext</span><span class="p">));</span>
    <span class="n">guestContext</span><span class="p">.</span><span class="n">AdditionalData</span><span class="o">-&gt;</span><span class="n">DoNotAdvanceRip</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>

    <span class="n">HandleExit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">guestContext</span><span class="p">);</span>

    <span class="n">context</span><span class="o">-&gt;</span><span class="n">Rsp</span> <span class="o">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">context</span><span class="o">-&gt;</span><span class="n">Rcx</span><span class="p">);</span>
    <span class="n">context</span><span class="o">-&gt;</span><span class="n">Rip</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">UINT64</span><span class="o">&gt;</span><span class="p">(</span><span class="n">VtxResume</span><span class="p">);</span>

    <span class="n">AsmRestoreContext</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">VTX</span><span class="o">::</span><span class="n">HandleExit</span><span class="p">(</span><span class="k">const</span> <span class="n">PSHV_VP_STATE</span> <span class="n">vpState</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">switch</span> <span class="p">(</span><span class="n">vpState</span><span class="o">-&gt;</span><span class="n">ExitReason</span><span class="p">)</span>
    <span class="p">{</span>
    <span class="k">case</span> <span class="n">EXIT_REASON_CPUID</span><span class="p">:</span>
        <span class="n">HandleCPUID</span><span class="p">(</span><span class="n">vpState</span><span class="p">);</span>
        <span class="k">break</span><span class="p">;</span>
    
    <span class="cm">/* more code */</span>

    <span class="nl">default:</span>
        <span class="n">KeBugCheck</span><span class="p">(</span><span class="n">INVALID_DRIVER_HANDLE</span><span class="p">);</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">vpState</span><span class="o">-&gt;</span><span class="n">AdditionalData</span><span class="o">-&gt;</span><span class="n">DoNotAdvanceRip</span><span class="p">)</span>
        <span class="k">return</span><span class="p">;</span>

    <span class="n">vpState</span><span class="o">-&gt;</span><span class="n">GuestRip</span> <span class="o">+=</span> <span class="n">VtxRead</span><span class="p">(</span><span class="n">VM_EXIT_INSTRUCTION_LEN</span><span class="p">);</span>
    <span class="n">__vmx_vmwrite</span><span class="p">(</span><span class="n">GUEST_RIP</span><span class="p">,</span> <span class="n">vpState</span><span class="o">-&gt;</span><span class="n">GuestRip</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">VTX</span><span class="o">::</span><span class="n">HandleCPUID</span><span class="p">(</span><span class="k">const</span> <span class="n">PSHV_VP_STATE</span> <span class="n">vpState</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">int</span> <span class="n">registers</span><span class="p">[</span><span class="mi">4</span><span class="p">];</span>
    <span class="n">__cpuidex</span><span class="p">(</span><span class="n">registers</span><span class="p">,</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">vpState</span><span class="o">-&gt;</span><span class="n">VpRegs</span><span class="o">-&gt;</span><span class="n">Rax</span><span class="p">),</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">vpState</span><span class="o">-&gt;</span><span class="n">VpRegs</span><span class="o">-&gt;</span><span class="n">Rcx</span><span class="p">));</span>

    <span class="n">vpState</span><span class="o">-&gt;</span><span class="n">VpRegs</span><span class="o">-&gt;</span><span class="n">Rax</span> <span class="o">=</span> <span class="n">registers</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
    <span class="n">vpState</span><span class="o">-&gt;</span><span class="n">VpRegs</span><span class="o">-&gt;</span><span class="n">Rbx</span> <span class="o">=</span> <span class="n">registers</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
    <span class="n">vpState</span><span class="o">-&gt;</span><span class="n">VpRegs</span><span class="o">-&gt;</span><span class="n">Rcx</span> <span class="o">=</span> <span class="n">registers</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
    <span class="n">vpState</span><span class="o">-&gt;</span><span class="n">VpRegs</span><span class="o">-&gt;</span><span class="n">Rdx</span> <span class="o">=</span> <span class="n">registers</span><span class="p">[</span><span class="mi">3</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>

<p>As you can imagine, this code generates quite a lot of instructions and requires us to capture the thread’s context so that we can restore it later. What if we rewrote it in assembly directly, so that we would not have to do any of that and could keep the instruction count as minimal as possible, while still handling <code class="language-plaintext highlighter-rouge">CPUID</code> properly?</p>

<p>Well, let’s try it…</p>

<p>This is code that I wrote within a few minutes. It reads the VM-exit reason; if it’s not <code class="language-plaintext highlighter-rouge">CPUID</code>, it will continue as usual to the C handler, and if it is, it will execute CPUID directly, while moving the guest <code class="language-plaintext highlighter-rouge">RIP</code> pointer forward. In theory, this is as minimal as it can get, while still allowing the hypervisor to run and the underlying virtualized system to operate normally.</p>

<pre><code class="language-asm">.CONST

GUEST_RIP EQU 0000681eh
VM_EXIT_INSTRUCTION_LEN EQU 0000440ch
VMEXIT_REASON EQU 4402h
EXIT_REASON_CPUID EQU 0Ah

AsmAdvanceGuestRIP PROC
push rcx
push rax
push r11

mov rcx, GUEST_RIP
xor rax, rax
vmread rax, rcx

mov rcx, VM_EXIT_INSTRUCTION_LEN
xor r11, r11
vmread r11, rcx

add rax, r11

mov rcx, GUEST_RIP
vmwrite rcx, rax

pop r11
pop rax
pop rcx

ret
AsmAdvanceGuestRIP ENDP

AsmHandleCPUID PROC
call AsmAdvanceGuestRIP

pop r9
pop r8
popf

cpuid

vmresume
AsmHandleCPUID ENDP

AsmVtxEntry PROC
pushf
push r8
push r9

mov r8, VMEXIT_REASON
xor r9, r9
vmread r9, r8
and r9, 0FFFFh

cmp r9, EXIT_REASON_CPUID
je AsmHandleCPUID

pop r9
pop r8
popf

; Capture context, C handler here
</code></pre>

<p>And the result is… lower time difference of 18.5%.</p>

<p><img src="/assets/img/posts/5_optimized.png" alt="screenshot" /></p>

<p>So yes, the VM-exit time improved so much that it is actually measurable, but on the other hand, even with this super minimal exit implementation, the time it takes to execute is significantly higher than what a single instruction would have on bare metal. Not really surprising, but I wanted to try it anyway.</p>]]></content><author><name>Samuel Tulach</name><email>noreply@tulach.cc</email></author><category term="blog" /><category term="amd64" /><category term="vm" /><category term="vmx" /><category term="vt-d" /><category term="hypervisor" /><summary type="html"><![CDATA[Trying to write the fastest possible VM-exit handler for CPUID]]></summary></entry><entry><title type="html">Hiding virtual machine presence in VMware</title><link href="https://tulach.cc/hiding-virtual-machine-presence-in-vmware/" rel="alternate" type="text/html" title="Hiding virtual machine presence in VMware" /><published>2023-12-06T21:34:00+01:00</published><updated>2023-12-06T21:34:00+01:00</updated><id>https://tulach.cc/hiding-virtual-machine-presence-in-vmware</id><content type="html" xml:base="https://tulach.cc/hiding-virtual-machine-presence-in-vmware/"><![CDATA[<p>This is a simple overview of how to set up a virtual machine in <a href="https://www.vmware.com/">VMware Workstation/Player</a> to make it more difficult for applications running inside the VM to detect that they are in a virtualized environment.</p>

<h2 id="creating-new-vm">Creating new VM</h2>
<p>When creating a new virtual machine, make sure not to select the installation ISO file right away, as that would enable <a href="https://docs.vmware.com/en/VMware-Fusion/13/com.vmware.fusion.using.doc/GUID-2B978927-D1F6-4AC5-BE9B-498257AE713A.html">Windows Easy Install</a>, which in turn would install the VMware tools.</p>

<p><img src="/assets/img/posts/3_setup_0.png" alt="screenshot" /></p>

<p>Allocate disk with at least 128 GBs of memory.</p>

<p><img src="/assets/img/posts/3_setup_1.png" alt="screenshot" /></p>

<p>In the hardware customization options, make sure to check ‘Virtualize Intel VT-x/EPT or AMD-V/RVI’.</p>

<p><img src="/assets/img/posts/3_setup_2.png" alt="screenshot" /></p>

<p>Finish the setup wizard, then go to the VM settings, and switch ‘Firmware type’ to ‘BIOS’.</p>

<p><img src="/assets/img/posts/3_setup_3.png" alt="screenshot" /></p>

<p>Change the MAC address to not start with <code class="language-plaintext highlighter-rouge">00:05:69</code>, <code class="language-plaintext highlighter-rouge">00:50:56</code>, <code class="language-plaintext highlighter-rouge">00:1C:14</code>, or <code class="language-plaintext highlighter-rouge">00:0c:29</code>.</p>

<p><img src="/assets/img/posts/3_mac.png" alt="screenshot" /></p>

<p>Then, add the installation ISO file and install the system as usual. Do not install VMware Tools when asked to.</p>

<h2 id="patching-bios-rom">Patching BIOS ROM</h2>
<p>The VMware BIOS ROM contains strings related to VMware and virtualization. Patching the ROM is quite simple, though not as straightforward as merely HEX editing the ROM file, as this would corrupt the internal checksums in the file.</p>

<p>The ROM file is located in the VMware Workstation/Player installation directory, specifically in the <code class="language-plaintext highlighter-rouge">x64</code> subdirectory. By default, this is <code class="language-plaintext highlighter-rouge">C:\Program Files (x86)\VMware\VMware Workstation\x64</code>.</p>

<p><img src="/assets/img/posts/3_vmware_dir.png" alt="screenshot" /></p>

<p>To patch this file, we can use <a href="https://www.phoenix.com/uefi-bios-utilities-package/">Phoenix BIOS Editor</a>. Simply open the file in the editor, find the ‘DMI Strings’ window and change the values so they don’t contain ‘VMware’ or ‘Virtual Platform’.</p>

<p><img src="/assets/img/posts/3_editor.png" alt="screenshot" /></p>

<p>Then, go to File -&gt; Build BIOS and save the patched BIOS somewhere. Do not overwrite the original file.</p>

<h2 id="editing-the-vmx-file">Editing the VMX file</h2>
<p>Navigate to the directory where your VM is stored. There should also be a file named <code class="language-plaintext highlighter-rouge">&lt;vmname&gt;.vmx</code>. Open this file in a text editor.</p>

<p><img src="/assets/img/posts/3_vm_dir.png" alt="screenshot" /></p>

<p>In order to use the patched BIOS file, add new line to the end.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bios440.filename = "G:\&lt;path_to_your_bios_file&gt;\BIOS.440.PATCH.ROM"
</code></pre></div></div>

<p>Also add few more important things. Disable hypervisor presence reporting when CPUID is executed.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hypervisor.cpuid.v0 = "FALSE"
</code></pre></div></div>

<p>Disable instructions that could be used for timing attacks (this won’t be that much helpful since we will exit on CPUID anyway) and VMware backdoors.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>monitor_control.restrict_backdoor = "TRUE"
monitor_control.disable_directexec = "TRUE"
monitor_control.disable_chksimd = "TRUE"
monitor_control.disable_selfmod = "TRUE"
monitor_control.disable_ntreloc = "TRUE"
monitor_control.disable_reloc = "TRUE"
monitor_control.disable_btmemspace = "TRUE"
monitor_control.disable_btpriv = "TRUE"
monitor_control.disable_btinout = "TRUE"
monitor_control.disable_btseg = "TRUE"
isolation.tools.getVersion.disable = "TRUE"
isolation.tools.setVersion.disable = "TRUE"
isolation.tools.getPtrLocation.disable = "TRUE"
isolation.tools.setPtrLocation.disable = "TRUE"
</code></pre></div></div>

<p>Enable host SMBIOS copying. While this might seem redundant since we have patched the BIOS ROM, certain strings will get replaced by default VMware strings again if we don’t set this option.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>smbios.reflectHost = "TRUE"
</code></pre></div></div>

<p>Now you can boot the VM. You should not see any VMware related strings in System Information, and Task Manager should not report that it is a virtual machine.</p>

<p><img src="/assets/img/posts/3_boot.png" alt="screenshot" /></p>]]></content><author><name>Samuel Tulach</name><email>noreply@tulach.cc</email></author><category term="blog" /><category term="vm" /><category term="vmware" /><summary type="html"><![CDATA[Patching BIOS ROM and setting up VM to hide virtual machine presence]]></summary></entry><entry><title type="html">Evil UniqueProcessId</title><link href="https://tulach.cc/evil-uniqueprocessid/" rel="alternate" type="text/html" title="Evil UniqueProcessId" /><published>2023-11-02T13:39:00+01:00</published><updated>2023-11-02T13:39:00+01:00</updated><id>https://tulach.cc/evil-uniqueprocessid</id><content type="html" xml:base="https://tulach.cc/evil-uniqueprocessid/"><![CDATA[<p>Completely hiding a process on a modern Windows system is nearly impossible, not only because of the many detection vectors but also because of <a href="https://en.wikipedia.org/wiki/Kernel_Patch_Protection">PatchGuard</a> protecting most important structures.</p>

<p>However, there is one field in the <code class="language-plaintext highlighter-rouge">EPROCESS</code> struct that is not protected, and it is the <code class="language-plaintext highlighter-rouge">UniqueProcessId</code>. While it might not seem that interesting at first glance, there are a few funny things you can do with it.</p>

<h2 id="stealing-id-of-other-process">Stealing ID of other process</h2>
<p>While I would not recommend this, since it can cause system instability (even when you restore it afterwards), it is possible to change the process ID to match another process. If this process was created before our target process (and therefore is first after the system process in the <code class="language-plaintext highlighter-rouge">EPROCESS</code> linked list), any call to <code class="language-plaintext highlighter-rouge">OpenProcess</code> or similar will result in a handle being opened for the process we stole the ID from, and not our target process, therefore “hiding” our target process.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PEPROCESS</span> <span class="n">process</span><span class="p">;</span>
<span class="n">NTSTATUS</span> <span class="n">status</span> <span class="o">=</span> <span class="n">PsLookupProcessByProcessId</span><span class="p">((</span><span class="n">HANDLE</span><span class="p">)</span><span class="mi">1234</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">process</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">NT_SUCCESS</span><span class="p">(</span><span class="n">status</span><span class="p">))</span>
    <span class="k">return</span> <span class="n">status</span><span class="p">;</span>

<span class="c1">// EPROCESS-&gt;UniqueProcessId</span>
<span class="o">*</span><span class="p">(</span><span class="n">PULONG64</span><span class="p">)((</span><span class="n">PBYTE</span><span class="p">)</span><span class="n">process</span> <span class="o">+</span> <span class="mh">0x440</span><span class="p">)</span> <span class="o">=</span> <span class="mi">6152</span><span class="p">;</span> <span class="c1">// PID of OneDrive</span>
</code></pre></div></div>

<p><img src="/assets/img/posts/2_vm_1.png" alt="screenshot" /></p>

<iframe width="100%" height="360" src="https://www.youtube.com/embed/9BCS8X__eiM?si=vQfyjh85qp_N7HIV" title="YouTube video player" frameborder="0" allow="clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe>

<h2 id="using-64-bit-id">Using 64-bit ID</h2>
<p>Most user-mode APIs like <code class="language-plaintext highlighter-rouge">OpenProcess</code>, <code class="language-plaintext highlighter-rouge">Process32First</code>, <code class="language-plaintext highlighter-rouge">Process32Next</code>, and <code class="language-plaintext highlighter-rouge">GetProcessId</code> only allow you to specify a 32-bit integer as a process ID or will strip the higher 32 bits from their return values. This means that if you set the process ID to a 64-bit integer, the kernel will handle it just fine, but user-mode APIs will start failing.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PEPROCESS</span> <span class="n">process</span><span class="p">;</span>
<span class="n">NTSTATUS</span> <span class="n">status</span> <span class="o">=</span> <span class="n">PsLookupProcessByProcessId</span><span class="p">((</span><span class="n">HANDLE</span><span class="p">)</span><span class="mi">1234</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">process</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">NT_SUCCESS</span><span class="p">(</span><span class="n">status</span><span class="p">))</span>
    <span class="k">return</span> <span class="n">status</span><span class="p">;</span>

<span class="c1">// EPROCESS-&gt;UniqueProcessId</span>
<span class="o">*</span><span class="p">(</span><span class="n">PULONG64</span><span class="p">)((</span><span class="n">PBYTE</span><span class="p">)</span><span class="n">process</span> <span class="o">+</span> <span class="mh">0x440</span><span class="p">)</span> <span class="o">=</span> <span class="mh">0xFFFFFFFFFFFFFFFF</span><span class="p">;</span>
</code></pre></div></div>

<p><img src="/assets/img/posts/2_vm_2.png" alt="screenshot" /></p>

<iframe width="100%" height="360" src="https://www.youtube.com/embed/zlmmloWvs3Y?si=SLT5dudKrHOEkHFQ" title="YouTube video player" frameborder="0" allow="clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe>]]></content><author><name>Samuel Tulach</name><email>noreply@tulach.cc</email></author><category term="blog" /><category term="windows" /><category term="kernel" /><summary type="html"><![CDATA[Playing with UniqueProcessId to hide or protect a process]]></summary></entry></feed>