crypto
Challenges

Secure-Server

John Doe uses this secure server where plaintext is never shared. Our Forensics Analyst was able to capture this traffic and the source code for the server. Can you recover John Doe's secrets?
❯ unzip -l files.zip
Archive: files.zip
Length Date Time Name
--------- ---------- ----- ----
2580 2025-08-04 12:47 capture.pcap
412 2025-07-17 14:22 server.py
--------- -------
2992 2 files
strings capture.pcap
Intel(R) Core(TM) i9-14900HX (with SSE4.2)
Linux 6.12.32-amd64
Dumpcap (Wireshark) 4.0.17 (Git v4.0.17 packaged as 4.0.17-0+deb12u1)
enp0s3
host 108.59.80.160
Linux 6.12.32-amd64
>WRU
tEl;P
>WRU
With the Secure Server, sharing secrets is safer than ever!
Enter the secret, XORed by your key (in hex):
151e71ce4addf692d5bac83bb87911a20c39b71da3fa5e7ff05a2b2b0a83ba03
>WRU
tGl;P
>WRU
Double encrypted secret (in hex): e1930164280e44386b389f7e3bc02b707188ea70d9617e3ced989f15d8a10d70
XOR the above with your key again (in hex):
87ee02c312a7f1fef8f92f75f1e60ba122df321925e8132068b0871ff303960e
>WRU
tEl;P
>WRU
t3l;P
Secret received!
>WRU
tCl;P
>WRU
tBl;P
>WRU
tAl;P
>WRU
t@l;P
~6zU
Counters provided by dumpcap
`6zU
server.py
import os
from pwn import xor
print("With the Secure Server, sharing secrets is safer than ever!")
enc = bytes.fromhex(input("Enter the secret, XORed by your key (in hex): ").strip())
key = os.urandom(32)
enc2 = xor(enc,key).hex()
print(f"Double encrypted secret (in hex): {enc2}")
dec = bytes.fromhex(input("XOR the above with your key again (in hex): ").strip())
secret = xor(dec,key)
print("Secret received!")
passive cryptanalysis — we just get the value and reverse engineer it
I didn't do too much beside
strings
thecapture.pcap
file and trace the flawed implementation
# Server's process:
enc = bytes.fromhex(input("Enter secret ...")) # John Doe input
key = os.urandom(32) # Server generates random key
enc2 = xor(enc, key).hex() # Server XORs with its key
print(f"Double encrypted secret: {enc2}") # Server reveals this
Since we know both from the pcap:
john_input
(what John enter) —flag ⊕ john_key = encrypted_flag
enc2
(the print statement)
We can recover the server's secret key using the XOR property:
enc2 = XOR(john_input, server_key)
Therefore:
server_key = XOR(john_input, enc2)
Once we have the server key, we can decrypt:
flag = XOR(final_input, server_key)
dec = bytes.fromhex(input("XOR the above with your key again (in hex): ").strip())
secret = xor(dec,key)
print("Secret received!")
the
dec
in the code is thefinal_input
we were looking for
from pwn import xor
# Input sent: 151e71ce4addf692d5bac83bb87911a20c39b71da3fa5e7ff05a2b2b0a83ba03
# Server returned enc2: e1930164280e44386b389f7e3bc02b707188ea70d9617e3ced989f15d8a10d70
# Second input: 87ee02c312a7f1fef8f92f75f1e60ba122df321925e8132068b0871ff303960e
john_input = bytes.fromhex("151e71ce4addf692d5bac83bb87911a20c39b71da3fa5e7ff05a2b2b0a83ba03")
enc2 = bytes.fromhex("e1930164280e44386b389f7e3bc02b707188ea70d9617e3ced989f15d8a10d70")
final_input = bytes.fromhex("87ee02c312a7f1fef8f92f75f1e60ba122df321925e8132068b0871ff303960e")
# The server does: XOR(john_input, server_key) = enc2
# So: server_key = XOR(john_input, enc2)
key = xor(john_input, enc2)
print(f"Extracted key: {key.hex()}")
# Now we can get the original secret: XOR(final_input, key)
secret = xor(enc2, key)
print(f"Original secret: {secret}")
print(f"Flag: {secret.decode()}")

scriptCTF{x0r_1s_not_s3cur3!!!!}
RSA - 1

Yú Tóngyī send a message to 3 peoples with unique modulus. But he left it vulnerable. Figure out :)
n1 = 156503881374173899106040027210320626006530930815116631795516553916547375688556673985142242828597628615920973708595994675661662789752600109906259326160805121029243681236938272723595463141696217880136400102526509149966767717309801293569923237158596968679754520209177602882862180528522927242280121868961697240587
c1 = 77845730447898247683281609913423107803974192483879771538601656664815266655476695261695401337124553851404038028413156487834500306455909128563474382527072827288203275942719998719612346322196694263967769165807133288612193509523277795556658877046100866328789163922952483990512216199556692553605487824176112568965
n2 = 81176790394812943895417667822424503891538103661290067749746811244149927293880771403600643202454602366489650358459283710738177024118857784526124643798095463427793912529729517724613501628957072457149015941596656959113353794192041220905793823162933257702459236541137457227898063370534472564804125139395000655909
c2 = 40787486105407063933087059717827107329565540104154871338902977389136976706405321232356479461501507502072366720712449240185342528262578445532244098369654742284814175079411915848114327880144883620517336793165329893295685773515696260299308407612535992098605156822281687718904414533480149775329948085800726089284
n3 = 140612513823906625290578950857303904693579488575072876654320011261621692347864140784716666929156719735696270348892475443744858844360080415632704363751274666498790051438616664967359811895773995052063222050631573888071188619609300034534118393135291537302821893141204544943440866238800133993600817014789308510399
c3 = 100744134973371882529524399965586539315832009564780881084353677824875367744381226140488591354751113977457961062275480984708865578896869353244823264759044617432862876208706282555040444253921290103354489356742706959370396360754029015494871561563778937571686573716714202098622688982817598258563381656498389039630
e = 3
I translated the challenge description as:
(Yú Tóngyī) sending
Same message to multiple people
Different moduli ("unique modulus" for each person)
Left it "vulnerable"
Looking at out.txt
, we see:
3 different moduli (
n1
,n2
,n3
)3 different ciphertexts (
c1
,c2
,c3
)Same small exponent (
e = 3
)Number of messages = exponent value (3 messages, e=3)
n1 = [some number 1]
c1 = [encrypted message 1]
n2 = [some number 2]
c2 = [encrypted message 2]
n3 = [some number 3]
c3 = [encrypted message 3]
e = 3
We could see that
Yú Tóngyī has a secret message (flag)
↓
Person 1: Has RSA keypair (n1, e=3, d1)
Person 2: Has RSA keypair (n2, e=3, d2)
Person 3: Has RSA keypair (n3, e=3, d3)
↓
Yú Tóngyī encrypts the SAME message with each person's public key:
c1 = m³ mod n1
c2 = m³ mod n2
c3 = m³ mod n3
This is a classic RSA vulnerability so I highly recommend you to read over the CTR article, since the conditions met. We can just follows the theorem and decrypt the flag
Here are the math for...whoever needs it.

def extended_gcd(a, b):
if a == 0:
return b, 0, 1
gcd, x1, y1 = extended_gcd(b % a, a)
x = y1 - (b // a) * x1
y = x1
return gcd, x, y
def chinese_remainder_theorem(remainders, moduli):
"""
Solve system of congruences using CRT
x ≡ remainders[i] (mod moduli[i])
"""
total = 0
prod = 1
for m in moduli:
prod *= m # Step 1: N = n1 * n2 * n3
for r, m in zip(remainders, moduli):
p = prod // m # Step 2
total += r * mul_inv(p, m) * p # Step 4: CRT formula
return total % prod
def mul_inv(a, b):
"""Calculate modular inverse of a mod b""" # Step 3: Find yi
b0 = b
x0, x1 = 0, 1
if b == 1:
return 1
while a > 1:
q = a // b
a, b = b, a % b
x0, x1 = x1 - q * x0, x0
if x1 < 0:
x1 += b0
return x1
def nth_root(n, e):
"""Calculate the e-th root of n""" # Step 5: Extract m
low = 0
high = n
while low <= high:
mid = (low + high) // 2
mid_e = pow(mid, e)
if mid_e == n:
return mid
elif mid_e < n:
low = mid + 1
else:
high = mid - 1
return high
def solve_rsa_crt():
# values from the challenge - Equations (1), (2), (3)
n1 = 156503881374173899106040027210320626006530930815116631795516553916547375688556673985142242828597628615920973708595994675661662789752600109906259326160805121029243681236938272723595463141696217880136400102526509149966767717309801293569923237158596968679754520209177602882862180528522927242280121868961697240587
c1 = 77845730447898247683281609913423107803974192483879771538601656664815266655476695261695401337124553851404038028413156487834500306455909128563474382527072827288203275942719998719612346322196694263967769165807133288612193509523277795556658877046100866328789163922952483990512216199556692553605487824176112568965
n2 = 81176790394812943895417667822424503891538103661290067749746811244149927293880771403600643202454602366489650358459283710738177024118857784526124643798095463427793912529729517724613501628957072457149015941596656959113353794192041220905793823162933257702459236541137457227898063370534472564804125139395000655909
c2 = 40787486105407063933087059717827107329565540104154871338902977389136976706405321232356479461501507502072366720712449240185342528262578445532244098369654742284814175079411915848114327880144883620517336793165329893295685773515696260299308407612535992098605156822281687718904414533480149775329948085800726089284
n3 = 140612513823906625290578950857303904693579488575072876654320011261621692347864140784716666929156719735696270348892475443744858844360080415632704363751274666498790051438616664967359811895773995052063222050631573888071188619609300034534118393135291537302821893141204544943440866238800133993600817014789308510399
c3 = 100744134973371882529524399965586539315832009564780881084353677824875367744381226140488591354751113977457961062275480984708865578896869353244823264759044617432862876208706282555040444253921290103354489356742706959370396360754029015494871561563778937571686573716714202098622688982817598258563381656498389039630
e = 3
print("RSA CRT Attack")
print("===================================")
print(f"e = {e}")
print()
# Set up the system of congruences - Equations (1), (2), (3)
# m^3 ≡ c1 (mod n1)
# m^3 ≡ c2 (mod n2)
# m^3 ≡ c3 (mod n3)
remainders = [c1, c2, c3]
moduli = [n1, n2, n3]
print("Applying...")
# Use CRT to find m^3 mod (n1 * n2 * n3) - Equation (8)
m_cubed = chinese_remainder_theorem(remainders, moduli)
print(f"m^3 = {m_cubed}")
print()
# Since e = 3 and we have 3 equations, m^3 < n1*n2*n3
# So we can just take the cube root directly - Equation (9)
print("Taking cube root...")
m = nth_root(m_cubed, e)
print(f"m = {m}")
print()
# Convert to text
message_bytes = m.to_bytes((m.bit_length() + 7) // 8, byteorder='big')
message = message_bytes.decode('utf-8', errors='ignore')
print(f"Message (UTF-8): {message}")
return m, message if 'message' in locals() else None
solve_rsa_crt()

scriptCTF{y0u_f0und_mr_yu's_s3cr3t_m3g_12a4e4}
Mod

Just a simple modulo challenge
#!/usr/local/bin/python3
import os
secret = int(os.urandom(32).hex(),16)
print("Welcome to Mod!")
num=int(input("Provide a number: "))
print(num % secret)
guess = int(input("Guess: "))
if guess==secret:
print(open('flag.txt').read())
else:
print("Incorrect!")
The challenge generates a random 32-byte (256-bit) secret and implements this simple protocol:
Accept a number from the user
Return
number % secret
(our input % secret)Ask for a guess of the secret
Give the flag if the guess is correct
I solved this challenge using a somewhat unconventional approach that involved a fair bit of "educated" guessing. Looking back, I think I got lucky with it 💀.
My solution on the other hands tho...
from pwn import *
# Connect to the challenge
# p = remote('play.scriptsorcerers.xyz', 10332)
p = process(['python3', 'chall.py'])
# Receive welcome message
welcome = p.recvuntil(b"Provide a number: ")
print(f"Received: {welcome.decode()}")
# Send a large prime number to get a remainder
large_prime = 2**256 + 1 # This is actually not prime, but large enough
print(f"Sending number: {large_prime}")
p.sendline(str(large_prime).encode())
# Get the result
result_line = p.recvuntil(b"Guess: ")
print(f"Raw result line: {result_line}")
# Parse the result from the debug output
lines = result_line.decode().split('\n')
for line in lines:
line = line.strip()
if line and not line.startswith('DEBUG:') and line.isdigit():
remainder = int(line)
break
print(f"{large_prime} % secret = {remainder}")
q = (large_prime - remainder) // ((2**255 + 2**254) // 2) # Estimate q
min_secret = 2**248 # At least this big typically
max_secret = 2**256 - 1
# q must be large_prime // secret, so:
max_q = large_prime // min_secret
min_q = large_prime // max_secret
print(f"Estimated quotient range: {min_q} to {max_q}")
# Try different values of q in this range
secret_found = False
for q in range(max(1, min_q), min(max_q + 1, 10)):
potential_secret = (large_prime - remainder) // q
if potential_secret > 0:
# Verify this secret works
if large_prime % potential_secret == remainder:
secret = potential_secret
print(f"Found secret with quotient {q}: {secret}")
secret_found = True
break
p.sendline(str(secret).encode())
response = p.recvall()
print(f"Flag?: {response.decode()}")
p.close()
In my mind: "This is a crypto challenge, so it must require advanced mathematical attacks"
Reality: "a simple logic flaw that can be exploited with basic knowledge"
I think I was heading towards the same direction...although slightly off.
I fundamentally exploits the relationship:
large_number = quotient × secret + remainder
So when we can send a
large_prime = 2^256 + 1
and receiveremainder
, we can rearrange it to:secret = (large_prime - remainder) / quotient
Next:
secret
ranges from ~2^248 to 2^256 (a wild assumption)large_prime = 2^256 + 1
-->
quotient = (2^256 + 1) // secret
will be 1 or 2Then try each possible quotient
q
in the estimated rangeCalculate potential secret using
secret = (large_prime - remainder) / q
Verify the answer by checking if
large_prime % potential_secret == remainder
scriptCTF{-1_f0r_7h3_w1n_4a3f7db1_43eed08ce9f6}
Secure-Server-2 (upsolve)
This is an upsolve - I couldn't solve this challenge during the live competition, but I'm working through it afterwards for learning purposes.
This time, the server is even more secure, but did it actually receive the secret? Simple brute-force won't work!
❯ unzip -l files.zip
Archive: files.zip
Length Date Time Name
--------- ---------- ----- ----
2648 2025-08-04 12:49 capture.pcap
672 2025-08-04 13:05 johndoe.py
1104 2025-08-04 12:55 server.py
--------- -------
4424 3 files
Solution:
Value 1: E2(E1(message))
Value 2: E4(E3(E2(E1(message))))
Value 3: D2(D1(E4(E3(E2(E1(message))))))
4 keys, each 16 bits (2 bytes)
k1 = b'AA' # 16 bits
k2 = b'AA' # 16 bits
k3 = b'BB' # 16 bits
k4 = b'B}' # 16 bits
individual keys are small and easily brute force, it is impossible if combined them.
So we have
Value 1: E2(E1(flag)) = 19574ac0... Value 2: E4(E3(E2(E1(flag)))) = 0239bcea...
We want to find K3 and K4 such that
E4(E3(Value 1)) = Value 2
The same logic for finding K1 and K2.
The "Meet in the Middle".
From the LEFT: encrypt all possible K3 values on Value 1
From the RIGHT: decrypt all possible K4 values on Value 2
Look for where they MEET
Step 1 - Finding K3 and K4:
Value_1: E2(E1(message))
↓ [encrypt with all possible K3]
E3(E2(E1(message))) ← Store in table
Value_2: E4(E3(E2(E1(message))))
↓ [decrypt with all possible K4]
E3(E2(E1(message))) ← When K4 is correct, this will matches table entry
Step 2 -- Finding K1 and K2
Value_3: D2(D1(E4(E3(E2(E1(message))))))
↓ [Encrypt with all possible K1]
D1(E4(E3(E2(E1(flag)))))) <-- store in table (guesses key)
Value_2: E4(E3(E2(E1(message))))
↓ [decrypt with all possible K2]
D1(E4(E3(E2(E1(flag)))))) <-- they matched the table entry, found K1, K2
We then proceed to reverse the entire process with the all the keys (from Value 1)
E2(E1(secret)) → D2(K2) → D1(K1) → secret
--> secret + k1 + k2 + k3 + k4
from Crypto.Cipher import AES
def format_key(key_int):
return bin(key_int)[2:].zfill(16).encode()
def mitm_attack():
"""
capture.pcap:
Value 1: E2(E1(message))
Value 2: E4(E3(E2(E1(message))))
Value 3: D2(D1(E4(E3(E2(E1(message))))))
"""
# Extracted values from network capture
value1 = bytes.fromhex('19574ac010cc9866e733adc616065e6c019d85dd0b46e5c2190c31209fc57727')
value2 = bytes.fromhex('0239bcea627d0ff4285a9e114b660ec0e97f65042a8ad209c35a091319541837')
value3 = bytes.fromhex('4b3d1613610143db984be05ef6f37b31790ad420d28e562ad105c7992882ff34')
# Build lookup table: encrypt value1 with all possible K3
k3_lookup = {}
for k3 in range(2**16):
cipher = AES.new(format_key(k3), mode=AES.MODE_ECB)
encrypted = cipher.encrypt(value1)
k3_lookup[encrypted.hex()] = k3
# Search: decrypt value2 with all possible K4 and check for matches
k3_final = None
k4_final = None
for k4 in range(2**16):
cipher = AES.new(format_key(k4), mode=AES.MODE_ECB)
decrypted = cipher.decrypt(value2)
if decrypted.hex() in k3_lookup:
k3_final = k3_lookup[decrypted.hex()]
k4_final = k4
# print(f"[!] Found K3: {k3_final}, K4: {k4_final}")
break
# Build lookup table: encrypt value3 with all possible K1
k1_lookup = {}
for k1 in range(2**16):
cipher = AES.new(format_key(k1), mode=AES.MODE_ECB)
encrypted = cipher.encrypt(value3)
k1_lookup[encrypted.hex()] = k1
# Search: decrypt value2 with all possible K2 and check for matches
k1_final = None
k2_final = None
for k2 in range(2**16):
cipher = AES.new(format_key(k2), mode=AES.MODE_ECB)
decrypted = cipher.decrypt(value2)
if decrypted.hex() in k1_lookup:
k1_final = k1_lookup[decrypted.hex()]
k2_final = k2
# print(f"[!] Found K1: {k1_final}, K2: {k2_final}")
break
cipher1 = AES.new(format_key(k1_final), mode=AES.MODE_ECB)
cipher2 = AES.new(format_key(k2_final), mode=AES.MODE_ECB)
# Decrypt value1 (which is E2(E1(message))) to get original message
# Order: D1(D2(E2(E1(message)))) = message
secret_message = cipher1.decrypt(cipher2.decrypt(value1))
# Convert keys to bytes for final flag construction
k1_bytes = bytes.fromhex(hex(k1_final)[2:].zfill(4))
k2_bytes = bytes.fromhex(hex(k2_final)[2:].zfill(4))
k3_bytes = bytes.fromhex(hex(k3_final)[2:].zfill(4))
k4_bytes = bytes.fromhex(hex(k4_final)[2:].zfill(4))
# Construct final flag
final_flag = secret_message + k1_bytes + k2_bytes + k3_bytes + k4_bytes
return final_flag
# run
print(mitm_attack().decode())
scriptCTF{s3cr37_m3ss4g3_1337!_7e4b3f8d}
EaaS (upsolve)

Email as a Service! Have fun...
❯ unzip -l eaas.zip
Archive: eaas.zip
Length Date Time Name
--------- ---------- ----- ----
1868 2025-06-12 08:17 server.py
16 2025-06-12 08:11 flag.txt
--------- -------
1884 2 files
#!/usr/bin/env python3
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import os
import random
email=''
flag=open('flag.txt').read()
has_flag=False
sent=False
key = os.urandom(32)
iv = os.urandom(16)
encrypt = AES.new(key, AES.MODE_CBC,iv)
decrypt = AES.new(key, AES.MODE_CBC,iv)
def send_email(recipient):
global has_flag
if recipient.count(b',')>0:
recipients=recipient.split(b',')
else:
recipients=recipient
for i in recipients:
if i == email.encode():
has_flag = True
for i in range(10):
email += random.choice('abcdefghijklmnopqrstuvwxyz')
email+='@notscript.sorcerer'
print(f"Welcome to Email as a Service!\nYour Email is: {email}\n")
password=bytes.fromhex(input("Enter secure password (in hex): "))
assert not len(password) % 16
assert b"@script.sorcerer" not in password
assert email.encode() not in password
encrypted_pass = encrypt.encrypt(password)
print("Please use this key for future login: " + encrypted_pass.hex())
while True:
choice = int(input("Enter your choice: "))
print(f"[1] Check for new messages\n[2] Get flag")
if choice == 1:
if has_flag:
print(f"New email!\nFrom: scriptsorcerers@script.sorcerer\nBody: {flag}")
else:
print("No new emails!")
elif choice == 2:
if sent:
exit(0)
sent=True
user_email_encrypted = bytes.fromhex(input("Enter encrypted email (in hex): ").strip())
if len(user_email_encrypted) % 16 != 0:
print("Email length needs to be a multiple of 16!")
exit(0)
user_email = decrypt.decrcypt(user_email_encrypted)
if user_email[-16:] != b"@script.sorcerer":
print("You are not part of ScriptSorcerers!")
exit(0)
send_email(user_email)
print("Email sent!")
Let's first analyze the source code that was given to us.
When we connect / run to the server:
We get assigned a random email: Something like
abcdefghij@notscript.sorcerer
We must provide a password in hex: Must be a multiple of 16 bytes (AES block size)
Password restrictions: Cannot contain our email address or the string
@script.sorcerer
The server encrypts our password using AES-CBC with the same key and IV for both encryption and decryption
So what is our win conditions? To get the flag, we need to:
Trigger
has_flag = True
by convincing the server that we received an email from the sorcerersCheck messages to read the flag
The server will only set has_flag = True
if we can submit encrypted data that when decrypted:
Ends with exactly
@script.sorcerer
(last 16 bytes)Contains our assigned email (hint: as one of the comma-separated recipients)
The problem: The server forbids us from including @script.sorcerer
and our email in the input, but requires the decrypted output to end with @script.sorcerer
and contain our email!, the lines responsible for our problem:
Input validation (what we submit):
assert b"@script.sorcerer" not in password
assert email.encode() not in password
Output validation (what gets decrypted):
if user_email[-16:] != b"@script.sorcerer":
print("You are not part of ScriptSorcerers!")
exit(0)
So of course...we need to find a way to manipulate our input so that after encryption and decryption, it magically contains the forbidden strings.
Well...then we have to understand what the encryption method is being used ...AES-CBC...classic
I think the articles below will do a better job at explaining the CBC Bit-Flipping Attack than I do
The key is that in CBC mode, you can precisely control changes to plaintext by manipulating the previous ciphertext block.
from pwn import remote, process
from Crypto.Util.strxor import strxor
# r = remote("play.scriptsorcerers.xyz", 10495)
r = process(["python3", "server.py"])
r.recvuntil(b"Your Email is: ")
email = r.recvline().strip().decode()
print(f"Assigned email: {email}")
control_block = b"a" * 16
email_part1 = b",1" + email.encode()[1:] # Replace first char with '1'
# Calculate how to split across blocks
if len(email_part1) <= 16:
block1 = email_part1 + b"a" * (16 - len(email_part1))
block2 = b",aaaaaaaaaaaaaa" # Comma + padding
else:
block1 = email_part1[:16]
remaining = email_part1[16:]
block2 = remaining + b"," + b"a" * (15 - len(remaining))
control_block2 = b"a" * 16
# Use @script.sorcerer but change 's' to 't' to pass validation
target_domain = b"@tcript.sorcerer"
payload = control_block + block1 + block2 + control_block2 + target_domain
print(f"Payload: {payload}")
print(f"Payload hex: {payload.hex()}")
# Submit payload
r.sendline(payload.hex())
# Get encrypted result
r.recvuntil(b"key for future login: ")
key_hex = r.recvline().strip().decode()
print(f"Encrypted: {key_hex}")
# Apply bit flips
encrypted = bytearray(bytes.fromhex(key_hex))
# Flip to restore first character of email
first_char_fix = strxor(b"1", email[0].encode())
encrypted[1] ^= ord(first_char_fix)
# Flip to change 't' back to 's' in @script.sorcerer
domain_fix = strxor(b"t", b"s")
encrypted[49] ^= ord(domain_fix) # Position in control block 3 that affects block 4
modified_hex = encrypted.hex()
print(f"Modified: {modified_hex}")
# Now submit to option 2
print("Submitting to option [2]...")
r.sendline(b"2")
r.recvuntil(b"Enter encrypted email (in hex): ")
r.sendline(modified_hex.encode())
# Check result
r.recvuntil(b"Email sent!")
# Now check for messages
print("Checking messages...")
r.sendline(b"1")
r.interactive()
python solve.py
[+] Opening connection to play.scriptsorcerers.xyz on port 10495: Done
Assigned email: shykpibmlu@notscript.sorcerer
Payload: b'aaaaaaaaaaaaaaaa,1hykpibmlu@notscript.sorcerer,aaaaaaaaaaaaaaaaa@tcript.sorcerer'
Payload hex: 616161616161616161616161616161612c3168796b7069626d6c75406e6f747363726970742e736f7263657265722c6161616161616161616161616161616161407463726970742e736f726365726572
Encrypted: fd79d1c25aed23c500b4d24ebbade063e385ea14209fe5ee780ee9a08bb99c8f30c0bd4d93f534e0c8705d128bffdef7b85469988b7bfc79f1f7083d39e72827453770c0030c35e7cb0b677b32933a52
Modified: fd3bd1c25aed23c500b4d24ebbade063e385ea14209fe5ee780ee9a08bb99c8f30c0bd4d93f534e0c8705d128bffdef7b85369988b7bfc79f1f7083d39e72827453770c0030c35e7cb0b677b32933a52
Submitting to option [2]...
Checking messages...
[*] Switching to interactive mode
Enter your choice: [1] Check for new messages
[2] Get flag
New email!
From: scriptsorcerers@script.sorcerer
Body: scriptCTF{CBC_1s_s3cur3_r1ght?_3c38ba9890e9}
scriptCTF{CBC_1s_s3cur3_r1ght?_3c38ba9890e9}
Last updated