Post

ScriptCTF 2025 Write-up

ScriptCTF 2025 Write-up

This write-up contains solutions of few challenges from ScriptCTF 2025, where I (solo) bagged 403rd place out of 1767 teams from all over the world. It contained challenges suitable for beginners to seasoned hackers. Do not miss the last challenge, I loved it the most.

Misc

Rules

Just read the rules and you will have a link to prizes, the flag is on top right corner scriptCTF{600D_1ucK_5011D3r1}

Div

You are provided with a chall.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import os
import decimal
decimal.getcontext().prec = 50

secret = int(os.urandom(16).hex(),16)
num = input('Enter a number: ')

if 'e' in num.lower():
    print("Nice try...")
    exit(0)

if len(num) >= 10:
    print('Number too long...')
    exit(0)

fl_num = decimal.Decimal(num)
div = secret / fl_num

if div == 0:
    print(open('flag.txt').read().strip())
else:
    print('Try again...')

You have to find a way to enter a number that is large enough to divide the secret and result is 0.

Solution: Enter the input as Infinity Flag

This will give you the flag scriptCTF{70_1nf1n17y_4nd_b3y0nd_f6c842098579}

Crypto

RSA-1

solve.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
from gmpy2 import isqrt, iroot

# Given values
n1 = 156503881374173899106040027210320626006530930815116631795516553916547375688556673985142242828597628615920973708595994675661662789752600109906259326160805121029243681236938272723595463141696217880136400102526509149966767717309801293569923237158596968679754520209177602882862180528522927242280121868961697240587
c1 = 77845730447898247683281609913423107803974192483879771538601656664815266655476695261695401337124553851404038028413156487834500306455909128563474382527072827288203275942719998719612346322196694263967769165807133288612193509523277795556658877046100866328789163922952483990512216199556692553605487824176112568965

n2 = 81176790394812943895417667822424503891538103661290067749746811244149927293880771403600643202454602366489650358459283710738177024118857784526124643798095463427793912529729517724613501628957072457149015941596656959113353794192041220905793823162933257702459236541137457227898063370534472564804125139395000655909
c2 = 40787486105407063933087059717827107329565540104154871338902977389136976706405321232356479461501507502072366720712449240185342528262578445532244098369654742284814175079411915848114327880144883620517336793165329893295685773515696260299308407612535992098605156822281687718904414533480149775329948085800726089284

n3 = 140612513823906625290578950857303904693579488575072876654320011261621692347864140784716666929156719735696270348892475443744858844360080415632704363751274666498790051438616664967359811895773995052063222050631573888071188619609300034534118393135291537302821893141204544943440866238800133993600817014789308510399
c3 = 100744134973371882529524399965586539315832009564780881084353677824875367744381226140488591354751113977457961062275480984708865578896869353244823264759044617432862876208706282555040444253921290103354489356742706959370396360754029015494871561563778937571686573716714202098622688982817598258563381656498389039630

e = 3

# Step 1: Verify GCDs to ensure pairwise coprimality
from math import gcd
gcd12 = gcd(n1, n2)
gcd23 = gcd(n2, n3)
gcd13 = gcd(n1, n3)

print(f"GCD(n1, n2) = {gcd12}")
print(f"GCD(n2, n3) = {gcd23}")
print(f"GCD(n1, n3) = {gcd13}")

# Assuming GCDs are 1 (pairwise coprime), proceed with CRT
# Note: If GCDs are not 1, we’d need to factor the moduli, but let’s try CRT first

# Step 2: Compute m^3 using CRT
# We use a simplified CRT implementation for three moduli
def crt(a, n):
    # a = [c1, c2, c3], n = [n1, n2, n3]
    N = n[0] * n[1] * n[2]
    result = 0
    for i in range(3):
        Ni = N // n[i]
        # Compute modular inverse of Ni mod n[i]
        y = pow(Ni, -1, n[i])  # Using Fermat’s little theorem or extended GCD
        result += a[i] * Ni * y
    return result % N, N

# Compute m^3
m3, N = crt([c1, c2, c3], [n1, n2, n3])
print(f"m^3 mod N = {m3}")

# Step 3: Check if m^3 < N
if m3 < N:
    print("m^3 is exact (no modular reduction needed)")
else:
    print("m^3 >= N, attack may fail or need adjustment")
    exit()

# Step 4: Compute the integer cube root
m, exact = iroot(m3, 3)
if exact:
    print(f"Plaintext m = {m}")
else:
    print("Cube root is not exact, attack failed")
    exit()

# Step 5: Convert m to bytes and check if it’s a meaningful message
try:
    message = m.to_bytes((m.bit_length() + 7) // 8, byteorder='big')
    print(f"Message (bytes): {message}")
    # Try decoding as ASCII
    print(f"Message (ASCII): {message.decode('ascii')}")
except UnicodeDecodeError:
    print("Message is not valid ASCII, outputting raw bytes")
    print(f"Raw bytes: {message.hex()}")

Used AI to write the above code…

Flag

flag scriptCTF{y0u_f0und_mr_yu's_s3cr3t_m3g_12a4e4}

Secure-server

Attachment: files.zip

You are provided with a capture.pcap file and a server.py file

server.py

1
2
3
4
5
6
7
8
9
10
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!", (secret).hex())

We observe the following from the packet capture file: Secret XORed with User Key (in hex):

1
151e71ce4addf692d5bac83bb87911a20c39b71da3fa5e7ff05a2b2b0a83ba03

Double encrypted secret (in hex):

1
e1930164280e44386b389f7e3bc02b707188ea70d9617e3ced989f15d8a10d70

XOR the above with your key again (in hex):

1
87ee02c312a7f1fef8f92f75f1e60ba122df321925e8132068b0871ff303960e

solve.py

1
2
3
4
5
6
7
8
9
10
11
from pwn import xor  
  
# Replace these with the actual hex strings from the conversation  
enc = bytes.fromhex("151e71ce4addf692d5bac83bb87911a20c39b71da3fa5e7ff05a2b2b0a83ba03")  # First input: secret XOR user's key  
enc2 = bytes.fromhex("e1930164280e44386b389f7e3bc02b707188ea70d9617e3ced989f15d8a10d70") # Output: double-encrypted secret  
dec = bytes.fromhex("87ee02c312a7f1fef8f92f75f1e60ba122df321925e8132068b0871ff303960e")  # Second input: enc2 XOR user's key  

# Compute secret = (dec XOR enc) XOR enc2  
secret = xor(xor(dec, enc), enc2)  
print("Secret (ASCII):", secret.decode())  

Explanation: XORing enc and dec cancels out user’s key, leaving us secret and enc2. When we XOR enc2 with secret and enc2 , we are left with just secret i.e flag. tip: look comments closely to understand how we are eliminating.

Flag

OSINT

Insider

Go to Noobmaster’s profile on discord.

Discord profile

Insider 2

Go to Noobmaster’s profile on discord. On his discord profile, scroll below you get link to login of 2026 ScriptCTF and link to his GitHub account.

profile description

on his GitHub, check his last commit you get creds.txt

credential

use the credentials to login. and boom! flag

Insider 3

Go to latest commits of GitHub account and you will get the flag

github image

Web

Renderer

We know that the cookie is at the following path

directory contents

Grab the cookie

cookie

app.py app.py

Only if we access the /developer directory with the correct cookie, we can get the flag.

Craft the magic request

1
curl -X GET http://play.scriptsorcerers.xyz:10289/developer -H "Cookie: developer_secret_cookie=400ba24a6f9e51dbbadd555e067c963ed1fe2430aa49a26de7ebe2a2019a5ed9"  

flag

Forensics

pdf challenge.pdf

perform binwalk and extract the flag.txt. flag

diskchal

you get a file stick.img Perform binwalk and extracted a file flag.txt.

1
2
3
binwalk -e stick.img
cd _stick.img.extracted
cat flag.txt

Flag: flag

Just Some Avocado This was my favorite challenge avocado.jpg

You are provided with a .jpg file, perform binwalk to extract it’s contents. Three files are provided

  • 188F7.zip - password protected
  • justsomezip.zip - empty
  • staticnoise.wav - empty

Bruteforce 188F7.zip with rockyou.txt using fcrackzip to get the password

1
fcrackzip -u -D -p rockyou.txt 188F7.zip
1
impassive3428

You will be provided with 2 files

  • justsomezip.zip - password protected
  • staticnoise.wav - normal audio file

To find the password of justsomezip.zip Open Sonic Visualiser

1
sonic-visualiser staticnoise.wav

Select Layer => Add Spectogram

Adjust to see the text clearly Spectogram displying password

1
d41v3ron

use the above password to extract the contents of the zip file.

We get flag.txt

1
scriptCTF{1_l0ve_d41_v3r0n}

That’s a wrap. Meet you in future write-ups. Hope you learnt new things.

This post is licensed under CC BY 4.0 by the author.