1

Published on August 1, 2025

1

Dikasih file public.pem sama encrypted. Dari script Python yang dikasih, kita bisa liat kalo flag-nya dienkripsi pake RSA, tapi nggak cuma sekali, tapi dua kali. Jadi, kita harus ngebuka gemboknya satu-satu

Lapisan2 Lapisan enkripsi kedua ini pake RSA standar, tapi ada kelemahan fatalnya. Modulus n (yang ada di public.pem) itu hasil perkalian dari dua bilangan prima, p dan q. Nah, masalahnya, si q ini cuma bilangan prima 16-bit.

Artinya, kita bisa dengan gampang nemuin q dengan cara brute-force, yaitu nyobain semua bilangan prima di bawah 2^16 (65536) sampe ketemu satu yang bisa ngebagi n.

Langkah-langkahnya gini:

  1. Ambil n dari public.pem: Kita baca public key-nya buat dapetin nilai n.
  2. Cari q: Kita bikin looping buat nyobain semua bilangan prima dari 2 sampe
  3. Kalo n % q_candidate == 0, berarti itu q-nya.
  4. Hitung p: Kalo q udah ketemu, p tinggal diitung pake p = n / q.
  5. Bikin Private Key Palsu: Dengan p dan q, kita bisa ngitung phi dan d, terus kita bisa bikin ulang private key buat lapisan kedua ini.
  6. Dekripsi File encrypted: Pake private key yang baru kita buat, kita dekripsi file encrypted. Hasilnya bukan flag, tapi ciphertext dari lapisan pertama. Kita sebut aja ini ct_intermediate.

Lapisan1 ini bagian yang sedikit lebih tricky. Ciphertext yang kita dapet dari tahap sebelumnya (ct_intermediate) itu dienkripsi pake kunci RSA yang beda lagi. Kuncinya ini dibikin pake proses yang unik di script aslinya.

Kuncinya ada di sini: bilangan prima p yang kita temuin di lapisan kedua itu ternyata adalah nextprime(n_layer1), di mana n_layer1 adalah modulus yang dipake di lapisan enkripsi pertama.

Jadi, alurnya dibalik:

  1. Cari n_layer1: Karena kita udah tau p, berarti n_layer1 adalah bilangan sebelum p. Kita bisa pake fungsi prevprime() buat nyari kandidat n_layer1 dengan cara mundur dari p.

  2. Verifikasi Kandidat n: Nggak semua bilangan sebelum p itu n_layer1 yang bener. Kita harus verifikasi. Caranya? Kita inget lagi kalo n_layer1 itu hasil perkalian berantai dari beberapa bilangan prima (p_start, nextprime(nextprime(p_start)), dst).

  3. Temukan p_start: Buat verifikasi, kita harus nemuin p_start-nya. Daripada nyari buta, kita bisa bikin aproksimasi/perkiraan p_start dengan cara ngitung akar pangkat sekian (misal pangkat 17) dari n_candidate. Hasilnya bakal deket banget sama p_start yang asli. Kita tinggal cari di sekitar angka itu.

  4. Rekonstruksi & Validasi: Kalo p_start yang pas udah ketemu, kita simulasiin lagi proses perkalian berantainya. Kalo hasil akhirnya sama dengan n_candidate kita, berarti BINGO! Kita nemu n_layer1 dan phi_layer1 yang bener.

  5. Dekripsi Terakhir: Sama kayak sebelumnya, kalo n dan phi udah ada, kita bisa itung d buat lapisan pertama ini. Pake kunci ini, kita dekripsi ct_intermediate dan… dapet deh flag-nya

Berikut solper nya

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Util.number import inverse, bytes_to_long, long_to_bytes, isPrime
from sympy import nextprime, primerange
from decimal import Decimal, getcontext
 
def solve_layer2():
    print("[+] Starting Layer 2 decryption...")
 
    try:
 
        with open('public.pem', 'rb') as f:
            pubkey_pem = f.read()
        pubkey = RSA.import_key(pubkey_pem)
        n_rsa = pubkey.n
        e_rsa = pubkey.e
 
        with open('encrypted', 'rb') as f:
            encrypted_data = f.read()
    except FileNotFoundError as e:
        print(f"[!] Error: {e}. Make sure 'public.pem' and 'encrypted' are in the same directory.")
        return None, None
 
    print(f"  [-] Layer 2 n_rsa (modulus) loaded.")
    print(f"  [-] Layer 2 e_rsa (public exponent) = {e_rsa}")
 
 
    print("  [-] Searching for the 16-bit prime factor 'q'...")
    found_q = None
    for q_candidate in primerange(2, 2**16):
        if n_rsa % q_candidate == 0:
            found_q = q_candidate
            break
 
    if not found_q:
        print("[!] Failed to find the 16-bit prime factor q. Exiting.")
        return None, None
 
    q = found_q
    p = n_rsa // q
    print(f"  [-] Found 16-bit prime q = {q}")
    print(f"  [-] Calculated large prime p.")
 
    # Verify that p is indeed prime, as a sanity check.
    if not isPrime(p):
        print("[!] Calculated p is not prime. Something is wrong. Exiting.")
        return None, None
    print("  [-] Verified that p is prime.")
 
    # Reconstruct the private key for layer 2
    phi_rsa = (p - 1) * (q - 1)
    d_rsa = inverse(e_rsa, phi_rsa)
 
    private_key_layer2 = RSA.construct((n_rsa, e_rsa, d_rsa, p, q))
    print("  [-] Reconstructed Layer 2 private key.")
 
 
    cipher_decrypt = PKCS1_OAEP.new(private_key_layer2)
    key_size = private_key_layer2.size_in_bytes()
 
    encrypted_chunks = [encrypted_data[i:i + key_size] for i in range(0, len(encrypted_data), key_size)]
 
    try:
        decrypted_chunks = [cipher_decrypt.decrypt(chunk) for chunk in encrypted_chunks]
        decrypted_data = b''.join(decrypted_chunks)
    except ValueError as e:
        print(f"[!] Decryption failed. This may happen if the factored primes are incorrect. Error: {e}")
        return None, None
 
    ct_intermediate = bytes_to_long(decrypted_data)
    print("[+] Layer 2 decryption successful!")
 
 
    return ct_intermediate, p
 
 
def solve_layer1(ct_intermediate, p_for_key):
    """
    Recreates the large 'n' and 'phi' from the first encryption layer by
    reverse-engineering the starting prime from the p recovered from layer 2.
    """
    print("\n[+] Starting Layer 1 decryption...")
    print("  [-] Using the large prime p recovered from Layer 2 as our target.")
 
 
    getcontext().prec = 3000
    p_for_key_dec = Decimal(p_for_key)
 
    for num_primes_guess in [17, 16, 18]:
        p_start_approx = int(p_for_key_dec ** (Decimal(1)/Decimal(num_primes_guess)))
        print(f"\n  [-] Approximating based on {num_primes_guess} primes...")
        print(f"  [-] Approximate p_start is near {p_start_approx}")
        print(f"  [-] Searching for the correct 512-bit p_start around this value...")
 
 
        for i in range(-10000, 10000):
            p_start_cand = p_start_approx + i
 
 
            if p_start_cand.bit_length() != 512 or not isPrime(p_start_cand):
                continue
 
 
            p = p_start_cand
            n_candidate = 1
            phi_candidate = 1
 
 
            while True:
                n_candidate *= p
                phi_candidate *= (p - 1)
                if n_candidate.bit_length() >= 8192:
                    break
                p = nextprime(nextprime(p))
 
            # Now, check if the n we generated is the correct one.
            # The correct n is the one for which nextprime(n) equals the p we recovered.
            if nextprime(n_candidate) == p_for_key:
                print(f"\n  [+] SUCCESS! Found correct p_start: {p_start_cand}")
                print(f"  [+] Verified correct n_layer1 with bit length {n_candidate.bit_length()}")
                n_layer1 = n_candidate
                phi_layer1 = phi_candidate
 
                # With n_layer1 and phi_layer1, we can decrypt the first layer.
                e_layer1 = 65537
 
                print("  [-] Calculating private exponent d for Layer 1...")
                d_layer1 = inverse(e_layer1, phi_layer1)
 
                print("  [-] Decrypting final layer to get the flag...")
                ct_original = pow(ct_intermediate, d_layer1, n_layer1)
 
                flag = long_to_bytes(ct_original)
 
                print("\n[+] DECRYPTION COMPLETE!")
                try:
                    print(f"  [+] FLAG: {flag.decode()}")
                except UnicodeDecodeError:
                    print(f"  [+] FLAG (raw bytes): {flag}")
                return
 
 
    print("\n[!] Search for p_start failed. The approximation or search range may be off.")
    print("    Cannot proceed to decrypt Layer 1.")
 
 
def main():
    ct_intermediate, p_for_key = solve_layer2()
    if ct_intermediate is not None and p_for_key is not None:
        solve_layer1(ct_intermediate, p_for_key)
 
if __name__ == "__main__":
    main()
Flag: Meta4Sec{l0r3m_1p5um_d0l0r_517_4m37_de6e5}