Scope
The text that came as description with the CTF was the following:
Naming things is hard. This challenge is not. Just some simple math.
The only interesting file for this challenge was chal.py
which looks like this:
from os import environ
from secrets import randbelow
from Crypto.Util.number import bytes_to_long
FLAG = bytes_to_long(environ.get("FLAG", "stairctf{redacted}").encode())
def main():
randval = randbelow(FLAG)
print(
"You provide an integer 'm', and we return the result of (m ^ randval) % FLAG."
)
print(f"The FLAG is {len(str(FLAG))}-digits long. Good luck!")
print("Type 'exit' to quit anytime.\n")
while True:
try:
user_input = input("Enter an integer m: ")
if user_input.lower() == "exit":
print("Goodbye!")
break
m = int(user_input)
result = pow(m, randval, FLAG)
print(f"Result: (m ^ randval) % FLAG = {result}\n")
except ValueError:
print(
"Invalid input. Please enter a valid integer or type 'exit' to quit.\n"
)
if __name__ == "__main__":
main()
Walkthrough
Even when executed there is the hint, that the calculation is
(m ^ randval) % FLAG
whereas randval
is the same once the script is
started. This means that if we enter -1
we either get 1
or FLAG - 1
,
depending on the randval
. If you get 1
restart the application and try
again until you get a really high number. With this number you can now do:
num = <number> + 1
length = (num.bit_length() + 7) // 8 # Calculate minimal number of bytes
byte_string = num.to_bytes(length, 'big')
print(byte_string.decode())
Which will print the decoded flag.