On analyzing the code, we see that the programme first generates a random number token, which has the same bytes as length of the flag.
key = RSA.generate(2048, e = 1337)
token = os.urandom(len(flag))Then using standard RSA with e=1337, it encrypts a message in every iteration of the loop and prints the encrypted message.
message = b'So far we had %03d failed attempts to find the token %s' % (counter, token)Since counter ranges from %03d substitutes a 0-padded three digit counter in the message text in every iteration.
Finally the programme takes an input from us, and if we correctly guess the token, it gives us the flag.
guess = sys.stdin.buffer.readline().strip()
if guess == token:
print('Congratulations for finding the token after %03d rounds. Here is your flag: %s' % (counter, flag))Seeing the structure of the cleartext message variable, we write an exploit.
Observe that for a particular instance of this programme, the first 2 values of message just differ by having counter as
Essentially, we have,
message0 = b'So far we had 000 failed attempts to find the token %s' % tokenand
message1 = b'So far we had 001 failed attempts to find the token %s' % tokenOnly 1 character changes from message0 to message1 which is the counter.
Hence, if we can get the length of token, we can determine the exact difference between the bytes_to_long encoding of these messages.
If bytes_to_long(message0) and bytes_to_long(message1) respectively,
where
Let us first find how exactly flag).
We can do that by comparing 2 messages with token as an empty string "".
>>> from Crypto.Util.number import bytes_to_long
>>> message0 = b'So far we had %03d failed attempts to find the token %s' % (0, b"")
>>> message1 = b'So far we had %03d failed attempts to find the token %s' % (1, b"")
>>> x = bytes_to_long(message1) - bytes_to_long(message0)
>>> x
1942668892225729070919461906823518906642406839052139521251812409738904285205208498176
>>> log(x, 2)
280.0Hence,
Since len(flag) is expected to be a small number, it can be brute-forced later.
Now we have the 2 equations,
where
We can now apply the Franklin-Reiter Related Message Attack on this RSA system.
The second equation can be written as
Hence, if we consider the polynomial ring modulo
Hence,
If we can factor the polynomial
From this, we can obtain
We can execute this exploit using sage and pwntools and brute force over n = len(flag) to get the flag.
Here is the code.
from pwn import *
from sage.all import *
from Crypto.Util.number import long_to_bytes
HOST = "52.59.124.14"
PORT = int(10008)
target = remote(HOST, PORT)
def recvline():
return target.recvuntil(b"\n").decode()
recvline() # My public modulus is:
n = int(recvline())
recvline() # Let me count how long it takes you to find the secret token.
c1 = int(recvline())
recvline() # What is your guess?
target.sendline(b"1")
c2 = int(recvline())
recvline() # What is your guess?
#===
def gcd(a, b):
while b:
a, b = b, a%b
return a.monic()
def franklinreiter(C1, C2, e, N, a, b):
P.<X> = PolynomialRing(Zmod(N))
g1 = (a*X + b)^e - C2
g0 = X^e - C1
g = gcd(g0, g1)
if g == 1:
return -1
else:
return int(-g[0])%n
a = 1
b = 1942668892225729070919461906823518906642406839052139521251812409738904285205208498176
for i in range(0, 40):
print(i)
if franklinreiter(c1, c2, 1337, n, a, b) != -1:
token = long_to_bytes(franklinreiter(c1, c2, 1337, n, a, b))[-i:]
break
b *= 256
print(token)
# token is a bytes object
target.sendline(token)
target.interactive()On running we get,
Congratulations for finding the token after 001 rounds. Here is your flag: ENO{th3_s0lut1on_i5_n0t_th4t_1337}