👾
Elijah's CTF Blog
  • 👋Home
  • 🇲🇾Wargames.MY CTF 2024
    • Credentials (crypto)
    • Stones (rev)
    • Rick'S Algorithm (crypto)
    • Rick'S Algorithm 2 (crypto)
    • Hohoho 3 continue (crypto)
  • 🎄Advent of CTF 2024
    • Jingle Bell ROP (pwn)
    • help (pwn)
  • Backdoor CTF 24
    • [rev] Ratatouille
  • 🇭🇰HKCERT CTF 24
    • Shellcode Runner 3 + Revenge (pwn)
    • ISH (1) (pwn)
    • Cyp.ress (rev)
    • Void (rev)
  • 🇮🇹ECSC 2024
    • ➕OffTopic (crypto)
  • 🎩Greyhats WelcomeCTF 24
    • EE2026 (misc)
  • 🚆UIUCTF 24
    • Syscalls (pwn)
    • Summarize (rev)
    • X Marked the Spot (crypto)
    • Without a Trace (crypto)
    • Determined (crypto)
    • Naptime (crypto)
    • Snore Signatures (crypto)
  • 🪼Jelly CTF 24
    • Cherry (crypto)
    • the_brewing_secrets (crypto)
  • 👨‍🦯vsCTF 24
    • Dream (crypto)
    • Cosmic Ray V3 (pwn)
  • 😎AKASEC CTF 24
    • Warmup (pwn)
    • Good_trip (pwn)
    • Sperm Rev (rev)
    • Paranoia (rev)
    • Grip (rev)
    • Risks (rev)
    • Lost (crypto)
  • 😁L3AK CTF 24
    • oorrww (pwn)
    • angry (rev)
    • Related (crypto)
    • BatBot (web-misc)
    • Matrix Magic (crypto)
  • 🥹CDDC Qualifiers 2024
    • WASM (rev)
    • crashMe (pwn)
Powered by GitBook
On this page
  1. L3AK CTF 24

Matrix Magic (crypto)

Last updated 11 months ago

The encryption algorithm is a sage file given below:

from sage.all import *
from Crypto.Util.number import getPrime, isPrime
import os

def get_random(n):
    return int.from_bytes(os.urandom(n//8  + 1), "big")

def get_safe_prime(nbit):
    result = 2 * getPrime(nbit // 2) * getPrime(nbit // 2) + 1
    while not isPrime(int(result)):
        result = 2 * getPrime(nbit // 2) * getPrime(nbit // 2) + 1
    return result


FLAG = int.from_bytes(open("flag.txt", "rb").read().strip(), "big")

nbit = 1024

p = get_safe_prime(nbit // 2)
q = get_safe_prime(nbit // 2)
n = p*q

Mat = matrix(Zmod(n), [
    [2, 1] + [get_random(nbit) for _ in range(4)],
    [0, 2] + [get_random(nbit) for _ in range(4)],
    [0, 0] + [4, 1] + [get_random(nbit) for _ in range(2)],
    [0, 0] + [0, 4] + [get_random(nbit) for _ in range(2)],
    [0, 0] + [0, 0] + [8, 1],
    [0, 0] + [0, 0] + [0, 8]
])

C = Mat^FLAG
C = list(C)

with open("output.txt", "w") as f:
    f.write(f"{C = }")

It obtains the primes p and q, and multiplies them to get the modulus n. It then creates a matrix using powers of 2 and random 1024-bit numbers. The matrix is exponentiated with the flag, and every element in the matrix is in an integer ring modulo n. We are given the elements of the resulting matrix.

Notice that this is an upper triangular matrix. As a result, the diagonal entries are just multiplied with themselves every time we perform the matrix multiplication. In other words, c[0][0]=2flag mod nc[0][0] = 2^{flag} \space mod \space nc[0][0]=2flag mod n, c[2][2]=4flag mod nc[2][2] = 4^{flag} \space mod \space nc[2][2]=4flag mod n, c[4][4]=8flag mod nc[4][4] = 8^{flag} \space mod \space nc[4][4]=8flag mod n.

By property of modulo, we also have the following:

2flag=an + x (we get x)2^{flag} = an\space+\space x\space \text{(we get x)}2flag=an + x (we get x)

4flag=bn + y (we get y)4^{flag} = bn\space+\space y\space \text{(we get y)}4flag=bn + y (we get y)

8flag=cn + z (we get z)8^{flag} = cn\space+\space z\space \text{(we get z)}8flag=cn + z (we get z)

x2=(2flag − an)2 =22×flag − 2an×2flag + a2n2 = 4flag + n(a2n − 2a×2flag)x^2 = (2^{flag} \space - \space an)^2 \space = 2^{2 \times flag}\space - \space 2an\times 2^{flag} \space + \space a^2n^2 \space = \space 4^{flag} \space + \space n(a^2n\space - \space 2a\times 2^{flag}) x2=(2flag − an)2 =22×flag − 2an×2flag + a2n2 = 4flag + n(a2n − 2a×2flag)

Therefore,

x3 − z will be a multiple of nx^3 \space - \space z \space \text{will be a multiple of } nx3 − z will be a multiple of n

Similarly,

y3 − z2 will be a multiple of ny^3 \space - \space z^2 \space \text{will be a multiple of } ny3 − z2 will be a multiple of n

and

x3 − z will be a multiple of nx^3 \space - \space z \space \text{will be a multiple of } n x3 − z will be a multiple of n

Therefore, we can get a multiple of nnn (and thereafter nnn) using

GCD(GCD(x2 − y, x3 − z), y3 − z2)GCD(GCD(x^2 \space - \space y, \space x^3 \space - \space z), \space y^3 \space - \space z^2)GCD(GCD(x2 − y, x3 − z), y3 − z2)

We also need to verify that it is 1024 bits long.

For c[0][1]c[0][1]c[0][1], notice that it follows the following sequence: 1, 4, 12, 32 as i = 1, 2, 3, 4 etc

Let c[0][0]c[0][0]c[0][0] be a0a_0a0​. In one round of matrix multiplication,

c[0][1]=a0(prev)+c[0][1](prev)×2c[0][1]=a_0(prev)+c[0][1](prev)\times 2c[0][1]=a0​(prev)+c[0][1](prev)×2

So with the i-th power,

c[0][1]=(i−1)×2i−1+2i−1=i×2i−1c[0][1] = (i - 1)\times 2^{i - 1} + 2^{i - 1} = i\times 2^{i - 1}c[0][1]=(i−1)×2i−1+2i−1=i×2i−1

Now that we know nnn, and that c[0][0]=2i mod n,c[0][1]=i×2i−1 mod nc[0][0] = 2^{i} \space mod \space n, c[0][1] = i\times 2^{i - 1} \space mod \space nc[0][0]=2i mod n,c[0][1]=i×2i−1 mod n,

2×c[0][1] mod n=i×2i mod n2\times c[0][1] \space mod \space n = i\times 2^{i} \space mod \space n2×c[0][1] mod n=i×2i mod n. If we know the modular inverse of c[0][0] c[0][0] c[0][0], say xxx, then

((2×c[0][1] mod n)×x) mod n=i((2\times c[0][1] \space mod \space n) \times x) \space mod \space n = i((2×c[0][1] mod n)×x) mod n=i

More simply,

(2×c[0][1] ×x) mod n=i(2\times c[0][1] \space \times x) \space mod \space n = i(2×c[0][1] ×x) mod n=i

Which gives us our flag!

The solve script is as follows:

from sage.all import *
from Crypto.Util.number import *
# c[0][0]
two_power_flag = 34285747519707996626385667339662460790207453899564234933279840884637317470277963204844978855648863282661822817842406467276103235437115285776600339324582650206354227684286926765912841373720037206104315460984467834472878792871363724319500026730744559032592830317957861116684230815080884748210510265257121727411
# c[2][2]
four_power_flag = 80891788386112939497749276978496951092908047537517752191112348346041605282820278415204830919700840360371114407873821260986140627885494462923855784343766007471406691139724032186081486065167152331309928933439563087594932165713814698240940689654799590764311096627705559355661741126880749623083953480524440103996
# c[4][4]
eight_power_flag = 36580210170460317737762146444885635769591964296289890711389087000847139534806165797517572963756420701630101713343229692997790887039708011239887899158602488099058023454307392387825436996066794401349180417178870019892425641929320878895403740655587101563655595067050855919740156100994268635382369423383067032821

n = GCD(GCD(pow(two_power_flag, 2) - four_power_flag, pow(two_power_flag, 3) - eight_power_flag), pow(four_power_flag, 3) - pow(eight_power_flag, 2))
print(int(n).bit_length()) 
# bit length still not 1024, need to find a small factor and divide it
n = n // 5
print(int(n).bit_length())
x = 82389813574193236731268728055226346949935905889688170425892513962291738546070054175286986817742962125712704451033935523159914920684418980229184675189392660990212113237529057511102468180315624316711603102279872782413287481544411903032504611191117588506754985276936284421146109462510942343677179125102460355334

flag = 2 * x * inverse(two_power_flag, n) % n

print(long_to_bytes(int(flag)))
# L3AK{jordan_normal_matrix_is_so_special_86351adeff}
😁
914B
main.sage
6KB
output.txt