Crypto 413 pts 7 solves

Ouchie

onta Write-up byonta
Peeps be coming up with cryptographic scheme like crazy. nc 20.198.224.34 8555

Attachments

Overview

We are given a chall.py that implements a Paillier-like cryptosystem over a modulus n = p**2 * q with a 384-bit p and q. It exposes an interactive menu with four options: Encrypt, Decrypt, Get Flag, and Exit.

from Crypto.Util.number import bytes_to_long, getPrime, isPrime
from Crypto.Random.random import randint
import signal
# from secret import FLAG

bit_length = 384
p = getPrime(bit_length)
q = getPrime(bit_length)
n = p**2 * q

while True :
    g = randint(2, n - 1)
    if pow(g, p - 1, p**2) != 1:
        break

h = pow(g, n, n)
r = randint(0, n - 1)

def encrypt(m):
    c = pow(g, m, n) * pow(h, r, n) % n
    return c

L = lambda x : (x - 1) // p
def decrypt(c):
    a = pow(c, p - 1, p**2)
    b = pow(g, p - 1, p**2)
    a = L(a)
    b = L(b)
    m = (a * pow(b, -1, p)) % p
    return m

options = ["Encrypt", "Decrypt", "Get Flag", "Exit"]
def menu() :
    [print(f"{i + 1}. {opt}") for i, opt in enumerate(options)]
    return int(input(">> "))

Analysis

The cryptography looks intimidating, but the solution is simple. The flag is handed out by option 3 (Get Flag):

elif choice == 3 :
    challenge = randint(p + 1, p << 10)
    print(f"Challenge: {challenge}")
    response = int(input("Plaintext: "))
    if challenge == response :
        print("Congrats!")
        print(FLAG)
        exit()
    else :
        print("Wrong!")
        exit()

The challenge is generated randomly between p + 1 and p << 10, then printed in the clear. The program simply asks us to echo the same integer back as our Plaintext. There is no cryptography to break here at all — we just read the challenge value and send it back verbatim.

Exploitation

from pwn import *

r = remote('20.198.224.34', 8555, level='debug')

def recv_menu():
    return r.recvuntil(b">> ").decode()

def send_choice(choice):
    r.sendline(str(choice).encode())

def send_data(data):
    r.sendline(str(data).encode())

def main():
    try:
        recv_menu()
        send_choice(3)                      # pick "Get Flag"
        challenge_response = r.recvline().decode().strip()

        # Extract the challenge value from the response
        if "Challenge:" in challenge_response:
            challenge = int(challenge_response.split(":")[1].strip())
        else:
            print("Unexpected response format!")
            return

        # Send the exact same value back
        send_data(challenge)

        # Receive the flag
        r.recvall().decode()
    except Exception as e:
        print(f"Error: {e}")
    finally:
        r.close()

if __name__ == "__main__":
    main()

Echoing the printed challenge back makes the equality check pass and the server prints the flag.

Flag

CTFITB{yeaaaaayyyyy_kita_ketemu_lagi_di_gelatik_nanti!!!, yeah like there even is a gelatik}